| 2 | | // +----------------------------------------------------------------------+ |
|---|
| 3 | | // | PHP version 4 | |
|---|
| 4 | | // +----------------------------------------------------------------------+ |
|---|
| 5 | | // | Copyright (c) 2005 Michal Migurski | |
|---|
| 6 | | // +----------------------------------------------------------------------+ |
|---|
| 7 | | // | This source file is subject to version 3.0 of the PHP license, | |
|---|
| 8 | | // | that is bundled with this package in the file LICENSE, and is | |
|---|
| 9 | | // | available through the world-wide-web at the following url: | |
|---|
| 10 | | // | http://www.php.net/license/3_0.txt. | |
|---|
| 11 | | // | If you did not receive a copy of the PHP license and are unable to | |
|---|
| 12 | | // | obtain it through the world-wide-web, please send a note to | |
|---|
| 13 | | // | license@php.net so we can mail you a copy immediately. | |
|---|
| 14 | | // +----------------------------------------------------------------------+ |
|---|
| 15 | | // | Author: Michal Migurski, mike-json[at]teczno[dot]com | |
|---|
| 16 | | // | with contributions from: | |
|---|
| 17 | | // | Matt Knapp, mdknapp[at]gmail[dot]com | |
|---|
| 18 | | // | Brett Stimmerman, brettstimmerman[at]gmail[dot]com | |
|---|
| 19 | | // +----------------------------------------------------------------------+ |
|---|
| 20 | | // |
|---|
| 21 | | // $Id: JSON.php,v 1.15 2005/06/03 17:31:47 migurski Exp $ |
|---|
| 22 | | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ |
|---|
| | 2 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ |
|---|
| 24 | | define('JSON_SLICE', 1); |
|---|
| 25 | | define('JSON_IN_STR', 2); |
|---|
| 26 | | define('JSON_IN_ARR', 4); |
|---|
| 27 | | define('JSON_IN_OBJ', 8); |
|---|
| 28 | | define('JSON_IN_CMT', 16); |
|---|
| 29 | | define('JSON_LOOSE_TYPE', 10); |
|---|
| 30 | | define('JSON_STRICT_TYPE', 11); |
|---|
| 31 | | |
|---|
| 32 | | /** JSON |
|---|
| 33 | | * Conversion to and from JSON format. |
|---|
| 34 | | * See http://json.org for details. |
|---|
| | 4 | /** |
|---|
| | 5 | * Converts to and from JSON format. |
|---|
| | 6 | * |
|---|
| | 7 | * JSON (JavaScript Object Notation) is a lightweight data-interchange |
|---|
| | 8 | * format. It is easy for humans to read and write. It is easy for machines |
|---|
| | 9 | * to parse and generate. It is based on a subset of the JavaScript |
|---|
| | 10 | * Programming Language, Standard ECMA-262 3rd Edition - December 1999. |
|---|
| | 11 | * This feature can also be found in Python. JSON is a text format that is |
|---|
| | 12 | * completely language independent but uses conventions that are familiar |
|---|
| | 13 | * to programmers of the C-family of languages, including C, C++, C#, Java, |
|---|
| | 14 | * JavaScript, Perl, TCL, and many others. These properties make JSON an |
|---|
| | 15 | * ideal data-interchange language. |
|---|
| | 16 | * |
|---|
| | 17 | * This package provides a simple encoder and decoder for JSON notation. It |
|---|
| | 18 | * is intended for use with client-side Javascript applications that make |
|---|
| | 19 | * use of HTTPRequest to perform server communication functions - data can |
|---|
| | 20 | * be encoded into JSON notation for use in a client-side javascript, or |
|---|
| | 21 | * decoded from incoming Javascript requests. JSON format is native to |
|---|
| | 22 | * Javascript, and can be directly eval()'ed with no further parsing |
|---|
| | 23 | * overhead |
|---|
| | 24 | * |
|---|
| | 25 | * All strings should be in ASCII or UTF-8 format! |
|---|
| | 26 | * |
|---|
| | 27 | * LICENSE: Redistribution and use in source and binary forms, with or |
|---|
| | 28 | * without modification, are permitted provided that the following |
|---|
| | 29 | * conditions are met: Redistributions of source code must retain the |
|---|
| | 30 | * above copyright notice, this list of conditions and the following |
|---|
| | 31 | * disclaimer. Redistributions in binary form must reproduce the above |
|---|
| | 32 | * copyright notice, this list of conditions and the following disclaimer |
|---|
| | 33 | * in the documentation and/or other materials provided with the |
|---|
| | 34 | * distribution. |
|---|
| | 35 | * |
|---|
| | 36 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED |
|---|
| | 37 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
|---|
| | 38 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN |
|---|
| | 39 | * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|---|
| | 40 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|---|
| | 41 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
|---|
| | 42 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|---|
| | 43 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
|---|
| | 44 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
|---|
| | 45 | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
|---|
| | 46 | * DAMAGE. |
|---|
| | 47 | * |
|---|
| | 48 | * @category |
|---|
| | 49 | * @package Services_JSON |
|---|
| | 50 | * @author Michal Migurski <mike-json@teczno.com> |
|---|
| | 51 | * @author Matt Knapp <mdknapp[at]gmail[dot]com> |
|---|
| | 52 | * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> |
|---|
| | 53 | * @copyright 2005 Michal Migurski |
|---|
| | 54 | * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ |
|---|
| | 55 | * @license http://www.opensource.org/licenses/bsd-license.php |
|---|
| | 56 | * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 |
|---|
| | 57 | */ |
|---|
| | 58 | |
|---|
| | 59 | /** |
|---|
| | 60 | * Marker constant for Services_JSON::decode(), used to flag stack state |
|---|
| | 61 | */ |
|---|
| | 62 | define('SERVICES_JSON_SLICE', 1); |
|---|
| | 63 | |
|---|
| | 64 | /** |
|---|
| | 65 | * Marker constant for Services_JSON::decode(), used to flag stack state |
|---|
| | 66 | */ |
|---|
| | 67 | define('SERVICES_JSON_IN_STR', 2); |
|---|
| | 68 | |
|---|
| | 69 | /** |
|---|
| | 70 | * Marker constant for Services_JSON::decode(), used to flag stack state |
|---|
| | 71 | */ |
|---|
| | 72 | define('SERVICES_JSON_IN_ARR', 3); |
|---|
| | 73 | |
|---|
| | 74 | /** |
|---|
| | 75 | * Marker constant for Services_JSON::decode(), used to flag stack state |
|---|
| | 76 | */ |
|---|
| | 77 | define('SERVICES_JSON_IN_OBJ', 4); |
|---|
| | 78 | |
|---|
| | 79 | /** |
|---|
| | 80 | * Marker constant for Services_JSON::decode(), used to flag stack state |
|---|
| | 81 | */ |
|---|
| | 82 | define('SERVICES_JSON_IN_CMT', 5); |
|---|
| | 83 | |
|---|
| | 84 | /** |
|---|
| | 85 | * Behavior switch for Services_JSON::decode() |
|---|
| | 86 | */ |
|---|
| | 87 | define('SERVICES_JSON_LOOSE_TYPE', 16); |
|---|
| | 88 | |
|---|
| | 89 | /** |
|---|
| | 90 | * Behavior switch for Services_JSON::decode() |
|---|
| | 91 | */ |
|---|
| | 92 | define('SERVICES_JSON_SUPPRESS_ERRORS', 32); |
|---|
| | 93 | |
|---|
| | 94 | /** |
|---|
| | 95 | * Converts to and from JSON format. |
|---|
| | 96 | * |
|---|
| | 97 | * Brief example of use: |
|---|
| | 98 | * |
|---|
| | 99 | * <code> |
|---|
| | 100 | * // create a new instance of Services_JSON |
|---|
| | 101 | * $json = new Services_JSON(); |
|---|
| | 102 | * |
|---|
| | 103 | * // convert a complexe value to JSON notation, and send it to the browser |
|---|
| | 104 | * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); |
|---|
| | 105 | * $output = $json->encode($value); |
|---|
| | 106 | * |
|---|
| | 107 | * print($output); |
|---|
| | 108 | * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] |
|---|
| | 109 | * |
|---|
| | 110 | * // accept incoming POST data, assumed to be in JSON notation |
|---|
| | 111 | * $input = file_get_contents('php://input', 1000000); |
|---|
| | 112 | * $value = $json->decode($input); |
|---|
| | 113 | * </code> |
|---|
| | 114 | */ |
|---|
| | 115 | class Services_JSON |
|---|
| | 116 | { |
|---|
| | 117 | /** |
|---|
| | 118 | * constructs a new JSON instance |
|---|
| 57 | | /** function encode |
|---|
| 58 | | * encode an arbitrary variable into JSON format |
|---|
| 59 | | * |
|---|
| 60 | | * @param var mixed any number, boolean, string, array, or object to be encoded. |
|---|
| 61 | | * see argument 1 to JSON() above for array-parsing behavior. |
|---|
| 62 | | * if var is a strng, note that encode() always expects it |
|---|
| 63 | | * to be in ASCII or UTF-8 format! |
|---|
| 64 | | * |
|---|
| 65 | | * @return string JSON string representation of input var |
|---|
| 66 | | */ |
|---|
| 67 | | function encode($var) |
|---|
| 68 | | { |
|---|
| 69 | | switch(gettype($var)) { |
|---|
| 70 | | case 'boolean': |
|---|
| 71 | | return $var ? 'true' : 'false'; |
|---|
| 72 | | |
|---|
| 73 | | case 'NULL': |
|---|
| 74 | | return 'null'; |
|---|
| 75 | | |
|---|
| 76 | | case 'integer': |
|---|
| 77 | | return sprintf('%d', $var); |
|---|
| 78 | | |
|---|
| 79 | | case 'double': |
|---|
| 80 | | case 'float': |
|---|
| 81 | | return sprintf('%f', $var); |
|---|
| 82 | | |
|---|
| 83 | | case 'string': // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT |
|---|
| 84 | | $ascii = ''; |
|---|
| 85 | | $strlen_var = strlen($var); |
|---|
| 86 | | |
|---|
| 87 | | for($c = 0; $c < $strlen_var; $c++) { |
|---|
| 88 | | |
|---|
| 89 | | $ord_var_c = ord($var{$c}); |
|---|
| 90 | | |
|---|
| 91 | | if($ord_var_c == 0x08) { |
|---|
| | 156 | $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); |
|---|
| | 157 | |
|---|
| | 158 | switch(true) { |
|---|
| | 159 | case ((0x7F & $bytes) == $bytes): |
|---|
| | 160 | // this case should never be reached, because we are in ASCII range |
|---|
| | 161 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 162 | return chr(0x7F & $bytes); |
|---|
| | 163 | |
|---|
| | 164 | case (0x07FF & $bytes) == $bytes: |
|---|
| | 165 | // return a 2-byte UTF-8 character |
|---|
| | 166 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 167 | return chr(0xC0 | (($bytes >> 6) & 0x1F)) |
|---|
| | 168 | . chr(0x80 | ($bytes & 0x3F)); |
|---|
| | 169 | |
|---|
| | 170 | case (0xFFFF & $bytes) == $bytes: |
|---|
| | 171 | // return a 3-byte UTF-8 character |
|---|
| | 172 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 173 | return chr(0xE0 | (($bytes >> 12) & 0x0F)) |
|---|
| | 174 | . chr(0x80 | (($bytes >> 6) & 0x3F)) |
|---|
| | 175 | . chr(0x80 | ($bytes & 0x3F)); |
|---|
| | 176 | } |
|---|
| | 177 | |
|---|
| | 178 | // ignoring UTF-32 for now, sorry |
|---|
| | 179 | return ''; |
|---|
| | 180 | } |
|---|
| | 181 | |
|---|
| | 182 | /** |
|---|
| | 183 | * convert a string from one UTF-8 char to one UTF-16 char |
|---|
| | 184 | * |
|---|
| | 185 | * Normally should be handled by mb_convert_encoding, but |
|---|
| | 186 | * provides a slower PHP-only method for installations |
|---|
| | 187 | * that lack the multibye string extension. |
|---|
| | 188 | * |
|---|
| | 189 | * @param string $utf8 UTF-8 character |
|---|
| | 190 | * @return string UTF-16 character |
|---|
| | 191 | * @access private |
|---|
| | 192 | */ |
|---|
| | 193 | function utf82utf16($utf8) |
|---|
| | 194 | { |
|---|
| | 195 | // oh please oh please oh please oh please oh please |
|---|
| | 196 | if(function_exists('mb_convert_encoding')) { |
|---|
| | 197 | return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); |
|---|
| | 198 | } |
|---|
| | 199 | |
|---|
| | 200 | switch(strlen($utf8)) { |
|---|
| | 201 | case 1: |
|---|
| | 202 | // this case should never be reached, because we are in ASCII range |
|---|
| | 203 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 204 | return $utf8; |
|---|
| | 205 | |
|---|
| | 206 | case 2: |
|---|
| | 207 | // return a UTF-16 character from a 2-byte UTF-8 char |
|---|
| | 208 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 209 | return chr(0x07 & (ord($utf8{0}) >> 2)) |
|---|
| | 210 | . chr((0xC0 & (ord($utf8{0}) << 6)) |
|---|
| | 211 | | (0x3F & ord($utf8{1}))); |
|---|
| | 212 | |
|---|
| | 213 | case 3: |
|---|
| | 214 | // return a UTF-16 character from a 3-byte UTF-8 char |
|---|
| | 215 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 216 | return chr((0xF0 & (ord($utf8{0}) << 4)) |
|---|
| | 217 | | (0x0F & (ord($utf8{1}) >> 2))) |
|---|
| | 218 | . chr((0xC0 & (ord($utf8{1}) << 6)) |
|---|
| | 219 | | (0x7F & ord($utf8{2}))); |
|---|
| | 220 | } |
|---|
| | 221 | |
|---|
| | 222 | // ignoring UTF-32 for now, sorry |
|---|
| | 223 | return ''; |
|---|
| | 224 | } |
|---|
| | 225 | |
|---|
| | 226 | /** |
|---|
| | 227 | * encodes an arbitrary variable into JSON format |
|---|
| | 228 | * |
|---|
| | 229 | * @param mixed $var any number, boolean, string, array, or object to be encoded. |
|---|
| | 230 | * see argument 1 to Services_JSON() above for array-parsing behavior. |
|---|
| | 231 | * if var is a strng, note that encode() always expects it |
|---|
| | 232 | * to be in ASCII or UTF-8 format! |
|---|
| | 233 | * |
|---|
| | 234 | * @return mixed JSON string representation of input var or an error if a problem occurs |
|---|
| | 235 | * @access public |
|---|
| | 236 | */ |
|---|
| | 237 | function encode($var) |
|---|
| | 238 | { |
|---|
| | 239 | switch (gettype($var)) { |
|---|
| | 240 | case 'boolean': |
|---|
| | 241 | return $var ? 'true' : 'false'; |
|---|
| | 242 | |
|---|
| | 243 | case 'NULL': |
|---|
| | 244 | return 'null'; |
|---|
| | 245 | |
|---|
| | 246 | case 'integer': |
|---|
| | 247 | return (int) $var; |
|---|
| | 248 | |
|---|
| | 249 | case 'double': |
|---|
| | 250 | case 'float': |
|---|
| | 251 | return (float) $var; |
|---|
| | 252 | |
|---|
| | 253 | case 'string': |
|---|
| | 254 | // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT |
|---|
| | 255 | $ascii = ''; |
|---|
| | 256 | $strlen_var = strlen($var); |
|---|
| | 257 | |
|---|
| | 258 | /* |
|---|
| | 259 | * Iterate over every character in the string, |
|---|
| | 260 | * escaping with a slash or encoding to UTF-8 where necessary |
|---|
| | 261 | */ |
|---|
| | 262 | for ($c = 0; $c < $strlen_var; ++$c) { |
|---|
| | 263 | |
|---|
| | 264 | $ord_var_c = ord($var{$c}); |
|---|
| | 265 | |
|---|
| | 266 | switch (true) { |
|---|
| | 267 | case $ord_var_c == 0x08: |
|---|
| 111 | | $ascii .= $var{$c}; // most normal ASCII chars |
|---|
| 112 | | |
|---|
| 113 | | } elseif(($ord_var_c & 0xE0) == 0xC0) { |
|---|
| 114 | | // characters U-00000080 - U-000007FF, mask 110XXXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| 115 | | $char = pack('C*', $ord_var_c, ord($var{$c+1})); $c+=1; |
|---|
| 116 | | $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); |
|---|
| 117 | | |
|---|
| 118 | | } elseif(($ord_var_c & 0xF0) == 0xE0) { |
|---|
| 119 | | // characters U-00000800 - U-0000FFFF, mask 1110XXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| 120 | | $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2})); $c+=2; |
|---|
| 121 | | $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); |
|---|
| 122 | | |
|---|
| 123 | | } elseif(($ord_var_c & 0xF8) == 0xF0) { |
|---|
| 124 | | // characters U-00010000 - U-001FFFFF, mask 11110XXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| 125 | | $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3})); $c+=3; |
|---|
| 126 | | $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); |
|---|
| 127 | | |
|---|
| 128 | | } elseif(($ord_var_c & 0xFC) == 0xF8) { |
|---|
| 129 | | // characters U-00200000 - U-03FFFFFF, mask 111110XX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| 130 | | $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3}), ord($var{$c+4})); $c+=4; |
|---|
| 131 | | $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); |
|---|
| 132 | | |
|---|
| 133 | | } elseif(($ord_var_c & 0xFE) == 0xFC) { |
|---|
| 134 | | // characters U-04000000 - U-7FFFFFFF, mask 1111110X, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| 135 | | $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3}), ord($var{$c+4}), ord($var{$c+5})); $c+=5; |
|---|
| 136 | | $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); |
|---|
| 137 | | |
|---|
| | 292 | $ascii .= $var{$c}; |
|---|
| | 293 | break; |
|---|
| | 294 | |
|---|
| | 295 | case (($ord_var_c & 0xE0) == 0xC0): |
|---|
| | 296 | // characters U-00000080 - U-000007FF, mask 110XXXXX |
|---|
| | 297 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 298 | $char = pack('C*', $ord_var_c, ord($var{$c + 1})); |
|---|
| | 299 | $c += 1; |
|---|
| | 300 | $utf16 = $this->utf82utf16($char); |
|---|
| | 301 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); |
|---|
| | 302 | break; |
|---|
| | 303 | |
|---|
| | 304 | case (($ord_var_c & 0xF0) == 0xE0): |
|---|
| | 305 | // characters U-00000800 - U-0000FFFF, mask 1110XXXX |
|---|
| | 306 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 307 | $char = pack('C*', $ord_var_c, |
|---|
| | 308 | ord($var{$c + 1}), |
|---|
| | 309 | ord($var{$c + 2})); |
|---|
| | 310 | $c += 2; |
|---|
| | 311 | $utf16 = $this->utf82utf16($char); |
|---|
| | 312 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); |
|---|
| | 313 | break; |
|---|
| | 314 | |
|---|
| | 315 | case (($ord_var_c & 0xF8) == 0xF0): |
|---|
| | 316 | // characters U-00010000 - U-001FFFFF, mask 11110XXX |
|---|
| | 317 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 318 | $char = pack('C*', $ord_var_c, |
|---|
| | 319 | ord($var{$c + 1}), |
|---|
| | 320 | ord($var{$c + 2}), |
|---|
| | 321 | ord($var{$c + 3})); |
|---|
| | 322 | $c += 3; |
|---|
| | 323 | $utf16 = $this->utf82utf16($char); |
|---|
| | 324 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); |
|---|
| | 325 | break; |
|---|
| | 326 | |
|---|
| | 327 | case (($ord_var_c & 0xFC) == 0xF8): |
|---|
| | 328 | // characters U-00200000 - U-03FFFFFF, mask 111110XX |
|---|
| | 329 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 330 | $char = pack('C*', $ord_var_c, |
|---|
| | 331 | ord($var{$c + 1}), |
|---|
| | 332 | ord($var{$c + 2}), |
|---|
| | 333 | ord($var{$c + 3}), |
|---|
| | 334 | ord($var{$c + 4})); |
|---|
| | 335 | $c += 4; |
|---|
| | 336 | $utf16 = $this->utf82utf16($char); |
|---|
| | 337 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); |
|---|
| | 338 | break; |
|---|
| | 339 | |
|---|
| | 340 | case (($ord_var_c & 0xFE) == 0xFC): |
|---|
| | 341 | // characters U-04000000 - U-7FFFFFFF, mask 1111110X |
|---|
| | 342 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 343 | $char = pack('C*', $ord_var_c, |
|---|
| | 344 | ord($var{$c + 1}), |
|---|
| | 345 | ord($var{$c + 2}), |
|---|
| | 346 | ord($var{$c + 3}), |
|---|
| | 347 | ord($var{$c + 4}), |
|---|
| | 348 | ord($var{$c + 5})); |
|---|
| | 349 | $c += 5; |
|---|
| | 350 | $utf16 = $this->utf82utf16($char); |
|---|
| | 351 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); |
|---|
| | 352 | break; |
|---|
| | 353 | } |
|---|
| | 354 | } |
|---|
| | 355 | |
|---|
| | 356 | return '"'.$ascii.'"'; |
|---|
| | 357 | |
|---|
| | 358 | case 'array': |
|---|
| | 359 | /* |
|---|
| | 360 | * As per JSON spec if any array key is not an integer |
|---|
| | 361 | * we must treat the the whole array as an object. We |
|---|
| | 362 | * also try to catch a sparsely populated associative |
|---|
| | 363 | * array with numeric keys here because some JS engines |
|---|
| | 364 | * will create an array with empty indexes up to |
|---|
| | 365 | * max_index which can cause memory issues and because |
|---|
| | 366 | * the keys, which may be relevant, will be remapped |
|---|
| | 367 | * otherwise. |
|---|
| | 368 | * |
|---|
| | 369 | * As per the ECMA and JSON specification an object may |
|---|
| | 370 | * have any string as a property. Unfortunately due to |
|---|
| | 371 | * a hole in the ECMA specification if the key is a |
|---|
| | 372 | * ECMA reserved word or starts with a digit the |
|---|
| | 373 | * parameter is only accessible using ECMAScript's |
|---|
| | 374 | * bracket notation. |
|---|
| | 375 | */ |
|---|
| | 376 | |
|---|
| | 377 | // treat as a JSON object |
|---|
| | 378 | if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { |
|---|
| | 379 | $properties = array_map(array($this, 'name_value'), |
|---|
| | 380 | array_keys($var), |
|---|
| | 381 | array_values($var)); |
|---|
| | 382 | |
|---|
| | 383 | foreach($properties as $property) { |
|---|
| | 384 | if(Services_JSON::isError($property)) { |
|---|
| | 385 | return $property; |
|---|
| 190 | | /** function reduce_string |
|---|
| 191 | | * reduce a string by removing leading and trailing comments and whitespace |
|---|
| 192 | | * |
|---|
| 193 | | * @param str string string value to strip of comments and whitespace |
|---|
| 194 | | * |
|---|
| 195 | | * @return string string value stripped of comments and whitespace |
|---|
| 196 | | */ |
|---|
| 197 | | function reduce_string($str) |
|---|
| 198 | | { |
|---|
| 199 | | $str = preg_replace('#^\s*//(.+)$#m', '', $str); // eliminate single line comments in '// ...' form |
|---|
| 200 | | $str = preg_replace('#^\s*/\*(.+)\*/#Us', '', $str); // eliminate multi-line comments in '/* ... */' form, at start of string |
|---|
| 201 | | $str = preg_replace('#/\*(.+)\*/\s*$#Us', '', $str); // eliminate multi-line comments in '/* ... */' form, at end of string |
|---|
| 202 | | $str = trim($str); // eliminate extraneous space |
|---|
| 203 | | |
|---|
| 204 | | return $str; |
|---|
| | 425 | /** |
|---|
| | 426 | * array-walking function for use in generating JSON-formatted name-value pairs |
|---|
| | 427 | * |
|---|
| | 428 | * @param string $name name of key to use |
|---|
| | 429 | * @param mixed $value reference to an array element to be encoded |
|---|
| | 430 | * |
|---|
| | 431 | * @return string JSON-formatted name-value pair, like '"name":value' |
|---|
| | 432 | * @access private |
|---|
| | 433 | */ |
|---|
| | 434 | function name_value($name, $value) |
|---|
| | 435 | { |
|---|
| | 436 | $encoded_value = $this->encode($value); |
|---|
| | 437 | |
|---|
| | 438 | if(Services_JSON::isError($encoded_value)) { |
|---|
| | 439 | return $encoded_value; |
|---|
| 207 | | /** function decode |
|---|
| 208 | | * decode a JSON string into appropriate variable |
|---|
| 209 | | * |
|---|
| 210 | | * @param str string JSON-formatted string |
|---|
| 211 | | * |
|---|
| 212 | | * @return mixed number, boolean, string, array, or object |
|---|
| 213 | | * corresponding to given JSON input string. |
|---|
| 214 | | * see argument 1 to JSON() above for object-output behavior. |
|---|
| 215 | | * note that decode() always returns strings |
|---|
| 216 | | * in ASCII or UTF-8 format! |
|---|
| 217 | | */ |
|---|
| 218 | | function decode($str) |
|---|
| 219 | | { |
|---|
| 220 | | $str = $this->reduce_string($str); |
|---|
| 221 | | |
|---|
| 222 | | switch(strtolower($str)) { |
|---|
| 223 | | case 'true': |
|---|
| 224 | | return true; |
|---|
| 225 | | |
|---|
| 226 | | case 'false': |
|---|
| 227 | | return false; |
|---|
| 228 | | |
|---|
| 229 | | case 'null': |
|---|
| 230 | | return null; |
|---|
| 231 | | |
|---|
| 232 | | default: |
|---|
| 233 | | if(is_numeric($str)) { // Lookie-loo, it's a number |
|---|
| 234 | | // return (float)$str; // This would work on its own, but I'm trying to be good about returning integers where appropriate |
|---|
| 235 | | return ((float)$str == (integer)$str) |
|---|
| 236 | | ? (integer)$str |
|---|
| 237 | | : (float)$str; |
|---|
| 238 | | |
|---|
| 239 | | } elseif(preg_match('/^".+"$/s', $str) || preg_match('/^\'.+\'$/s', $str)) { // STRINGS RETURNED IN UTF-8 FORMAT |
|---|
| 240 | | $delim = substr($str, 0, 1); |
|---|
| 241 | | $chrs = substr($str, 1, -1); |
|---|
| 242 | | $utf8 = ''; |
|---|
| 243 | | $strlen_chrs = strlen($chrs); |
|---|
| 244 | | |
|---|
| 245 | | for($c = 0; $c < $strlen_chrs; $c++) { |
|---|
| 246 | | |
|---|
| 247 | | $substr_chrs_c_2 = substr($chrs, $c, 2); |
|---|
| | 442 | return $this->encode(strval($name)) . ':' . $encoded_value; |
|---|
| | 443 | } |
|---|
| 249 | | if($substr_chrs_c_2 == '\b') { |
|---|
| 250 | | $utf8 .= chr(0x08); $c+=1; |
|---|
| 251 | | |
|---|
| 252 | | } elseif($substr_chrs_c_2 == '\t') { |
|---|
| 253 | | $utf8 .= chr(0x09); $c+=1; |
|---|
| 254 | | |
|---|
| 255 | | } elseif($substr_chrs_c_2 == '\n') { |
|---|
| 256 | | $utf8 .= chr(0x0A); $c+=1; |
|---|
| 257 | | |
|---|
| 258 | | } elseif($substr_chrs_c_2 == '\f') { |
|---|
| 259 | | $utf8 .= chr(0x0C); $c+=1; |
|---|
| 260 | | |
|---|
| 261 | | } elseif($substr_chrs_c_2 == '\r') { |
|---|
| 262 | | $utf8 .= chr(0x0D); $c+=1; |
|---|
| 263 | | |
|---|
| 264 | | } elseif(($delim == '"') && (($substr_chrs_c_2 == '\\"') || ($substr_chrs_c_2 == '\\\\') || ($substr_chrs_c_2 == '\\/'))) { |
|---|
| 265 | | $utf8 .= $chrs{++$c}; |
|---|
| 266 | | |
|---|
| 267 | | } elseif(($delim == "'") && (($substr_chrs_c_2 == '\\\'') || ($substr_chrs_c_2 == '\\\\') || ($substr_chrs_c_2 == '\\/'))) { |
|---|
| 268 | | $utf8 .= $chrs{++$c}; |
|---|
| 269 | | |
|---|
| 270 | | } elseif(preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6))) { // single, escaped unicode character |
|---|
| 271 | | $utf16 = chr(hexdec(substr($chrs, ($c+2), 2))) . chr(hexdec(substr($chrs, ($c+4), 2))); |
|---|
| 272 | | $utf8 .= mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); |
|---|
| 273 | | $c+=5; |
|---|
| 274 | | |
|---|
| 275 | | } elseif((ord($chrs{$c}) >= 0x20) && (ord($chrs{$c}) <= 0x7F)) { |
|---|
| | 445 | /** |
|---|
| | 446 | * reduce a string by removing leading and trailing comments and whitespace |
|---|
| | 447 | * |
|---|
| | 448 | * @param $str string string value to strip of comments and whitespace |
|---|
| | 449 | * |
|---|
| | 450 | * @return string string value stripped of comments and whitespace |
|---|
| | 451 | * @access private |
|---|
| | 452 | */ |
|---|
| | 453 | function reduce_string($str) |
|---|
| | 454 | { |
|---|
| | 455 | $str = preg_replace(array( |
|---|
| | 456 | |
|---|
| | 457 | // eliminate single line comments in '// ...' form |
|---|
| | 458 | '#^\s*//(.+)$#m', |
|---|
| | 459 | |
|---|
| | 460 | // eliminate multi-line comments in '/* ... */' form, at start of string |
|---|
| | 461 | '#^\s*/\*(.+)\*/#Us', |
|---|
| | 462 | |
|---|
| | 463 | // eliminate multi-line comments in '/* ... */' form, at end of string |
|---|
| | 464 | '#/\*(.+)\*/\s*$#Us' |
|---|
| | 465 | |
|---|
| | 466 | ), '', $str); |
|---|
| | 467 | |
|---|
| | 468 | // eliminate extraneous space |
|---|
| | 469 | return trim($str); |
|---|
| | 470 | } |
|---|
| | 471 | |
|---|
| | 472 | /** |
|---|
| | 473 | * decodes a JSON string into appropriate variable |
|---|
| | 474 | * |
|---|
| | 475 | * @param string $str JSON-formatted string |
|---|
| | 476 | * |
|---|
| | 477 | * @return mixed number, boolean, string, array, or object |
|---|
| | 478 | * corresponding to given JSON input string. |
|---|
| | 479 | * See argument 1 to Services_JSON() above for object-output behavior. |
|---|
| | 480 | * Note that decode() always returns strings |
|---|
| | 481 | * in ASCII or UTF-8 format! |
|---|
| | 482 | * @access public |
|---|
| | 483 | */ |
|---|
| | 484 | function decode($str) |
|---|
| | 485 | { |
|---|
| | 486 | $str = $this->reduce_string($str); |
|---|
| | 487 | |
|---|
| | 488 | switch (strtolower($str)) { |
|---|
| | 489 | case 'true': |
|---|
| | 490 | return true; |
|---|
| | 491 | |
|---|
| | 492 | case 'false': |
|---|
| | 493 | return false; |
|---|
| | 494 | |
|---|
| | 495 | case 'null': |
|---|
| | 496 | return null; |
|---|
| | 497 | |
|---|
| | 498 | default: |
|---|
| | 499 | $m = array(); |
|---|
| | 500 | |
|---|
| | 501 | if (is_numeric($str)) { |
|---|
| | 502 | // Lookie-loo, it's a number |
|---|
| | 503 | |
|---|
| | 504 | // This would work on its own, but I'm trying to be |
|---|
| | 505 | // good about returning integers where appropriate: |
|---|
| | 506 | // return (float)$str; |
|---|
| | 507 | |
|---|
| | 508 | // Return float or int, as appropriate |
|---|
| | 509 | return ((float)$str == (integer)$str) |
|---|
| | 510 | ? (integer)$str |
|---|
| | 511 | : (float)$str; |
|---|
| | 512 | |
|---|
| | 513 | } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { |
|---|
| | 514 | // STRINGS RETURNED IN UTF-8 FORMAT |
|---|
| | 515 | $delim = substr($str, 0, 1); |
|---|
| | 516 | $chrs = substr($str, 1, -1); |
|---|
| | 517 | $utf8 = ''; |
|---|
| | 518 | $strlen_chrs = strlen($chrs); |
|---|
| | 519 | |
|---|
| | 520 | for ($c = 0; $c < $strlen_chrs; ++$c) { |
|---|
| | 521 | |
|---|
| | 522 | $substr_chrs_c_2 = substr($chrs, $c, 2); |
|---|
| | 523 | $ord_chrs_c = ord($chrs{$c}); |
|---|
| | 524 | |
|---|
| | 525 | switch (true) { |
|---|
| | 526 | case $substr_chrs_c_2 == '\b': |
|---|
| | 527 | $utf8 .= chr(0x08); |
|---|
| | 528 | ++$c; |
|---|
| | 529 | break; |
|---|
| | 530 | case $substr_chrs_c_2 == '\t': |
|---|
| | 531 | $utf8 .= chr(0x09); |
|---|
| | 532 | ++$c; |
|---|
| | 533 | break; |
|---|
| | 534 | case $substr_chrs_c_2 == '\n': |
|---|
| | 535 | $utf8 .= chr(0x0A); |
|---|
| | 536 | ++$c; |
|---|
| | 537 | break; |
|---|
| | 538 | case $substr_chrs_c_2 == '\f': |
|---|
| | 539 | $utf8 .= chr(0x0C); |
|---|
| | 540 | ++$c; |
|---|
| | 541 | break; |
|---|
| | 542 | case $substr_chrs_c_2 == '\r': |
|---|
| | 543 | $utf8 .= chr(0x0D); |
|---|
| | 544 | ++$c; |
|---|
| | 545 | break; |
|---|
| | 546 | |
|---|
| | 547 | case $substr_chrs_c_2 == '\\"': |
|---|
| | 548 | case $substr_chrs_c_2 == '\\\'': |
|---|
| | 549 | case $substr_chrs_c_2 == '\\\\': |
|---|
| | 550 | case $substr_chrs_c_2 == '\\/': |
|---|
| | 551 | if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || |
|---|
| | 552 | ($delim == "'" && $substr_chrs_c_2 != '\\"')) { |
|---|
| | 553 | $utf8 .= $chrs{++$c}; |
|---|
| | 554 | } |
|---|
| | 555 | break; |
|---|
| | 556 | |
|---|
| | 557 | case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): |
|---|
| | 558 | // single, escaped unicode character |
|---|
| | 559 | $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) |
|---|
| | 560 | . chr(hexdec(substr($chrs, ($c + 4), 2))); |
|---|
| | 561 | $utf8 .= $this->utf162utf8($utf16); |
|---|
| | 562 | $c += 5; |
|---|
| | 563 | break; |
|---|
| | 564 | |
|---|
| | 565 | case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): |
|---|
| 277 | | |
|---|
| 278 | | } |
|---|
| 279 | | |
|---|
| | 567 | break; |
|---|
| | 568 | |
|---|
| | 569 | case ($ord_chrs_c & 0xE0) == 0xC0: |
|---|
| | 570 | // characters U-00000080 - U-000007FF, mask 110XXXXX |
|---|
| | 571 | //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 572 | $utf8 .= substr($chrs, $c, 2); |
|---|
| | 573 | ++$c; |
|---|
| | 574 | break; |
|---|
| | 575 | |
|---|
| | 576 | case ($ord_chrs_c & 0xF0) == 0xE0: |
|---|
| | 577 | // characters U-00000800 - U-0000FFFF, mask 1110XXXX |
|---|
| | 578 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 579 | $utf8 .= substr($chrs, $c, 3); |
|---|
| | 580 | $c += 2; |
|---|
| | 581 | break; |
|---|
| | 582 | |
|---|
| | 583 | case ($ord_chrs_c & 0xF8) == 0xF0: |
|---|
| | 584 | // characters U-00010000 - U-001FFFFF, mask 11110XXX |
|---|
| | 585 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 586 | $utf8 .= substr($chrs, $c, 4); |
|---|
| | 587 | $c += 3; |
|---|
| | 588 | break; |
|---|
| | 589 | |
|---|
| | 590 | case ($ord_chrs_c & 0xFC) == 0xF8: |
|---|
| | 591 | // characters U-00200000 - U-03FFFFFF, mask 111110XX |
|---|
| | 592 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 593 | $utf8 .= substr($chrs, $c, 5); |
|---|
| | 594 | $c += 4; |
|---|
| | 595 | break; |
|---|
| | 596 | |
|---|
| | 597 | case ($ord_chrs_c & 0xFE) == 0xFC: |
|---|
| | 598 | // characters U-04000000 - U-7FFFFFFF, mask 1111110X |
|---|
| | 599 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 |
|---|
| | 600 | $utf8 .= substr($chrs, $c, 6); |
|---|
| | 601 | $c += 5; |
|---|
| | 602 | break; |
|---|
| | 603 | |
|---|