├── LICENSE ├── README.md ├── amf3-decode.c ├── amf3-encode.c ├── amf3.c ├── amf3.h ├── config.m4 ├── config.w32 ├── php_amf3.h └── tests └── amf3.phpt /LICENSE: -------------------------------------------------------------------------------- 1 | =============================================================================== 2 | This program is licensed under the terms of the MIT license reproduced below. 3 | This means that the program is free software and can be used for both academic 4 | and commercial purposes at absolutely no cost. 5 | =============================================================================== 6 | 7 | Copyright (C) 2010-2018 Arseny Vakhrushev 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AMF3 encoding/decoding extension for PHP 2 | ======================================== 3 | 4 | [PHP-AMF3] extension provides the following API: 5 | 6 | ### amf3_encode(mixed $value [, int $opts = 0 ]) 7 | Returns a binary string containing an AMF3 representation of `$value`. On error, returns `FALSE` 8 | and issues a warning message. The `$opts` argument is a bitmask of the following bit constants: 9 | - `AMF3_FORCE_OBJECT`: force encoding non-indexed arrays as anonymous objects; 10 | 11 | Objects implementing `AMF3Serializable` interface can customize their AMF3 representation: 12 | ```php 13 | class MyClass implements AMF3Serializable { 14 | ... 15 | public function __toAMF3() { 16 | return [ 17 | 'foo' => $this->getFoo(), 18 | 'bar' => $this->bar, 19 | ]; 20 | } 21 | } 22 | ``` 23 | 24 | ### amf3_decode(string $data [, int &$pos [, int $opts = 0 ]]) 25 | Returns the value encoded in `$data`. Optional `$pos` marks where to start reading in `$data` 26 | (default is 0). Upon return, it contains the index of the first unread byte (-1 indicates an error). 27 | The `$opts` argument is a bitmask of the following bit constants: 28 | - `AMF3_CLASS_MAP`: enable class mapping mode (see the usage constrains below); 29 | - `AMF3_CLASS_AUTOLOAD`: enable the PHP class autoloading mechanism in class mapping mode; 30 | - `AMF3_CLASS_CONSTRUCT`: call the default constructor for every new object in class mapping mode; 31 | 32 | 33 | Installation 34 | ------------ 35 | 36 | To install the extension, type the following in the source directory: 37 | 38 | phpize 39 | ./configure 40 | make 41 | make install 42 | 43 | This should install the extension in your default PHP extension directory. If it doesn't work as 44 | expected, manually put the target `amf3.so` library where the `extension_dir` variable in your 45 | `php.ini` points to. Add the following line to the corresponding section in your `php.ini`: 46 | 47 | extension=amf3.so 48 | 49 | To run tests, type: 50 | 51 | make test 52 | 53 | 54 | Usage constraints 55 | ----------------- 56 | 57 | - PHP `NULL`, `boolean`, `integer`, `float` (double), `string`, `array`, and `object` values are 58 | fully convertible to/from their corresponding AMF3 types; 59 | - AMF3 `Date` becomes a float value whereas `XML`, `XMLDocument`, and `ByteArray` become strings; 60 | - In a special case, PHP integers are converted to AMF3 doubles according to the specification. 61 | - A PHP `array` is encoded as an indexed array when it has purely integer keys that start from zero 62 | and have no gaps. An empty array adheres to this rule. In all other cases, an array is encoded as 63 | as associative array to avoid ambiguity. 64 | - When class mapping is disabled (the default), AMF3 objects are returned as associative PHP arrays. 65 | Otherwise, they are returned as PHP objects. 66 | 67 | 68 | [PHP-AMF3]: https://github.com/neoxic/php-amf3 69 | -------------------------------------------------------------------------------- /amf3-decode.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2010-2018 Arseny Vakhrushev 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a copy 5 | ** of this software and associated documentation files (the "Software"), to deal 6 | ** in the Software without restriction, including without limitation the rights 7 | ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | ** copies of the Software, and to permit persons to whom the Software is 9 | ** furnished to do so, subject to the following conditions: 10 | ** 11 | ** The above copyright notice and this permission notice shall be included in 12 | ** all copies or substantial portions of the Software. 13 | ** 14 | ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | ** THE SOFTWARE. 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | #include "config.h" 25 | #endif 26 | 27 | #include "php.h" 28 | #include "php_amf3.h" 29 | #include "zend_interfaces.h" 30 | #include "amf3.h" 31 | 32 | /* For PHP 7.0 and 7.1 */ 33 | #ifndef HT_ALLOW_COW_VIOLATION 34 | #define HT_ALLOW_COW_VIOLATION(ht) 35 | #endif 36 | 37 | typedef struct { 38 | int fmt, cnt; 39 | zend_string *cls; 40 | const char **fld; 41 | int *flen; 42 | } Traits; 43 | 44 | static size_t decodeByte(const char *buf, size_t pos, size_t size, int *val) { 45 | if (pos >= size) { 46 | php_error(E_WARNING, "Insufficient data at position %zu", pos); 47 | return 0; 48 | } 49 | *val = buf[pos] & 0xff; 50 | return pos + 1; 51 | } 52 | 53 | static size_t decodeU29(const char *buf, size_t pos, size_t size, int *val) { 54 | int len = 0, x = 0; 55 | unsigned char c; 56 | buf += pos; 57 | do { 58 | if (pos + len >= size) { 59 | php_error(E_WARNING, "Insufficient U29 data at position %zu", pos); 60 | return 0; 61 | } 62 | c = buf[len++]; 63 | if (len == 4) { 64 | x <<= 8; 65 | x |= c; 66 | break; 67 | } 68 | x <<= 7; 69 | x |= c & 0x7f; 70 | } while (c & 0x80); 71 | *val = x; 72 | return pos + len; 73 | } 74 | 75 | static size_t decodeInteger(const char *buf, size_t pos, size_t size, zval *val) { 76 | int x; 77 | pos = decodeU29(buf, pos, size, &x); 78 | if (!pos) return 0; 79 | if (x & 0x10000000) x -= 0x20000000; 80 | ZVAL_LONG(val, x); 81 | return pos; 82 | } 83 | 84 | static size_t decodeU32(const char *buf, size_t pos, size_t size, zval *val, int sign) { 85 | union { int i; char c; } t; 86 | union { unsigned u; char c[4]; } u; 87 | long x; 88 | if (pos + 4 > size) { 89 | php_error(E_WARNING, "Insufficient U32 data at position %zu", pos); 90 | return 0; 91 | } 92 | buf += pos; 93 | t.i = 1; 94 | if (!t.c) memcpy(u.c, buf, 4); 95 | else { /* Little-endian machine */ 96 | int i; 97 | for (i = 0; i < 4; ++i) u.c[i] = buf[3 - i]; 98 | } 99 | if (sign) x = (signed)u.u; 100 | else x = u.u; 101 | ZVAL_LONG(val, x); 102 | return pos + 4; 103 | } 104 | 105 | static size_t decodeDouble(const char *buf, size_t pos, size_t size, zval *val) { 106 | union { int i; char c; } t; 107 | union { double d; char c[8]; } u; 108 | if (pos + 8 > size) { 109 | php_error(E_WARNING, "Insufficient IEEE-754 data at position %zu", pos); 110 | return 0; 111 | } 112 | buf += pos; 113 | t.i = 1; 114 | if (!t.c) memcpy(u.c, buf, 8); 115 | else { /* Little-endian machine */ 116 | int i; 117 | for (i = 0; i < 8; ++i) u.c[i] = buf[7 - i]; 118 | } 119 | ZVAL_DOUBLE(val, u.d); 120 | return pos + 8; 121 | } 122 | 123 | static size_t decodeString(const char *buf, size_t pos, size_t size, zval *val, const char **str, int *len, HashTable *ht, int raw) { 124 | int pfx, def; 125 | size_t _pos = pos; 126 | pos = decodeU29(buf, pos, size, &pfx); 127 | if (!pos) return 0; 128 | def = pfx & 1; 129 | pfx >>= 1; 130 | if (def) { 131 | if (pos + pfx > size) { 132 | php_error(E_WARNING, "Insufficient data of length %d at position %zu", pfx, pos); 133 | return 0; 134 | } 135 | buf += pos; 136 | pos += pfx; 137 | if (val) ZVAL_STRINGL(val, buf, pfx); 138 | else { 139 | *str = buf; 140 | *len = pfx; 141 | } 142 | if (raw || pfx) { /* Empty string is never sent by reference */ 143 | zval hv; 144 | if (val) ZVAL_COPY(&hv, val); 145 | else ZVAL_STRINGL(&hv, buf, pfx); 146 | zend_hash_next_index_insert(ht, &hv); 147 | } 148 | } else { 149 | zval *hv; 150 | if (!(hv = zend_hash_index_find(ht, pfx))) { 151 | php_error(E_WARNING, "Invalid reference %d at position %zu", pfx, _pos); 152 | return 0; 153 | } 154 | if (val) ZVAL_COPY(val, hv); 155 | else { 156 | *str = Z_STRVAL_P(hv); 157 | *len = Z_STRLEN_P(hv); 158 | } 159 | } 160 | return pos; 161 | } 162 | 163 | static size_t decodeRef(const char *buf, size_t pos, size_t size, int *num, zval *val, HashTable *ht) { 164 | int pfx, def; 165 | size_t _pos = pos; 166 | pos = decodeU29(buf, pos, size, &pfx); 167 | if (!pos) return 0; 168 | def = pfx & 1; 169 | pfx >>= 1; 170 | if (def) *num = pfx; 171 | else { 172 | zval *hv; 173 | if (!(hv = zend_hash_index_find(ht, pfx))) { 174 | php_error(E_WARNING, "Invalid reference %d at position %zu", pfx, _pos); 175 | return 0; 176 | } 177 | *num = -1; 178 | ZVAL_COPY(val, hv); 179 | } 180 | return pos; 181 | } 182 | 183 | static void storeRef(zval *val, HashTable *ht) { 184 | zval hv; 185 | ZVAL_NEW_REF(&hv, val); 186 | Z_TRY_ADDREF_P(val); 187 | zend_hash_next_index_insert(ht, &hv); 188 | } 189 | 190 | static size_t decodeDate(const char *buf, size_t pos, size_t size, zval *val, HashTable *ht) { 191 | int pfx; 192 | pos = decodeRef(buf, pos, size, &pfx, val, ht); 193 | if (!pos) return 0; 194 | if (pfx != -1) { 195 | pos = decodeDouble(buf, pos, size, val); 196 | if (!pos) return 0; 197 | storeRef(val, ht); 198 | } 199 | return pos; 200 | } 201 | 202 | static zval *newHashIdx(zval *val) { 203 | zval hv; 204 | ZVAL_UNDEF(&hv); 205 | HT_ALLOW_COW_VIOLATION(HASH_OF(val)); /* PHP DEBUG: suppress reference counter check */ 206 | return zend_hash_next_index_insert(HASH_OF(val), &hv); 207 | } 208 | 209 | static zval *newHashKey(zval *val, const char *key, size_t len) { 210 | zval hv; 211 | ZVAL_UNDEF(&hv); 212 | HT_ALLOW_COW_VIOLATION(HASH_OF(val)); /* PHP DEBUG: suppress reference counter check */ 213 | return zend_symtable_str_update(HASH_OF(val), key, len, &hv); 214 | } 215 | 216 | static size_t decodeValue(const char *buf, size_t pos, size_t size, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht); 217 | 218 | static size_t decodeArray(const char *buf, size_t pos, size_t size, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht) { 219 | int len; 220 | pos = decodeRef(buf, pos, size, &len, val, oht); 221 | if (!pos) return 0; 222 | if (len != -1) { 223 | const char *key; 224 | int klen; 225 | array_init(val); 226 | storeRef(val, oht); 227 | for (;;) { /* Associative portion */ 228 | pos = decodeString(buf, pos, size, 0, &key, &klen, sht, 0); 229 | if (!pos) return 0; 230 | if (!klen) break; 231 | pos = decodeValue(buf, pos, size, newHashKey(val, key, klen), opts, sht, oht, tht); 232 | if (!pos) return 0; 233 | } 234 | while (len--) { /* Dense portion */ 235 | pos = decodeValue(buf, pos, size, newHashIdx(val), opts, sht, oht, tht); 236 | if (!pos) return 0; 237 | } 238 | } 239 | return pos; 240 | } 241 | 242 | static size_t decodeObject(const char *buf, size_t pos, size_t size, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht) { 243 | int pfx; 244 | size_t _pos = pos; 245 | pos = decodeRef(buf, pos, size, &pfx, val, oht); 246 | if (!pos) return 0; 247 | if (pfx != -1) { 248 | int map = opts & AMF3_CLASS_MAP; 249 | zend_class_entry *ce = 0; 250 | Traits *tr; 251 | const char *key; 252 | int klen; 253 | int def = pfx & 1; 254 | pfx >>= 1; 255 | if (def) { /* New class definition */ 256 | int i, n = pfx >> 2; 257 | const char *cls; 258 | int clen; 259 | const char **fld = 0; 260 | int *flen = 0; 261 | pos = decodeString(buf, pos, size, 0, &cls, &clen, sht, 0); /* Class name */ 262 | if (!pos) return 0; 263 | if (n > 0) { 264 | if (pos + n > size) { 265 | php_error(E_WARNING, "Invalid number of class members %d at position %zu", n, _pos); 266 | return 0; 267 | } 268 | fld = emalloc(n * sizeof *fld); 269 | flen = emalloc(n * sizeof *flen); 270 | for (i = 0; i < n; ++i) { /* Static member names */ 271 | size_t __pos = pos; 272 | pos = decodeString(buf, pos, size, 0, &key, &klen, sht, 0); 273 | if (!pos) { 274 | n = -1; 275 | break; 276 | } 277 | if (!klen || !key[0]) { 278 | php_error(E_WARNING, "Invalid class member name at position %zu", __pos); 279 | n = -1; 280 | break; 281 | } 282 | fld[i] = key; 283 | flen[i] = klen; 284 | } 285 | if (n < 0) { 286 | efree(fld); 287 | efree(flen); 288 | return 0; 289 | } 290 | } 291 | tr = emalloc(sizeof *tr); 292 | tr->fmt = pfx & 3; 293 | tr->cnt = n; 294 | tr->cls = clen ? zend_string_init(cls, clen, 0) : 0; 295 | tr->fld = fld; 296 | tr->flen = flen; 297 | zend_hash_next_index_insert_ptr(tht, tr); 298 | } else if (!(tr = zend_hash_index_find_ptr(tht, pfx))) { /* Existing class definition */ 299 | php_error(E_WARNING, "Invalid class reference %d at position %zu", pfx, _pos); 300 | return 0; 301 | } 302 | if (!map) array_init(val); 303 | else { 304 | if (!tr->cls) object_init(val); 305 | else { 306 | int mode = ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_SILENT; 307 | if (!(opts & AMF3_CLASS_AUTOLOAD)) mode |= ZEND_FETCH_CLASS_NO_AUTOLOAD; 308 | ce = zend_fetch_class(tr->cls, mode); 309 | if (!ce) { 310 | php_error(E_WARNING, "Unknown class '%s' at position %zu", ZSTR_VAL(tr->cls), _pos); 311 | return 0; 312 | } 313 | object_init_ex(val, ce); 314 | } 315 | } 316 | storeRef(val, oht); 317 | if (tr->fmt & 1) { /* Externalizable */ 318 | pos = decodeValue(buf, pos, size, newHashKey(val, "__data", sizeof "__data" - 1), opts, sht, oht, tht); 319 | if (!pos) return 0; 320 | } else { 321 | int i; 322 | for (i = 0; i < tr->cnt; ++i) { 323 | pos = decodeValue(buf, pos, size, newHashKey(val, tr->fld[i], tr->flen[i]), opts, sht, oht, tht); 324 | if (!pos) return 0; 325 | } 326 | if (tr->fmt & 2) { /* Dynamic */ 327 | for (;;) { 328 | size_t __pos = pos; 329 | pos = decodeString(buf, pos, size, 0, &key, &klen, sht, 0); 330 | if (!pos) return 0; 331 | if (!klen) break; 332 | if (map && !key[0]) { 333 | php_error(E_WARNING, "Invalid class member name at position %zu", __pos); 334 | return 0; 335 | } 336 | pos = decodeValue(buf, pos, size, newHashKey(val, key, klen), opts, sht, oht, tht); 337 | if (!pos) return 0; 338 | } 339 | } 340 | } 341 | if (!map && tr->cls) { 342 | HT_ALLOW_COW_VIOLATION(HASH_OF(val)); /* PHP DEBUG: suppress reference counter check */ 343 | add_assoc_stringl(val, "__class", ZSTR_VAL(tr->cls), ZSTR_LEN(tr->cls)); 344 | } else if (ce && (opts & AMF3_CLASS_CONSTRUCT)) { /* Call the constructor */ 345 | zend_call_method_with_0_params(Z_OBJ_P(val), ce, &ce->constructor, 0, 0); 346 | if (EG(exception)) return 0; 347 | } 348 | } 349 | return pos; 350 | } 351 | 352 | static int decodeVectorItem(const char *buf, int pos, int size, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht, int type) { 353 | switch (type) { 354 | case AMF3_VECTOR_INT: 355 | return decodeU32(buf, pos, size, val, 1); 356 | case AMF3_VECTOR_UINT: 357 | return decodeU32(buf, pos, size, val, 0); 358 | case AMF3_VECTOR_DOUBLE: 359 | return decodeDouble(buf, pos, size, val); 360 | default: 361 | return decodeValue(buf, pos, size, val, opts, sht, oht, tht); 362 | } 363 | } 364 | 365 | static size_t decodeVector(const char *buf, size_t pos, size_t size, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht, int type) { 366 | int len; 367 | pos = decodeRef(buf, pos, size, &len, val, oht); 368 | if (!pos) return 0; 369 | if (len != -1) { 370 | int fv; 371 | pos = decodeByte(buf, pos, size, &fv); /* 'fixed-vector' marker */ 372 | if (!pos) return 0; 373 | if (type == AMF3_VECTOR_OBJECT) { /* 'object-type-name' marker */ 374 | const char *ot; 375 | int otl; 376 | pos = decodeString(buf, pos, size, 0, &ot, &otl, sht, 0); 377 | if (!pos) return 0; 378 | } 379 | array_init(val); 380 | storeRef(val, oht); 381 | while (len--) { 382 | pos = decodeVectorItem(buf, pos, size, newHashIdx(val), opts, sht, oht, tht, type); 383 | if (!pos) return 0; 384 | } 385 | } 386 | return pos; 387 | } 388 | 389 | static size_t decodeDictionary(const char *buf, size_t pos, size_t size, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht) { 390 | /* No support for dictionary in PHP */ 391 | php_error(E_WARNING, "Unsupported 'Dictionary' value at position %zu", pos); 392 | return 0; 393 | } 394 | 395 | static size_t decodeValue(const char *buf, size_t pos, size_t size, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht) { 396 | int type; 397 | size_t _pos = pos; 398 | pos = decodeByte(buf, pos, size, &type); 399 | if (!pos) return 0; 400 | switch (type) { 401 | case AMF3_UNDEFINED: 402 | case AMF3_NULL: 403 | ZVAL_NULL(val); 404 | break; 405 | case AMF3_FALSE: 406 | ZVAL_FALSE(val); 407 | break; 408 | case AMF3_TRUE: 409 | ZVAL_TRUE(val); 410 | break; 411 | case AMF3_INTEGER: 412 | return decodeInteger(buf, pos, size, val); 413 | case AMF3_DOUBLE: 414 | return decodeDouble(buf, pos, size, val); 415 | case AMF3_STRING: 416 | return decodeString(buf, pos, size, val, 0, 0, sht, 0); 417 | case AMF3_XML: 418 | case AMF3_XMLDOC: 419 | case AMF3_BYTEARRAY: 420 | return decodeString(buf, pos, size, val, 0, 0, oht, 1); 421 | case AMF3_DATE: 422 | return decodeDate(buf, pos, size, val, oht); 423 | case AMF3_ARRAY: 424 | return decodeArray(buf, pos, size, val, opts, sht, oht, tht); 425 | case AMF3_OBJECT: 426 | return decodeObject(buf, pos, size, val, opts, sht, oht, tht); 427 | case AMF3_VECTOR_INT: 428 | case AMF3_VECTOR_UINT: 429 | case AMF3_VECTOR_DOUBLE: 430 | case AMF3_VECTOR_OBJECT: 431 | return decodeVector(buf, pos, size, val, opts, sht, oht, tht, type); 432 | case AMF3_DICTIONARY: 433 | return decodeDictionary(buf, pos, size, val, opts, sht, oht, tht); 434 | default: 435 | php_error(E_WARNING, "Invalid value type %d at position %zu", type, _pos); 436 | return 0; 437 | } 438 | return pos; 439 | } 440 | 441 | static void freeTraits(zval *val) { 442 | Traits *tr = Z_PTR_P(val); 443 | if (tr->cls) zend_string_release(tr->cls); 444 | efree(tr->fld); 445 | efree(tr->flen); 446 | efree(tr); 447 | } 448 | 449 | PHP_FUNCTION(amf3_decode) { 450 | const char *buf; 451 | size_t size, pos = 0; 452 | zval *pval = 0; 453 | zend_long opts = 0; 454 | HashTable sht, oht, tht; 455 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|z/l", &buf, &size, &pval, &opts) == FAILURE) return; 456 | if (pval) { 457 | if (Z_TYPE_P(pval) == IS_LONG) { 458 | pos = Z_LVAL_P(pval); 459 | if (pos > size) { 460 | php_error(E_WARNING, "Position out of range"); 461 | ZVAL_LONG(pval, -1); 462 | return; 463 | } 464 | } 465 | zval_ptr_dtor(pval); 466 | } 467 | zend_hash_init(&sht, 0, 0, ZVAL_PTR_DTOR, 0); 468 | zend_hash_init(&oht, 0, 0, ZVAL_PTR_DTOR, 0); 469 | zend_hash_init(&tht, 0, 0, freeTraits, 0); 470 | pos = decodeValue(buf, pos, size, return_value, opts, &sht, &oht, &tht); 471 | zend_hash_destroy(&sht); 472 | zend_hash_destroy(&oht); 473 | zend_hash_destroy(&tht); 474 | if (pval) ZVAL_LONG(pval, pos ? pos : -1); 475 | if (pos) return; 476 | zval_ptr_dtor(return_value); 477 | ZVAL_NULL(return_value); 478 | } 479 | -------------------------------------------------------------------------------- /amf3-encode.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2010-2018 Arseny Vakhrushev 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a copy 5 | ** of this software and associated documentation files (the "Software"), to deal 6 | ** in the Software without restriction, including without limitation the rights 7 | ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | ** copies of the Software, and to permit persons to whom the Software is 9 | ** furnished to do so, subject to the following conditions: 10 | ** 11 | ** The above copyright notice and this permission notice shall be included in 12 | ** all copies or substantial portions of the Software. 13 | ** 14 | ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | ** THE SOFTWARE. 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | #include "config.h" 25 | #endif 26 | 27 | #include "php.h" 28 | #include "php_amf3.h" 29 | #include "zend_smart_str.h" 30 | #include "amf3.h" 31 | 32 | #define MAXDEPTH 100 /* Arbitrary call depth limit for recursion check */ 33 | 34 | static void encodeU29(smart_str *ss, int val) { 35 | char buf[4]; 36 | int len; 37 | val &= 0x1fffffff; 38 | if (val <= 0x7f) { 39 | buf[0] = val; 40 | len = 1; 41 | } else if (val <= 0x3fff) { 42 | buf[0] = (val >> 7) | 0x80; 43 | buf[1] = val & 0x7f; 44 | len = 2; 45 | } else if (val <= 0x1fffff) { 46 | buf[0] = (val >> 14) | 0x80; 47 | buf[1] = (val >> 7) | 0x80; 48 | buf[2] = val & 0x7f; 49 | len = 3; 50 | } else { 51 | buf[0] = (val >> 22) | 0x80; 52 | buf[1] = (val >> 15) | 0x80; 53 | buf[2] = (val >> 8) | 0x80; 54 | buf[3] = val; 55 | len = 4; 56 | } 57 | smart_str_appendl(ss, buf, len); 58 | } 59 | 60 | static void encodeDouble(smart_str *ss, double val) { 61 | union { int i; char c; } t; 62 | union { double d; char c[8]; } u; 63 | char buf[8]; 64 | t.i = 1; 65 | u.d = val; 66 | if (!t.c) memcpy(buf, u.c, 8); 67 | else { /* Little-endian machine */ 68 | int i; 69 | for (i = 0; i < 8; ++i) buf[7 - i] = u.c[i]; 70 | } 71 | smart_str_appendl(ss, buf, 8); 72 | } 73 | 74 | static int encodeRefEx(smart_str *ss, const char *str, size_t len, HashTable *ht) { 75 | int *oidx, nidx; 76 | if ((oidx = zend_hash_str_find_ptr(ht, str, len))) { 77 | encodeU29(ss, *oidx << 1); 78 | return 1; 79 | } 80 | nidx = zend_hash_num_elements(ht); 81 | if (nidx <= AMF3_INT_MAX) zend_hash_str_add_mem(ht, str, len, &nidx, sizeof nidx); 82 | return 0; 83 | } 84 | 85 | static int encodeRef(smart_str *ss, void *ptr, HashTable *ht) { 86 | return encodeRefEx(ss, (char *)&ptr, sizeof ptr, ht); 87 | } 88 | 89 | static void encodeString(smart_str *ss, const char *str, size_t len, HashTable *ht) { 90 | if (len > AMF3_INT_MAX) len = AMF3_INT_MAX; 91 | if (len && encodeRefEx(ss, str, len, ht)) return; /* Empty string is never sent by reference */ 92 | encodeU29(ss, (len << 1) | 1); 93 | smart_str_appendl(ss, str, len); 94 | } 95 | 96 | static void encodeValue(smart_str *ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht, int lvl); 97 | 98 | static void encodeHash(smart_str *ss, HashTable *ht, int opts, HashTable *sht, HashTable *oht, HashTable *tht, int lvl, int obj) { 99 | zend_ulong idx; 100 | zend_string *key; 101 | zval *val; 102 | ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, val) { 103 | if (key) { 104 | const char *str = ZSTR_VAL(key); 105 | size_t len = ZSTR_LEN(key); 106 | if (!len) continue; /* Empty key can't be represented in AMF3 */ 107 | if (obj && !str[0]) continue; /* Skip private/protected property */ 108 | encodeString(ss, str, len, sht); 109 | } else { 110 | char buf[22]; 111 | encodeString(ss, buf, sprintf(buf, "%ld", idx), sht); 112 | } 113 | encodeValue(ss, val, opts, sht, oht, tht, lvl + 1); 114 | } ZEND_HASH_FOREACH_END(); 115 | smart_str_appendc(ss, 0x01); 116 | } 117 | 118 | static void encodeArray(smart_str *ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht, int lvl, int len) { 119 | HashTable *ht = HASH_OF(val); 120 | if (encodeRef(ss, ht, oht)) return; 121 | if (len != -1) { /* Encode as dense array */ 122 | encodeU29(ss, (len << 1) | 1); 123 | smart_str_appendc(ss, 0x01); 124 | ZEND_HASH_FOREACH_VAL(ht, val) { 125 | encodeValue(ss, val, opts, sht, oht, tht, lvl + 1); 126 | } ZEND_HASH_FOREACH_END(); 127 | } else { /* Encode as associative array */ 128 | smart_str_appendc(ss, 0x01); 129 | encodeHash(ss, ht, opts, sht, oht, tht, lvl, 0); 130 | } 131 | } 132 | 133 | static void encodeObject(smart_str *ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht, int lvl) { 134 | HashTable *ht = HASH_OF(val); 135 | zend_class_entry *ce = Z_TYPE_P(val) == IS_OBJECT ? Z_OBJCE_P(val) : zend_standard_class_def; 136 | int *oidx, nidx; 137 | if (encodeRef(ss, ht, oht)) return; 138 | if ((oidx = zend_hash_str_find_ptr(tht, (char *)&ce, sizeof ce))) encodeU29(ss, (*oidx << 2) | 1); 139 | else { 140 | nidx = zend_hash_num_elements(tht); 141 | if (nidx <= AMF3_INT_MAX) zend_hash_str_add_mem(tht, (char *)&ce, sizeof ce, &nidx, sizeof nidx); 142 | smart_str_appendc(ss, 0x0b); 143 | if (ce == zend_standard_class_def) smart_str_appendc(ss, 0x01); /* Anonymous object */ 144 | else encodeString(ss, ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), sht); /* Typed object */ 145 | } 146 | encodeHash(ss, ht, opts, sht, oht, tht, lvl, 1); 147 | } 148 | 149 | static int getArrayLength(zval *val) { 150 | int len = 0; 151 | zend_ulong idx; 152 | zend_string *key; 153 | ZEND_HASH_FOREACH_KEY(HASH_OF(val), idx, key) { 154 | if (key || idx != len || ++len == AMF3_INT_MAX) return -1; 155 | } ZEND_HASH_FOREACH_END(); 156 | return len; 157 | } 158 | 159 | static void encodeValueData(smart_str *ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht, int lvl) { 160 | switch (Z_TYPE_P(val)) { 161 | default: 162 | smart_str_appendc(ss, AMF3_UNDEFINED); 163 | break; 164 | case IS_NULL: 165 | smart_str_appendc(ss, AMF3_NULL); 166 | break; 167 | case IS_FALSE: 168 | smart_str_appendc(ss, AMF3_FALSE); 169 | break; 170 | case IS_TRUE: 171 | smart_str_appendc(ss, AMF3_TRUE); 172 | break; 173 | case IS_LONG: { 174 | zend_long x = Z_LVAL_P(val); 175 | if (x >= AMF3_INT_MIN && x <= AMF3_INT_MAX) { 176 | smart_str_appendc(ss, AMF3_INTEGER); 177 | encodeU29(ss, x); 178 | } else { 179 | smart_str_appendc(ss, AMF3_DOUBLE); 180 | encodeDouble(ss, x); 181 | } 182 | break; 183 | } 184 | case IS_DOUBLE: 185 | smart_str_appendc(ss, AMF3_DOUBLE); 186 | encodeDouble(ss, Z_DVAL_P(val)); 187 | break; 188 | case IS_STRING: 189 | smart_str_appendc(ss, AMF3_STRING); 190 | encodeString(ss, Z_STRVAL_P(val), Z_STRLEN_P(val), sht); 191 | break; 192 | case IS_ARRAY: { 193 | int len = getArrayLength(val); 194 | if (!(opts & AMF3_FORCE_OBJECT) || len != -1) { 195 | smart_str_appendc(ss, AMF3_ARRAY); 196 | encodeArray(ss, val, opts, sht, oht, tht, lvl, len); 197 | break; 198 | } 199 | } /* Fall through; encode array as object */ 200 | case IS_OBJECT: 201 | smart_str_appendc(ss, AMF3_OBJECT); 202 | encodeObject(ss, val, opts, sht, oht, tht, lvl); 203 | break; 204 | case IS_REFERENCE: 205 | encodeValue(ss, Z_REFVAL_P(val), opts, sht, oht, tht, lvl); 206 | break; 207 | } 208 | } 209 | 210 | static void encodeValue(smart_str *ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht, int lvl) { 211 | zval func, res; 212 | if (lvl > MAXDEPTH) zend_error_noreturn(E_ERROR, "Recursion detected"); 213 | if (Z_TYPE_P(val) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(val), amf3_serializable_ce)) { 214 | encodeValueData(ss, val, opts, sht, oht, tht, lvl); 215 | return; 216 | } 217 | ZVAL_STRING(&func, "__toAMF3"); 218 | call_user_function(0, val, &func, &res, 0, 0); 219 | zval_ptr_dtor(&func); 220 | if (EG(exception)) { 221 | zval_ptr_dtor(&res); 222 | return; 223 | } 224 | encodeValueData(ss, &res, opts, sht, oht, tht, lvl); 225 | zval_ptr_dtor(&res); 226 | } 227 | 228 | static void freePtr(zval *val) { 229 | efree(Z_PTR_P(val)); 230 | } 231 | 232 | PHP_FUNCTION(amf3_encode) { 233 | smart_str ss = {0}; 234 | zval *val; 235 | zend_long opts = 0; 236 | HashTable sht, oht, tht; 237 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|l", &val, &opts) == FAILURE) return; 238 | zend_hash_init(&sht, 0, 0, freePtr, 0); 239 | zend_hash_init(&oht, 0, 0, freePtr, 0); 240 | zend_hash_init(&tht, 0, 0, freePtr, 0); 241 | encodeValue(&ss, val, opts, &sht, &oht, &tht, 0); 242 | zend_hash_destroy(&sht); 243 | zend_hash_destroy(&oht); 244 | zend_hash_destroy(&tht); 245 | if (EG(exception)) { 246 | smart_str_free(&ss); 247 | return; 248 | } 249 | smart_str_0(&ss); 250 | RETURN_STR(ss.s); 251 | } 252 | -------------------------------------------------------------------------------- /amf3.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2010-2018 Arseny Vakhrushev 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a copy 5 | ** of this software and associated documentation files (the "Software"), to deal 6 | ** in the Software without restriction, including without limitation the rights 7 | ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | ** copies of the Software, and to permit persons to whom the Software is 9 | ** furnished to do so, subject to the following conditions: 10 | ** 11 | ** The above copyright notice and this permission notice shall be included in 12 | ** all copies or substantial portions of the Software. 13 | ** 14 | ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | ** THE SOFTWARE. 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | #include "config.h" 25 | #endif 26 | 27 | #include "php.h" 28 | #include "php_amf3.h" 29 | #include "ext/standard/info.h" 30 | #include "amf3.h" 31 | 32 | ZEND_BEGIN_ARG_INFO_EX(arginfo_amf3_encode, 0, 0, 1) 33 | ZEND_ARG_INFO(0, value) 34 | ZEND_ARG_INFO(0, options) 35 | ZEND_END_ARG_INFO() 36 | 37 | ZEND_BEGIN_ARG_INFO_EX(arginfo_amf3_decode, 0, 0, 1) 38 | ZEND_ARG_INFO(0, amf3) 39 | ZEND_ARG_INFO(1, count) 40 | ZEND_ARG_INFO(0, options) 41 | ZEND_END_ARG_INFO() 42 | 43 | ZEND_BEGIN_ARG_INFO(arginfo_AMF3Serializable___toAMF3, 0) 44 | ZEND_END_ARG_INFO() 45 | 46 | static const zend_function_entry amf3_functions[] = { 47 | PHP_FE(amf3_encode, arginfo_amf3_encode) 48 | PHP_FE(amf3_decode, arginfo_amf3_decode) 49 | PHP_FE_END 50 | }; 51 | 52 | static const zend_function_entry class_AMF3Serializable_methods[] = { 53 | PHP_ABSTRACT_ME(AMF3Serializable, __toAMF3, arginfo_AMF3Serializable___toAMF3) 54 | PHP_FE_END 55 | }; 56 | 57 | zend_class_entry *amf3_serializable_ce; 58 | 59 | zend_module_entry amf3_module_entry = { 60 | STANDARD_MODULE_HEADER, 61 | "amf3", 62 | amf3_functions, 63 | PHP_MINIT(amf3), 64 | 0, 65 | 0, 66 | 0, 67 | PHP_MINFO(amf3), 68 | PHP_AMF3_VERSION, 69 | STANDARD_MODULE_PROPERTIES 70 | }; 71 | 72 | #ifdef COMPILE_DL_AMF3 73 | ZEND_GET_MODULE(amf3) 74 | #endif 75 | 76 | PHP_MINIT_FUNCTION(amf3) { 77 | zend_class_entry ce; 78 | INIT_CLASS_ENTRY(ce, "AMF3Serializable", class_AMF3Serializable_methods); 79 | amf3_serializable_ce = zend_register_internal_interface(&ce); 80 | REGISTER_LONG_CONSTANT("AMF3_FORCE_OBJECT", AMF3_FORCE_OBJECT, CONST_CS | CONST_PERSISTENT); 81 | REGISTER_LONG_CONSTANT("AMF3_CLASS_MAP", AMF3_CLASS_MAP, CONST_CS | CONST_PERSISTENT); 82 | REGISTER_LONG_CONSTANT("AMF3_CLASS_AUTOLOAD", AMF3_CLASS_AUTOLOAD, CONST_CS | CONST_PERSISTENT); 83 | REGISTER_LONG_CONSTANT("AMF3_CLASS_CONSTRUCT", AMF3_CLASS_CONSTRUCT, CONST_CS | CONST_PERSISTENT); 84 | return SUCCESS; 85 | } 86 | 87 | PHP_MINFO_FUNCTION(amf3) { 88 | php_info_print_table_start(); 89 | php_info_print_table_row(2, "AMF3 support", "enabled"); 90 | php_info_print_table_row(2, "Version", PHP_AMF3_VERSION); 91 | php_info_print_table_end(); 92 | } 93 | -------------------------------------------------------------------------------- /amf3.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2010-2018 Arseny Vakhrushev 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a copy 5 | ** of this software and associated documentation files (the "Software"), to deal 6 | ** in the Software without restriction, including without limitation the rights 7 | ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | ** copies of the Software, and to permit persons to whom the Software is 9 | ** furnished to do so, subject to the following conditions: 10 | ** 11 | ** The above copyright notice and this permission notice shall be included in 12 | ** all copies or substantial portions of the Software. 13 | ** 14 | ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | ** THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #define AMF3_UNDEFINED 0x00 26 | #define AMF3_NULL 0x01 27 | #define AMF3_FALSE 0x02 28 | #define AMF3_TRUE 0x03 29 | #define AMF3_INTEGER 0x04 30 | #define AMF3_DOUBLE 0x05 31 | #define AMF3_STRING 0x06 32 | #define AMF3_XMLDOC 0x07 33 | #define AMF3_DATE 0x08 34 | #define AMF3_ARRAY 0x09 35 | #define AMF3_OBJECT 0x0a 36 | #define AMF3_XML 0x0b 37 | #define AMF3_BYTEARRAY 0x0c 38 | #define AMF3_VECTOR_INT 0x0d 39 | #define AMF3_VECTOR_UINT 0x0e 40 | #define AMF3_VECTOR_DOUBLE 0x0f 41 | #define AMF3_VECTOR_OBJECT 0x10 42 | #define AMF3_DICTIONARY 0x11 43 | 44 | #define AMF3_INT_MIN -268435456 45 | #define AMF3_INT_MAX 268435455 46 | 47 | /* Encoding options */ 48 | #define AMF3_FORCE_OBJECT 0x01 49 | 50 | /* Decoding options */ 51 | #define AMF3_CLASS_MAP 0x01 52 | #define AMF3_CLASS_AUTOLOAD 0x02 53 | #define AMF3_CLASS_CONSTRUCT 0x04 54 | 55 | extern zend_class_entry *amf3_serializable_ce; 56 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | PHP_ARG_ENABLE(amf3, AMF3 support, 2 | [ --enable-amf3 Enable AMF3 support]) 3 | 4 | if test "$PHP_AMF3" != "no"; then 5 | PHP_NEW_EXTENSION(amf3, amf3.c amf3-encode.c amf3-decode.c, $ext_shared) 6 | PHP_SUBST(AMF3_SHARED_LIBADD) 7 | AC_DEFINE([HAVE_AMF3], 1, [AMF3 support]) 8 | fi 9 | -------------------------------------------------------------------------------- /config.w32: -------------------------------------------------------------------------------- 1 | ARG_ENABLE("amf3", "AMF3 support", "no"); 2 | 3 | if (PHP_AMF3 != "no") { 4 | EXTENSION("amf3", "amf3.c amf3-encode.c amf3-decode.c"); 5 | AC_DEFINE("HAVE_AMF3", 1, "AMF3 support"); 6 | } 7 | -------------------------------------------------------------------------------- /php_amf3.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2010-2018 Arseny Vakhrushev 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a copy 5 | ** of this software and associated documentation files (the "Software"), to deal 6 | ** in the Software without restriction, including without limitation the rights 7 | ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | ** copies of the Software, and to permit persons to whom the Software is 9 | ** furnished to do so, subject to the following conditions: 10 | ** 11 | ** The above copyright notice and this permission notice shall be included in 12 | ** all copies or substantial portions of the Software. 13 | ** 14 | ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | ** THE SOFTWARE. 21 | */ 22 | 23 | #ifndef PHP_AMF3_H 24 | #define PHP_AMF3_H 25 | 26 | 27 | #define PHP_AMF3_VERSION "2.1.1" 28 | 29 | extern zend_module_entry amf3_module_entry; 30 | #define phpext_amf3_ptr &amf3_module_entry 31 | 32 | #ifdef ZTS 33 | #include "TSRM.h" 34 | #endif 35 | 36 | PHP_MINIT_FUNCTION(amf3); 37 | PHP_MINFO_FUNCTION(amf3); 38 | 39 | PHP_FUNCTION(amf3_encode); 40 | PHP_FUNCTION(amf3_decode); 41 | 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /tests/amf3.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | PHP-AMF3 test 3 | --SKIPIF-- 4 | 7 | --FILE-- 8 | $k = $any($d + 1); 78 | } 79 | $refs[] = $o; 80 | return $o; 81 | }, 82 | ); 83 | $nobj; 84 | $any = function ($d) use (&$vals, &$objs, &$nobj) { 85 | if ($d < 4 && prob(0.7)) return $objs[rand(0, count($objs) - ($nobj ? 2 : 1))]($d); 86 | return $vals[rand(0, count($vals) - 1)](); 87 | }; 88 | 89 | function spawn() { 90 | global $refs, $any; 91 | $refs = array(); 92 | return $any(0); 93 | } 94 | 95 | //-------------// 96 | // Stress test // 97 | //-------------// 98 | 99 | for ($i = 0; $i < 1000; ++$i) { 100 | $nobj = prob(0.5); 101 | $obj = spawn(); 102 | $str = amf3_encode($obj, $nobj ? AMF3_FORCE_OBJECT : 0); 103 | $pos = 0; 104 | $obj_ = amf3_decode($str, $pos, !$nobj ? AMF3_CLASS_MAP : 0); 105 | if ($pos != strlen($str) || $obj != $obj_) die("Stress test failed!\n"); 106 | 107 | // Additional decoder's robustness test 108 | for ($pos_ = 1; $pos_ <= strlen($str); ++$pos_) { 109 | $pos = $pos_; 110 | @amf3_decode($str, $pos); 111 | } 112 | } 113 | 114 | //-----------------// 115 | // Compliance test // 116 | //-----------------// 117 | 118 | $strs = array( 119 | // Date, XML, XMLDoc, ByteArray 120 | "\x09\x11\x01" // Array (length 8) 121 | . "\x08\x01\x3f\xb9\x99\x99\x99\x99\x99\x9a" // Date (0.1) 122 | . "\x0b\x07\x41\x42\x43" // XML ('ABC') 123 | . "\x07\x07\x44\x45\x46" // XMLDoc ('DEF') 124 | . "\x0c\x07\x11\x22\x33" // ByteArray (0x11 0x22 0x33) 125 | . "\x08\x02" // Date (reference 1) 126 | . "\x0b\x04" // XML (reference 2) 127 | . "\x0b\x06" // XMLDoc (reference 3) 128 | . "\x0c\x08" // ByteArray (reference 4) 129 | , 130 | // Array 131 | "\x09\x05\x01" // Array (length 2) 132 | . "\x09\x07" // Array (length 3) 133 | . "\x03\x41\x04\x00\x03\x42\x04\x01\x00\x04\x02" // Associative part: A:0, B:1, A:2 (should reset the key) 134 | . "\x01" // End of associative part 135 | . "\x02\x03\x04\x00" // Dense part: [false, true, 0] 136 | . "\x09\x02" // Array (reference 1) 137 | , 138 | // Object 139 | "\x09\x11\x01" // Array (length 8) 140 | . "\x0a\x3b\x07\x41\x42\x43\x03\x41\x03\x42\x03\x43" // Dynamic class ABC with static members A, B, C 141 | . "\x04\x01\x04\x02\x04\x03" // Static member values: A:1, B:2, C:3 142 | . "\x03\x44\x04\x04\x03\x45\x04\x05" // Dynamic members: D:4, E:5 143 | . "\x01" // End of dymanic part 144 | . "\x0a\x01" // Object (class reference 0) 145 | . "\x01\x02\x03" // Static member values: A:null, B:false, C:true 146 | . "\x03\x46\x02\x03\x46\x03" // Dynamic members: F:false, F:true (should reset the key) 147 | . "\x01" // End of dymanic part 148 | . "\x0a\x07\x07\x44\x45\x46" // Externalizable class DEF 149 | . "\x02" // __data:false 150 | . "\x0a\x05" // Object (class reference 1) 151 | . "\x03" // __data:true 152 | . "\x0a\x02" // Object (reference 1) 153 | . "\x0a\x04" // Object (reference 2) 154 | . "\x0a\x06" // Object (reference 3) 155 | . "\x0a\x08" // Object (reference 4) 156 | , 157 | // Vector 158 | "\x09\x11\x01" // Array (length 8) 159 | . "\x0d\x05\x00\x00\x01\x02\x03\xff\xff\xff\xff" // Vector of ints [66051, -1] 160 | . "\x0e\x05\x00\x00\x01\x02\x03\xff\xff\xff\xff" // Vector of uints [66051, 4294967295] 161 | . "\x0f\x05\x00\x3f\xb9\x99\x99\x99\x99\x99\x9a\x3f\xc9\x99\x99\x99\x99\x99\x9a" // Vector of doubles [0.1, 0.2] 162 | . "\x10\x07\x01\x03\x2a\x02\x03\x04\x00" // Vector of objects (type '*') [false, true, 0] 163 | . "\x0d\x02" // Vector of ints (reference 1) 164 | . "\x0e\x04" // Vector of uints (reference 2) 165 | . "\x0f\x06" // Vector of doubles (reference 3) 166 | . "\x10\x08" // Vector of objects (reference 4) 167 | ); 168 | 169 | $ba = "\x11\x22\x33"; 170 | $ma = array('A' => 2, 'B' => 1, 0 => false, true, 0); 171 | $o1 = array('A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, '__class' => 'ABC'); 172 | $o2 = array('A' => null, 'B' => false, 'C' => true, 'F' => true, '__class' => 'ABC'); 173 | $o3 = array('__data' => false, '__class' => 'DEF'); 174 | $o4 = array('__data' => true, '__class' => 'DEF'); 175 | $vi = array(66051, -1); 176 | $vu = array(66051, (int)4294967295); // Type cast makes 32-bit systems happy 177 | $vd = array(0.1, 0.2); 178 | $vo = array(false, true, 0); 179 | $objs = array( 180 | array(0.1, 'ABC', 'DEF', $ba, 0.1, 'ABC', 'DEF', $ba), 181 | array($ma, $ma), 182 | array($o1, $o2, $o3, $o4, $o1, $o2, $o3, $o4), 183 | array($vi, $vu, $vd, $vo, $vi, $vu, $vd, $vo), 184 | ); 185 | 186 | for ($i = 0; $i < count($strs); ++$i) { 187 | $str = $strs[$i]; 188 | $obj = $objs[$i]; 189 | $pos = 0; 190 | $obj_ = amf3_decode($str, $pos); 191 | if ($pos != strlen($str) || $obj != $obj_) die("Compliance test failed!\n"); 192 | } 193 | 194 | //-----------------------// 195 | // AMF3Serializable test // 196 | //-----------------------// 197 | 198 | class S1 implements AMF3Serializable { 199 | public function __toAMF3() { 200 | return ['foo' => 123]; 201 | } 202 | } 203 | 204 | class S2 implements AMF3Serializable { 205 | public function __toAMF3() { 206 | return ['bar' => new S1()]; 207 | } 208 | } 209 | 210 | print(bin2hex(amf3_encode(new S2())) . "\n"); 211 | 212 | ?> 213 | --EXPECT-- 214 | 090107626172090107666f6f047b0101 215 | --------------------------------------------------------------------------------