Commit: patch 9.2.0961: base64_encode() gives wrong result for a zero byte
Christian Brabandt <[email protected]>
| Newsgroups | gmane.editors.vim.devel |
|---|---|
| Message-ID | <[email protected]> |
patch 9.2.0961: base64_encode() gives wrong result for a zero byte Commit: https://github.com/vim/vim/commit/9d1f0081c74bd32ccde2bc66a4702eef7101d4fe Author: Foxe Chen <[email protected]> Date: Mon Aug 17 19:22:08 2026 +0000 patch 9.2.0961: base64_encode() gives wrong result for a zero byte Problem: base64_encode() encodes a zero byte in the last group as padding. Solution: Decide the padding from the number of remaining input bytes, refactor the code and move to misc2.c (Foxe Chen). related: #21018 Signed-off-by: Foxe Chen <[email protected]> Signed-off-by: Christian Brabandt <[email protected]> diff --git a/src/evalfunc.c b/src/evalfunc.c index 1b8cce9da..92a316354 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -3716,148 +3716,6 @@ f_balloon_split(typval_T *argvars, typval_T *rettv UNUSED) # endif #endif -// Base64 character set -static const char_u base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -// Base64 decoding table (initialized in init_base64_dec_table() below) -static char_u base64_dec_table[256]; - -/* - * Initialize the base64 decoding table - */ - static void -init_base64_dec_table(void) -{ - static int base64_dec_tbl_initialized = FALSE; - - if (base64_dec_tbl_initialized) - return; - - // Unsupported characters are set to 0xFF - vim_memset(base64_dec_table, 0xFF, sizeof(base64_dec_table)); - - // Initialize the index for the base64 alphabets - for (size_t i = 0; i < sizeof(base64_table) - 1; i++) - base64_dec_table[(char_u)base64_table[i]] = (char_u)i; - - // base64 padding character - base64_dec_table['='] = 0; - - base64_dec_tbl_initialized = TRUE; -} - -/* - * Encode the bytes in "blob" using base-64 encoding. - */ - static char_u * -base64_encode(blob_T *blob) -{ - size_t input_len = blob->bv_ga.ga_len; - size_t encoded_len = ((input_len + 2) / 3) * 4; - char_u *data = blob->bv_ga.ga_data; - - char_u *encoded = alloc(encoded_len + 1); - if (encoded == NULL) - return NULL; - - size_t i, j; - for (i = 0, j = 0; i < input_len;) - { - int_u octet_a = i < input_len ? data[i++] : 0; - int_u octet_b = i < input_len ? data[i++] : 0; - int_u octet_c = i < input_len ? data[i++] : 0; - - int_u triple = (octet_a << 16) | (octet_b << 8) | octet_c; - - encoded[j++] = base64_table[(triple >> 18) & 0x3F]; - encoded[j++] = base64_table[(triple >> 12) & 0x3F]; - encoded[j++] = (!octet_b && i >= input_len) ? '=' - : base64_table[(triple >> 6) & 0x3F]; - encoded[j++] = (!octet_c && i >= input_len) ? '=' - : base64_table[triple & 0x3F]; - } - encoded[j] = NUL; - - return encoded; -} - -/* - * Decode the string "data" using base-64 encoding. - */ - static void -base64_decode(const char_u *data, blob_T *blob) -{ - size_t input_len = STRLEN(data); - - if (input_len == 0) - return; - - if (input_len % 4 != 0) - { - // Invalid input length - semsg(_(e_invalid_argument_str), data); - return; - } - - init_base64_dec_table(); - - size_t decoded_len = (input_len / 4) * 3; - if (data[input_len - 1] == '=') - decoded_len--; - if (data[input_len - 2] == '=') - decoded_len--; - - size_t i, j; - for (i = 0, j = 0; i < input_len;) - { - int_u sextet_a = base64_dec_table[(char_u)data[i++]]; - int_u sextet_b = base64_dec_table[(char_u)data[i++]]; - int_u sextet_c = base64_dec_table[(char_u)data[i++]]; - int_u sextet_d = base64_dec_table[(char_u)data[i++]]; - - if (sextet_a == 0xFF || sextet_b == 0xFF || sextet_c == 0xFF - || sextet_d == 0xFF) - { - // Invalid character - semsg(_(e_invalid_argument_str), data); - ga_clear(&blob->bv_ga); - return; - } - - int_u triple = (sextet_a << 18) | (sextet_b << 12) - | (sextet_c << 6) | sextet_d; - - if (j < decoded_len) - { - ga_append(&blob->bv_ga, (triple >> 16) & 0xFF); - j++; - } - if (j < decoded_len) - { - ga_append(&blob->bv_ga, (triple >> 8) & 0xFF); - j++; - } - if (j < decoded_len) - { - ga_append(&blob->bv_ga, triple & 0xFF); - j++; - } - - if (j == decoded_len) - { - // Check for invalid padding bytes (based on the - // "Base64 Malleability in Practice" ACM paper). - if ((data[input_len - 2] == '=' && ((sextet_b & 0xF) != 0)) - || ((data[input_len - 1] == '=') && ((sextet_c & 0x3) != 0))) - { - semsg(_(e_invalid_argument_str), data); - ga_clear(&blob->bv_ga); - return; - } - } - } -} - /* * "base64_decode(string)" function */ @@ -3872,7 +3730,7 @@ f_base64_decode(typval_T *argvars, typval_T *rettv) char_u *str = tv_get_string_chk(&argvars[0]); if (str != NULL) - base64_decode(str, rettv->vval.v_blob); + base64_decode(str, STRLEN(str), &rettv->vval.v_blob->bv_ga); } /* @@ -3889,7 +3747,8 @@ f_base64_encode(typval_T *argvars, typval_T *rettv) blob_T *blob = argvars->vval.v_blob; if (blob != NULL) - rettv->vval.v_string = base64_encode(blob); + rettv->vval.v_string = + base64_encode(blob->bv_ga.ga_data, blob->bv_ga.ga_len); } /* diff --git a/src/misc2.c b/src/misc2.c index 25dab5ed1..a33591cc4 100644 --- a/src/misc2.c +++ b/src/misc2.c @@ -3316,3 +3316,152 @@ mergesort_list( return head; } + +static const char_u base64_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static char_u base64_dec_table[256]; + +#define BASE64_ENCODED_LEN(len) ((((len) + 2) / 3) * 4 + 1) + +/* + * Initialize the base64 decoding table + */ + static void +init_base64_dec_table(void) +{ + static int base64_dec_tbl_initialized = FALSE; + + if (base64_dec_tbl_initialized) + return; + + vim_memset(base64_dec_table, 0xFF, sizeof(base64_dec_table)); + for (size_t i = 0; i < sizeof(base64_table) - 1; i++) + base64_dec_table[(char_u)base64_table[i]] = (char_u)i; + base64_dec_table['='] = 0; + + base64_dec_tbl_initialized = TRUE; +} + +/* + * Base64 encode "src[len]" into caller-supplied "dst", which must have room for + * at least BASE64_ENCODED_LEN(len) bytes (NUL terminated). Returns the number + * of bytes written, excluding the NUL. + */ + long +base64_encode_buf(char_u *dst, const char_u *src, size_t len) +{ + size_t i, j; + + for (i = 0, j = 0; i < len;) + { + size_t remaining = len - i; + + int_u octet_a = src[i++]; + int_u octet_b = remaining > 1 ? src[i++] : 0; + int_u octet_c = remaining > 2 ? src[i++] : 0; + + int_u triple = (octet_a << 16) | (octet_b << 8) | octet_c; + + dst[j++] = base64_table[(triple >> 18) & 0x3F]; + dst[j++] = base64_table[(triple >> 12) & 0x3F]; + dst[j++] = remaining > 1 + ? base64_table[(triple >> 6) & 0x3F] + : '='; + dst[j++] = remaining > 2 + ? base64_table[triple & 0x3F] + : '='; + } + + dst[j] = NUL; + return (long)j; +} + +/* + * Base64-encode "data[len]". Returns an allocated NUL-terminated string, or + * NULL on OOM. Caller frees with vim_free(). + */ + char_u * +base64_encode(const char_u *data, size_t len) +{ + char_u *encoded = alloc(BASE64_ENCODED_LEN(len)); + + if (encoded == NULL) + return NULL; + base64_encode_buf(encoded, data, len); + return encoded; +} + +/* + * Decode base64 text "data[len]" (len must be a multiple of 4) into growarray + * "out" (appended byte-by-byte, e.g. a blob's bv_ga). Returns OK on success and + * FAIL on failure. + */ + int +base64_decode(const char_u *data, size_t len, garray_T *out) +{ + if (len == 0) + return OK; + + if (len % 4 != 0) + { + semsg(_(e_invalid_argument_str), data); + return FAIL; + } + + init_base64_dec_table(); + + size_t decoded_len = (len / 4) * 3; + if (data[len - 1] == '=') + decoded_len--; + if (data[len - 2] == '=') + decoded_len--; + + size_t i, j; + for (i = 0, j = 0; i < len;) + { + int_u sextet_a = base64_dec_table[(char_u)data[i++]]; + int_u sextet_b = base64_dec_table[(char_u)data[i++]]; + int_u sextet_c = base64_dec_table[(char_u)data[i++]]; + int_u sextet_d = base64_dec_table[(char_u)data[i++]]; + + if (sextet_a == 0xFF || sextet_b == 0xFF || sextet_c == 0xFF + || sextet_d == 0xFF) + { + semsg(_(e_invalid_argument_str), data); + ga_clear(out); + return FAIL; + } + + int_u triple = (sextet_a << 18) | (sextet_b << 12) + | (sextet_c << 6) | sextet_d; + + if (j < decoded_len) + { + ga_append(out, (triple >> 16) & 0xFF); + j++; + } + if (j < decoded_len) + { + ga_append(out, (triple >> 8) & 0xFF); + j++; + } + if (j < decoded_len) + { + ga_append(out, triple & 0xFF); + j++; + } + + if (j == decoded_len) + { + if ((data[len - 2] == '=' && ((sextet_b & 0xF) != 0)) + || ((data[len - 1] == '=') && ((sextet_c & 0x3) != 0))) + { + semsg(_(e_invalid_argument_str), data); + ga_clear(out); + return FAIL; + } + } + } + return OK; +} diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro index af779259a..95a5a8fd4 100644 --- a/src/proto/misc2.pro +++ b/src/proto/misc2.pro @@ -66,4 +66,7 @@ int cmp_keyvalue_value_n(const void *a, const void *b); int cmp_keyvalue_value_i(const void *a, const void *b); int cmp_keyvalue_value_ni(const void *a, const void *b); void *mergesort_list(void *head, void *(*get_next)(void *), void (*set_next)(void *, void *), void *(*get_prev)(void *), void (*set_prev)(void *, void *), int (*compare)(const void *, const void *)); +long base64_encode_buf(char_u *dst, const char_u *src, size_t len); +char_u *base64_encode(const char_u *data, size_t len); +int base64_decode(const char_u *data, size_t len, garray_T *out); /* vim: set ft=c : */ diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index 7052ad171..e3693c913 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -4601,6 +4601,9 @@ func Test_base64_encoding() call assert_equal(0z00, base64_decode("====")) call assert_equal(0z, base64_decode("")) + #" a zero byte in the last group is not padding + call assert_equal('AQAC', base64_encode(0z010002)) + #" Test for invalid padding call assert_equal('Hello', g:Blob2Str(base64_decode("SGVsbG8="))) call assert_fails('call base64_decode("SGVsbG9=")', 'E475:') diff --git a/src/version.c b/src/version.c index 19542bdde..2033abfa4 100644 --- a/src/version.c +++ b/src/version.c @@ -763,6 +763,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 961, /**/ 960, /**/ -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1ww3y9-0091tK-S2%40256bit.org.