#include #include #include #include "src/lib/types.cpp" #include "src/lib/dvalue.cpp" #include "src/lib/functionlib.cpp" #include "src/lib/php_serialization.h" int main(int argc, char** argv) { if(argc > 1 && String(argv[1]) == "--normalize") { String wire((std::istreambuf_iterator(std::cin)), std::istreambuf_iterator()); DValue decoded = php_decode(wire); if(!decoded["ok"].to_bool()) { std::cerr << decoded["error_code"].to_string() << "\n"; return(1); } DValue encoded = php_encode(decoded["value"]); if(!encoded["ok"].to_bool()) return(1); std::cout << encoded["value"].to_string(); return(0); } bool ok = true; auto need = [&](bool condition, const char* name) { if(!condition) { std::cerr << name << "\n"; ok = false; } }; auto good = [&](const String& wire) { return(php_decode(wire)["ok"].to_bool()); }; auto bad = [&](const String& wire, const String& code) { DValue result = php_decode(wire); need(!result["ok"].to_bool() && result["value"].is_none() && !result["error"].to_string().empty() && result["error_code"].to_string() == code, wire.substr(0, 70).c_str()); }; for(const String& wire : {"N;", "b:0;", "b:1;", "i:0;", "i:+42;", "i:9007199254740991;", "i:9007199254740992;", "i:9007199254740994;", "i:-9223372036854775808;", "i:9223372036854774784;", "d:INF;", "d:-INF;", "d:NAN;", "d:-0;", "d:1.2345678901234567;", "d:4.9406564584124654e-324;", "a:0:{}"}) need(good(wire), wire.c_str()); for(const String& wire : {"i:9007199254740993;", "i:-9007199254740993;", "i:9223372036854775807;"}) { DValue decoded = php_decode(wire); need(decoded["ok"].to_bool() && decoded["value"].type == 'I', "exact PHP signed integer"); need(php_encode(decoded["value"])["value"].to_string() == wire, "exact PHP integer round trip"); } DValue wide_unsigned; wide_unsigned.set_u64(std::numeric_limits::max()); DValue unsigned_error = php_encode(wide_unsigned); need(!unsigned_error["ok"].to_bool() && unsigned_error["value"].to_string().empty() && unsigned_error["error_code"].to_string() == "integer_range", "unsigned PHP range failure"); wide_unsigned.set_u64((u64)std::numeric_limits::max()); need(php_encode(wide_unsigned)["value"].to_string() == "i:9223372036854775807;", "representable PHP unsigned integer"); for(const String& wire : {"i:9223372036854775808;", "i:-9223372036854775809;"}) bad(wire, "integer_range"); for(const String& wire : {"", "N", "N;x", "b:2;", "i:;", "i:1x;", "d:;", "d: 1;", "d:1e;", "d:0x1;", "s:4:\"abc\";", "s:1:\"x\"", "a:1:{}", "a:1:{b:1;N;}", "a:0:{", "a:-1:{}"}) bad(wire, "invalid_input"); for(const String& wire : {"R:1;", "r:1;", "O:0:\"\":0:{}", "C:0:\"\":0:{}", "E:1:\"x\";", "x;"}) bad(wire, "unsupported_type"); bad("a:100001:{}", "entry_limit"); bad("s:999999999999999999999999:\"\";", "byte_limit"); bad(String(php_serialization::max_bytes + 1, 'x'), "byte_limit"); String deep; for(size_t i = 0; i <= php_serialization::max_depth; ++i) deep += "a:1:{i:0;"; deep += "N;"; deep += String(php_serialization::max_depth + 1, '}'); bad(deep, "depth_limit"); String boundary; for(size_t i = 0; i < php_serialization::max_depth; ++i) boundary += "a:1:{i:0;"; boundary += "N;"; boundary += String(php_serialization::max_depth, '}'); DValue depth_boundary = php_decode(boundary); need(depth_boundary["ok"].to_bool() && php_encode(depth_boundary["value"])["ok"].to_bool(), "depth boundary"); String nested_entries = "a:1:{i:0;a:100000:{"; bad(nested_entries, "entry_limit"); DValue duplicate = php_decode("a:3:{s:1:\"x\";i:1;s:1:\"x\";i:2;i:0;s:1:\"z\";}"); need(duplicate["ok"].to_bool() && duplicate["value"]["x"].to_f64() == 2, "last key wins"); DValue keys = php_decode("a:1:{i:9223372036854775807;N;}"); need(keys["ok"].to_bool() && keys["value"].has("9223372036854775807"), "integer keys retain all digits"); DValue list; list.set_array(); String expected = "a:20:{"; for(int i = 0; i < 20; ++i) { DValue n; n.set(double(i)); list.push(n); expected += "i:" + std::to_string(i) + ";i:" + std::to_string(i) + ";"; } expected += "}"; need(php_encode(list)["value"].to_string() == expected, "numeric list iteration"); DValue map; map["z"] = "last"; map["a"] = "first"; need(php_encode(map)["value"].to_string() == "a:2:{s:1:\"a\";s:5:\"first\";s:1:\"z\";s:4:\"last\";}", "map string keys and sorting"); String binary("\0\xff\";\xc0", 5); DValue bytes(binary); String valid_binary = "s:5:\"" + binary + "\";"; for(size_t length = 0; length < valid_binary.size(); ++length) need(!good(valid_binary.substr(0, length)), "truncated binary string"); String valid_array = "a:1:{s:1:\"x\";a:1:{i:0;d:1.25;}}"; for(size_t length = 0; length < valid_array.size(); ++length) need(!good(valid_array.substr(0, length)), "truncated nested array"); DValue encoded = php_encode(bytes); need(php_decode(encoded["value"].to_string())["value"].to_string() == binary, "binary bytes"); for(double n : { -0.0, 1.25, 1e300, 1e-300, std::numeric_limits::denorm_min(), 9007199254740992.0, -9223372036854775808.0, 9223372036854775808.0, std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN() }) { DValue number; number.set(n); DValue round = php_decode(php_encode(number)["value"].to_string()); double actual = round["value"].to_f64(); need(round["ok"].to_bool() && (std::isnan(n) ? std::isnan(actual) : actual == n && std::signbit(n) == std::signbit(actual)), "double round trip"); } struct Comma : std::numpunct { char do_decimal_point() const override { return(','); } }; std::locale previous = std::locale(); std::locale::global(std::locale(std::locale::classic(), new Comma)); DValue fraction; fraction.set(1.25); need(php_encode(fraction)["value"].to_string() == "d:1.25;" && php_decode("d:1.25;")["value"].to_f64() == 1.25, "locale independence"); std::locale::global(previous); DValue byte_boundary(String(php_serialization::max_bytes - 13, 'x')); DValue boundary_encoded = php_encode(byte_boundary); need(boundary_encoded["ok"].to_bool() && boundary_encoded["value"].to_string().size() == php_serialization::max_bytes && good(boundary_encoded["value"].to_string()), "byte boundary"); DValue large(String(php_serialization::max_bytes, 'x')); need(php_encode(large)["error_code"].to_string() == "byte_limit", "encode byte limit"); DValue many; many.set_array(); for(size_t i = 0; i < php_serialization::max_entries; ++i) many[i].set_none(); DValue many_encoded = php_encode(many); need(many_encoded["ok"].to_bool() && good(many_encoded["value"].to_string()), "entry boundary"); many[php_serialization::max_entries].set_none(); need(php_encode(many)["error_code"].to_string() == "entry_limit", "encode entry limit"); DValue cycle; cycle.set_array(); cycle.put_node("0", &cycle); need(php_encode(cycle)["error_code"].to_string() == "depth_limit", "encode cyclic depth limit"); cycle.remove("0"); DValue unsupported; unsupported.set_type('C'); need(php_encode(unsupported)["error_code"].to_string() == "unsupported_type", "unsupported any"); if(argc > 1 && String(argv[1]) == "--fixtures") { DValue fixture; fixture["list"].set(list); fixture["map"].set(map); fixture["binary"].set(bytes); fixture["none"].set_none(); fixture["bool"].set_bool(true); fixture["fraction"].set(1.2345678901234567); std::cout << php_encode(fixture)["value"].to_string(); } return(ok ? 0 : 1); }