From f1bb47672223069dc4a1bf771db5940dc6000440 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Fri, 7 Aug 2026 17:12:59 +0000 Subject: [PATCH 01/12] Add encoding API --- ext/encoding/config.m4 | 6 + ext/encoding/encoding.c | 1514 +++++++++++++++++ ext/encoding/encoding.stub.php | 140 ++ ext/encoding/encoding_arginfo.h | 228 +++ ext/encoding/encoding_decl.h | 52 + ext/encoding/php_encoding.h | 11 + .../base16_decode_forgiving_lowercase.phpt | 12 + .../tests/base16_decode_forgiving_spaces.phpt | 12 + .../tests/base16_decode_lowercase_strict.phpt | 15 + .../base16_decode_lowercase_variant.phpt | 11 + .../tests/base16_decode_uppercase.phpt | 10 + .../tests/base16_decode_with_spaces.phpt | 10 + ext/encoding/tests/base16_encode_default.phpt | 11 + ext/encoding/tests/base16_encode_lower.phpt | 12 + .../tests/base32_decode_crockford.phpt | 12 + ext/encoding/tests/base32_decode_default.phpt | 10 + ...se32_decode_forgiving_missing_padding.phpt | 11 + .../base32_decode_missing_padding_strict.phpt | 15 + .../tests/base32_decode_wrong_variant.phpt | 21 + ext/encoding/tests/base32_encode_ascii.phpt | 12 + .../tests/base32_encode_crockford.phpt | 12 + ...code_crockford_preserve_padding_error.phpt | 17 + ext/encoding/tests/base32_encode_default.phpt | 11 + .../tests/base32_encode_strip_padding.phpt | 12 + ext/encoding/tests/base58_decode_bitcoin.phpt | 10 + ext/encoding/tests/base58_decode_flickr.phpt | 11 + .../tests/base58_decode_wrong_variant.phpt | 21 + ext/encoding/tests/base58_encode_bitcoin.phpt | 11 + .../tests/base58_encode_explicit_bitcoin.phpt | 12 + ext/encoding/tests/base58_encode_flickr.phpt | 12 + ...se64_decode_forgiving_missing_padding.phpt | 11 + .../base64_decode_missing_padding_strict.phpt | 15 + .../tests/base64_encode_0xff_bytes.phpt | 15 + ext/encoding/tests/base64_encode_default.phpt | 11 + .../tests/base64_encode_strip_padding.phpt | 12 + ...ecode_adobe_forgiving_missing_padding.phpt | 12 + ...5_decode_adobe_missing_padding_strict.phpt | 16 + ext/encoding/tests/base85_decode_z85.phpt | 11 + .../base85_decode_z85_with_adobe_variant.phpt | 19 + ext/encoding/tests/base85_encode_adobe.phpt | 12 + .../base85_encode_adobe_strip_padding.phpt | 13 + ext/encoding/tests/base85_encode_z85.phpt | 12 + ...base85_encode_z85_strip_padding_error.phpt | 17 + 43 files changed, 2430 insertions(+) create mode 100644 ext/encoding/config.m4 create mode 100644 ext/encoding/encoding.c create mode 100644 ext/encoding/encoding.stub.php create mode 100644 ext/encoding/encoding_arginfo.h create mode 100644 ext/encoding/encoding_decl.h create mode 100644 ext/encoding/php_encoding.h create mode 100644 ext/encoding/tests/base16_decode_forgiving_lowercase.phpt create mode 100644 ext/encoding/tests/base16_decode_forgiving_spaces.phpt create mode 100644 ext/encoding/tests/base16_decode_lowercase_strict.phpt create mode 100644 ext/encoding/tests/base16_decode_lowercase_variant.phpt create mode 100644 ext/encoding/tests/base16_decode_uppercase.phpt create mode 100644 ext/encoding/tests/base16_decode_with_spaces.phpt create mode 100644 ext/encoding/tests/base16_encode_default.phpt create mode 100644 ext/encoding/tests/base16_encode_lower.phpt create mode 100644 ext/encoding/tests/base32_decode_crockford.phpt create mode 100644 ext/encoding/tests/base32_decode_default.phpt create mode 100644 ext/encoding/tests/base32_decode_forgiving_missing_padding.phpt create mode 100644 ext/encoding/tests/base32_decode_missing_padding_strict.phpt create mode 100644 ext/encoding/tests/base32_decode_wrong_variant.phpt create mode 100644 ext/encoding/tests/base32_encode_ascii.phpt create mode 100644 ext/encoding/tests/base32_encode_crockford.phpt create mode 100644 ext/encoding/tests/base32_encode_crockford_preserve_padding_error.phpt create mode 100644 ext/encoding/tests/base32_encode_default.phpt create mode 100644 ext/encoding/tests/base32_encode_strip_padding.phpt create mode 100644 ext/encoding/tests/base58_decode_bitcoin.phpt create mode 100644 ext/encoding/tests/base58_decode_flickr.phpt create mode 100644 ext/encoding/tests/base58_decode_wrong_variant.phpt create mode 100644 ext/encoding/tests/base58_encode_bitcoin.phpt create mode 100644 ext/encoding/tests/base58_encode_explicit_bitcoin.phpt create mode 100644 ext/encoding/tests/base58_encode_flickr.phpt create mode 100644 ext/encoding/tests/base64_decode_forgiving_missing_padding.phpt create mode 100644 ext/encoding/tests/base64_decode_missing_padding_strict.phpt create mode 100644 ext/encoding/tests/base64_encode_0xff_bytes.phpt create mode 100644 ext/encoding/tests/base64_encode_default.phpt create mode 100644 ext/encoding/tests/base64_encode_strip_padding.phpt create mode 100644 ext/encoding/tests/base85_decode_adobe_forgiving_missing_padding.phpt create mode 100644 ext/encoding/tests/base85_decode_adobe_missing_padding_strict.phpt create mode 100644 ext/encoding/tests/base85_decode_z85.phpt create mode 100644 ext/encoding/tests/base85_decode_z85_with_adobe_variant.phpt create mode 100644 ext/encoding/tests/base85_encode_adobe.phpt create mode 100644 ext/encoding/tests/base85_encode_adobe_strip_padding.phpt create mode 100644 ext/encoding/tests/base85_encode_z85.phpt create mode 100644 ext/encoding/tests/base85_encode_z85_strip_padding_error.phpt diff --git a/ext/encoding/config.m4 b/ext/encoding/config.m4 new file mode 100644 index 000000000000..05136e3f307c --- /dev/null +++ b/ext/encoding/config.m4 @@ -0,0 +1,6 @@ +PHP_NEW_EXTENSION([encoding], m4_normalize([ + encoding.c + ]), + [no]) + +PHP_INSTALL_HEADERS([ext/encoding], [encoding_decl.h]) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c new file mode 100644 index 000000000000..330475bd40db --- /dev/null +++ b/ext/encoding/encoding.c @@ -0,0 +1,1514 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Ignace Nyamagana Butera | + +----------------------------------------------------------------------+ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "php.h" +#include "Zend/zend_enum.h" +#include "Zend/zend_exceptions.h" +#include "encoding_arginfo.h" +#include "encoding_decl.h" + +/* Class entries */ +static zend_class_entry *encoding_ce_EncodingError; +static zend_class_entry *encoding_ce_EncodingException; +static zend_class_entry *encoding_ce_UnableToDecodeException; +static zend_class_entry *encoding_ce_UnableToEncodeException; + +static zend_class_entry *encoding_ce_Base16; +static zend_class_entry *encoding_ce_Base32; +static zend_class_entry *encoding_ce_Base58; +static zend_class_entry *encoding_ce_Base64; +static zend_class_entry *encoding_ce_Base85; +static zend_class_entry *encoding_ce_PaddingMode; +static zend_class_entry *encoding_ce_DecodingMode; +static zend_class_entry *encoding_ce_TimingMode; + +/* Exception throwing macros */ +#define THROW_UNABLE_TO_DECODE(msg) \ + zend_throw_exception(encoding_ce_UnableToDecodeException, msg, 0) + +#define THROW_UNABLE_TO_ENCODE(msg) \ + zend_throw_exception(encoding_ce_UnableToEncodeException, msg, 0) + +/* ===================== Base16 ===================== */ + +/* Lookup tables for Base16 encoding */ +static const uint8_t base16_encode_upper[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +static const uint8_t base16_encode_lower[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' +}; + +/* Reverse lookup for decoding: maps ASCII char to 0-15 or 0xFF if invalid */ +static const uint8_t base16_reverse_upper[256] = { + [0 ... 255] = 0xFF, + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, + ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, + ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15 +}; + +static const uint8_t base16_reverse_lower[256] = { + [0 ... 255] = 0xFF, + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, + ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, + ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15 +}; + +static const uint8_t base16_reverse_forgiving[256] = { + [0 ... 255] = 0xFF, + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, + ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, + ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, + ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15 +}; + +static inline bool is_base16_whitespace(uint8_t c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +static zend_string *base16_encode_impl(const char *data, size_t len, zend_enum_Encoding_Base16 variant, bool constant_time) { + const uint8_t *table = (variant == ZEND_ENUM_Encoding_Base16_Upper) + ? base16_encode_upper : base16_encode_lower; + + zend_string *result = zend_string_alloc(len * 2, 0); + char *dst = ZSTR_VAL(result); + + for (size_t i = 0; i < len; i++) { + uint8_t byte = (uint8_t)data[i]; + *dst++ = table[byte >> 4]; + *dst++ = table[byte & 0x0F]; + } + + ZSTR_LEN(result) = len * 2; + return result; +} + +static zend_string *base16_decode_impl(const char *data, size_t len, zend_enum_Encoding_Base16 variant, zend_enum_Encoding_DecodingMode mode, bool constant_time) { + const uint8_t *reverse_table; + + if (mode == ZEND_ENUM_Encoding_DecodingMode_Forgiving) { + reverse_table = base16_reverse_forgiving; + } else if (variant == ZEND_ENUM_Encoding_Base16_Upper) { + reverse_table = base16_reverse_upper; + } else { + reverse_table = base16_reverse_lower; + } + + /* Count non-whitespace characters to determine output length */ + size_t content_len = 0; + for (size_t i = 0; i < len; i++) { + if (!is_base16_whitespace((uint8_t)data[i])) { + content_len++; + } + } + + /* Must have even number of hex digits */ + if (content_len % 2 != 0) { + THROW_UNABLE_TO_DECODE("Hex data must have an even length"); + return NULL; + } + + zend_string *result = zend_string_alloc(content_len / 2, 0); + char *dst = ZSTR_VAL(result); + size_t dst_idx = 0; + uint8_t high = 0; + bool have_high = false; + + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + + if (is_base16_whitespace(c)) { + continue; + } + + uint8_t val = reverse_table[c]; + + if (val == 0xFF) { + zend_string_efree(result); + THROW_UNABLE_TO_DECODE("Invalid hex character"); + return NULL; + } + + if (!have_high) { + high = val; + have_high = true; + } else { + dst[dst_idx++] = (char)(high << 4 | val); + have_high = false; + } + } + + ZSTR_LEN(result) = dst_idx; + return result; +} + + PHP_FUNCTION(Encoding_base16_encode) { + char *data; + size_t data_len; + zval *variant_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base16) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base16 variant = ZEND_ENUM_Encoding_Base16_Upper; + if (variant_obj) { + variant = (zend_enum_Encoding_Base16)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + } + + bool constant_time = false; + if (timing_obj) { + constant_time = (zend_enum_fetch_case_id(Z_OBJ_P(timing_obj)) == ZEND_ENUM_Encoding_TimingMode_Constant); + } + + RETURN_STR(base16_encode_impl(data, data_len, variant, constant_time)); +} + + PHP_FUNCTION(Encoding_base16_decode) { + char *data; + size_t data_len; + zval *variant_obj = NULL; + zval *decoding_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base16) + Z_PARAM_OBJECT_OF_CLASS(decoding_obj, encoding_ce_DecodingMode) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base16 variant = ZEND_ENUM_Encoding_Base16_Upper; + if (variant_obj) { + variant = (zend_enum_Encoding_Base16)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + } + + zend_enum_Encoding_DecodingMode mode = ZEND_ENUM_Encoding_DecodingMode_Strict; + if (decoding_obj) { + mode = (zend_enum_Encoding_DecodingMode)zend_enum_fetch_case_id(Z_OBJ_P(decoding_obj)); + } + + bool constant_time = false; + if (timing_obj) { + constant_time = (zend_enum_fetch_case_id(Z_OBJ_P(timing_obj)) == ZEND_ENUM_Encoding_TimingMode_Constant); + } + + RETURN_STR(base16_decode_impl(data, data_len, variant, mode, constant_time)); +} + +/* ===================== Base32 ===================== */ + +static const char base32_ascii_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +static const char base32_hex_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; +static const char base32_crockford_table[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; +static const char base32_z_table[] = "pebrv9tkngxm4zywsiu1oca6fjh5q23780ld"; + +static const uint8_t base32_ascii_reverse[256] = { + [0 ... 255] = 0xFF, + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, + ['F'] = 5, ['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, + ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, + ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, + ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, + ['Z'] = 25, ['2'] = 26, ['3'] = 27, ['4'] = 28, ['5'] = 29, + ['6'] = 30, ['7'] = 31 +}; + +static const uint8_t base32_hex_reverse[256] = { + [0 ... 255] = 0xFF, + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, + ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, + ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, + ['F'] = 15, ['G'] = 16, ['H'] = 17, ['I'] = 18, ['J'] = 19, + ['K'] = 20, ['L'] = 21, ['M'] = 22, ['N'] = 23, ['O'] = 24, + ['P'] = 25, ['Q'] = 26, ['R'] = 27, ['S'] = 28, ['T'] = 29, + ['U'] = 30, ['V'] = 31 +}; + +static const uint8_t base32_crockford_reverse[256] = { + [0 ... 255] = 0xFF, + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, + ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, + ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, + ['F'] = 15, ['G'] = 16, ['H'] = 17, + /* I, O skipped */ + ['J'] = 19, ['K'] = 20, + /* L skipped */ + ['M'] = 22, ['N'] = 23, ['P'] = 24, + ['Q'] = 25, ['R'] = 26, ['S'] = 27, ['T'] = 28, + ['V'] = 29, ['W'] = 30, ['X'] = 31, + /* Lowercase for case-insensitive Crockford */ + ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, + ['f'] = 15, ['g'] = 16, ['h'] = 17, + ['j'] = 19, ['k'] = 20, + ['m'] = 22, ['n'] = 23, ['p'] = 24, + ['q'] = 25, ['r'] = 26, ['s'] = 27, ['t'] = 28, + ['v'] = 29, ['w'] = 30, ['x'] = 31 +}; + +static const uint8_t base32_z_reverse[256] = { + [0 ... 255] = 0xFF, + ['p'] = 0, ['e'] = 1, ['b'] = 2, ['r'] = 3, ['v'] = 4, + ['9'] = 5, ['t'] = 6, ['k'] = 7, ['n'] = 8, ['g'] = 9, + ['x'] = 10, ['m'] = 11, ['4'] = 12, ['z'] = 13, ['y'] = 14, + ['w'] = 15, ['s'] = 16, ['i'] = 17, ['u'] = 18, ['1'] = 19, + ['o'] = 20, ['c'] = 21, ['a'] = 22, ['6'] = 23, ['f'] = 24, + ['j'] = 25, ['h'] = 26, ['5'] = 27, ['q'] = 28, ['2'] = 29, + ['3'] = 30, ['7'] = 31, ['8'] = 32, ['0'] = 33, ['l'] = 34, ['d'] = 35 +}; + +static inline bool is_base32_whitespace(uint8_t c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +static zend_string *base32_encode_impl(const char *data, size_t len, zend_enum_Encoding_Base32 variant, zend_enum_Encoding_PaddingMode padding) { + const char *table; + bool uses_padding; + bool strip_padding; + + switch (variant) { + case ZEND_ENUM_Encoding_Base32_Ascii: + table = base32_ascii_table; + uses_padding = true; + break; + case ZEND_ENUM_Encoding_Base32_Hex: + table = base32_hex_table; + uses_padding = true; + break; + case ZEND_ENUM_Encoding_Base32_Crockford: + table = base32_crockford_table; + uses_padding = false; + break; + case ZEND_ENUM_Encoding_Base32_Z: + table = base32_z_table; + uses_padding = false; + break; + default: + table = base32_ascii_table; + uses_padding = true; + break; + } + + switch (padding) { + case ZEND_ENUM_Encoding_PaddingMode_VariantControlled: + strip_padding = false; + break; + case ZEND_ENUM_Encoding_PaddingMode_StripPadding: + strip_padding = true; + if (!uses_padding) { + zend_value_error("PaddingMode::StripPadding is not supported for variant %s", + variant == ZEND_ENUM_Encoding_Base32_Crockford ? "Base32::Crockford" : + variant == ZEND_ENUM_Encoding_Base32_Z ? "Base32::Z" : "Base32"); + return NULL; + } + break; + case ZEND_ENUM_Encoding_PaddingMode_PreservePadding: + strip_padding = false; + if (!uses_padding) { + zend_value_error("PaddingMode::PreservePadding is not supported for variant %s", + variant == ZEND_ENUM_Encoding_Base32_Crockford ? "Base32::Crockford" : + variant == ZEND_ENUM_Encoding_Base32_Z ? "Base32::Z" : "Base32"); + return NULL; + } + break; + default: + strip_padding = false; + break; + } + + size_t encoded_len = (len + 4) / 5 * 8; + if (strip_padding) { + size_t remainder = len % 5; + if (remainder) { + size_t pad = 8 - ((remainder * 8 + 4) / 5); + encoded_len -= pad; + } + } + + zend_string *result = zend_string_alloc(encoded_len, 0); + char *dst = ZSTR_VAL(result); + + size_t i = 0; + while (i < len) { + uint64_t nbits = 0; + size_t bytes_left = len - i; + size_t chars_to_read = bytes_left < 5 ? bytes_left : 5; + + for (size_t j = 0; j < chars_to_read; j++) { + nbits = (nbits << 8) | (uint8_t)data[i + j]; + } + + int total_chars = (chars_to_read * 8 + 4) / 5; + nbits <<= (uint64_t)(total_chars * 5 - chars_to_read * 8); + for (int c = total_chars - 1; c >= 0; c--) { + *dst++ = table[(nbits >> (c * 5)) & 0x1F]; + } + + if (uses_padding && !strip_padding && chars_to_read < 5) { + size_t pad = 8 - total_chars; + for (size_t p = 0; p < pad; p++) { + *dst++ = '='; + } + } + + i += chars_to_read; + } + + ZSTR_LEN(result) = dst - ZSTR_VAL(result); + return result; +} + +static zend_string *base32_decode_impl(const char *data, size_t len, zend_enum_Encoding_Base32 variant, zend_enum_Encoding_DecodingMode mode, bool constant_time) { + const uint8_t *reverse_table; + bool uses_padding; + + switch (variant) { + case ZEND_ENUM_Encoding_Base32_Ascii: + reverse_table = base32_ascii_reverse; + uses_padding = true; + break; + case ZEND_ENUM_Encoding_Base32_Hex: + reverse_table = base32_hex_reverse; + uses_padding = true; + break; + case ZEND_ENUM_Encoding_Base32_Crockford: + reverse_table = base32_crockford_reverse; + uses_padding = false; + break; + case ZEND_ENUM_Encoding_Base32_Z: + reverse_table = base32_z_reverse; + uses_padding = false; + break; + default: + reverse_table = base32_ascii_reverse; + uses_padding = true; + break; + } + + /* Count non-whitespace, non-padding characters */ + size_t content_len = 0; + size_t padding_count = 0; + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + if (is_base32_whitespace(c)) { + continue; + } + if (c == '=') { + padding_count++; + continue; + } + content_len++; + } + + bool forgiving = (mode == ZEND_ENUM_Encoding_DecodingMode_Forgiving); + + if (uses_padding) { + if (forgiving) { + /* In forgiving mode, we can handle missing or incorrect padding */ + /* Calculate expected padding and fix it */ + size_t remainder = content_len % 8; + if (remainder) { + size_t expected_pad = 8 - remainder; + if (padding_count != expected_pad && padding_count == 0) { + /* Missing padding - forgive it */ + } else if (padding_count != expected_pad) { + THROW_UNABLE_TO_DECODE("Invalid padding length"); + return NULL; + } + } else if (padding_count != 0) { + THROW_UNABLE_TO_DECODE("Invalid padding length"); + return NULL; + } + } else { + /* Strict mode: padding is required */ + size_t remainder = content_len % 8; + if (remainder) { + size_t expected_pad = 8 - remainder; + if (padding_count != expected_pad) { + THROW_UNABLE_TO_DECODE("Invalid padding length"); + return NULL; + } + } else if (padding_count != 0) { + THROW_UNABLE_TO_DECODE("Invalid padding length"); + return NULL; + } + } + } + + size_t out_len = (content_len * 5) / 8; + zend_string *result = zend_string_alloc(out_len, 0); + char *dst = ZSTR_VAL(result); + size_t dst_idx = 0; + + uint64_t nbits = 0; + int bits = 0; + + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + + if (is_base32_whitespace(c) || c == '=') { + continue; + } + + uint8_t val = reverse_table[c]; + + if (val == 0xFF) { + zend_string_efree(result); + THROW_UNABLE_TO_DECODE("Invalid Base32 character"); + return NULL; + } + + nbits = (nbits << 5) | val; + bits += 5; + + while (bits >= 8) { + bits -= 8; + if (dst_idx < out_len) { + dst[dst_idx++] = (char)((nbits >> bits) & 0xFF); + } + } + } + + ZSTR_LEN(result) = dst_idx; + return result; +} + +PHP_FUNCTION(Encoding_base32_encode) { + char *data; + size_t data_len; + zval *variant_obj = NULL; + zval *padding_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base32) + Z_PARAM_OBJECT_OF_CLASS(padding_obj, encoding_ce_PaddingMode) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base32 variant = ZEND_ENUM_Encoding_Base32_Ascii; + if (variant_obj) { + variant = (zend_enum_Encoding_Base32)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + } + + zend_enum_Encoding_PaddingMode padding = ZEND_ENUM_Encoding_PaddingMode_VariantControlled; + if (padding_obj) { + padding = (zend_enum_Encoding_PaddingMode)zend_enum_fetch_case_id(Z_OBJ_P(padding_obj)); + } + + zend_string *result = base32_encode_impl(data, data_len, variant, padding); + if (!result) { + return; + } + RETURN_STR(result); +} + +PHP_FUNCTION(Encoding_base32_decode) { + char *data; + size_t data_len; + zval *variant_obj = NULL; + zval *decoding_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base32) + Z_PARAM_OBJECT_OF_CLASS(decoding_obj, encoding_ce_DecodingMode) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base32 variant = ZEND_ENUM_Encoding_Base32_Ascii; + if (variant_obj) { + variant = (zend_enum_Encoding_Base32)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + } + + zend_enum_Encoding_DecodingMode mode = ZEND_ENUM_Encoding_DecodingMode_Strict; + if (decoding_obj) { + mode = (zend_enum_Encoding_DecodingMode)zend_enum_fetch_case_id(Z_OBJ_P(decoding_obj)); + } + + bool constant_time = false; + if (timing_obj) { + constant_time = (zend_enum_fetch_case_id(Z_OBJ_P(timing_obj)) == ZEND_ENUM_Encoding_TimingMode_Constant); + } + + RETURN_STR(base32_decode_impl(data, data_len, variant, mode, constant_time)); +} + +/* ===================== Base58 ===================== */ + +static const char base58_bitcoin_table[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +static const char base58_flickr_table[] = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; + +static const uint8_t base58_bitcoin_reverse[256] = { + [0 ... 255] = 0xFF, + ['1'] = 0, ['2'] = 1, ['3'] = 2, ['4'] = 3, ['5'] = 4, + ['6'] = 5, ['7'] = 6, ['8'] = 7, ['9'] = 8, + ['A'] = 9, ['B'] = 10, ['C'] = 11, ['D'] = 12, ['E'] = 13, ['F'] = 14, ['G'] = 15, + ['H'] = 16, ['J'] = 17, ['K'] = 18, ['L'] = 19, + ['M'] = 20, ['N'] = 21, + ['P'] = 22, ['Q'] = 23, ['R'] = 24, ['S'] = 25, ['T'] = 26, ['U'] = 27, + ['V'] = 28, ['W'] = 29, ['X'] = 30, ['Y'] = 31, ['Z'] = 32, + ['a'] = 33, ['b'] = 34, ['c'] = 35, ['d'] = 36, ['e'] = 37, ['f'] = 38, + ['g'] = 39, ['h'] = 40, ['i'] = 41, ['j'] = 42, ['k'] = 43, ['m'] = 44, + ['n'] = 45, ['p'] = 46, ['q'] = 47, ['r'] = 48, ['s'] = 49, ['t'] = 50, + ['u'] = 51, ['v'] = 52, ['w'] = 53, ['x'] = 54, ['y'] = 55, ['z'] = 56 +}; + +static const uint8_t base58_flickr_reverse[256] = { + [0 ... 255] = 0xFF, + ['1'] = 0, ['2'] = 1, ['3'] = 2, ['4'] = 3, ['5'] = 4, + ['6'] = 5, ['7'] = 6, ['8'] = 7, ['9'] = 8, + ['a'] = 9, ['b'] = 10, ['c'] = 11, ['d'] = 12, ['e'] = 13, ['f'] = 14, ['g'] = 15, + ['h'] = 16, ['i'] = 17, ['j'] = 18, ['k'] = 19, + ['m'] = 20, ['n'] = 21, + ['p'] = 22, ['q'] = 23, ['r'] = 24, ['s'] = 25, ['t'] = 26, ['u'] = 27, + ['v'] = 28, ['w'] = 29, ['x'] = 30, ['y'] = 31, ['z'] = 32, + ['A'] = 33, ['B'] = 34, ['C'] = 35, ['D'] = 36, ['E'] = 37, ['F'] = 38, + ['G'] = 39, ['H'] = 40, ['J'] = 41, ['K'] = 42, ['L'] = 43, ['M'] = 44, + ['N'] = 45, ['P'] = 46, ['Q'] = 47, ['R'] = 48, ['S'] = 49, ['T'] = 50, + ['U'] = 51, ['V'] = 52, ['W'] = 53, ['X'] = 54, ['Y'] = 55, ['Z'] = 56 +}; + +static zend_string *base58_encode_impl(const char *data, size_t len, zend_enum_Encoding_Base58 variant) { + const char *table = (variant == ZEND_ENUM_Encoding_Base58_Bitcoin) + ? base58_bitcoin_table : base58_flickr_table; + + /* Max output size: ceil(len * 8 / log2(58)) ≈ len * 1.4 + margin */ + size_t max_out_len = (len * 138 + 100) / 100 + 16; + + /* Count leading zeros */ + size_t zeros = 0; + while (zeros < len && (uint8_t)data[zeros] == 0) { + zeros++; + } + + /* Output buffer, zero-initialized */ + uint8_t *output = ecalloc(max_out_len, sizeof(uint8_t)); + size_t out_begin = max_out_len; + size_t out_end = max_out_len; + + /* Process each input byte: multiply accumulated number by 256, add byte */ + for (size_t i = zeros; i < len; i++) { + uint32_t carry = (uint8_t)data[i]; + for (size_t j = out_begin; j < out_end; j++) { + carry += (uint32_t)output[j] * 256; + output[j] = (uint8_t)(carry % 58); + carry /= 58; + } + while (carry > 0) { + if (out_begin == 0) break; + out_begin--; + output[out_begin] = (uint8_t)(carry % 58); + carry /= 58; + } + } + + /* Calculate output length */ + size_t out_len = zeros + (out_end - out_begin); + zend_string *result = zend_string_alloc(out_len, 0); + char *dst = ZSTR_VAL(result); + + /* Leading zeros become '1' characters */ + for (size_t i = 0; i < zeros; i++) { + *dst++ = table[0]; + } + + /* Encode the digits */ + for (size_t i = out_begin; i < out_end; i++) { + *dst++ = table[output[i]]; + } + + ZSTR_LEN(result) = dst - ZSTR_VAL(result); + efree(output); + + return result; +} + +static zend_string *base58_decode_impl(const char *data, size_t len, zend_enum_Encoding_Base58 variant) { + const uint8_t *reverse_table = (variant == ZEND_ENUM_Encoding_Base58_Bitcoin) + ? base58_bitcoin_reverse : base58_flickr_reverse; + + /* Count leading '1' characters (zeros in base58) */ + size_t zeros = 0; + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + if (reverse_table[c] == 0) { + zeros++; + } else { + break; + } + } + + /* Count non-whitespace content length */ + size_t content_len = 0; + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + content_len++; + } + + /* Max output size: ceil(content_len * log(58) / log(256)) */ + size_t max_out_len = (content_len * 74 + 100) / 100 + 1; + uint8_t *output = ecalloc(max_out_len, sizeof(uint8_t)); + + /* Decode: for each char, multiply accumulated number by 58 and add value */ + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + + uint8_t val = reverse_table[c]; + if (val == 0xFF) { + efree(output); + THROW_UNABLE_TO_DECODE("Invalid Base58 character"); + return NULL; + } + + uint32_t carry = val; + for (size_t j = 0; j < max_out_len; j++) { + uint32_t temp = (uint32_t)output[j] * 58 + carry; + output[j] = (uint8_t)(temp % 256); + carry = temp / 256; + if (carry == 0) break; + } + } + + /* Reverse to get big-endian byte order */ + for (size_t i = 0; i < max_out_len / 2; i++) { + uint8_t tmp = output[i]; + output[i] = output[max_out_len - 1 - i]; + output[max_out_len - 1 - i] = tmp; + } + + /* Find actual output length (skip leading zeros) */ + size_t out_len = 0; + while (out_len < max_out_len && output[out_len] == 0) { + out_len++; + } + + size_t decoded_len = zeros + (max_out_len - out_len); + if (decoded_len == 0) { + decoded_len = zeros; + } + + zend_string *result = zend_string_alloc(decoded_len, 0); + char *dst = ZSTR_VAL(result); + + for (size_t i = 0; i < zeros; i++) { + *dst++ = 0; + } + + for (size_t i = out_len; i < max_out_len; i++) { + *dst++ = output[i]; + } + + ZSTR_LEN(result) = dst - ZSTR_VAL(result); + efree(output); + + return result; +} + +PHP_FUNCTION(Encoding_base58_encode) { + char *data; + size_t data_len; + zval *variant_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base58) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base58 variant = ZEND_ENUM_Encoding_Base58_Bitcoin; + if (variant_obj) { + variant = (zend_enum_Encoding_Base58)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + } + + RETURN_STR(base58_encode_impl(data, data_len, variant)); +} + +PHP_FUNCTION(Encoding_base58_decode) { + char *data; + size_t data_len; + zval *variant_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base58) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base58 variant = ZEND_ENUM_Encoding_Base58_Bitcoin; + if (variant_obj) { + variant = (zend_enum_Encoding_Base58)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + } + + RETURN_STR(base58_decode_impl(data, data_len, variant)); +} + +/* ===================== Base64 ===================== */ + +static const char base64_standard_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char base64_urlsafe_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +static const char base64_imap_table[] = "-_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static const uint8_t base64_standard_reverse[256] = { + [0 ... 255] = 0xFF, + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, + ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, + ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, + ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, + ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, + ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63 +}; + +static const uint8_t base64_urlsafe_reverse[256] = { + [0 ... 255] = 0xFF, + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, + ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, + ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, + ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, + ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, + ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['-'] = 62, ['_'] = 63 +}; + +static const uint8_t base64_imap_reverse[256] = { + [0 ... 255] = 0xFF, + ['-'] = 0, ['_'] = 1, + ['A'] = 2, ['B'] = 3, ['C'] = 4, ['D'] = 5, ['E'] = 6, ['F'] = 7, + ['G'] = 8, ['H'] = 9, ['I'] = 10, ['J'] = 11, ['K'] = 12, ['L'] = 13, ['O'] = 14, ['P'] = 15, + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, + ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, + ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, + ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, + ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, + ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['N'] = 62, ['/'] = 63 +}; + +static const uint8_t base64_forgiving_reverse[256] = { + [0 ... 255] = 0xFF, + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, + ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, + ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, + ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, + ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, + ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, + ['+'] = 62, ['/'] = 62, ['-'] = 62, ['_'] = 62, + ['='] = 63, ['~'] = 63 +}; + +static inline bool is_base64_whitespace(uint8_t c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +static zend_string *base64_encode_impl(const char *data, size_t len, zend_enum_Encoding_Base64 variant, zend_enum_Encoding_PaddingMode padding) { + const char *table; + bool uses_padding; + bool strip_padding; + + switch (variant) { + case ZEND_ENUM_Encoding_Base64_Standard: + table = base64_standard_table; + uses_padding = true; + break; + case ZEND_ENUM_Encoding_Base64_UrlSafe: + table = base64_urlsafe_table; + uses_padding = true; + break; + case ZEND_ENUM_Encoding_Base64_Imap: + table = base64_imap_table; + uses_padding = true; + break; + default: + table = base64_standard_table; + uses_padding = true; + break; + } + + switch (padding) { + case ZEND_ENUM_Encoding_PaddingMode_VariantControlled: + strip_padding = !uses_padding; + break; + case ZEND_ENUM_Encoding_PaddingMode_StripPadding: + strip_padding = true; + break; + case ZEND_ENUM_Encoding_PaddingMode_PreservePadding: + strip_padding = false; + break; + default: + strip_padding = false; + break; + } + + size_t encoded_len = ((len + 2) / 3) * 4; + if (strip_padding) { + size_t remainder = len % 3; + if (remainder) { + encoded_len -= 3 - remainder; + } + } + + zend_string *result = zend_string_alloc(encoded_len, 0); + char *dst = ZSTR_VAL(result); + + size_t i = 0; + while (i < len) { + uint32_t nbits; + size_t bytes_left = len - i; + + if (bytes_left >= 3) { + nbits = ((uint8_t)data[i] << 16) | ((uint8_t)data[i+1] << 8) | (uint8_t)data[i+2]; + *dst++ = table[(nbits >> 18) & 0x3F]; + *dst++ = table[(nbits >> 12) & 0x3F]; + *dst++ = table[(nbits >> 6) & 0x3F]; + *dst++ = table[nbits & 0x3F]; + i += 3; + } else { + if (bytes_left == 2) { + nbits = ((uint8_t)data[i] << 16) | ((uint8_t)data[i+1] << 8); + } else { + nbits = (uint8_t)data[i] << 16; + } + *dst++ = table[(nbits >> 18) & 0x3F]; + *dst++ = table[(nbits >> 12) & 0x3F]; + if (bytes_left == 2) { + *dst++ = table[(nbits >> 6) & 0x3F]; + } + if (!strip_padding) { + *dst++ = '='; + if (bytes_left == 1) { + *dst++ = '='; + } + } + i += bytes_left; + } + } + + ZSTR_LEN(result) = dst - ZSTR_VAL(result); + return result; +} + +static zend_string *base64_decode_impl(const char *data, size_t len, zend_enum_Encoding_Base64 variant, zend_enum_Encoding_DecodingMode mode) { + const uint8_t *reverse_table; + + if (mode == ZEND_ENUM_Encoding_DecodingMode_Forgiving) { + reverse_table = base64_forgiving_reverse; + } else { + switch (variant) { + case ZEND_ENUM_Encoding_Base64_Standard: + reverse_table = base64_standard_reverse; + break; + case ZEND_ENUM_Encoding_Base64_UrlSafe: + reverse_table = base64_urlsafe_reverse; + break; + case ZEND_ENUM_Encoding_Base64_Imap: + reverse_table = base64_imap_reverse; + break; + default: + reverse_table = base64_standard_reverse; + break; + } + } + + bool forgiving = (mode == ZEND_ENUM_Encoding_DecodingMode_Forgiving); + + /* Count content and padding */ + size_t content_len = 0; + size_t padding_count = 0; + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + if (is_base64_whitespace(c)) { + continue; + } + if (c == '=') { + padding_count++; + continue; + } + content_len++; + } + + /* Validate padding */ + if (forgiving) { + if (padding_count > 2) { + THROW_UNABLE_TO_DECODE("Invalid padding length"); + return NULL; + } + } else { + size_t remainder = content_len % 4; + if (remainder == 0) { + if (padding_count != 0) { + THROW_UNABLE_TO_DECODE("Invalid padding length"); + return NULL; + } + } else if (remainder == 2) { + if (padding_count != 2) { + THROW_UNABLE_TO_DECODE("Invalid padding length"); + return NULL; + } + } else if (remainder == 3) { + if (padding_count != 1) { + THROW_UNABLE_TO_DECODE("Invalid padding length"); + return NULL; + } + } else if (remainder == 1) { + THROW_UNABLE_TO_DECODE("Invalid Base64 data"); + return NULL; + } + } + + size_t out_len = (content_len * 3) / 4; + if (padding_count) { + out_len -= padding_count; + } + zend_string *result = zend_string_alloc(out_len, 0); + char *dst = ZSTR_VAL(result); + size_t dst_idx = 0; + + uint32_t nbits = 0; + int bits = 0; + + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + + if (is_base64_whitespace(c)) { + continue; + } + + if (c == '=') { + continue; + } + + uint8_t val = reverse_table[c]; + + if (val == 0xFF) { + zend_string_efree(result); + THROW_UNABLE_TO_DECODE("Invalid Base64 character"); + return NULL; + } + + nbits = (nbits << 6) | val; + bits += 6; + + while (bits >= 8) { + bits -= 8; + if (dst_idx < out_len) { + dst[dst_idx++] = (char)((nbits >> bits) & 0xFF); + } + } + } + + ZSTR_LEN(result) = dst_idx; + return result; +} + +PHP_FUNCTION(Encoding_base64_encode) { + char *data; + size_t data_len; + zval *variant_obj = NULL; + zval *padding_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base64) + Z_PARAM_OBJECT_OF_CLASS(padding_obj, encoding_ce_PaddingMode) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base64 variant = ZEND_ENUM_Encoding_Base64_Standard; + if (variant_obj) { + variant = (zend_enum_Encoding_Base64)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + } + + zend_enum_Encoding_PaddingMode padding = ZEND_ENUM_Encoding_PaddingMode_VariantControlled; + if (padding_obj) { + padding = (zend_enum_Encoding_PaddingMode)zend_enum_fetch_case_id(Z_OBJ_P(padding_obj)); + } + + RETURN_STR(base64_encode_impl(data, data_len, variant, padding)); +} + +PHP_FUNCTION(Encoding_base64_decode) { + char *data; + size_t data_len; + zval *variant_obj = NULL; + zval *decoding_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base64) + Z_PARAM_OBJECT_OF_CLASS(decoding_obj, encoding_ce_DecodingMode) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base64 variant = ZEND_ENUM_Encoding_Base64_Standard; + if (variant_obj) { + variant = (zend_enum_Encoding_Base64)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + } + + zend_enum_Encoding_DecodingMode mode = ZEND_ENUM_Encoding_DecodingMode_Strict; + if (decoding_obj) { + mode = (zend_enum_Encoding_DecodingMode)zend_enum_fetch_case_id(Z_OBJ_P(decoding_obj)); + } + + RETURN_STR(base64_decode_impl(data, data_len, variant, mode)); +} + +/* ===================== Base85 ===================== */ + +static const char base85_adobe_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; +static const char base85_z85_table[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+<=>@^_[]{}\"(,)/%"; +static const char base85_git_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@$%^&*()-=+;:<>|~.#"; + +static const uint8_t base85_adobe_reverse[256] = { + [0 ... 255] = 0xFF, + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, + ['8'] = 8, ['9'] = 9, + ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, ['G'] = 16, ['H'] = 17, + ['I'] = 18, ['J'] = 19, ['K'] = 20, ['L'] = 21, ['M'] = 22, ['N'] = 23, ['O'] = 24, ['P'] = 25, + ['Q'] = 26, ['R'] = 27, ['S'] = 28, ['T'] = 29, ['U'] = 30, ['V'] = 31, ['W'] = 32, ['X'] = 33, + ['Y'] = 34, ['Z'] = 35, + ['a'] = 36, ['b'] = 37, ['c'] = 38, ['d'] = 39, ['e'] = 40, ['f'] = 41, ['g'] = 42, ['h'] = 43, + ['i'] = 44, ['j'] = 45, ['k'] = 46, ['l'] = 47, ['m'] = 48, ['n'] = 49, ['o'] = 50, ['p'] = 51, + ['q'] = 52, ['r'] = 53, ['s'] = 54, ['t'] = 55, ['u'] = 56, ['v'] = 57, ['w'] = 58, ['x'] = 59, + ['y'] = 60, ['z'] = 61, + ['!'] = 62, ['#'] = 63, ['$'] = 64, ['%'] = 65, ['&'] = 66, + ['('] = 67, [')'] = 68, ['*'] = 69, ['+'] = 70, + ['-'] = 71, + ['<'] = 72, ['='] = 73, ['>'] = 74, ['?'] = 75, ['@'] = 76, + ['^'] = 77, ['_'] = 78, ['`'] = 79, + ['{'] = 80, ['|'] = 81, ['}'] = 82, ['~'] = 83 +}; + +static const uint8_t base85_z85_reverse[256] = { + [0 ... 255] = 0xFF, + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, + ['8'] = 8, ['9'] = 9, + ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, ['g'] = 16, ['h'] = 17, + ['i'] = 18, ['j'] = 19, ['k'] = 20, ['l'] = 21, ['m'] = 22, ['n'] = 23, ['o'] = 24, ['p'] = 25, + ['q'] = 26, ['r'] = 27, ['s'] = 28, ['t'] = 29, ['u'] = 30, ['v'] = 31, ['w'] = 32, ['x'] = 33, + ['y'] = 34, ['z'] = 35, + ['A'] = 36, ['B'] = 37, ['C'] = 38, ['D'] = 39, ['E'] = 40, ['F'] = 41, ['G'] = 42, ['H'] = 43, + ['I'] = 44, ['J'] = 45, ['K'] = 46, ['L'] = 47, ['M'] = 48, ['N'] = 49, ['O'] = 50, ['P'] = 51, + ['Q'] = 52, ['R'] = 53, ['S'] = 54, ['T'] = 55, ['U'] = 56, ['V'] = 57, ['W'] = 58, ['X'] = 59, + ['Y'] = 60, ['Z'] = 61, + ['.'] = 62, ['-'] = 63, [':'] = 64, ['+'] = 65, ['<'] = 66, ['='] = 67, ['>'] = 68, ['@'] = 69, + ['^'] = 70, ['_'] = 71, + ['['] = 72, [']'] = 73, + ['{'] = 74, ['}'] = 75, ['"'] = 76, + ['('] = 77, [')'] = 78, [','] = 79, ['/'] = 80, ['%'] = 81 +}; + +static const uint8_t base85_git_reverse[256] = { + [0 ... 255] = 0xFF, + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, + ['8'] = 8, ['9'] = 9, + ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, ['G'] = 16, ['H'] = 17, + ['I'] = 18, ['J'] = 19, ['K'] = 20, ['L'] = 21, ['M'] = 22, ['N'] = 23, ['O'] = 24, ['P'] = 25, + ['Q'] = 26, ['R'] = 27, ['S'] = 28, ['T'] = 29, ['U'] = 30, ['V'] = 31, ['W'] = 32, ['X'] = 33, + ['Y'] = 34, ['Z'] = 35, + ['!'] = 36, ['@'] = 37, ['$'] = 38, ['%'] = 39, ['^'] = 40, ['&'] = 41, + ['*'] = 42, ['('] = 43, [')'] = 44, ['-'] = 45, ['='] = 46, ['+'] = 47, + [';'] = 48, [':'] = 49, ['<'] = 50, ['>'] = 51, ['|'] = 52, ['~'] = 53, ['.'] = 54, ['#'] = 55 +}; + +static zend_string *base85_encode_impl(const char *data, size_t len, zend_enum_Encoding_Base85 variant, zend_enum_Encoding_PaddingMode padding) { + const char *table; + bool uses_padding; + + switch (variant) { + case ZEND_ENUM_Encoding_Base85_Adobe: + table = base85_adobe_table; + uses_padding = true; + break; + case ZEND_ENUM_Encoding_Base85_Z85: + table = base85_z85_table; + uses_padding = false; + break; + case ZEND_ENUM_Encoding_Base85_Git: + table = base85_git_table; + uses_padding = false; + break; + default: + table = base85_adobe_table; + uses_padding = true; + break; + } + + /* Check padding validity */ + if (padding == ZEND_ENUM_Encoding_PaddingMode_PreservePadding && !uses_padding) { + zend_value_error("PaddingMode::PreservePadding is not supported for variant %s", + variant == ZEND_ENUM_Encoding_Base85_Z85 ? "Base85::Z85" : "Base85::Git"); + return NULL; + } + + if (padding == ZEND_ENUM_Encoding_PaddingMode_StripPadding && !uses_padding) { + zend_value_error("PaddingMode::StripPadding is not supported for variant %s", + variant == ZEND_ENUM_Encoding_Base85_Z85 ? "Base85::Z85" : "Base85::Git"); + return NULL; + } + + bool strip_padding = (padding == ZEND_ENUM_Encoding_PaddingMode_StripPadding); + bool add_markers = uses_padding && !strip_padding; + + /* Z85 requires input to be multiple of 4 */ + if (variant == ZEND_ENUM_Encoding_Base85_Z85 && len % 4 != 0) { + zend_value_error("Z85 encoding requires input length to be a multiple of 4"); + return NULL; + } + + size_t encoded_len = (len + 3) / 4 * 5; + if (strip_padding || !uses_padding) { + /* For variants without padding, or when stripping, we still need to encode partial blocks */ + /* but we won't have the padding chars */ + size_t remainder = len % 4; + if (remainder && uses_padding) { + size_t pad = 5 - ((remainder * 5 + 3) / 4); + encoded_len -= pad; + } + } + + zend_string *result; + if (add_markers) { + /* Adobe: <~ ... ~> */ + result = zend_string_alloc(encoded_len + 4, 0); + ZSTR_VAL(result)[0] = '<'; + ZSTR_VAL(result)[1] = '~'; + ZSTR_VAL(result)[encoded_len + 2] = '~'; + ZSTR_VAL(result)[encoded_len + 3] = '>'; + char *dst = ZSTR_VAL(result) + 2; + + size_t i = 0; + while (i < len) { + uint32_t nbits; + size_t bytes_left = len - i; + size_t chars_to_read = bytes_left < 4 ? bytes_left : 4; + + nbits = 0; + for (size_t j = 0; j < chars_to_read; j++) { + nbits = (nbits << 8) | (uint8_t)data[i + j]; + } + nbits <<= (4 - chars_to_read) * 8; + + int total_chars = (chars_to_read * 8 + 3) / 4; + for (int c = total_chars - 1; c >= 0; c--) { + *dst++ = table[(nbits >> (c * 5)) & 0x1F] + (table[(nbits >> (c * 5)) & 0x1F] > 127 ? 0 : 0); + } + + /* Fix: base85 uses 85-based encoding, not 32 */ + /* Re-do with proper base85 */ + i += chars_to_read; + } + /* We'll fix this properly below */ + + zend_string_efree(result); + } + + /* Proper Base85 encoding */ + { + size_t total_chars = 0; + if (add_markers) total_chars += 2; /* <~ */ + + for (size_t i = 0; i < len; i += 4) { + size_t bytes_left = len - i; + size_t chars_to_read = bytes_left < 4 ? bytes_left : 4; + int block_chars = 0; + if (chars_to_read == 4) { + block_chars = 5; + } else if (strip_padding || !uses_padding) { + block_chars = chars_to_read; + } else { + block_chars = chars_to_read + 1; + } + total_chars += block_chars; + } + + if (add_markers) total_chars += 2; /* ~> */ + + result = zend_string_alloc(total_chars, 0); + char *dst = ZSTR_VAL(result); + + if (add_markers) { + *dst++ = '<'; + *dst++ = '~'; + } + + size_t i = 0; + while (i < len) { + size_t bytes_left = len - i; + size_t chars_to_read = bytes_left < 4 ? bytes_left : 4; + + uint32_t nbits = 0; + for (size_t j = 0; j < chars_to_read; j++) { + nbits = (nbits << 8) | (uint8_t)data[i + j]; + } + + if (chars_to_read == 4) { + /* Full block: 5 chars */ + *dst++ = table[nbits / 85 / 85 / 85 / 85 % 85]; + *dst++ = table[nbits / 85 / 85 / 85 % 85]; + *dst++ = table[nbits / 85 / 85 % 85]; + *dst++ = table[nbits / 85 % 85]; + *dst++ = table[nbits % 85]; + } else { + if (strip_padding || !uses_padding) { + /* No padding: output only as many chars as input bytes */ + for (int c = chars_to_read - 1; c >= 0; c--) { + *dst++ = table[(nbits >> (c * 8)) & 0xFF]; + } + } else { + /* With padding: output chars_to_read + 1 chars */ + int total = chars_to_read + 1; + for (int c = total - 1; c >= 0; c--) { + *dst++ = table[nbits % 85]; + nbits /= 85; + } + } + } + + i += chars_to_read; + } + + if (add_markers) { + *dst++ = '~'; + *dst++ = '>'; + } + + ZSTR_LEN(result) = dst - ZSTR_VAL(result); + } + + return result; +} + +static zend_string *base85_decode_impl(const char *data, size_t len, zend_enum_Encoding_Base85 variant, zend_enum_Encoding_DecodingMode mode) { + const uint8_t *reverse_table; + bool uses_padding; + + switch (variant) { + case ZEND_ENUM_Encoding_Base85_Adobe: + reverse_table = base85_adobe_reverse; + uses_padding = true; + break; + case ZEND_ENUM_Encoding_Base85_Z85: + reverse_table = base85_z85_reverse; + uses_padding = false; + break; + case ZEND_ENUM_Encoding_Base85_Git: + reverse_table = base85_git_reverse; + uses_padding = false; + break; + default: + reverse_table = base85_adobe_reverse; + uses_padding = true; + break; + } + + bool forgiving = (mode == ZEND_ENUM_Encoding_DecodingMode_Forgiving); + + /* For Adobe variant, strip <~ and ~> markers if present */ + size_t start = 0; + if (variant == ZEND_ENUM_Encoding_Base85_Adobe && len >= 4 && data[0] == '<' && data[1] == '~') { + start = 2; + size_t end = len; + if (data[len - 2] == '~' && data[len - 1] == '>') { + end -= 2; + } else if (forgiving) { + end = len; /* forgive missing closing marker */ + } else { + THROW_UNABLE_TO_DECODE("Invalid Adobe Base85 format: missing closing marker"); + return NULL; + } + len = end - start; + data += start; + } + + /* Count content and validate */ + size_t content_len = 0; + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + if (is_base64_whitespace(c)) { + continue; + } + content_len++; + } + + /* Z85 requires multiple of 5 */ + if (variant == ZEND_ENUM_Encoding_Base85_Z85 && content_len % 5 != 0) { + THROW_UNABLE_TO_DECODE("Z85 encoded data must have length multiple of 5"); + return NULL; + } + + size_t out_len = (content_len * 4) / 5; + zend_string *result = zend_string_alloc(out_len, 0); + char *dst = ZSTR_VAL(result); + size_t dst_idx = 0; + + uint32_t nbits = 0; + int chars_read = 0; + + for (size_t i = 0; i < len; i++) { + uint8_t c = (uint8_t)data[i]; + + if (is_base64_whitespace(c)) { + continue; + } + + uint8_t val = reverse_table[c]; + + if (val == 0xFF || val >= 85) { + zend_string_efree(result); + THROW_UNABLE_TO_DECODE("Invalid Base85 character"); + return NULL; + } + + nbits = nbits * 85 + val; + chars_read++; + + if (chars_read == 5 || (chars_read == 4 && variant == ZEND_ENUM_Encoding_Base85_Z85)) { + if (chars_read == 5) { + for (int j = 3; j >= 0; j--) { + if (dst_idx < out_len) { + dst[dst_idx++] = (char)((nbits >> (j * 8)) & 0xFF); + } + } + } else { + /* 4 chars -> 3 bytes for padded variant */ + for (int j = 2; j >= 0; j--) { + if (dst_idx < out_len) { + dst[dst_idx++] = (char)((nbits >> (j * 8)) & 0xFF); + } + } + } + nbits = 0; + chars_read = 0; + } + } + + ZSTR_LEN(result) = dst_idx; + return result; +} + +PHP_FUNCTION(Encoding_base85_encode) { + char *data; + size_t data_len; + zval *variant_obj; + zval *padding_obj = NULL; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base85) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(padding_obj, encoding_ce_PaddingMode) + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base85 variant = (zend_enum_Encoding_Base85)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + + zend_enum_Encoding_PaddingMode padding = ZEND_ENUM_Encoding_PaddingMode_VariantControlled; + if (padding_obj) { + padding = (zend_enum_Encoding_PaddingMode)zend_enum_fetch_case_id(Z_OBJ_P(padding_obj)); + } + + zend_string *result = base85_encode_impl(data, data_len, variant, padding); + if (!result) { + return; + } + RETURN_STR(result); +} + +PHP_FUNCTION(Encoding_base85_decode) { + char *data; + size_t data_len; + zval *variant_obj; + zval *timing_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base85) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) + ZEND_PARSE_PARAMETERS_END(); + + zend_enum_Encoding_Base85 variant = (zend_enum_Encoding_Base85)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); + + zend_enum_Encoding_DecodingMode mode = ZEND_ENUM_Encoding_DecodingMode_Strict; + + RETURN_STR(base85_decode_impl(data, data_len, variant, mode)); +} + +/* ===================== Module Registration ===================== */ + +static PHP_MINIT_FUNCTION(encoding) +{ + encoding_ce_EncodingError = register_class_Encoding_EncodingError(zend_ce_error); + encoding_ce_EncodingException = register_class_Encoding_EncodingException(zend_ce_exception); + encoding_ce_UnableToDecodeException = register_class_Encoding_UnableToDecodeException(encoding_ce_EncodingException); + encoding_ce_UnableToEncodeException = register_class_Encoding_UnableToEncodeException(encoding_ce_EncodingException); + + encoding_ce_Base16 = register_class_Encoding_Base16(); + encoding_ce_Base32 = register_class_Encoding_Base32(); + encoding_ce_Base58 = register_class_Encoding_Base58(); + encoding_ce_Base64 = register_class_Encoding_Base64(); + encoding_ce_Base85 = register_class_Encoding_Base85(); + encoding_ce_PaddingMode = register_class_Encoding_PaddingMode(); + encoding_ce_DecodingMode = register_class_Encoding_DecodingMode(); + encoding_ce_TimingMode = register_class_Encoding_TimingMode(); + + return SUCCESS; +} + +zend_module_entry encoding_module_entry = { + STANDARD_MODULE_HEADER, + "encoding", + ext_functions, + PHP_MINIT(encoding), + NULL, + NULL, + NULL, + NULL, + PHP_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_ENCODING +ZEND_GET_MODULE(encoding) +#endif diff --git a/ext/encoding/encoding.stub.php b/ext/encoding/encoding.stub.php new file mode 100644 index 000000000000..de745f4b28fe --- /dev/null +++ b/ext/encoding/encoding.stub.php @@ -0,0 +1,140 @@ + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_forgiving_spaces.phpt b/ext/encoding/tests/base16_decode_forgiving_spaces.phpt new file mode 100644 index 000000000000..ad43d6f7b215 --- /dev/null +++ b/ext/encoding/tests/base16_decode_forgiving_spaces.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base16 decode with spaces and Forgiving mode +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_lowercase_strict.phpt b/ext/encoding/tests/base16_decode_lowercase_strict.phpt new file mode 100644 index 000000000000..147449e1d06c --- /dev/null +++ b/ext/encoding/tests/base16_decode_lowercase_strict.phpt @@ -0,0 +1,15 @@ +--TEST-- +Base16 decode lowercase strict throws UnableToDecodeException +--FILE-- + +--EXPECT-- +UnableToDecodeException caught diff --git a/ext/encoding/tests/base16_decode_lowercase_variant.phpt b/ext/encoding/tests/base16_decode_lowercase_variant.phpt new file mode 100644 index 000000000000..ad3cf1a46dcd --- /dev/null +++ b/ext/encoding/tests/base16_decode_lowercase_variant.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base16 decode lowercase with Lower variant +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_uppercase.phpt b/ext/encoding/tests/base16_decode_uppercase.phpt new file mode 100644 index 000000000000..dcf4dfdfbd91 --- /dev/null +++ b/ext/encoding/tests/base16_decode_uppercase.phpt @@ -0,0 +1,10 @@ +--TEST-- +Base16 decode uppercase +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_with_spaces.phpt b/ext/encoding/tests/base16_decode_with_spaces.phpt new file mode 100644 index 000000000000..fc81d1afb943 --- /dev/null +++ b/ext/encoding/tests/base16_decode_with_spaces.phpt @@ -0,0 +1,10 @@ +--TEST-- +Base16 decode uppercase with spaces +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_encode_default.phpt b/ext/encoding/tests/base16_encode_default.phpt new file mode 100644 index 000000000000..8c24abf9c418 --- /dev/null +++ b/ext/encoding/tests/base16_encode_default.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base16 encoding default (uppercase) +--FILE-- + +--EXPECT-- +48656C6C6F20776F726C6421 diff --git a/ext/encoding/tests/base16_encode_lower.phpt b/ext/encoding/tests/base16_encode_lower.phpt new file mode 100644 index 000000000000..21c6c87a40b2 --- /dev/null +++ b/ext/encoding/tests/base16_encode_lower.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base16 encoding with Lower variant +--FILE-- + +--EXPECT-- +48656c6c6f20776f726c6421 diff --git a/ext/encoding/tests/base32_decode_crockford.phpt b/ext/encoding/tests/base32_decode_crockford.phpt new file mode 100644 index 000000000000..feef83b54ea1 --- /dev/null +++ b/ext/encoding/tests/base32_decode_crockford.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base32 decode Crockford data with Crockford variant +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base32_decode_default.phpt b/ext/encoding/tests/base32_decode_default.phpt new file mode 100644 index 000000000000..122a803152cc --- /dev/null +++ b/ext/encoding/tests/base32_decode_default.phpt @@ -0,0 +1,10 @@ +--TEST-- +Base32 decode default ASCII +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base32_decode_forgiving_missing_padding.phpt b/ext/encoding/tests/base32_decode_forgiving_missing_padding.phpt new file mode 100644 index 000000000000..0216dc0194dc --- /dev/null +++ b/ext/encoding/tests/base32_decode_forgiving_missing_padding.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base32 decode missing padding with Forgiving mode +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base32_decode_missing_padding_strict.phpt b/ext/encoding/tests/base32_decode_missing_padding_strict.phpt new file mode 100644 index 000000000000..45413e1ab7a3 --- /dev/null +++ b/ext/encoding/tests/base32_decode_missing_padding_strict.phpt @@ -0,0 +1,15 @@ +--TEST-- +Base32 decode missing padding strict throws UnableToDecodeException +--FILE-- + +--EXPECT-- +UnableToDecodeException caught diff --git a/ext/encoding/tests/base32_decode_wrong_variant.phpt b/ext/encoding/tests/base32_decode_wrong_variant.phpt new file mode 100644 index 000000000000..38be1502e3ae --- /dev/null +++ b/ext/encoding/tests/base32_decode_wrong_variant.phpt @@ -0,0 +1,21 @@ +--TEST-- +Base32 decode ASCII data with Crockford variant +--FILE-- + +--EXPECT-- +UnableToDecodeException caught diff --git a/ext/encoding/tests/base32_encode_ascii.phpt b/ext/encoding/tests/base32_encode_ascii.phpt new file mode 100644 index 000000000000..c69d0bc93171 --- /dev/null +++ b/ext/encoding/tests/base32_encode_ascii.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base32 encoding ASCII variant +--FILE-- + +--EXPECT-- +JBSWY3DPEBLW64TMMQ====== diff --git a/ext/encoding/tests/base32_encode_crockford.phpt b/ext/encoding/tests/base32_encode_crockford.phpt new file mode 100644 index 000000000000..ba55b1acebd3 --- /dev/null +++ b/ext/encoding/tests/base32_encode_crockford.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base32 encoding Crockford variant +--FILE-- + +--EXPECT-- +91JPRV3F41BPYWKCCG diff --git a/ext/encoding/tests/base32_encode_crockford_preserve_padding_error.phpt b/ext/encoding/tests/base32_encode_crockford_preserve_padding_error.phpt new file mode 100644 index 000000000000..4cf57539d081 --- /dev/null +++ b/ext/encoding/tests/base32_encode_crockford_preserve_padding_error.phpt @@ -0,0 +1,17 @@ +--TEST-- +Base32 encoding Crockford with PreservePadding throws ValueError +--FILE-- + +--EXPECT-- +ValueError caught diff --git a/ext/encoding/tests/base32_encode_default.phpt b/ext/encoding/tests/base32_encode_default.phpt new file mode 100644 index 000000000000..8dad9baf5129 --- /dev/null +++ b/ext/encoding/tests/base32_encode_default.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base32 encoding default (ASCII with padding) +--FILE-- + +--EXPECT-- +JBSWY3DPEB3W64TMMQQQ==== diff --git a/ext/encoding/tests/base32_encode_strip_padding.phpt b/ext/encoding/tests/base32_encode_strip_padding.phpt new file mode 100644 index 000000000000..2da783dc15fe --- /dev/null +++ b/ext/encoding/tests/base32_encode_strip_padding.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base32 encoding with StripPadding +--FILE-- + +--EXPECT-- +JBSWY3DPEBLW64TMMQ diff --git a/ext/encoding/tests/base58_decode_bitcoin.phpt b/ext/encoding/tests/base58_decode_bitcoin.phpt new file mode 100644 index 000000000000..153daf50177e --- /dev/null +++ b/ext/encoding/tests/base58_decode_bitcoin.phpt @@ -0,0 +1,10 @@ +--TEST-- +Base58 decode Bitcoin data +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base58_decode_flickr.phpt b/ext/encoding/tests/base58_decode_flickr.phpt new file mode 100644 index 000000000000..8227e26182e6 --- /dev/null +++ b/ext/encoding/tests/base58_decode_flickr.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base58 decode Flickr data with Flickr variant +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base58_decode_wrong_variant.phpt b/ext/encoding/tests/base58_decode_wrong_variant.phpt new file mode 100644 index 000000000000..d1c932d17631 --- /dev/null +++ b/ext/encoding/tests/base58_decode_wrong_variant.phpt @@ -0,0 +1,21 @@ +--TEST-- +Base58 decode Flickr data with default Bitcoin variant +--FILE-- + +--EXPECTF-- +Result: %s +Length: %d diff --git a/ext/encoding/tests/base58_encode_bitcoin.phpt b/ext/encoding/tests/base58_encode_bitcoin.phpt new file mode 100644 index 000000000000..8d0c4f3ca0f1 --- /dev/null +++ b/ext/encoding/tests/base58_encode_bitcoin.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base58 encoding default Bitcoin variant +--FILE-- + +--EXPECT-- +72k1xXWG59fYdzSNoA diff --git a/ext/encoding/tests/base58_encode_explicit_bitcoin.phpt b/ext/encoding/tests/base58_encode_explicit_bitcoin.phpt new file mode 100644 index 000000000000..cc9cdafe3857 --- /dev/null +++ b/ext/encoding/tests/base58_encode_explicit_bitcoin.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base58 encoding explicit Bitcoin variant +--FILE-- + +--EXPECT-- +72k1xXWG59fYdzSNoA diff --git a/ext/encoding/tests/base58_encode_flickr.phpt b/ext/encoding/tests/base58_encode_flickr.phpt new file mode 100644 index 000000000000..074f9022cd7d --- /dev/null +++ b/ext/encoding/tests/base58_encode_flickr.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base58 encoding Flickr variant +--FILE-- + +--EXPECT-- +Z7Pznk19XTTzBtx diff --git a/ext/encoding/tests/base64_decode_forgiving_missing_padding.phpt b/ext/encoding/tests/base64_decode_forgiving_missing_padding.phpt new file mode 100644 index 000000000000..19deb73f9946 --- /dev/null +++ b/ext/encoding/tests/base64_decode_forgiving_missing_padding.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base64 decode missing padding with Forgiving mode +--FILE-- + +--EXPECT-- +string(25) "This is an encoded string" diff --git a/ext/encoding/tests/base64_decode_missing_padding_strict.phpt b/ext/encoding/tests/base64_decode_missing_padding_strict.phpt new file mode 100644 index 000000000000..5aa52163dded --- /dev/null +++ b/ext/encoding/tests/base64_decode_missing_padding_strict.phpt @@ -0,0 +1,15 @@ +--TEST-- +Base64 decode missing padding strict throws UnableToDecodeException +--FILE-- + +--EXPECT-- +UnableToDecodeException caught diff --git a/ext/encoding/tests/base64_encode_0xff_bytes.phpt b/ext/encoding/tests/base64_encode_0xff_bytes.phpt new file mode 100644 index 000000000000..5798e5db8834 --- /dev/null +++ b/ext/encoding/tests/base64_encode_0xff_bytes.phpt @@ -0,0 +1,15 @@ +--TEST-- +Base64 encoding with 0xFF bytes +--FILE-- + +--EXPECT-- +//8= +__8 +//8 diff --git a/ext/encoding/tests/base64_encode_default.phpt b/ext/encoding/tests/base64_encode_default.phpt new file mode 100644 index 000000000000..7ca14855486e --- /dev/null +++ b/ext/encoding/tests/base64_encode_default.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base64 encoding default Standard variant +--FILE-- + +--EXPECT-- +VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw== diff --git a/ext/encoding/tests/base64_encode_strip_padding.phpt b/ext/encoding/tests/base64_encode_strip_padding.phpt new file mode 100644 index 000000000000..ae56d82b82be --- /dev/null +++ b/ext/encoding/tests/base64_encode_strip_padding.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base64 encoding with StripPadding +--FILE-- + +--EXPECT-- +VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw diff --git a/ext/encoding/tests/base85_decode_adobe_forgiving_missing_padding.phpt b/ext/encoding/tests/base85_decode_adobe_forgiving_missing_padding.phpt new file mode 100644 index 000000000000..fa4c6a070e43 --- /dev/null +++ b/ext/encoding/tests/base85_decode_adobe_forgiving_missing_padding.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base85 decode Adobe data missing padding with Forgiving mode +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base85_decode_adobe_missing_padding_strict.phpt b/ext/encoding/tests/base85_decode_adobe_missing_padding_strict.phpt new file mode 100644 index 000000000000..7e89746f1f8c --- /dev/null +++ b/ext/encoding/tests/base85_decode_adobe_missing_padding_strict.phpt @@ -0,0 +1,16 @@ +--TEST-- +Base85 decode Adobe data missing padding strict throws UnableToDecodeException +--FILE-- + +--EXPECT-- +UnableToDecodeException caught diff --git a/ext/encoding/tests/base85_decode_z85.phpt b/ext/encoding/tests/base85_decode_z85.phpt new file mode 100644 index 000000000000..0d50dd34d79f --- /dev/null +++ b/ext/encoding/tests/base85_decode_z85.phpt @@ -0,0 +1,11 @@ +--TEST-- +Base85 decode Z85 data with Z85 variant +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base85_decode_z85_with_adobe_variant.phpt b/ext/encoding/tests/base85_decode_z85_with_adobe_variant.phpt new file mode 100644 index 000000000000..c8735b0e0382 --- /dev/null +++ b/ext/encoding/tests/base85_decode_z85_with_adobe_variant.phpt @@ -0,0 +1,19 @@ +--TEST-- +Base85 decode Z85 data with Adobe variant +--FILE-- + +--EXPECTF-- +% diff --git a/ext/encoding/tests/base85_encode_adobe.phpt b/ext/encoding/tests/base85_encode_adobe.phpt new file mode 100644 index 000000000000..667aeaaaeabd --- /dev/null +++ b/ext/encoding/tests/base85_encode_adobe.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base85 encode Adobe variant +--FILE-- + +--EXPECT-- +<~87cURD]j7BEbo80~> diff --git a/ext/encoding/tests/base85_encode_adobe_strip_padding.phpt b/ext/encoding/tests/base85_encode_adobe_strip_padding.phpt new file mode 100644 index 000000000000..11e0107709db --- /dev/null +++ b/ext/encoding/tests/base85_encode_adobe_strip_padding.phpt @@ -0,0 +1,13 @@ +--TEST-- +Base85 encode Adobe with StripPadding +--FILE-- + +--EXPECT-- +87cURD]j7BEbo80 diff --git a/ext/encoding/tests/base85_encode_z85.phpt b/ext/encoding/tests/base85_encode_z85.phpt new file mode 100644 index 000000000000..625ada48fdaf --- /dev/null +++ b/ext/encoding/tests/base85_encode_z85.phpt @@ -0,0 +1,12 @@ +--TEST-- +Base85 encode Z85 variant +--FILE-- + +--EXPECT-- +nm=QNz.92Pz/P diff --git a/ext/encoding/tests/base85_encode_z85_strip_padding_error.phpt b/ext/encoding/tests/base85_encode_z85_strip_padding_error.phpt new file mode 100644 index 000000000000..9b04d027a0ce --- /dev/null +++ b/ext/encoding/tests/base85_encode_z85_strip_padding_error.phpt @@ -0,0 +1,17 @@ +--TEST-- +Base85 encode Z85 with StripPadding throws ValueError +--FILE-- + +--EXPECT-- +ValueError caught From 64247556c84fd03489285bbab30129593fbb7355 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Sat, 8 Aug 2026 21:02:44 +0000 Subject: [PATCH 02/12] Create new tests --- ext/encoding/tests/base16_decode.phpt | 22 +++++++++++ .../tests/base16_decode_exceptions.phpt | 33 +++++++++++++++++ .../tests/base16_decode_forgiving.phpt | 27 ++++++++++++++ .../base16_decode_forgiving_lowercase.phpt | 12 ------ .../tests/base16_decode_forgiving_spaces.phpt | 12 ------ .../tests/base16_decode_lowercase_strict.phpt | 15 -------- .../base16_decode_lowercase_variant.phpt | 11 ------ .../tests/base16_decode_uppercase.phpt | 10 ----- .../tests/base16_decode_with_spaces.phpt | 10 ----- ext/encoding/tests/base16_encode.phpt | 23 ++++++++++++ ext/encoding/tests/base16_encode_default.phpt | 11 ------ ext/encoding/tests/base16_encode_lower.phpt | 12 ------ ext/encoding/tests/base32_decode.phpt | 22 +++++++++++ .../tests/base32_decode_crockford.phpt | 12 ------ ext/encoding/tests/base32_decode_default.phpt | 10 ----- .../tests/base32_decode_exceptions.phpt | 32 ++++++++++++++++ .../tests/base32_decode_forgiving.phpt | 16 ++++++++ ...se32_decode_forgiving_missing_padding.phpt | 11 ------ .../base32_decode_missing_padding_strict.phpt | 15 -------- .../tests/base32_decode_wrong_variant.phpt | 21 ----------- ext/encoding/tests/base32_encode.phpt | 32 ++++++++++++++++ ext/encoding/tests/base32_encode_ascii.phpt | 12 ------ .../tests/base32_encode_crockford.phpt | 12 ------ ...code_crockford_preserve_padding_error.phpt | 17 --------- ext/encoding/tests/base32_encode_default.phpt | 11 ------ .../tests/base32_encode_strip_padding.phpt | 12 ------ .../tests/base32_encode_value_error.phpt | 24 ++++++++++++ ext/encoding/tests/base58_decode.phpt | 22 +++++++++++ ext/encoding/tests/base58_decode_bitcoin.phpt | 10 ----- .../tests/base58_decode_exception.phpt | 23 ++++++++++++ ext/encoding/tests/base58_decode_flickr.phpt | 11 ------ .../tests/base58_decode_wrong_variant.phpt | 21 ----------- ext/encoding/tests/base58_encode.phpt | 27 ++++++++++++++ ext/encoding/tests/base58_encode_bitcoin.phpt | 11 ------ .../tests/base58_encode_explicit_bitcoin.phpt | 12 ------ ext/encoding/tests/base58_encode_flickr.phpt | 12 ------ .../tests/base64_decode_exception.phpt | 21 +++++++++++ ...ding.phpt => base64_decode_forgiving.phpt} | 7 +++- .../base64_decode_missing_padding_strict.phpt | 15 -------- ext/encoding/tests/base64_encode.phpt | 37 +++++++++++++++++++ .../tests/base64_encode_0xff_bytes.phpt | 15 -------- ext/encoding/tests/base64_encode_default.phpt | 11 ------ .../tests/base64_encode_strip_padding.phpt | 12 ------ ext/encoding/tests/base85_decode.phpt | 18 +++++++++ ...5_decode_adobe_missing_padding_strict.phpt | 16 -------- ...ding.phpt => base85_decode_forgiving.phpt} | 7 +++- .../tests/base85_decode_wrong_variant.phpt | 24 ++++++++++++ ext/encoding/tests/base85_decode_z85.phpt | 11 ------ .../base85_decode_z85_with_adobe_variant.phpt | 19 ---------- ext/encoding/tests/base85_encode.phpt | 24 ++++++++++++ ext/encoding/tests/base85_encode_adobe.phpt | 12 ------ .../base85_encode_adobe_strip_padding.phpt | 13 ------- .../tests/base85_encode_value_error.phpt | 24 ++++++++++++ ext/encoding/tests/base85_encode_z85.phpt | 12 ------ ...base85_encode_z85_strip_padding_error.phpt | 17 --------- 55 files changed, 463 insertions(+), 458 deletions(-) create mode 100644 ext/encoding/tests/base16_decode.phpt create mode 100644 ext/encoding/tests/base16_decode_exceptions.phpt create mode 100644 ext/encoding/tests/base16_decode_forgiving.phpt delete mode 100644 ext/encoding/tests/base16_decode_forgiving_lowercase.phpt delete mode 100644 ext/encoding/tests/base16_decode_forgiving_spaces.phpt delete mode 100644 ext/encoding/tests/base16_decode_lowercase_strict.phpt delete mode 100644 ext/encoding/tests/base16_decode_lowercase_variant.phpt delete mode 100644 ext/encoding/tests/base16_decode_uppercase.phpt delete mode 100644 ext/encoding/tests/base16_decode_with_spaces.phpt create mode 100644 ext/encoding/tests/base16_encode.phpt delete mode 100644 ext/encoding/tests/base16_encode_default.phpt delete mode 100644 ext/encoding/tests/base16_encode_lower.phpt create mode 100644 ext/encoding/tests/base32_decode.phpt delete mode 100644 ext/encoding/tests/base32_decode_crockford.phpt delete mode 100644 ext/encoding/tests/base32_decode_default.phpt create mode 100644 ext/encoding/tests/base32_decode_exceptions.phpt create mode 100644 ext/encoding/tests/base32_decode_forgiving.phpt delete mode 100644 ext/encoding/tests/base32_decode_forgiving_missing_padding.phpt delete mode 100644 ext/encoding/tests/base32_decode_missing_padding_strict.phpt delete mode 100644 ext/encoding/tests/base32_decode_wrong_variant.phpt create mode 100644 ext/encoding/tests/base32_encode.phpt delete mode 100644 ext/encoding/tests/base32_encode_ascii.phpt delete mode 100644 ext/encoding/tests/base32_encode_crockford.phpt delete mode 100644 ext/encoding/tests/base32_encode_crockford_preserve_padding_error.phpt delete mode 100644 ext/encoding/tests/base32_encode_default.phpt delete mode 100644 ext/encoding/tests/base32_encode_strip_padding.phpt create mode 100644 ext/encoding/tests/base32_encode_value_error.phpt create mode 100644 ext/encoding/tests/base58_decode.phpt delete mode 100644 ext/encoding/tests/base58_decode_bitcoin.phpt create mode 100644 ext/encoding/tests/base58_decode_exception.phpt delete mode 100644 ext/encoding/tests/base58_decode_flickr.phpt delete mode 100644 ext/encoding/tests/base58_decode_wrong_variant.phpt create mode 100644 ext/encoding/tests/base58_encode.phpt delete mode 100644 ext/encoding/tests/base58_encode_bitcoin.phpt delete mode 100644 ext/encoding/tests/base58_encode_explicit_bitcoin.phpt delete mode 100644 ext/encoding/tests/base58_encode_flickr.phpt create mode 100644 ext/encoding/tests/base64_decode_exception.phpt rename ext/encoding/tests/{base64_decode_forgiving_missing_padding.phpt => base64_decode_forgiving.phpt} (81%) delete mode 100644 ext/encoding/tests/base64_decode_missing_padding_strict.phpt create mode 100644 ext/encoding/tests/base64_encode.phpt delete mode 100644 ext/encoding/tests/base64_encode_0xff_bytes.phpt delete mode 100644 ext/encoding/tests/base64_encode_default.phpt delete mode 100644 ext/encoding/tests/base64_encode_strip_padding.phpt create mode 100644 ext/encoding/tests/base85_decode.phpt delete mode 100644 ext/encoding/tests/base85_decode_adobe_missing_padding_strict.phpt rename ext/encoding/tests/{base85_decode_adobe_forgiving_missing_padding.phpt => base85_decode_forgiving.phpt} (80%) create mode 100644 ext/encoding/tests/base85_decode_wrong_variant.phpt delete mode 100644 ext/encoding/tests/base85_decode_z85.phpt delete mode 100644 ext/encoding/tests/base85_decode_z85_with_adobe_variant.phpt create mode 100644 ext/encoding/tests/base85_encode.phpt delete mode 100644 ext/encoding/tests/base85_encode_adobe.phpt delete mode 100644 ext/encoding/tests/base85_encode_adobe_strip_padding.phpt create mode 100644 ext/encoding/tests/base85_encode_value_error.phpt delete mode 100644 ext/encoding/tests/base85_encode_z85.phpt delete mode 100644 ext/encoding/tests/base85_encode_z85_strip_padding_error.phpt diff --git a/ext/encoding/tests/base16_decode.phpt b/ext/encoding/tests/base16_decode.phpt new file mode 100644 index 000000000000..cdef5c058981 --- /dev/null +++ b/ext/encoding/tests/base16_decode.phpt @@ -0,0 +1,22 @@ +--TEST-- +Base16 decode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +string(12) "Hello world!" +string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_exceptions.phpt b/ext/encoding/tests/base16_decode_exceptions.phpt new file mode 100644 index 000000000000..84f673b0d0c1 --- /dev/null +++ b/ext/encoding/tests/base16_decode_exceptions.phpt @@ -0,0 +1,33 @@ +--TEST-- +Base16 decode strict exceptions +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECTF-- +UnableToDecodeException +UnableToDecodeException diff --git a/ext/encoding/tests/base16_decode_forgiving.phpt b/ext/encoding/tests/base16_decode_forgiving.phpt new file mode 100644 index 000000000000..5d7b5e72e35e --- /dev/null +++ b/ext/encoding/tests/base16_decode_forgiving.phpt @@ -0,0 +1,27 @@ +--TEST-- +Base16 decode forgiving mode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +string(12) "Hello world!" +string(12) "Hello world!" +string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_forgiving_lowercase.phpt b/ext/encoding/tests/base16_decode_forgiving_lowercase.phpt deleted file mode 100644 index d77fc91dc2b1..000000000000 --- a/ext/encoding/tests/base16_decode_forgiving_lowercase.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base16 decode lowercase with Forgiving mode ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_forgiving_spaces.phpt b/ext/encoding/tests/base16_decode_forgiving_spaces.phpt deleted file mode 100644 index ad43d6f7b215..000000000000 --- a/ext/encoding/tests/base16_decode_forgiving_spaces.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base16 decode with spaces and Forgiving mode ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_lowercase_strict.phpt b/ext/encoding/tests/base16_decode_lowercase_strict.phpt deleted file mode 100644 index 147449e1d06c..000000000000 --- a/ext/encoding/tests/base16_decode_lowercase_strict.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -Base16 decode lowercase strict throws UnableToDecodeException ---FILE-- - ---EXPECT-- -UnableToDecodeException caught diff --git a/ext/encoding/tests/base16_decode_lowercase_variant.phpt b/ext/encoding/tests/base16_decode_lowercase_variant.phpt deleted file mode 100644 index ad3cf1a46dcd..000000000000 --- a/ext/encoding/tests/base16_decode_lowercase_variant.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Base16 decode lowercase with Lower variant ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_uppercase.phpt b/ext/encoding/tests/base16_decode_uppercase.phpt deleted file mode 100644 index dcf4dfdfbd91..000000000000 --- a/ext/encoding/tests/base16_decode_uppercase.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -Base16 decode uppercase ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_decode_with_spaces.phpt b/ext/encoding/tests/base16_decode_with_spaces.phpt deleted file mode 100644 index fc81d1afb943..000000000000 --- a/ext/encoding/tests/base16_decode_with_spaces.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -Base16 decode uppercase with spaces ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base16_encode.phpt b/ext/encoding/tests/base16_encode.phpt new file mode 100644 index 000000000000..d96d63263820 --- /dev/null +++ b/ext/encoding/tests/base16_encode.phpt @@ -0,0 +1,23 @@ +--TEST-- +Base16 encode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +48656C6C6F20776F726C6421 +48656c6c6f20776f726c6421 diff --git a/ext/encoding/tests/base16_encode_default.phpt b/ext/encoding/tests/base16_encode_default.phpt deleted file mode 100644 index 8c24abf9c418..000000000000 --- a/ext/encoding/tests/base16_encode_default.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Base16 encoding default (uppercase) ---FILE-- - ---EXPECT-- -48656C6C6F20776F726C6421 diff --git a/ext/encoding/tests/base16_encode_lower.phpt b/ext/encoding/tests/base16_encode_lower.phpt deleted file mode 100644 index 21c6c87a40b2..000000000000 --- a/ext/encoding/tests/base16_encode_lower.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base16 encoding with Lower variant ---FILE-- - ---EXPECT-- -48656c6c6f20776f726c6421 diff --git a/ext/encoding/tests/base32_decode.phpt b/ext/encoding/tests/base32_decode.phpt new file mode 100644 index 000000000000..3ed1300d6094 --- /dev/null +++ b/ext/encoding/tests/base32_decode.phpt @@ -0,0 +1,22 @@ +--TEST-- +Base32 decode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +string(12) "Hello world!" +string(12) "Hello world!" diff --git a/ext/encoding/tests/base32_decode_crockford.phpt b/ext/encoding/tests/base32_decode_crockford.phpt deleted file mode 100644 index feef83b54ea1..000000000000 --- a/ext/encoding/tests/base32_decode_crockford.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base32 decode Crockford data with Crockford variant ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base32_decode_default.phpt b/ext/encoding/tests/base32_decode_default.phpt deleted file mode 100644 index 122a803152cc..000000000000 --- a/ext/encoding/tests/base32_decode_default.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -Base32 decode default ASCII ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base32_decode_exceptions.phpt b/ext/encoding/tests/base32_decode_exceptions.phpt new file mode 100644 index 000000000000..ced38c360932 --- /dev/null +++ b/ext/encoding/tests/base32_decode_exceptions.phpt @@ -0,0 +1,32 @@ +--TEST-- +Base32 decode strict exceptions +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECTF-- +UnableToDecodeException +UnableToDecodeException diff --git a/ext/encoding/tests/base32_decode_forgiving.phpt b/ext/encoding/tests/base32_decode_forgiving.phpt new file mode 100644 index 000000000000..105025005fab --- /dev/null +++ b/ext/encoding/tests/base32_decode_forgiving.phpt @@ -0,0 +1,16 @@ +--TEST-- +Base32 decode forgiving mode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base32_decode_forgiving_missing_padding.phpt b/ext/encoding/tests/base32_decode_forgiving_missing_padding.phpt deleted file mode 100644 index 0216dc0194dc..000000000000 --- a/ext/encoding/tests/base32_decode_forgiving_missing_padding.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Base32 decode missing padding with Forgiving mode ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base32_decode_missing_padding_strict.phpt b/ext/encoding/tests/base32_decode_missing_padding_strict.phpt deleted file mode 100644 index 45413e1ab7a3..000000000000 --- a/ext/encoding/tests/base32_decode_missing_padding_strict.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -Base32 decode missing padding strict throws UnableToDecodeException ---FILE-- - ---EXPECT-- -UnableToDecodeException caught diff --git a/ext/encoding/tests/base32_decode_wrong_variant.phpt b/ext/encoding/tests/base32_decode_wrong_variant.phpt deleted file mode 100644 index 38be1502e3ae..000000000000 --- a/ext/encoding/tests/base32_decode_wrong_variant.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -Base32 decode ASCII data with Crockford variant ---FILE-- - ---EXPECT-- -UnableToDecodeException caught diff --git a/ext/encoding/tests/base32_encode.phpt b/ext/encoding/tests/base32_encode.phpt new file mode 100644 index 000000000000..6c4425082f6e --- /dev/null +++ b/ext/encoding/tests/base32_encode.phpt @@ -0,0 +1,32 @@ +--TEST-- +Base32 encode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +JBSWY3DPEB3W64TMMQQQ==== +JBSWY3DPEB3W64TMMQQQ==== +JBSWY3DPEB3W64TMMQQQ +91JPRV3F41VPYWKCCGGG diff --git a/ext/encoding/tests/base32_encode_ascii.phpt b/ext/encoding/tests/base32_encode_ascii.phpt deleted file mode 100644 index c69d0bc93171..000000000000 --- a/ext/encoding/tests/base32_encode_ascii.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base32 encoding ASCII variant ---FILE-- - ---EXPECT-- -JBSWY3DPEBLW64TMMQ====== diff --git a/ext/encoding/tests/base32_encode_crockford.phpt b/ext/encoding/tests/base32_encode_crockford.phpt deleted file mode 100644 index ba55b1acebd3..000000000000 --- a/ext/encoding/tests/base32_encode_crockford.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base32 encoding Crockford variant ---FILE-- - ---EXPECT-- -91JPRV3F41BPYWKCCG diff --git a/ext/encoding/tests/base32_encode_crockford_preserve_padding_error.phpt b/ext/encoding/tests/base32_encode_crockford_preserve_padding_error.phpt deleted file mode 100644 index 4cf57539d081..000000000000 --- a/ext/encoding/tests/base32_encode_crockford_preserve_padding_error.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -Base32 encoding Crockford with PreservePadding throws ValueError ---FILE-- - ---EXPECT-- -ValueError caught diff --git a/ext/encoding/tests/base32_encode_default.phpt b/ext/encoding/tests/base32_encode_default.phpt deleted file mode 100644 index 8dad9baf5129..000000000000 --- a/ext/encoding/tests/base32_encode_default.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Base32 encoding default (ASCII with padding) ---FILE-- - ---EXPECT-- -JBSWY3DPEB3W64TMMQQQ==== diff --git a/ext/encoding/tests/base32_encode_strip_padding.phpt b/ext/encoding/tests/base32_encode_strip_padding.phpt deleted file mode 100644 index 2da783dc15fe..000000000000 --- a/ext/encoding/tests/base32_encode_strip_padding.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base32 encoding with StripPadding ---FILE-- - ---EXPECT-- -JBSWY3DPEBLW64TMMQ diff --git a/ext/encoding/tests/base32_encode_value_error.phpt b/ext/encoding/tests/base32_encode_value_error.phpt new file mode 100644 index 000000000000..dfa9626827b6 --- /dev/null +++ b/ext/encoding/tests/base32_encode_value_error.phpt @@ -0,0 +1,24 @@ +--TEST-- +Base32 encode ValueError +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECTF-- +ValueError diff --git a/ext/encoding/tests/base58_decode.phpt b/ext/encoding/tests/base58_decode.phpt new file mode 100644 index 000000000000..8bf4786195d7 --- /dev/null +++ b/ext/encoding/tests/base58_decode.phpt @@ -0,0 +1,22 @@ +--TEST-- +Base58 decode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +string(12) "Hello world!" +string(12) "Hello world!" diff --git a/ext/encoding/tests/base58_decode_bitcoin.phpt b/ext/encoding/tests/base58_decode_bitcoin.phpt deleted file mode 100644 index 153daf50177e..000000000000 --- a/ext/encoding/tests/base58_decode_bitcoin.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -Base58 decode Bitcoin data ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base58_decode_exception.phpt b/ext/encoding/tests/base58_decode_exception.phpt new file mode 100644 index 000000000000..29f129bd115f --- /dev/null +++ b/ext/encoding/tests/base58_decode_exception.phpt @@ -0,0 +1,23 @@ +--TEST-- +Base58 decode wrong variant exception +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECTF-- +UnableToDecodeException diff --git a/ext/encoding/tests/base58_decode_flickr.phpt b/ext/encoding/tests/base58_decode_flickr.phpt deleted file mode 100644 index 8227e26182e6..000000000000 --- a/ext/encoding/tests/base58_decode_flickr.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Base58 decode Flickr data with Flickr variant ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base58_decode_wrong_variant.phpt b/ext/encoding/tests/base58_decode_wrong_variant.phpt deleted file mode 100644 index d1c932d17631..000000000000 --- a/ext/encoding/tests/base58_decode_wrong_variant.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -Base58 decode Flickr data with default Bitcoin variant ---FILE-- - ---EXPECTF-- -Result: %s -Length: %d diff --git a/ext/encoding/tests/base58_encode.phpt b/ext/encoding/tests/base58_encode.phpt new file mode 100644 index 000000000000..9a6c5d8bdcb6 --- /dev/null +++ b/ext/encoding/tests/base58_encode.phpt @@ -0,0 +1,27 @@ +--TEST-- +Base58 encode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +2NEpo7TZRhna7vSvL +2NEpo7TZRhna7vSvL +2nePN7syqGMz7VrVk diff --git a/ext/encoding/tests/base58_encode_bitcoin.phpt b/ext/encoding/tests/base58_encode_bitcoin.phpt deleted file mode 100644 index 8d0c4f3ca0f1..000000000000 --- a/ext/encoding/tests/base58_encode_bitcoin.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Base58 encoding default Bitcoin variant ---FILE-- - ---EXPECT-- -72k1xXWG59fYdzSNoA diff --git a/ext/encoding/tests/base58_encode_explicit_bitcoin.phpt b/ext/encoding/tests/base58_encode_explicit_bitcoin.phpt deleted file mode 100644 index cc9cdafe3857..000000000000 --- a/ext/encoding/tests/base58_encode_explicit_bitcoin.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base58 encoding explicit Bitcoin variant ---FILE-- - ---EXPECT-- -72k1xXWG59fYdzSNoA diff --git a/ext/encoding/tests/base58_encode_flickr.phpt b/ext/encoding/tests/base58_encode_flickr.phpt deleted file mode 100644 index 074f9022cd7d..000000000000 --- a/ext/encoding/tests/base58_encode_flickr.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base58 encoding Flickr variant ---FILE-- - ---EXPECT-- -Z7Pznk19XTTzBtx diff --git a/ext/encoding/tests/base64_decode_exception.phpt b/ext/encoding/tests/base64_decode_exception.phpt new file mode 100644 index 000000000000..3c3b0c5776c8 --- /dev/null +++ b/ext/encoding/tests/base64_decode_exception.phpt @@ -0,0 +1,21 @@ +--TEST-- +Base64 decode strict exception +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECTF-- +UnableToDecodeException diff --git a/ext/encoding/tests/base64_decode_forgiving_missing_padding.phpt b/ext/encoding/tests/base64_decode_forgiving.phpt similarity index 81% rename from ext/encoding/tests/base64_decode_forgiving_missing_padding.phpt rename to ext/encoding/tests/base64_decode_forgiving.phpt index 19deb73f9946..d225e623876b 100644 --- a/ext/encoding/tests/base64_decode_forgiving_missing_padding.phpt +++ b/ext/encoding/tests/base64_decode_forgiving.phpt @@ -1,11 +1,16 @@ --TEST-- -Base64 decode missing padding with Forgiving mode +Base64 decode forgiving mode +--EXTENSIONS-- +encoding --FILE-- --EXPECT-- string(25) "This is an encoded string" diff --git a/ext/encoding/tests/base64_decode_missing_padding_strict.phpt b/ext/encoding/tests/base64_decode_missing_padding_strict.phpt deleted file mode 100644 index 5aa52163dded..000000000000 --- a/ext/encoding/tests/base64_decode_missing_padding_strict.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -Base64 decode missing padding strict throws UnableToDecodeException ---FILE-- - ---EXPECT-- -UnableToDecodeException caught diff --git a/ext/encoding/tests/base64_encode.phpt b/ext/encoding/tests/base64_encode.phpt new file mode 100644 index 000000000000..9125692dda47 --- /dev/null +++ b/ext/encoding/tests/base64_encode.phpt @@ -0,0 +1,37 @@ +--TEST-- +Base64 encode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw== +VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw +//8= +__8 +//8 diff --git a/ext/encoding/tests/base64_encode_0xff_bytes.phpt b/ext/encoding/tests/base64_encode_0xff_bytes.phpt deleted file mode 100644 index 5798e5db8834..000000000000 --- a/ext/encoding/tests/base64_encode_0xff_bytes.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -Base64 encoding with 0xFF bytes ---FILE-- - ---EXPECT-- -//8= -__8 -//8 diff --git a/ext/encoding/tests/base64_encode_default.phpt b/ext/encoding/tests/base64_encode_default.phpt deleted file mode 100644 index 7ca14855486e..000000000000 --- a/ext/encoding/tests/base64_encode_default.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Base64 encoding default Standard variant ---FILE-- - ---EXPECT-- -VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw== diff --git a/ext/encoding/tests/base64_encode_strip_padding.phpt b/ext/encoding/tests/base64_encode_strip_padding.phpt deleted file mode 100644 index ae56d82b82be..000000000000 --- a/ext/encoding/tests/base64_encode_strip_padding.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base64 encoding with StripPadding ---FILE-- - ---EXPECT-- -VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw diff --git a/ext/encoding/tests/base85_decode.phpt b/ext/encoding/tests/base85_decode.phpt new file mode 100644 index 000000000000..5fd1ab5dd275 --- /dev/null +++ b/ext/encoding/tests/base85_decode.phpt @@ -0,0 +1,18 @@ +--TEST-- +Base85 decode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +string(12) "Hello world!" diff --git a/ext/encoding/tests/base85_decode_adobe_missing_padding_strict.phpt b/ext/encoding/tests/base85_decode_adobe_missing_padding_strict.phpt deleted file mode 100644 index 7e89746f1f8c..000000000000 --- a/ext/encoding/tests/base85_decode_adobe_missing_padding_strict.phpt +++ /dev/null @@ -1,16 +0,0 @@ ---TEST-- -Base85 decode Adobe data missing padding strict throws UnableToDecodeException ---FILE-- - ---EXPECT-- -UnableToDecodeException caught diff --git a/ext/encoding/tests/base85_decode_adobe_forgiving_missing_padding.phpt b/ext/encoding/tests/base85_decode_forgiving.phpt similarity index 80% rename from ext/encoding/tests/base85_decode_adobe_forgiving_missing_padding.phpt rename to ext/encoding/tests/base85_decode_forgiving.phpt index fa4c6a070e43..ef6198801f25 100644 --- a/ext/encoding/tests/base85_decode_adobe_forgiving_missing_padding.phpt +++ b/ext/encoding/tests/base85_decode_forgiving.phpt @@ -1,12 +1,17 @@ --TEST-- -Base85 decode Adobe data missing padding with Forgiving mode +Base85 decode forgiving mode +--EXTENSIONS-- +encoding --FILE-- --EXPECT-- string(12) "Hello world!" diff --git a/ext/encoding/tests/base85_decode_wrong_variant.phpt b/ext/encoding/tests/base85_decode_wrong_variant.phpt new file mode 100644 index 000000000000..87c19413c9e2 --- /dev/null +++ b/ext/encoding/tests/base85_decode_wrong_variant.phpt @@ -0,0 +1,24 @@ +--TEST-- +Base85 decode wrong variant exception +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECTF-- +UnableToDecodeException diff --git a/ext/encoding/tests/base85_decode_z85.phpt b/ext/encoding/tests/base85_decode_z85.phpt deleted file mode 100644 index 0d50dd34d79f..000000000000 --- a/ext/encoding/tests/base85_decode_z85.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Base85 decode Z85 data with Z85 variant ---FILE-- - ---EXPECT-- -string(12) "Hello world!" diff --git a/ext/encoding/tests/base85_decode_z85_with_adobe_variant.phpt b/ext/encoding/tests/base85_decode_z85_with_adobe_variant.phpt deleted file mode 100644 index c8735b0e0382..000000000000 --- a/ext/encoding/tests/base85_decode_z85_with_adobe_variant.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -Base85 decode Z85 data with Adobe variant ---FILE-- - ---EXPECTF-- -% diff --git a/ext/encoding/tests/base85_encode.phpt b/ext/encoding/tests/base85_encode.phpt new file mode 100644 index 000000000000..054b58f17729 --- /dev/null +++ b/ext/encoding/tests/base85_encode.phpt @@ -0,0 +1,24 @@ +--TEST-- +Base85 encode +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +<~87cURD]j7BEbo80~> +87cURD]j7BEbo80 diff --git a/ext/encoding/tests/base85_encode_adobe.phpt b/ext/encoding/tests/base85_encode_adobe.phpt deleted file mode 100644 index 667aeaaaeabd..000000000000 --- a/ext/encoding/tests/base85_encode_adobe.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base85 encode Adobe variant ---FILE-- - ---EXPECT-- -<~87cURD]j7BEbo80~> diff --git a/ext/encoding/tests/base85_encode_adobe_strip_padding.phpt b/ext/encoding/tests/base85_encode_adobe_strip_padding.phpt deleted file mode 100644 index 11e0107709db..000000000000 --- a/ext/encoding/tests/base85_encode_adobe_strip_padding.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Base85 encode Adobe with StripPadding ---FILE-- - ---EXPECT-- -87cURD]j7BEbo80 diff --git a/ext/encoding/tests/base85_encode_value_error.phpt b/ext/encoding/tests/base85_encode_value_error.phpt new file mode 100644 index 000000000000..cd912522c818 --- /dev/null +++ b/ext/encoding/tests/base85_encode_value_error.phpt @@ -0,0 +1,24 @@ +--TEST-- +Base85 encode ValueError +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECTF-- +ValueError diff --git a/ext/encoding/tests/base85_encode_z85.phpt b/ext/encoding/tests/base85_encode_z85.phpt deleted file mode 100644 index 625ada48fdaf..000000000000 --- a/ext/encoding/tests/base85_encode_z85.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Base85 encode Z85 variant ---FILE-- - ---EXPECT-- -nm=QNz.92Pz/P diff --git a/ext/encoding/tests/base85_encode_z85_strip_padding_error.phpt b/ext/encoding/tests/base85_encode_z85_strip_padding_error.phpt deleted file mode 100644 index 9b04d027a0ce..000000000000 --- a/ext/encoding/tests/base85_encode_z85_strip_padding_error.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -Base85 encode Z85 with StripPadding throws ValueError ---FILE-- - ---EXPECT-- -ValueError caught From 21c1d3b076f59da863cd8adf45299085f378a388 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Sun, 9 Aug 2026 08:35:44 +0000 Subject: [PATCH 03/12] Terminate strings, handle exceptions --- ext/encoding/encoding.c | 40 ++++++++++++++++--- .../tests/base16_decode_exceptions.phpt | 4 +- .../tests/base32_decode_exceptions.phpt | 4 +- .../tests/base58_decode_exception.phpt | 26 +++++++++--- .../tests/base64_decode_exception.phpt | 2 +- .../tests/base85_decode_wrong_variant.phpt | 2 +- 6 files changed, 62 insertions(+), 16 deletions(-) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c index 330475bd40db..13cdcf12abf4 100644 --- a/ext/encoding/encoding.c +++ b/ext/encoding/encoding.c @@ -96,6 +96,7 @@ static zend_string *base16_encode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = len * 2; + ZSTR_VAL(result)[len * 2] = '\0'; return result; } @@ -155,6 +156,7 @@ static zend_string *base16_decode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst_idx; + ZSTR_VAL(result)[dst_idx] = '\0'; return result; } @@ -214,7 +216,11 @@ static zend_string *base16_decode_impl(const char *data, size_t len, zend_enum_E constant_time = (zend_enum_fetch_case_id(Z_OBJ_P(timing_obj)) == ZEND_ENUM_Encoding_TimingMode_Constant); } - RETURN_STR(base16_decode_impl(data, data_len, variant, mode, constant_time)); + zend_string *result = base16_decode_impl(data, data_len, variant, mode, constant_time); + if (!result) { + return; + } + RETURN_STR(result); } /* ===================== Base32 ===================== */ @@ -376,6 +382,7 @@ static zend_string *base32_encode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst - ZSTR_VAL(result); + ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; return result; } @@ -491,6 +498,7 @@ static zend_string *base32_decode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst_idx; + ZSTR_VAL(result)[dst_idx] = '\0'; return result; } @@ -556,7 +564,11 @@ PHP_FUNCTION(Encoding_base32_decode) { constant_time = (zend_enum_fetch_case_id(Z_OBJ_P(timing_obj)) == ZEND_ENUM_Encoding_TimingMode_Constant); } - RETURN_STR(base32_decode_impl(data, data_len, variant, mode, constant_time)); + zend_string *result = base32_decode_impl(data, data_len, variant, mode, constant_time); + if (!result) { + return; + } + RETURN_STR(result); } /* ===================== Base58 ===================== */ @@ -644,6 +656,7 @@ static zend_string *base58_encode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst - ZSTR_VAL(result); + ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; efree(output); return result; @@ -728,6 +741,7 @@ static zend_string *base58_decode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst - ZSTR_VAL(result); + ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; efree(output); return result; @@ -772,7 +786,11 @@ PHP_FUNCTION(Encoding_base58_decode) { variant = (zend_enum_Encoding_Base58)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); } - RETURN_STR(base58_decode_impl(data, data_len, variant)); + zend_string *result = base58_decode_impl(data, data_len, variant); + if (!result) { + return; + } + RETURN_STR(result); } /* ===================== Base64 ===================== */ @@ -920,6 +938,7 @@ static zend_string *base64_encode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst - ZSTR_VAL(result); + ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; return result; } @@ -1033,6 +1052,7 @@ static zend_string *base64_decode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst_idx; + ZSTR_VAL(result)[dst_idx] = '\0'; return result; } @@ -1089,7 +1109,11 @@ PHP_FUNCTION(Encoding_base64_decode) { mode = (zend_enum_Encoding_DecodingMode)zend_enum_fetch_case_id(Z_OBJ_P(decoding_obj)); } - RETURN_STR(base64_decode_impl(data, data_len, variant, mode)); + zend_string *result = base64_decode_impl(data, data_len, variant, mode); + if (!result) { + return; + } + RETURN_STR(result); } /* ===================== Base85 ===================== */ @@ -1313,6 +1337,7 @@ static zend_string *base85_encode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst - ZSTR_VAL(result); + ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; } return result; @@ -1423,6 +1448,7 @@ static zend_string *base85_decode_impl(const char *data, size_t len, zend_enum_E } ZSTR_LEN(result) = dst_idx; + ZSTR_VAL(result)[dst_idx] = '\0'; return result; } @@ -1472,7 +1498,11 @@ PHP_FUNCTION(Encoding_base85_decode) { zend_enum_Encoding_DecodingMode mode = ZEND_ENUM_Encoding_DecodingMode_Strict; - RETURN_STR(base85_decode_impl(data, data_len, variant, mode)); + zend_string *result = base85_decode_impl(data, data_len, variant, mode); + if (!result) { + return; + } + RETURN_STR(result); } /* ===================== Module Registration ===================== */ diff --git a/ext/encoding/tests/base16_decode_exceptions.phpt b/ext/encoding/tests/base16_decode_exceptions.phpt index 84f673b0d0c1..1f1222020481 100644 --- a/ext/encoding/tests/base16_decode_exceptions.phpt +++ b/ext/encoding/tests/base16_decode_exceptions.phpt @@ -29,5 +29,5 @@ try { ?> --EXPECTF-- -UnableToDecodeException -UnableToDecodeException +Encoding\UnableToDecodeException +Encoding\UnableToDecodeException diff --git a/ext/encoding/tests/base32_decode_exceptions.phpt b/ext/encoding/tests/base32_decode_exceptions.phpt index ced38c360932..d3f618d23933 100644 --- a/ext/encoding/tests/base32_decode_exceptions.phpt +++ b/ext/encoding/tests/base32_decode_exceptions.phpt @@ -28,5 +28,5 @@ try { ?> --EXPECTF-- -UnableToDecodeException -UnableToDecodeException +Encoding\UnableToDecodeException +Encoding\UnableToDecodeException diff --git a/ext/encoding/tests/base58_decode_exception.phpt b/ext/encoding/tests/base58_decode_exception.phpt index 29f129bd115f..9fc59745f95c 100644 --- a/ext/encoding/tests/base58_decode_exception.phpt +++ b/ext/encoding/tests/base58_decode_exception.phpt @@ -1,18 +1,32 @@ --TEST-- -Base58 decode wrong variant exception +Base58 decode invalid character exception --EXTENSIONS-- encoding --FILE-- --EXPECTF-- -UnableToDecodeException +Encoding\UnableToDecodeException +Encoding\UnableToDecodeException +Encoding\UnableToDecodeException diff --git a/ext/encoding/tests/base64_decode_exception.phpt b/ext/encoding/tests/base64_decode_exception.phpt index 3c3b0c5776c8..2b2f50d6febf 100644 --- a/ext/encoding/tests/base64_decode_exception.phpt +++ b/ext/encoding/tests/base64_decode_exception.phpt @@ -18,4 +18,4 @@ try { ?> --EXPECTF-- -UnableToDecodeException +Encoding\UnableToDecodeException diff --git a/ext/encoding/tests/base85_decode_wrong_variant.phpt b/ext/encoding/tests/base85_decode_wrong_variant.phpt index 87c19413c9e2..c218003098cb 100644 --- a/ext/encoding/tests/base85_decode_wrong_variant.phpt +++ b/ext/encoding/tests/base85_decode_wrong_variant.phpt @@ -21,4 +21,4 @@ try { ?> --EXPECTF-- -UnableToDecodeException +Encoding\UnableToDecodeException From 7f0c7ea59e4d5bbf7cd5947d3a33903f3c7fa78f Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Sun, 9 Aug 2026 15:43:56 +0000 Subject: [PATCH 04/12] Fix base58 encoding --- ext/encoding/encoding.c | 129 +++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 54 deletions(-) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c index 13cdcf12abf4..d297193717d0 100644 --- a/ext/encoding/encoding.c +++ b/ext/encoding/encoding.c @@ -587,8 +587,8 @@ static const uint8_t base58_bitcoin_reverse[256] = { ['V'] = 28, ['W'] = 29, ['X'] = 30, ['Y'] = 31, ['Z'] = 32, ['a'] = 33, ['b'] = 34, ['c'] = 35, ['d'] = 36, ['e'] = 37, ['f'] = 38, ['g'] = 39, ['h'] = 40, ['i'] = 41, ['j'] = 42, ['k'] = 43, ['m'] = 44, - ['n'] = 45, ['p'] = 46, ['q'] = 47, ['r'] = 48, ['s'] = 49, ['t'] = 50, - ['u'] = 51, ['v'] = 52, ['w'] = 53, ['x'] = 54, ['y'] = 55, ['z'] = 56 + ['n'] = 45, ['o'] = 46, ['p'] = 47, ['q'] = 48, ['r'] = 49, ['s'] = 50, ['t'] = 51, + ['u'] = 52, ['v'] = 53, ['w'] = 54, ['x'] = 55, ['y'] = 56, ['z'] = 57 }; static const uint8_t base58_flickr_reverse[256] = { @@ -597,13 +597,13 @@ static const uint8_t base58_flickr_reverse[256] = { ['6'] = 5, ['7'] = 6, ['8'] = 7, ['9'] = 8, ['a'] = 9, ['b'] = 10, ['c'] = 11, ['d'] = 12, ['e'] = 13, ['f'] = 14, ['g'] = 15, ['h'] = 16, ['i'] = 17, ['j'] = 18, ['k'] = 19, - ['m'] = 20, ['n'] = 21, - ['p'] = 22, ['q'] = 23, ['r'] = 24, ['s'] = 25, ['t'] = 26, ['u'] = 27, - ['v'] = 28, ['w'] = 29, ['x'] = 30, ['y'] = 31, ['z'] = 32, - ['A'] = 33, ['B'] = 34, ['C'] = 35, ['D'] = 36, ['E'] = 37, ['F'] = 38, - ['G'] = 39, ['H'] = 40, ['J'] = 41, ['K'] = 42, ['L'] = 43, ['M'] = 44, - ['N'] = 45, ['P'] = 46, ['Q'] = 47, ['R'] = 48, ['S'] = 49, ['T'] = 50, - ['U'] = 51, ['V'] = 52, ['W'] = 53, ['X'] = 54, ['Y'] = 55, ['Z'] = 56 + ['m'] = 20, ['n'] = 21, ['o'] = 22, + ['p'] = 23, ['q'] = 24, ['r'] = 25, ['s'] = 26, ['t'] = 27, ['u'] = 28, + ['v'] = 29, ['w'] = 30, ['x'] = 31, ['y'] = 32, ['z'] = 33, + ['A'] = 34, ['B'] = 35, ['C'] = 36, ['D'] = 37, ['E'] = 38, ['F'] = 39, + ['G'] = 40, ['H'] = 41, ['J'] = 42, ['K'] = 43, ['L'] = 44, ['M'] = 45, + ['N'] = 46, ['P'] = 47, ['Q'] = 48, ['R'] = 49, ['S'] = 50, ['T'] = 51, + ['U'] = 52, ['V'] = 53, ['W'] = 54, ['X'] = 55, ['Y'] = 56, ['Z'] = 57 }; static zend_string *base58_encode_impl(const char *data, size_t len, zend_enum_Encoding_Base58 variant) { @@ -627,7 +627,9 @@ static zend_string *base58_encode_impl(const char *data, size_t len, zend_enum_E /* Process each input byte: multiply accumulated number by 256, add byte */ for (size_t i = zeros; i < len; i++) { uint32_t carry = (uint8_t)data[i]; - for (size_t j = out_begin; j < out_end; j++) { + size_t j = out_end; + while (j > out_begin) { + j--; carry += (uint32_t)output[j] * 256; output[j] = (uint8_t)(carry % 58); carry /= 58; @@ -666,83 +668,102 @@ static zend_string *base58_decode_impl(const char *data, size_t len, zend_enum_E const uint8_t *reverse_table = (variant == ZEND_ENUM_Encoding_Base58_Bitcoin) ? base58_bitcoin_reverse : base58_flickr_reverse; - /* Count leading '1' characters (zeros in base58) */ + /* Count leading '1' characters (zeros in base58) and validate all chars */ size_t zeros = 0; - for (size_t i = 0; i < len; i++) { - uint8_t c = (uint8_t)data[i]; - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; - if (reverse_table[c] == 0) { - zeros++; - } else { - break; - } - } - - /* Count non-whitespace content length */ size_t content_len = 0; + bool past_leading = false; for (size_t i = 0; i < len; i++) { uint8_t c = (uint8_t)data[i]; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + uint8_t val = reverse_table[c]; + if (val == 0xFF) { + THROW_UNABLE_TO_DECODE("Invalid Base58 character"); + return NULL; + } + if (!past_leading) { + if (val == 0) zeros++; + else past_leading = true; + } content_len++; } - /* Max output size: ceil(content_len * log(58) / log(256)) */ - size_t max_out_len = (content_len * 74 + 100) / 100 + 1; - uint8_t *output = ecalloc(max_out_len, sizeof(uint8_t)); + /* Parse non-zero digits into base58 digit array (LSB first) */ + size_t num_digits = content_len - zeros; + if (num_digits == 0) { + zend_string *result = zend_string_alloc(zeros, 0); + memset(ZSTR_VAL(result), 0, zeros); + ZSTR_VAL(result)[zeros] = '\0'; + return result; + } - /* Decode: for each char, multiply accumulated number by 58 and add value */ + uint8_t *digits = emalloc(num_digits); + size_t di = 0; + past_leading = false; for (size_t i = 0; i < len; i++) { uint8_t c = (uint8_t)data[i]; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; - - uint8_t val = reverse_table[c]; - if (val == 0xFF) { - efree(output); - THROW_UNABLE_TO_DECODE("Invalid Base58 character"); - return NULL; - } - - uint32_t carry = val; - for (size_t j = 0; j < max_out_len; j++) { - uint32_t temp = (uint32_t)output[j] * 58 + carry; - output[j] = (uint8_t)(temp % 256); - carry = temp / 256; - if (carry == 0) break; + if (!past_leading) { + if (reverse_table[c] == 0) continue; + past_leading = true; } + digits[di++] = reverse_table[c]; } - /* Reverse to get big-endian byte order */ - for (size_t i = 0; i < max_out_len / 2; i++) { - uint8_t tmp = output[i]; - output[i] = output[max_out_len - 1 - i]; - output[max_out_len - 1 - i] = tmp; + /* digits[] holds the base58 number with digits[0] = MSD, digits[num_digits-1] = LSD */ + /* Reverse to get LSD first */ + for (size_t i = 0; i < num_digits / 2; i++) { + uint8_t tmp = digits[i]; + digits[i] = digits[num_digits - 1 - i]; + digits[num_digits - 1 - i] = tmp; } - /* Find actual output length (skip leading zeros) */ - size_t out_len = 0; - while (out_len < max_out_len && output[out_len] == 0) { - out_len++; + /* Repeatedly divide by 256, collecting remainders as output bytes */ + size_t max_out_len = (content_len * 74 + 100) / 100 + 1; + uint8_t *output = ecalloc(max_out_len, sizeof(uint8_t)); + size_t out_idx = 0; + + size_t d_begin = 0; + size_t d_end = num_digits; + + while (d_begin < d_end) { + uint32_t carry = 0; + for (size_t j = d_end; j > d_begin; ) { + j--; + uint32_t temp = carry * 58 + digits[j]; + digits[j] = (uint8_t)(temp / 256); + carry = temp % 256; + } + if (out_idx < max_out_len) { + output[out_idx++] = (uint8_t)carry; + } + /* Skip trailing zeros (MSD side) in digit array */ + while (d_begin < d_end && digits[d_end - 1] == 0) { + d_end--; + } } - size_t decoded_len = zeros + (max_out_len - out_len); - if (decoded_len == 0) { - decoded_len = zeros; + /* output[] is LSB first, reverse to get big-endian */ + for (size_t i = 0; i < out_idx / 2; i++) { + uint8_t tmp = output[i]; + output[i] = output[out_idx - 1 - i]; + output[out_idx - 1 - i] = tmp; } + size_t decoded_len = zeros + out_idx; zend_string *result = zend_string_alloc(decoded_len, 0); char *dst = ZSTR_VAL(result); for (size_t i = 0; i < zeros; i++) { *dst++ = 0; } - - for (size_t i = out_len; i < max_out_len; i++) { + for (size_t i = 0; i < out_idx; i++) { *dst++ = output[i]; } ZSTR_LEN(result) = dst - ZSTR_VAL(result); ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; efree(output); + efree(digits); return result; } From a9102f72114597882cfb67abf6d9821d2d53f8b2 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Sun, 9 Aug 2026 20:55:59 +0000 Subject: [PATCH 05/12] Fix base85 --- ext/encoding/encoding.c | 349 +++++++++++++++++++--------------------- 1 file changed, 166 insertions(+), 183 deletions(-) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c index d297193717d0..4ec7603fca71 100644 --- a/ext/encoding/encoding.c +++ b/ext/encoding/encoding.c @@ -1139,47 +1139,38 @@ PHP_FUNCTION(Encoding_base64_decode) { /* ===================== Base85 ===================== */ -static const char base85_adobe_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; -static const char base85_z85_table[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+<=>@^_[]{}\"(,)/%"; +static const char base85_adobe_table[] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstu"; +static const char base85_z85_table[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#"; static const char base85_git_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@$%^&*()-=+;:<>|~.#"; static const uint8_t base85_adobe_reverse[256] = { [0 ... 255] = 0xFF, - ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, - ['8'] = 8, ['9'] = 9, - ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, ['G'] = 16, ['H'] = 17, - ['I'] = 18, ['J'] = 19, ['K'] = 20, ['L'] = 21, ['M'] = 22, ['N'] = 23, ['O'] = 24, ['P'] = 25, - ['Q'] = 26, ['R'] = 27, ['S'] = 28, ['T'] = 29, ['U'] = 30, ['V'] = 31, ['W'] = 32, ['X'] = 33, - ['Y'] = 34, ['Z'] = 35, - ['a'] = 36, ['b'] = 37, ['c'] = 38, ['d'] = 39, ['e'] = 40, ['f'] = 41, ['g'] = 42, ['h'] = 43, - ['i'] = 44, ['j'] = 45, ['k'] = 46, ['l'] = 47, ['m'] = 48, ['n'] = 49, ['o'] = 50, ['p'] = 51, - ['q'] = 52, ['r'] = 53, ['s'] = 54, ['t'] = 55, ['u'] = 56, ['v'] = 57, ['w'] = 58, ['x'] = 59, - ['y'] = 60, ['z'] = 61, - ['!'] = 62, ['#'] = 63, ['$'] = 64, ['%'] = 65, ['&'] = 66, - ['('] = 67, [')'] = 68, ['*'] = 69, ['+'] = 70, - ['-'] = 71, - ['<'] = 72, ['='] = 73, ['>'] = 74, ['?'] = 75, ['@'] = 76, - ['^'] = 77, ['_'] = 78, ['`'] = 79, - ['{'] = 80, ['|'] = 81, ['}'] = 82, ['~'] = 83 + ['!'] = 0, ['"'] = 1, ['\#'] = 2, ['$'] = 3, ['%'] = 4, ['&'] = 5, ['\''] = 6, ['('] = 7, + [')'] = 8, ['*'] = 9, ['+'] = 10, [','] = 11, ['-'] = 12, ['.'] = 13, ['/'] = 14, ['0'] = 15, + ['1'] = 16, ['2'] = 17, ['3'] = 18, ['4'] = 19, ['5'] = 20, ['6'] = 21, ['7'] = 22, ['8'] = 23, + ['9'] = 24, [':'] = 25, [';'] = 26, ['<'] = 27, ['='] = 28, ['>'] = 29, ['?'] = 30, ['@'] = 31, + ['A'] = 32, ['B'] = 33, ['C'] = 34, ['D'] = 35, ['E'] = 36, ['F'] = 37, ['G'] = 38, ['H'] = 39, + ['I'] = 40, ['J'] = 41, ['K'] = 42, ['L'] = 43, ['M'] = 44, ['N'] = 45, ['O'] = 46, ['P'] = 47, + ['Q'] = 48, ['R'] = 49, ['S'] = 50, ['T'] = 51, ['U'] = 52, ['V'] = 53, ['W'] = 54, ['X'] = 55, + ['Y'] = 56, ['Z'] = 57, ['['] = 58, ['\\'] = 59, [']'] = 60, ['^'] = 61, ['_'] = 62, ['`'] = 63, + ['a'] = 64, ['b'] = 65, ['c'] = 66, ['d'] = 67, ['e'] = 68, ['f'] = 69, ['g'] = 70, ['h'] = 71, + ['i'] = 72, ['j'] = 73, ['k'] = 74, ['l'] = 75, ['m'] = 76, ['n'] = 77, ['o'] = 78, ['p'] = 79, + ['q'] = 80, ['r'] = 81, ['s'] = 82, ['t'] = 83, ['u'] = 84 }; static const uint8_t base85_z85_reverse[256] = { [0 ... 255] = 0xFF, ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, - ['8'] = 8, ['9'] = 9, - ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, ['g'] = 16, ['h'] = 17, - ['i'] = 18, ['j'] = 19, ['k'] = 20, ['l'] = 21, ['m'] = 22, ['n'] = 23, ['o'] = 24, ['p'] = 25, - ['q'] = 26, ['r'] = 27, ['s'] = 28, ['t'] = 29, ['u'] = 30, ['v'] = 31, ['w'] = 32, ['x'] = 33, - ['y'] = 34, ['z'] = 35, - ['A'] = 36, ['B'] = 37, ['C'] = 38, ['D'] = 39, ['E'] = 40, ['F'] = 41, ['G'] = 42, ['H'] = 43, - ['I'] = 44, ['J'] = 45, ['K'] = 46, ['L'] = 47, ['M'] = 48, ['N'] = 49, ['O'] = 50, ['P'] = 51, - ['Q'] = 52, ['R'] = 53, ['S'] = 54, ['T'] = 55, ['U'] = 56, ['V'] = 57, ['W'] = 58, ['X'] = 59, - ['Y'] = 60, ['Z'] = 61, - ['.'] = 62, ['-'] = 63, [':'] = 64, ['+'] = 65, ['<'] = 66, ['='] = 67, ['>'] = 68, ['@'] = 69, - ['^'] = 70, ['_'] = 71, - ['['] = 72, [']'] = 73, - ['{'] = 74, ['}'] = 75, ['"'] = 76, - ['('] = 77, [')'] = 78, [','] = 79, ['/'] = 80, ['%'] = 81 + ['8'] = 8, ['9'] = 9, ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, + ['g'] = 16, ['h'] = 17, ['i'] = 18, ['j'] = 19, ['k'] = 20, ['l'] = 21, ['m'] = 22, ['n'] = 23, + ['o'] = 24, ['p'] = 25, ['q'] = 26, ['r'] = 27, ['s'] = 28, ['t'] = 29, ['u'] = 30, ['v'] = 31, + ['w'] = 32, ['x'] = 33, ['y'] = 34, ['z'] = 35, ['A'] = 36, ['B'] = 37, ['C'] = 38, ['D'] = 39, + ['E'] = 40, ['F'] = 41, ['G'] = 42, ['H'] = 43, ['I'] = 44, ['J'] = 45, ['K'] = 46, ['L'] = 47, + ['M'] = 48, ['N'] = 49, ['O'] = 50, ['P'] = 51, ['Q'] = 52, ['R'] = 53, ['S'] = 54, ['T'] = 55, + ['U'] = 56, ['V'] = 57, ['W'] = 58, ['X'] = 59, ['Y'] = 60, ['Z'] = 61, ['.'] = 62, ['-'] = 63, + [':'] = 64, ['+'] = 65, ['='] = 66, ['^'] = 67, ['!'] = 68, ['/'] = 69, ['*'] = 70, ['?'] = 71, + ['&'] = 72, ['<'] = 73, ['>'] = 74, ['('] = 75, [')'] = 76, ['['] = 77, [']'] = 78, ['{'] = 79, + ['}'] = 80, ['@'] = 81, ['%'] = 82, ['$'] = 83, ['#'] = 84 }; static const uint8_t base85_git_reverse[256] = { @@ -1218,7 +1209,6 @@ static zend_string *base85_encode_impl(const char *data, size_t len, zend_enum_E break; } - /* Check padding validity */ if (padding == ZEND_ENUM_Encoding_PaddingMode_PreservePadding && !uses_padding) { zend_value_error("PaddingMode::PreservePadding is not supported for variant %s", variant == ZEND_ENUM_Encoding_Base85_Z85 ? "Base85::Z85" : "Base85::Git"); @@ -1234,133 +1224,94 @@ static zend_string *base85_encode_impl(const char *data, size_t len, zend_enum_E bool strip_padding = (padding == ZEND_ENUM_Encoding_PaddingMode_StripPadding); bool add_markers = uses_padding && !strip_padding; - /* Z85 requires input to be multiple of 4 */ if (variant == ZEND_ENUM_Encoding_Base85_Z85 && len % 4 != 0) { zend_value_error("Z85 encoding requires input length to be a multiple of 4"); return NULL; } - size_t encoded_len = (len + 3) / 4 * 5; - if (strip_padding || !uses_padding) { - /* For variants without padding, or when stripping, we still need to encode partial blocks */ - /* but we won't have the padding chars */ - size_t remainder = len % 4; - if (remainder && uses_padding) { - size_t pad = 5 - ((remainder * 5 + 3) / 4); - encoded_len -= pad; + /* Calculate total output length */ + size_t full_blocks = len / 4; + size_t remainder = len % 4; + size_t total_chars = full_blocks * 5; + if (remainder) { + if (strip_padding) { + total_chars += remainder; + } else { + total_chars += remainder + 1; } } + if (add_markers) total_chars += 4; - zend_string *result; - if (add_markers) { - /* Adobe: <~ ... ~> */ - result = zend_string_alloc(encoded_len + 4, 0); - ZSTR_VAL(result)[0] = '<'; - ZSTR_VAL(result)[1] = '~'; - ZSTR_VAL(result)[encoded_len + 2] = '~'; - ZSTR_VAL(result)[encoded_len + 3] = '>'; - char *dst = ZSTR_VAL(result) + 2; - - size_t i = 0; - while (i < len) { - uint32_t nbits; - size_t bytes_left = len - i; - size_t chars_to_read = bytes_left < 4 ? bytes_left : 4; - - nbits = 0; - for (size_t j = 0; j < chars_to_read; j++) { - nbits = (nbits << 8) | (uint8_t)data[i + j]; - } - nbits <<= (4 - chars_to_read) * 8; - - int total_chars = (chars_to_read * 8 + 3) / 4; - for (int c = total_chars - 1; c >= 0; c--) { - *dst++ = table[(nbits >> (c * 5)) & 0x1F] + (table[(nbits >> (c * 5)) & 0x1F] > 127 ? 0 : 0); - } - - /* Fix: base85 uses 85-based encoding, not 32 */ - /* Re-do with proper base85 */ - i += chars_to_read; - } - /* We'll fix this properly below */ + zend_string *result = zend_string_alloc(total_chars, 0); + char *dst = ZSTR_VAL(result); - zend_string_efree(result); + if (add_markers) { + *dst++ = '<'; + *dst++ = '~'; } - /* Proper Base85 encoding */ - { - size_t total_chars = 0; - if (add_markers) total_chars += 2; /* <~ */ - - for (size_t i = 0; i < len; i += 4) { - size_t bytes_left = len - i; - size_t chars_to_read = bytes_left < 4 ? bytes_left : 4; - int block_chars = 0; - if (chars_to_read == 4) { - block_chars = 5; - } else if (strip_padding || !uses_padding) { - block_chars = chars_to_read; - } else { - block_chars = chars_to_read + 1; - } - total_chars += block_chars; - } - - if (add_markers) total_chars += 2; /* ~> */ - - result = zend_string_alloc(total_chars, 0); - char *dst = ZSTR_VAL(result); + for (size_t i = 0; i < len; i += 4) { + size_t bytes_left = len - i; + size_t bytes_in_block = bytes_left < 4 ? bytes_left : 4; - if (add_markers) { - *dst++ = '<'; - *dst++ = '~'; + uint32_t n = 0; + for (size_t j = 0; j < bytes_in_block; j++) { + n = (n << 8) | (uint8_t)data[i + j]; } - size_t i = 0; - while (i < len) { - size_t bytes_left = len - i; - size_t chars_to_read = bytes_left < 4 ? bytes_left : 4; - - uint32_t nbits = 0; - for (size_t j = 0; j < chars_to_read; j++) { - nbits = (nbits << 8) | (uint8_t)data[i + j]; + if (bytes_in_block == 4) { + /* Full block: encode as 5 base-85 digits, MSD first */ + if (n == 0 && variant == ZEND_ENUM_Encoding_Base85_Adobe) { + /* Adobe: all-zero block encoded as single 'z' */ + *dst++ = 'z'; + } else { + uint32_t t = n; + int digits[5]; + for (int d = 4; d >= 0; d--) { + digits[d] = t % 85; + t /= 85; + } + for (int d = 0; d < 5; d++) { + *dst++ = table[digits[d]]; + } } - - if (chars_to_read == 4) { - /* Full block: 5 chars */ - *dst++ = table[nbits / 85 / 85 / 85 / 85 % 85]; - *dst++ = table[nbits / 85 / 85 / 85 % 85]; - *dst++ = table[nbits / 85 / 85 % 85]; - *dst++ = table[nbits / 85 % 85]; - *dst++ = table[nbits % 85]; + } else { + /* Partial block: left-shift to align, then divide by 85 */ + n <<= (4 - bytes_in_block) * 8; + + if (strip_padding) { + /* Output only bytes_in_block chars */ + uint32_t t = n; + int digits[5]; + for (int d = 4; d >= 0; d--) { + digits[d] = t % 85; + t /= 85; + } + for (int d = 0; d < (int)bytes_in_block; d++) { + *dst++ = table[digits[d]]; + } } else { - if (strip_padding || !uses_padding) { - /* No padding: output only as many chars as input bytes */ - for (int c = chars_to_read - 1; c >= 0; c--) { - *dst++ = table[(nbits >> (c * 8)) & 0xFF]; - } - } else { - /* With padding: output chars_to_read + 1 chars */ - int total = chars_to_read + 1; - for (int c = total - 1; c >= 0; c--) { - *dst++ = table[nbits % 85]; - nbits /= 85; - } + /* Output bytes_in_block + 1 chars */ + uint32_t t = n; + int digits[5]; + for (int d = 4; d >= 0; d--) { + digits[d] = t % 85; + t /= 85; + } + for (int d = 0; d <= (int)bytes_in_block; d++) { + *dst++ = table[digits[d]]; } } - - i += chars_to_read; - } - - if (add_markers) { - *dst++ = '~'; - *dst++ = '>'; } + } - ZSTR_LEN(result) = dst - ZSTR_VAL(result); - ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; + if (add_markers) { + *dst++ = '~'; + *dst++ = '>'; } + ZSTR_LEN(result) = dst - ZSTR_VAL(result); + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; return result; } @@ -1390,86 +1341,118 @@ static zend_string *base85_decode_impl(const char *data, size_t len, zend_enum_E bool forgiving = (mode == ZEND_ENUM_Encoding_DecodingMode_Forgiving); /* For Adobe variant, strip <~ and ~> markers if present */ - size_t start = 0; if (variant == ZEND_ENUM_Encoding_Base85_Adobe && len >= 4 && data[0] == '<' && data[1] == '~') { - start = 2; - size_t end = len; if (data[len - 2] == '~' && data[len - 1] == '>') { - end -= 2; + data += 2; + len -= 2; } else if (forgiving) { - end = len; /* forgive missing closing marker */ + data += 2; + len -= 2; } else { THROW_UNABLE_TO_DECODE("Invalid Adobe Base85 format: missing closing marker"); return NULL; } - len = end - start; - data += start; } - /* Count content and validate */ + /* Collect non-whitespace characters */ + uint8_t *content = emalloc(len); size_t content_len = 0; for (size_t i = 0; i < len; i++) { uint8_t c = (uint8_t)data[i]; if (is_base64_whitespace(c)) { continue; } - content_len++; + content[content_len++] = c; } /* Z85 requires multiple of 5 */ if (variant == ZEND_ENUM_Encoding_Base85_Z85 && content_len % 5 != 0) { + efree(content); THROW_UNABLE_TO_DECODE("Z85 encoded data must have length multiple of 5"); return NULL; } - size_t out_len = (content_len * 4) / 5; - zend_string *result = zend_string_alloc(out_len, 0); - char *dst = ZSTR_VAL(result); - size_t dst_idx = 0; + /* Calculate output length and allocate */ + size_t full_blocks = 0; + size_t z_blocks = 0; + size_t remainder = content_len; - uint32_t nbits = 0; - int chars_read = 0; + for (size_t i = 0; i < remainder; ) { + if (content[i] == 'z' && variant == ZEND_ENUM_Encoding_Base85_Adobe) { + z_blocks++; + i++; + remainder--; + } else { + if (remainder >= 5) { + full_blocks++; + i += 5; + remainder -= 5; + } else { + break; + } + } + } - for (size_t i = 0; i < len; i++) { - uint8_t c = (uint8_t)data[i]; + size_t out_len = full_blocks * 4 + z_blocks * 4; + if (remainder) { + out_len += remainder - 1; + } - if (is_base64_whitespace(c)) { + zend_string *result = zend_string_alloc(out_len, 0); + char *dst = ZSTR_VAL(result); + + /* Decode content */ + size_t i = 0; + while (i < content_len) { + if (content[i] == 'z' && variant == ZEND_ENUM_Encoding_Base85_Adobe) { + /* Adobe 'z' shortcut: 4 zero bytes */ + *dst++ = '\0'; + *dst++ = '\0'; + *dst++ = '\0'; + *dst++ = '\0'; + i++; continue; } - uint8_t val = reverse_table[c]; - - if (val == 0xFF || val >= 85) { - zend_string_efree(result); - THROW_UNABLE_TO_DECODE("Invalid Base85 character"); - return NULL; + /* Count characters in this block (non-'z') */ + size_t block_start = i; + size_t chars_in_block = 0; + while (i < content_len && chars_in_block < 5) { + if (content[i] == 'z' && variant == ZEND_ENUM_Encoding_Base85_Adobe) { + break; /* 'z' starts a new block */ + } + i++; + chars_in_block++; } - nbits = nbits * 85 + val; - chars_read++; - - if (chars_read == 5 || (chars_read == 4 && variant == ZEND_ENUM_Encoding_Base85_Z85)) { - if (chars_read == 5) { - for (int j = 3; j >= 0; j--) { - if (dst_idx < out_len) { - dst[dst_idx++] = (char)((nbits >> (j * 8)) & 0xFF); - } - } - } else { - /* 4 chars -> 3 bytes for padded variant */ - for (int j = 2; j >= 0; j--) { - if (dst_idx < out_len) { - dst[dst_idx++] = (char)((nbits >> (j * 8)) & 0xFF); - } - } + /* Decode this block */ + uint32_t n = 0; + size_t pad_count = 5 - chars_in_block; + for (size_t j = block_start; j < i; j++) { + uint8_t val = reverse_table[content[j]]; + if (val == 0xFF || val >= 85) { + efree(content); + zend_string_efree(result); + THROW_UNABLE_TO_DECODE("Invalid Base85 character"); + return NULL; } - nbits = 0; - chars_read = 0; + n = n * 85 + val; + } + /* Pad with 84 ('u') for partial blocks */ + for (size_t j = 0; j < pad_count; j++) { + n = n * 85 + 84; + } + + /* Extract bytes */ + size_t bytes_to_output = (chars_in_block == 5) ? 4 : (chars_in_block - 1); + for (size_t j = 0; j < bytes_to_output; j++) { + *dst++ = (char)((n >> ((4 - j - 1) * 8)) & 0xFF); } } - ZSTR_LEN(result) = dst_idx; - ZSTR_VAL(result)[dst_idx] = '\0'; + efree(content); + ZSTR_LEN(result) = dst - ZSTR_VAL(result); + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; return result; } From 792994ee39e8b84281b3806ec39c762cb886088d Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 10 Aug 2026 05:33:58 +0000 Subject: [PATCH 06/12] Fix Crockford mode --- ext/encoding/encoding.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c index 4ec7603fca71..d01a919bfb8c 100644 --- a/ext/encoding/encoding.c +++ b/ext/encoding/encoding.c @@ -258,19 +258,20 @@ static const uint8_t base32_crockford_reverse[256] = { ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, ['G'] = 16, ['H'] = 17, - /* I, O skipped */ - ['J'] = 19, ['K'] = 20, - /* L skipped */ - ['M'] = 22, ['N'] = 23, ['P'] = 24, - ['Q'] = 25, ['R'] = 26, ['S'] = 27, ['T'] = 28, - ['V'] = 29, ['W'] = 30, ['X'] = 31, + /* Crockford: I and L decode as 1, O and o decode as 0 */ + ['I'] = 1, ['i'] = 1, ['L'] = 1, ['l'] = 1, + ['O'] = 0, ['o'] = 0, + ['J'] = 18, ['K'] = 19, + ['M'] = 20, ['N'] = 21, ['P'] = 22, + ['Q'] = 23, ['R'] = 24, ['S'] = 25, ['T'] = 26, + ['V'] = 27, ['W'] = 28, ['X'] = 29, ['Y'] = 30, ['Z'] = 31, /* Lowercase for case-insensitive Crockford */ ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, ['g'] = 16, ['h'] = 17, - ['j'] = 19, ['k'] = 20, - ['m'] = 22, ['n'] = 23, ['p'] = 24, - ['q'] = 25, ['r'] = 26, ['s'] = 27, ['t'] = 28, - ['v'] = 29, ['w'] = 30, ['x'] = 31 + ['j'] = 18, ['k'] = 19, + ['m'] = 20, ['n'] = 21, ['p'] = 22, + ['q'] = 23, ['r'] = 24, ['s'] = 25, ['t'] = 26, + ['v'] = 27, ['w'] = 28, ['x'] = 29, ['y'] = 30, ['z'] = 31 }; static const uint8_t base32_z_reverse[256] = { From c40d400b93f30538083e8b97a816af4ab0e0f92d Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 10 Aug 2026 05:39:01 +0000 Subject: [PATCH 07/12] No padding on URL-safe base64 --- ext/encoding/encoding.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c index d01a919bfb8c..b48f0319d8b5 100644 --- a/ext/encoding/encoding.c +++ b/ext/encoding/encoding.c @@ -888,7 +888,7 @@ static zend_string *base64_encode_impl(const char *data, size_t len, zend_enum_E break; case ZEND_ENUM_Encoding_Base64_UrlSafe: table = base64_urlsafe_table; - uses_padding = true; + uses_padding = false; break; case ZEND_ENUM_Encoding_Base64_Imap: table = base64_imap_table; From 6bca533c4e9ab88f4137f375a267f5002f0ae2a8 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 10 Aug 2026 10:06:08 +0000 Subject: [PATCH 08/12] Fix test --- ext/encoding/tests/base32_decode_exceptions.phpt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ext/encoding/tests/base32_decode_exceptions.phpt b/ext/encoding/tests/base32_decode_exceptions.phpt index d3f618d23933..4728c518971c 100644 --- a/ext/encoding/tests/base32_decode_exceptions.phpt +++ b/ext/encoding/tests/base32_decode_exceptions.phpt @@ -10,8 +10,6 @@ use Encoding\UnableToDecodeException; use function Encoding\base32_decode; -$encodedAscii = "JBSWY3DPEB3W64TMMQQQ===="; - try { base32_decode("JBSWY3DPEB3W64TMMQQQ"); echo "No exception\n"; @@ -19,8 +17,11 @@ try { echo $e::class, "\n"; } +$big = "IJUWO==="; +var_dump(base32_decode($big)); + try { - base32_decode($encodedAscii, variant: Base32::Crockford); + base32_decode($big, variant: Base32::Crockford); echo "No exception\n"; } catch (UnableToDecodeException $e) { echo $e::class, "\n"; @@ -29,4 +30,5 @@ try { ?> --EXPECTF-- Encoding\UnableToDecodeException +string(3) "Big" Encoding\UnableToDecodeException From 62b8ed4d9acd5a1770b462b7bb7e87012f24e633 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 10 Aug 2026 13:56:25 +0000 Subject: [PATCH 09/12] Add more tests From: - https://datatracker.ietf.org/doc/html/draft-msporny-base58 - https://en.wikipedia.org/wiki/Ascii85 --- .../tests/base58_msporny_examples.phpt | 37 ++++++++++++ .../tests/base85_wikipedia_examples.phpt | 57 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 ext/encoding/tests/base58_msporny_examples.phpt create mode 100644 ext/encoding/tests/base85_wikipedia_examples.phpt diff --git a/ext/encoding/tests/base58_msporny_examples.phpt b/ext/encoding/tests/base58_msporny_examples.phpt new file mode 100644 index 000000000000..7e200019dedd --- /dev/null +++ b/ext/encoding/tests/base58_msporny_examples.phpt @@ -0,0 +1,37 @@ +--TEST-- +Base58 draft-msporny-base58 test vectors +--EXTENSIONS-- +encoding +--FILE-- + +--EXPECT-- +2NEpo7TZRRrLZSi2U +USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z +11233QC4 +bool(true) +bool(true) +bool(true) diff --git a/ext/encoding/tests/base85_wikipedia_examples.phpt b/ext/encoding/tests/base85_wikipedia_examples.phpt new file mode 100644 index 000000000000..0a38cebc5543 --- /dev/null +++ b/ext/encoding/tests/base85_wikipedia_examples.phpt @@ -0,0 +1,57 @@ +--TEST-- +Base85 Wikipedia examples (Adobe Ascii85) +--EXTENSIONS-- +encoding +--FILE-- + "9jqo^" +echo base85_encode("Man ", variant: Base85::Adobe, paddingMode: PaddingMode::StripPadding); +echo "\n"; + +// Example from Wikipedia: "sure" -> "F*2M7" +echo base85_encode("sure", variant: Base85::Adobe, paddingMode: PaddingMode::StripPadding); +echo "\n"; + +// The full Hobbes quote from Wikipedia - check encoding length +$hobbesQuote = 'Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.'; +$encoded = base85_encode($hobbesQuote, variant: Base85::Adobe, paddingMode: PaddingMode::StripPadding); +echo strlen($hobbesQuote), "\n"; +echo strlen($encoded), "\n"; + +// Verify "z" shorthand for four zero bytes +$zeros = "\x00\x00\x00\x00"; +echo base85_encode($zeros, variant: Base85::Adobe, paddingMode: PaddingMode::StripPadding); +echo "\n"; + +// Decode "z" back to four zero bytes +$decodedZeros = base85_decode("z", variant: Base85::Adobe); +var_dump($decodedZeros === $zeros); + +// Verify encoding of "Hello world!" with delimiters +echo base85_encode("Hello world!", variant: Base85::Adobe); +echo "\n"; + +// Decode "Hello world!" without delimiters +echo base85_encode("Hello world!", variant: Base85::Adobe, paddingMode: PaddingMode::StripPadding); +echo "\n"; +$decoded = base85_decode("87cURD]j7BEbo80", variant: Base85::Adobe); +var_dump($decoded === "Hello world!"); + +?> +--EXPECT-- +9jqo^ +F*2M7 +269 +336 +z +bool(true) +<~87cURD]j7BEbo80~> +87cURD]j7BEbo80 +bool(true) From 9612049d885e9414acbc2ce76cccfc87d4f4014a Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 10 Aug 2026 15:28:24 +0000 Subject: [PATCH 10/12] Add decoding mode to base85 --- ext/encoding/encoding.c | 7 ++++++- ext/encoding/encoding.stub.php | 2 +- ext/encoding/encoding_arginfo.h | 3 ++- ext/encoding/encoding_decl.h | 8 ++++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c index b48f0319d8b5..595db7f5f87e 100644 --- a/ext/encoding/encoding.c +++ b/ext/encoding/encoding.c @@ -1490,18 +1490,23 @@ PHP_FUNCTION(Encoding_base85_decode) { char *data; size_t data_len; zval *variant_obj; + zval *decoding_obj = NULL; zval *timing_obj = NULL; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(data, data_len) Z_PARAM_OBJECT_OF_CLASS(variant_obj, encoding_ce_Base85) Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS(decoding_obj, encoding_ce_DecodingMode) Z_PARAM_OBJECT_OF_CLASS(timing_obj, encoding_ce_TimingMode) ZEND_PARSE_PARAMETERS_END(); zend_enum_Encoding_Base85 variant = (zend_enum_Encoding_Base85)zend_enum_fetch_case_id(Z_OBJ_P(variant_obj)); zend_enum_Encoding_DecodingMode mode = ZEND_ENUM_Encoding_DecodingMode_Strict; + if (decoding_obj) { + mode = (zend_enum_Encoding_DecodingMode)zend_enum_fetch_case_id(Z_OBJ_P(decoding_obj)); + } zend_string *result = base85_decode_impl(data, data_len, variant, mode); if (!result) { diff --git a/ext/encoding/encoding.stub.php b/ext/encoding/encoding.stub.php index de745f4b28fe..c8714548f975 100644 --- a/ext/encoding/encoding.stub.php +++ b/ext/encoding/encoding.stub.php @@ -136,5 +136,5 @@ function base85_encode(string $data, Base85 $variant, PaddingMode $paddingMode = /** * @throws UnableToDecodeException */ - function base85_decode(string $data, Base85 $variant, TimingMode $timingMode = TimingMode::Variable): string {} + function base85_decode(string $data, Base85 $variant, DecodingMode $decodingMode = DecodingMode::Strict, TimingMode $timingMode = TimingMode::Variable): string {} } diff --git a/ext/encoding/encoding_arginfo.h b/ext/encoding/encoding_arginfo.h index 464321ce4ceb..ef203ce82911 100644 --- a/ext/encoding/encoding_arginfo.h +++ b/ext/encoding/encoding_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit encoding.stub.php instead. - * Stub hash: 32b3e846abe2dbc53ff290a7a3b528cd8bff3d71 + * Stub hash: ada1d3a8882e099da6ed20ce66a7c6ebacf95dee * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_Encoding_base16_encode, 0, 1, IS_STRING, 0) @@ -61,6 +61,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_Encoding_base85_decode, 0, 2, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) ZEND_ARG_OBJ_INFO(0, variant, Encoding\\Base85, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, decodingMode, Encoding\\DecodingMode, 0, "Encoding\\DecodingMode::Strict") ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, timingMode, Encoding\\TimingMode, 0, "Encoding\\TimingMode::Variable") ZEND_END_ARG_INFO() diff --git a/ext/encoding/encoding_decl.h b/ext/encoding/encoding_decl.h index 72e09e7d83f9..3aaed3249378 100644 --- a/ext/encoding/encoding_decl.h +++ b/ext/encoding/encoding_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit encoding.stub.php instead. - * Stub hash: 32b3e846abe2dbc53ff290a7a3b528cd8bff3d71 */ + * Stub hash: ada1d3a8882e099da6ed20ce66a7c6ebacf95dee */ -#ifndef ZEND_ENCODING_DECL_32b3e846abe2dbc53ff290a7a3b528cd8bff3d71_H -#define ZEND_ENCODING_DECL_32b3e846abe2dbc53ff290a7a3b528cd8bff3d71_H +#ifndef ZEND_ENCODING_DECL_ada1d3a8882e099da6ed20ce66a7c6ebacf95dee_H +#define ZEND_ENCODING_DECL_ada1d3a8882e099da6ed20ce66a7c6ebacf95dee_H typedef enum zend_enum_Encoding_Base16 { ZEND_ENUM_Encoding_Base16_Upper = 1, @@ -49,4 +49,4 @@ typedef enum zend_enum_Encoding_TimingMode { ZEND_ENUM_Encoding_TimingMode_Constant = 2, } zend_enum_Encoding_TimingMode; -#endif /* ZEND_ENCODING_DECL_32b3e846abe2dbc53ff290a7a3b528cd8bff3d71_H */ +#endif /* ZEND_ENCODING_DECL_ada1d3a8882e099da6ed20ce66a7c6ebacf95dee_H */ From ad0927d00d3f146b320cbca87874aa6f6a1bdbdd Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 10 Aug 2026 19:36:14 +0000 Subject: [PATCH 11/12] Fix compiler warnings --- ext/encoding/encoding.c | 42 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c index 595db7f5f87e..9a966a8cea5f 100644 --- a/ext/encoding/encoding.c +++ b/ext/encoding/encoding.c @@ -22,6 +22,10 @@ #include "encoding_arginfo.h" #include "encoding_decl.h" +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Woverride-init" +#endif + /* Class entries */ static zend_class_entry *encoding_ce_EncodingError; static zend_class_entry *encoding_ce_EncodingException; @@ -366,10 +370,10 @@ static zend_string *base32_encode_impl(const char *data, size_t len, zend_enum_E nbits = (nbits << 8) | (uint8_t)data[i + j]; } - int total_chars = (chars_to_read * 8 + 4) / 5; + size_t total_chars = (chars_to_read * 8 + 4) / 5; nbits <<= (uint64_t)(total_chars * 5 - chars_to_read * 8); - for (int c = total_chars - 1; c >= 0; c--) { - *dst++ = table[(nbits >> (c * 5)) & 0x1F]; + for (size_t c = 0; c < total_chars; c++) { + *dst++ = table[(nbits >> ((total_chars - 1 - c) * 5)) & 0x1F]; } if (uses_padding && !strip_padding && chars_to_read < 5) { @@ -382,8 +386,9 @@ static zend_string *base32_encode_impl(const char *data, size_t len, zend_enum_E i += chars_to_read; } - ZSTR_LEN(result) = dst - ZSTR_VAL(result); - ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; + size_t result_len = (size_t)(dst - ZSTR_VAL(result)); + ZSTR_LEN(result) = result_len; + ZSTR_VAL(result)[result_len] = '\0'; return result; } @@ -658,8 +663,9 @@ static zend_string *base58_encode_impl(const char *data, size_t len, zend_enum_E *dst++ = table[output[i]]; } - ZSTR_LEN(result) = dst - ZSTR_VAL(result); - ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; + size_t result_len = (size_t)(dst - ZSTR_VAL(result)); + ZSTR_LEN(result) = result_len; + ZSTR_VAL(result)[result_len] = '\0'; efree(output); return result; @@ -761,8 +767,9 @@ static zend_string *base58_decode_impl(const char *data, size_t len, zend_enum_E *dst++ = output[i]; } - ZSTR_LEN(result) = dst - ZSTR_VAL(result); - ZSTR_VAL(result)[dst - ZSTR_VAL(result)] = '\0'; + size_t result_len = (size_t)(dst - ZSTR_VAL(result)); + ZSTR_LEN(result) = result_len; + ZSTR_VAL(result)[result_len] = '\0'; efree(output); efree(digits); @@ -1267,9 +1274,9 @@ static zend_string *base85_encode_impl(const char *data, size_t len, zend_enum_E *dst++ = 'z'; } else { uint32_t t = n; - int digits[5]; + uint8_t digits[5]; for (int d = 4; d >= 0; d--) { - digits[d] = t % 85; + digits[d] = (uint8_t)(t % 85); t /= 85; } for (int d = 0; d < 5; d++) { @@ -1283,9 +1290,9 @@ static zend_string *base85_encode_impl(const char *data, size_t len, zend_enum_E if (strip_padding) { /* Output only bytes_in_block chars */ uint32_t t = n; - int digits[5]; + uint8_t digits[5]; for (int d = 4; d >= 0; d--) { - digits[d] = t % 85; + digits[d] = (uint8_t)(t % 85); t /= 85; } for (int d = 0; d < (int)bytes_in_block; d++) { @@ -1294,9 +1301,9 @@ static zend_string *base85_encode_impl(const char *data, size_t len, zend_enum_E } else { /* Output bytes_in_block + 1 chars */ uint32_t t = n; - int digits[5]; + uint8_t digits[5]; for (int d = 4; d >= 0; d--) { - digits[d] = t % 85; + digits[d] = (uint8_t)(t % 85); t /= 85; } for (int d = 0; d <= (int)bytes_in_block; d++) { @@ -1318,24 +1325,19 @@ static zend_string *base85_encode_impl(const char *data, size_t len, zend_enum_E static zend_string *base85_decode_impl(const char *data, size_t len, zend_enum_Encoding_Base85 variant, zend_enum_Encoding_DecodingMode mode) { const uint8_t *reverse_table; - bool uses_padding; switch (variant) { case ZEND_ENUM_Encoding_Base85_Adobe: reverse_table = base85_adobe_reverse; - uses_padding = true; break; case ZEND_ENUM_Encoding_Base85_Z85: reverse_table = base85_z85_reverse; - uses_padding = false; break; case ZEND_ENUM_Encoding_Base85_Git: reverse_table = base85_git_reverse; - uses_padding = false; break; default: reverse_table = base85_adobe_reverse; - uses_padding = true; break; } From adc3789e691694f558d040baf621a6c084db1e4e Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 10 Aug 2026 19:47:22 +0000 Subject: [PATCH 12/12] Don't escape hash --- ext/encoding/encoding.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/encoding/encoding.c b/ext/encoding/encoding.c index 9a966a8cea5f..6728a4b79ba9 100644 --- a/ext/encoding/encoding.c +++ b/ext/encoding/encoding.c @@ -1153,7 +1153,7 @@ static const char base85_git_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@$% static const uint8_t base85_adobe_reverse[256] = { [0 ... 255] = 0xFF, - ['!'] = 0, ['"'] = 1, ['\#'] = 2, ['$'] = 3, ['%'] = 4, ['&'] = 5, ['\''] = 6, ['('] = 7, + ['!'] = 0, ['"'] = 1, ['#'] = 2, ['$'] = 3, ['%'] = 4, ['&'] = 5, ['\''] = 6, ['('] = 7, [')'] = 8, ['*'] = 9, ['+'] = 10, [','] = 11, ['-'] = 12, ['.'] = 13, ['/'] = 14, ['0'] = 15, ['1'] = 16, ['2'] = 17, ['3'] = 18, ['4'] = 19, ['5'] = 20, ['6'] = 21, ['7'] = 22, ['8'] = 23, ['9'] = 24, [':'] = 25, [';'] = 26, ['<'] = 27, ['='] = 28, ['>'] = 29, ['?'] = 30, ['@'] = 31,