source: spip-zone/_acotes_/ortho/ortho_serveur.php @ 4814

Last change on this file since 4814 was 4814, checked in by gilles.vincent@…, 13 years ago

Le message était mal terminé. Le marqueur de fin n'était pas présent, ce qui est bloqué par mod_security côté Apache

File size: 11.9 KB
Line 
1<?php
2
3/*
4ortho_serveur.php - Serveur d'orthographe en PHP
5Copyright (C) 2004 Antoine Pitrou
6Distribue sous licence GNU GPL
7cf. http://lab.spip.net/spikini/?wiki=CorrecteurOrthographique
8
9----------------------------------------------
10
11This program is free software; you can redistribute it and/or
12modify it under the terms of the GNU General Public License
13as published by the Free Software Foundation; either version 2
14of the License, or (at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU General Public License for more details.
20
21You should have received a copy of the GNU General Public License
22along with this program; if not, write to the Free Software
23Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24
25----------------------------------------------
26
27*/
28
29define('E_LANG_ABSENT', 1);
30define('E_TOO_MANY_MISTAKES', 2);
31
32ignore_user_abort(true);
33
34function _dl($ext) {
35        if (extension_loaded($ext)) return true;
36        $so = $ext.".so";
37        return @dl($so);
38}
39
40$dir_data = "data/";
41if (!chdir($dir_data)) {
42        echo "<h1>Veuillez cr&eacute;er le sous-r&eacute;pertoire data et le prot&eacute;ger contre l'acc&egrave;s distant (.htaccess).</h1>";
43        exit;
44}
45
46_dl('pspell');
47_dl('sockets');
48
49class Resultat {
50        var $erreurs;
51        var $retours;
52       
53        function Resultat() {
54                $this->erreurs = array();
55                $this->retours = array();
56        }
57
58        function erreur($code) {
59                $this->erreurs[] = $code;
60        }
61        function retour($texte) {
62                $this->retours[] = $texte;
63        }
64        function xml($debug = "") {
65                $xml = '<'.'?xml version="1.0" encoding="utf-8"?'.'>'."\n";
66                $xml .= "<ortho>\n";
67                if ($debug) {
68                        $xml .= "\t<debug>$debug</debug>\n";
69                }
70                foreach ($this->erreurs as $erreur) {
71                        $xml .= "\t<erreur><code>$erreur</code><texte>$erreur</texte></erreur>\n";
72                }
73                foreach ($this->retours as $retour) {
74                        $xml .= "\t<ok>$retour</ok>\n";
75                }
76                $xml .= "</ortho>\n";
77                return $xml;
78        }
79}
80
81class Correcteur {
82        var $lang;
83        var $ok;
84        var $p;
85
86        function Correcteur($lang) {
87                $this->lang = $lang;
88                $this->p = @pspell_new($lang, "", "", "utf-8", PSPELL_FAST);
89                $this->ok = !!$this->p;
90        }
91
92        function corriger($texte, &$resultat) {
93                if (!$this->p) {
94                        $resultat->erreur('E_LANG_ABSENT');
95                        return;
96                }
97                $r = "";
98                $mots = explode(" ", $texte);
99                $bad = array();
100                $suggest = true;
101                foreach ($mots as $mot) {
102                        if (!pspell_check($this->p, $mot)) {
103                                $bad[$mot] = $mot;
104                        }
105                }
106                if (count($bad) > 250) {
107                        $resultat->erreur('E_TOO_MANY_MISTAKES');
108                        $suggest = false;
109                }
110                foreach ($bad as $mot) {
111                        $r .= "<mot>".htmlspecialchars($mot)."</mot>\n";
112                        $r .= "<suggest>";
113                        if ($suggest) $r .= htmlspecialchars(join(", ", pspell_suggest($this->p, $mot)));
114                        $r .= "</suggest>\n";
115                }
116                $resultat->retour($r);
117        }
118}
119
120function corriger($texte, $lang, $debug = "") {
121        static $cache;
122
123        // Gestion d'un cache de correcteurs afin de minimiser les appels a pspell_new()
124        if (!$cache) $cache = array();
125        if (!$cache[$lang]) $cache[$lang] = new Correcteur($lang);
126        $resultat = new Resultat;
127        $cache[$lang]->corriger($texte, $resultat);
128        $body = $resultat->xml($debug);
129        return $body;
130}
131
132
133//
134// Gestion de l'IPC (via sockets Unix)
135//
136
137function creer_client($fichier_socket) {
138        $client = "";
139        // Essayer de se connecter a la socket
140        if (file_exists($fichier_socket)) {
141                $client = fsockopen("unix://".realpath($fichier_socket), 0);
142                if (!$client) $client = fsockopen($fichier_socket, 0);
143        }
144        return $client;
145}
146
147function corriger_client($client, $texte, $lang) {
148        $texte = str_replace("\0", "", $texte);
149        $params = array('texte' => $texte, 'lang' => $lang);
150        // Pour marquer la fin des donnees envoyees, on utilise le caractere NUL
151        $buf = serialize($params)."\0";
152        while ($l = fwrite($client, $buf) AND $l < strlen($buf))
153                $buf = substr($buf, $l);
154        $result = "";
155        // Lire le resultat jusqu'a terminaison de la socket
156        while (!feof($client)) $result .= fread($client, 50000);
157        return $result;
158}
159
160function creer_serveur($fichier_socket) {
161        // Essayer de creer une socket Unix en mode connecte
162        $ss = socket_create(AF_UNIX, SOCK_STREAM, 0);
163        if (!$ss) return false;
164        @unlink($fichier_socket);
165        if (!socket_bind($ss, $fichier_socket)) return false;
166        if (!socket_listen($ss)) return false;
167        chmod($fichier_socket, 0666);
168        return $ss;
169}
170
171function corriger_serveur($msg) {
172        $params = unserialize($msg);
173        $result = corriger($params['texte'], $params['lang'], "serveur");
174        return $result;
175}
176
177function boucle_serveur($serveur) {
178        $t = time();
179        $max = intval(ini_get('max_execution_time'));
180        if (!$max) $max = 30;
181        //while ($socket = socket_accept($serveur)) {
182        while (socket_select($r = array($serveur), $w = NULL, $e = NULL, max(0, $max - 5 - (time() - $t)))) {
183                $socket = socket_accept($serveur);
184                $msg = "";
185                // Lire les donnees envoyees par le client jusqu'a renconter le caractere de fin
186                while ($msg .= socket_read($socket, 10000) AND substr($msg, -1, 1) != "\0");
187                $msg = corriger_serveur(substr($msg, 0, -1));
188                // Envoyer le resultat au client
189                while ($l = socket_write($socket, $msg) AND $l < strlen($msg))
190                        $msg = substr($msg, $l);
191                socket_close($socket);
192                if (time() - $t > $max - 5) {
193                        flush();
194                        break;
195                }
196        }
197        socket_close($serveur);
198        exit;
199}
200
201
202//
203// Repondre a la requete POST
204//
205
206$flag_sockets = function_exists("socket_create");
207$fichier_lock = "pipeline.lock";
208$fichier_socket = "pipeline.sock";
209
210
211function magic_unquote($s) {
212        if (get_magic_quotes_gpc()) return stripslashes($s);
213        else return $s;
214}
215
216function ajout_log($log, $ligne) {
217        $max_logs = 4;
218
219        $f = fopen($log, "ab");
220        flock($f, LOCK_EX);
221        if (@filemtime($log.'.1') < time() - 7 * 24 * 3600) {
222                @unlink($log.'.'.$max_logs);
223                for ($i = $max_logs; $i > 1; $i--) {
224                        @rename($log.'.'.($i - 1), $log.'.'.$i);
225                }
226                copy($log, $log.'.1');
227                ftruncate($f, 0);
228        }
229        fwrite($f, $ligne."\n");
230        flock($f, LOCK_UN);
231        fclose($f);
232}
233
234// Cette fonction remplit un fichier de log avec une syntaxe similaire
235// a celle d'un log Apache (permet de l'analyser avec AWStats et al.)
236function ortho_log($op, $lang, $taille, $duree, $mode) {
237        if (!$ip = $_SERVER['HTTP_X_FORWARDED_FOR'])
238                $ip = $_SERVER['REMOTE_ADDR'];
239
240        $referer = 'http://'.$ip.'/'.$op.','.$lang;
241        if (!$method = $_SERVER['REQUEST_METHOD'])
242                $method = 'POST';
243        $uri = 'http://'.$_SERVER['HTTP_HOST'].':'.$_SERVER['SERVER_PORT'].'/'.$op.','.$lang.','.$mode;
244        $user_agent = $_SERVER['HTTP_USER_AGENT'];
245        $request = $method.' '.$uri.' HTTP/1.0';
246        $date = gmdate('d/m/Y:H:i:s').' +0000';
247
248        $ligne = "$ip - - [$date] \"$request\" 200 $taille \"$referer\" \"$user_agent\"";
249        ajout_log("access.log", $ligne);
250
251        $ligne = "$ip - - [$date] \"$request\" $taille $duree";
252        ajout_log("time.log", $ligne);
253}
254
255
256if ($op = magic_unquote($_REQUEST['op'])) {
257        switch($op) {
258        case 'spell':
259                $log_debut = explode(" ", microtime());
260                $log_taille = 0;
261                $log_mode = '';
262
263                $client = $serveur = "";
264
265                // Debut verrou
266                if (!file_exists($fichier_lock)) {
267                        touch($fichier_lock);
268                        @chmod($fichier_lock, 0666);
269                }
270                $fp = fopen($fichier_lock, "w+");
271                if ($fp) flock($fp, LOCK_EX);
272               
273                if ($flag_sockets) {
274                        $client = creer_client($fichier_socket);
275                        if (!$client) $serveur = creer_serveur($fichier_socket);
276                }
277
278                // Fin verrou
279                if ($fp) flock($fp, LOCK_UN);
280                fclose($fp);
281
282                // Correction ortho proprement dite
283                $texte = magic_unquote($_REQUEST['texte']);
284                $log_taille += strlen($texte);
285                if ($gz = $_REQUEST['gz']) {
286                        // PHP n'aime pas le caractere NUL dans les multipart/form-data ...
287                        $echap = magic_unquote($_REQUEST['nul_echap']);
288                        if (!$echap) $echap = "\xFF\xFF\xFF\xFF";
289                        $texte = gzuncompress(str_replace($echap, "\x00", $texte));
290                }
291                $lang = magic_unquote($_REQUEST['lang']);
292                $body = "";
293                if ($client) {
294                        $body = corriger_client($client, $texte, $lang);
295                        $log_mode = 'client';
296                }
297                if (!strpos($body, "</ortho>")) {
298                        $body = corriger($texte, $lang);
299                        $log_mode = '';
300                }
301                if ($gz) $body = gzcompress($body);
302                $length = strlen($body);
303                Header("Content-Type: text/xml; charset=utf-8");
304                Header("Content-Length: ".$length);
305                Header("Connection: close");
306                echo $body;
307                flush();
308
309                $log_fin = explode(" ", microtime());
310                $log_taille += $length;
311                $log_duree = floor(1000 * ($log_fin[0] + $log_fin[1] - $log_debut[0] - $log_debut[1])) / 1000;
312                ortho_log($op, $lang, $log_taille, $log_duree, $log_mode);
313
314                if ($serveur) {
315                        boucle_serveur($serveur);
316                }
317                break;
318        }
319        exit;
320}
321
322
323
324// --------------------------------------------------------------------------
325// Ici routine de test manuel
326
327if ($_REQUEST['op']) exit;
328
329$uri = $_SERVER['REQUEST_URI'];
330if ($p = strpos($uri, '?')) $uri = substr($uri, 0, $p);
331
332$texte = stripslashes($_POST['texte']);
333$lang = $_POST['lang'];
334
335Header("Content-Type: text/html; charset=utf-8");
336
337echo "<html><head><title>Test du serveur d'orthographe</title></head>";
338echo "<body>";
339
340echo "<form method='POST' action='$uri' enctype='multipart/form-data'>";
341echo "<textarea name='texte' style='width: 100%;' rows='10'>";
342echo htmlspecialchars($texte);
343echo "</textarea>";
344echo "<input type='text' size='5' name='lang' value='".($lang ? $lang : 'fr')."'> ";
345echo "<input type='submit' name='submit' value='V&eacute;rifier l&apos;orthorgaphe'>";
346//echo "<input type='hidden' name='op' value='spell'>";
347echo "</form>";
348
349if ($texte && $lang) {
350/*      $resultat = new Resultat;
351        $correcteur = new Correcteur($lang, $resultat);
352        $correcteur->corriger(stripslashes($_POST['texte']));
353        echo "<p><pre>".htmlspecialchars($resultat->xml())."</pre>";*/
354        $texte = preg_replace('@([[:space:],;.:/?!"()«»&]|’)+@', ' ', $texte);
355        $host = $_SERVER['HTTP_HOST'];
356        $serveur = $_SERVER['SERVER_ADDR'];
357        $port = $_SERVER['SERVER_PORT'];
358        $f = fsockopen($serveur, $port);
359
360        $boundary = 'SPIP-Ortho--'.md5(rand().'ortho');
361        $texte = gzcompress($texte);
362        // ouyayay
363        for ($echap = 255; $echap > 0; $echap--) {
364                $str_echap = chr($echap ^ 1).chr($echap).chr($echap).chr($echap ^ 2);
365                if (!is_int(strpos($texte, $str_echap))) break;
366        }
367        $texte = str_replace("\x00", $str_echap, $texte);
368        //echo bin2hex($str_echap).' '.strlen($texte).' '.bin2hex($texte);
369        $vars = array('op' => 'spell', 'lang' => $lang, 'texte' => $texte, 'gz' => 1, 'nul_echap' => $str_echap);
370        $body = '';
371        foreach ($vars as $key => $val) {
372                $body .= "\r\n--$boundary\r\n";
373                $body .= "Content-Disposition: form-data; name=\"$key\"\r\n";
374                $body .= "Content-Type: application/octet-stream\r\n";
375                $body .= "Content-Length: ".strlen($val)."\r\n";
376                $body .= "\r\n";
377                $body .= $val;
378        }
379        $body .= "\r\n--$boundary--\r\n";
380        fputs($f, "POST $uri HTTP/1.0\r\n");
381        fputs($f, "Content-Type: multipart/form-data; boundary=$boundary\r\n");
382        fputs($f, "Content-Length: ".strlen($body)."\r\n");
383        fputs($f, "Host: $host\r\n\r\n");
384
385        fputs($f, $body);
386        $t0 = explode(" ", microtime());
387       
388        echo "<div style='border: #505050 1px solid; background: #e0e0e0; font-size: 95%;'>";
389        $length = 0;
390        while ($s = trim(fgets($f))) {
391                echo "$s<br>";
392                if (preg_match(',Content-Length:(.*),i', $s, $r))
393                        $length = intval($r[1]);
394        }
395        echo "$length</div>\n";
396        $r = "";
397
398        if ($length) $r = fread($f, $length);
399        else while (!feof($f) AND $r .= fread($f, 1024));
400        $t1 = explode(" ", microtime());
401        fclose($f);
402
403        $dt = floor(1000 * ($t1[0] + $t1[1] - $t0[0] - $t0[1])) / 1000;
404        echo "<div style='font-weight: bold; color: red;'>$dt s.</div>";
405
406        //echo $r;
407        echo "<p><pre>".htmlspecialchars(gzuncompress($r))."</pre>";
408        //echo "<p><pre>".htmlspecialchars($r)."</pre>";
409}
410else {
411        echo "<div align='right'><small><a href='http://lab.spip.net/spikini/?wiki=CorrecteurOrthographique'>"
412                ."Ce programme</a> est distribu&eacute; sous licence GNU GPL.</small></div>";
413}
414
415echo "</body></html>";
416
417?>
Note: See TracBrowser for help on using the repository browser.