Files
capy-bearer/scripts/test_php_serialization_native.cpp
T

126 lines
7.3 KiB
C++

#include <iostream>
#include <iterator>
#include <utility>
#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<char>(std::cin)), std::istreambuf_iterator<char>());
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<u64>::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<s64>::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<double>::denorm_min(), 9007199254740992.0, -9223372036854775808.0, 9223372036854775808.0, std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(), std::numeric_limits<double>::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> { 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);
}