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.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.