root/cemail/include/JsHttpRequest.php

Revision 51, 16.9 kB (checked in by teiko, 4 years ago)

набросок cemail

Line 
1 <?php
2 /**
3  * JsHttpRequest: PHP backend for JavaScript DHTML loader.
4  * (C) Dmitry Koterov, http://en.dklab.ru
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  * See http://www.gnu.org/copyleft/lesser.html
11  *
12  * Do not remove this comment if you want to use the script!
13  * � ��� �� �������� ���� ��
14  *
15  * This backend library also supports POST requests additionally to GET.
16  *
17  * @author Dmitry Koterov
18  * @version 5.x $Id: JsHttpRequest.php,v 1.1 2007/03/29 18:21:54 pv Exp $
19  */
20
21 class JsHttpRequest
22 {
23     var $SCRIPT_ENCODING = "windows-1251";
24     var $SCRIPT_DECODE_MODE = '';
25     var $LOADER = null;
26     var $ID = null;   
27     
28     // Internal; uniq value.
29     var $_uniqHash;   
30     // Internal: response content-type depending on loader type.
31     var $_contentTypes = array(
32         "script" => "text/javascript",
33         "xml"    => "text/plain", // In XMLHttpRequest mode we must return text/plain - stupid Opera 8.0. :(
34         "form"   => "text/html",
35         ""       => "text/plain", // for unknown loader
36     );
37     // Internal: conversion to UTF-8 JSON cancelled because of non-ascii key.
38     var $_toUtfFailed = false;
39     // Internal: list of characters 128...255 (for strpbrk() ASCII check).
40     var $_nonAsciiChars = '';
41
42     
43     /**
44      * Constructor.
45      *
46      * Create new JsHttpRequest backend object and attach it
47      * to script output buffer. As a result - script will always return
48      * correct JavaScript code, even in case of fatal errors.
49      */
50     function JsHttpRequest($enc)
51     {
52         // QUERY_STRING is in form of: PHPSESSID=<sid>&a=aaa&b=bbb&JsHttpRequest=<id>-<loader>
53         // where <id> is a request ID, <loader> is a loader name, <sid> - a session ID (if present),
54         // PHPSESSID - session parameter name (by default = "PHPSESSID").
55         
56         // Parse QUERY_STRING.
57         if (preg_match('/^(.*)(?:&|^)JsHttpRequest=(?:(\d+)-)?([^&]+)((?:&|$).*)$/s', $_SERVER['QUERY_STRING'], $m)) {
58             $this->ID = $m[2];
59             $this->LOADER = strtolower($m[3]);
60             $_SERVER['QUERY_STRING'] = preg_replace('/^&+|&+$/s', '', preg_replace('/(^|&)'.session_name().'=[^&]*&?/s', '&', $m[1] . $m[4]));
61             unset(
62                 $_GET['JsHttpRequest'],
63                 $_REQUEST['JsHttpRequest'],
64                 $_GET[session_name()],
65                 $_POST[session_name()],
66                 $_REQUEST[session_name()]
67             );
68         } else {
69             $this->ID = 0;
70             $this->LOADER = 'unknown';
71         }
72
73         // Start OB handling early.
74         $this->_uniqHash = md5(microtime() . getmypid());
75         ini_set('error_prepend_string', ini_get('error_prepend_string') . $this->_uniqHash);
76         ini_set('error_append_string'ini_get('error_append_string') . $this->_uniqHash);
77         ob_start(array(&$this, "_obHandler"));
78
79         // Set up the encoding.
80         $this->setEncoding($enc);
81
82         // Check if headers are already sent (see Content-Type library usage).
83         // If true - generate a debug message and exit.
84         $file = $line = null;
85         if (headers_sent($file, $line)) {
86             trigger_error(
87                 "HTTP headers are already sent" . ($line !== null? " in $file on line $line" : "") . ". "
88                 . "Possibly you have an extra space (or a newline) before the first line of the script or any library. "
89                 . "Please note that JsHttpRequest uses its own Content-Type header and fails if "
90                 . "this header cannot be set. See header() function documentation for more details",
91                 E_USER_ERROR
92             );
93             exit();
94         }
95     }
96
97
98     /**
99      * string getJsCode()
100      *
101      * Return JavaScript part of the library.
102      */
103     function getJsCode()
104     {
105         return file_get_contents(dirname(__FILE__).'/JsHttpRequest.js');
106     }
107
108
109     /**
110      * void setEncoding(string $encoding)
111      *
112      * Set an active script encoding & correct QUERY_STRING according to it.
113      * Examples:
114      *   "windows-1251"          - set plain encoding (non-windows characters,
115      *                             e.g. hieroglyphs, are totally ignored)
116      *   "windows-1251 entities" - set windows encoding, BUT additionally replace:
117      *                             "&"         ->  "&amp;"
118      *                             hieroglyph  ->  &#XXXX; entity
119      */
120     function setEncoding($enc)
121     {
122         // Parse an encoding.
123         preg_match('/^(\S*)(?:\s+(\S*))$/', $enc, $p);
124         $this->SCRIPT_ENCODING    = strtolower(!empty($p[1])? $p[1] : $enc);
125         $this->SCRIPT_DECODE_MODE = !empty($p[2])? $p[2] : '';
126         // Manually parse QUERY_STRING because of damned Unicode's %uXXXX.
127         $this->_correctSuperglobals();
128     }
129
130     
131     /**
132      * string quoteInput(string $input)
133      *
134      * Quote a string according to the input decoding mode.
135      * If entities are used (see setEncoding()), no '&' character is quoted,
136      * only '"', '>' and '<' (we presume that '&' is already quoted by
137      * an input reader function).
138      *
139      * Use this function INSTEAD of htmlspecialchars() for $_GET data
140      * in your scripts.
141      */
142     function quoteInput($s)
143     {
144         if ($this->SCRIPT_DECODE_MODE == 'entities')
145             return str_replace(array('"', '<', '>'), array('&quot;', '&lt;', '&gt;'), $s);
146         else
147             return htmlspecialchars($s);
148     }
149     
150
151     /**
152      * Convert a PHP scalar, array or hash to JS scalar/array/hash. This function is
153      * an analog of json_encode(), but it can work with a non-UTF8 input and does not
154      * analyze the passed data. Output format must be fully JSON compatible.
155      *
156      * @param mixed $a   Any structure to convert to JS.
157      * @return string    JavaScript equivalent structure.
158      */
159     function php2js($a=false)
160     {
161         if (is_null($a)) return 'null';
162         if ($a === false) return 'false';
163         if ($a === true) return 'true';
164         if (is_scalar($a)) {
165             if (is_float($a)) {
166                 // Always use "." for floats.
167                 $a = str_replace(",", ".", strval($a));
168             }
169             // All scalars are converted to strings to avoid indeterminism.
170             // PHP's "1" and 1 are equal for all PHP operators, but
171             // JS's "1" and 1 are not. So if we pass "1" or 1 from the PHP backend,
172             // we should get the same result in the JS frontend (string).
173             // Character replacements for JSON.
174             static $jsonReplaces = array(
175                 array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"'),
176                 array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"')
177             );
178             return '"' . str_replace($jsonReplaces[0], $jsonReplaces[1], $a) . '"';
179         }
180         $isList = true;
181         for ($i = 0, reset($a); $i < count($a); $i++, next($a)) {
182             if (key($a) !== $i) {
183                 $isList = false;
184                 break;
185             }
186         }
187         $result = array();
188         if ($isList) {
189             foreach ($a as $v) {
190                 $result[] = JsHttpRequest::php2js($v);
191             }
192             return '[ ' . join(', ', $result) . ' ]';
193         } else {
194             foreach ($a as $k => $v) {
195                 $result[] = JsHttpRequest::php2js($k) . ': ' . JsHttpRequest::php2js($v);
196             }
197             return '{ ' . join(', ', $result) . ' }';
198         }
199     }
200     
201
202         
203     /**
204      * Internal methods.
205      */
206
207     /**
208      * Parse & decode QUERY_STRING.
209      */
210     function _correctSuperglobals()
211     {
212         // In case of FORM loader we may go to nirvana, everything is already parsed by PHP.
213         if ($this->LOADER == 'form') return;
214         
215         // ATTENTION!!!
216         // HTTP_RAW_POST_DATA is only accessible when Content-Type of POST request
217         // is NOT default "application/x-www-form-urlencoded"!!!
218         // Library frontend sets "application/octet-stream" for that purpose,
219         // see JavaScript code.
220         $source = array(
221             '_GET' => !empty($_SERVER['QUERY_STRING'])? $_SERVER['QUERY_STRING'] : null,
222             '_POST'=> !empty($GLOBALS['HTTP_RAW_POST_DATA'])? $GLOBALS['HTTP_RAW_POST_DATA'] : null
223         );
224         foreach ($source as $dst=>$src) {
225             // First correct all 2-byte entities.
226             $s = preg_replace('/%(?!5B)(?!5D)([0-9a-f]{2})/si', '%u00\\1', $src);
227             // Now we can use standard parse_str() with no worry!
228             $data = null;
229             parse_str($s, $data);
230             $GLOBALS[$dst] = $this->_ucs2EntitiesDecode($data);
231         }
232         $GLOBALS['HTTP_GET_VARS'] = $_GET; // deprecated vars
233         $GLOBALS['HTTP_POST_VARS'] = $_POST;
234         $_REQUEST =
235             (isset($_COOKIE)? $_COOKIE : array()) +
236             (isset($_POST)? $_POST : array()) +
237             (isset($_GET)? $_GET : array());
238         if (ini_get('register_globals')) {
239             // TODO?
240         }
241     }
242
243
244     /**
245      * Called in case of error too!
246      */
247     function _obHandler($text)
248     {
249         // Check for error.
250         if (preg_match('{'.$this->_uniqHash.'(.*?)'.$this->_uniqHash.'}sx', $text)) {
251             $text = str_replace($this->_uniqHash, '', $text);
252         }
253         
254         // Make a resulting hash.
255         if (!isset($this->RESULT)) {
256             $this->RESULT = isset($GLOBALS['_RESULT'])? $GLOBALS['_RESULT'] : null;
257         }
258         $encoding = $this->SCRIPT_ENCODING;
259         $result = array(
260             'id'   => $this->ID,
261             'js'   => $this->RESULT,
262             'text' => $text,
263         );
264         if (function_exists('array_walk_recursive') && function_exists('iconv') && function_exists('json_encode')) {
265             $encoding = "UTF-8";
266             $this->_nonAsciiChars = join("", array_map('chr', range(128, 255)));
267             $this->_toUtfFailed = false;
268             array_walk_recursive($result, array(&$this, '_toUtf8_callback'), $this->SCRIPT_ENCODING);
269             if (!$this->_toUtfFailed) {
270                 // If some key contains non-ASCII character, convert everything manually.
271                 $text = json_encode($result);
272             } else {
273                 $text = $this->php2js($result);
274             }
275         } else {
276             $text = $this->php2js($result);
277         }
278
279         // Content-type header.
280         // In XMLHttpRequest mode we must return text/plain - damned stupid Opera 8.0. :(
281         $ctype = !empty($this->_contentTypes[$this->LOADER])? $this->_contentTypes[$this->LOADER] : $this->_contentTypes[''];
282         header("Content-type: $ctype; charset=$encoding");
283         
284         if ($this->LOADER != "xml") {
285             // In non-XML mode we cannot use plain JSON. So - wrap with JS function call.
286             // If top.JsHttpRequestGlobal is not defined, loading is aborted and
287             // iframe is removed, so - do not call dataReady().
288             $text = ""
289                 . ($this->LOADER == "form"? 'top && top.JsHttpRequestGlobal && top.JsHttpRequestGlobal' : 'JsHttpRequest')
290                 . ".dataReady(" . $text . ")\n"
291                 . "";
292             if ($this->LOADER == "form") {
293                 $text = '<script type="text/javascript" language="JavaScript"><!--' . "\n$text" . '//--></script>';
294             }
295         }
296
297         return $text;
298     }
299
300
301     /**
302      * Internal function, used in array_walk_recursive() before json_encode() call.
303      * If a key contains non-ASCII characters, this function sets $this->_toUtfFailed = true,
304      * becaues array_walk_recursive() cannot modify array keys.
305      */
306     function _toUtf8_callback(&$v, $k, $fromEnc)
307     {
308         if ($this->_toUtfFailed || strpbrk($k, $this->_nonAsciiChars) !== false) {
309             $this->_toUtfFailed = true;
310         } else {
311             $v = iconv($fromEnc, 'UTF-8', $v);
312         }
313     }
314     
315
316     /**
317      * Decode all %uXXXX entities in string or array (recurrent).
318      * String must not contain %XX entities - they are ignored!
319      */
320     function _ucs2EntitiesDecode($data)
321     {
322         if (is_array($data)) {
323             $d = array();
324             foreach ($data as $k=>$v) {
325                 $d[$this->_ucs2EntitiesDecode($k)] = $this->_ucs2EntitiesDecode($v);
326             }
327             return $d;
328         } else {
329             if (strpos($data, '%u') !== false) { // improve speed
330         $data = str_replace('u0025', '', $data); // hack for Konqueror
331                 $data = preg_replace_callback('/%u([0-9A-F]{1,4})/si', array(&$this, '_ucs2EntitiesDecodeCallback'), $data);
332             }
333             return $data;
334         }
335     }
336
337
338     /**
339      * Decode one %uXXXX entity (RE callback).
340      */
341     function _ucs2EntitiesDecodeCallback($p)
342     {
343         $hex = $p[1];
344         $dec = hexdec($hex);
345         if ($dec === "38" && $this->SCRIPT_DECODE_MODE == 'entities') {
346             // Process "&" separately in "entities" decode mode.
347             $c = "&amp;";
348         } else {
349             if (is_callable('iconv')) {
350                 $c = @iconv('UCS-2BE', $this->SCRIPT_ENCODING, pack('n', $dec));
351             } else {
352                 $c = $this->_decUcs2Decode($dec, $this->SCRIPT_ENCODING);
353             }
354             if (!strlen($c)) {
355                 if ($this->SCRIPT_DECODE_MODE == 'entities') {
356                     $c = '&#'.$dec.';';
357                 } else {
358                     $c = '?';
359                 }
360             }
361         }
362         return $c;
363     }
364
365
366     /**
367      * If there is no ICONV, try to decode 1-byte characters manually
368      * (for most popular charsets only).
369      */
370
371     /**
372      * Convert from UCS-2BE decimal to $toEnc.
373      */
374     function _decUcs2Decode($code, $toEnc)
375     {
376         if ($code < 128) return chr($code);
377         if (isset($this->_encTables[$toEnc])) {
378             // TODO: possible speedup by using array_flip($this->_encTables) and later hash access in the constructor.
379             $p = array_search($code, $this->_encTables[$toEnc]);
380             if ($p !== false) return chr(128 + $p);
381         }
382         return "";
383     }
384     
385
386     /**
387      * UCS-2BE -> 1-byte encodings (from #128).
388      */
389     var $_encTables = array(
390         'windows-1251' => array(
391             0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021,
392             0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F,
393             0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
394             0x0098, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F,
395             0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7,
396             0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407,
397             0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7,
398             0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457,
399             0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
400             0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
401             0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
402             0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
403             0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
404             0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
405             0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
406             0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
407         ),
408         'koi8-r' => array(
409             0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524,
410             0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590,
411             0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2219, 0x221A, 0x2248,
412             0x2264, 0x2265, 0x00A0, 0x2321, 0x00B0, 0x00B2, 0x00B7, 0x00F7,
413             0x2550, 0x2551, 0x2552, 0x0451, 0x2553, 0x2554, 0x2555, 0x2556,
414             0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x255C, 0x255d, 0x255E,
415             0x255F, 0x2560, 0x2561, 0x0401, 0x2562, 0x2563, 0x2564, 0x2565,
416             0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x256B, 0x256C, 0x00A9,
417             0x044E, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433,
418             0x0445, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043d, 0x043E,
419             0x043F, 0x044F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432,
420             0x044C, 0x044B, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044A,
421             0x042E, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413,
422             0x0425, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041d, 0x041E,
423             0x041F, 0x042F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412,
424             0x042C, 0x042B, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042A     
425         ),
426     );
427 }
428 ?>
Note: See TracBrowser for help on using the browser.