patch 9.1.0980: no support for base64 en-/decoding functions in Vim Script

Commit: 
https://github.com/vim/vim/commit/810785c6890ef0a1cd2428410c8488cacfbac77b
Author: Yegappan Lakshmanan <yegap...@yahoo.com>
Date:   Mon Dec 30 10:29:44 2024 +0100

    patch 9.1.0980: no support for base64 en-/decoding functions in Vim Script
    
    Problem:  no support for base64 en-/decoding functions in Vim Script
              (networkhermit)
    Solution: Add the base64_encode() and base64_decode() functions
              (Yegappan Lakshmanan)
    
    fixes: #16291
    closes: #16330
    
    Signed-off-by: Yegappan Lakshmanan <yegap...@yahoo.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index ac10590b1..c82ff8115 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt*  For Vim version 9.1.  Last change: 2024 Dec 03
+*builtin.txt*  For Vim version 9.1.  Last change: 2024 Dec 30
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -67,6 +67,8 @@ autocmd_get([{opts}])         List    return a list of 
autocmds
 balloon_gettext()              String  current text in the balloon
 balloon_show({expr})           none    show {expr} inside the balloon
 balloon_split({msg})           List    split {msg} as used for a balloon
+base64_decode({string})                Blob    base-64 decode {string} 
characters
+base64_encode({blob})          String  base-64 encode the bytes in {blob}
 bindtextdomain({package}, {path})
                                Bool    bind text domain to specified path
 blob2list({blob})              List    convert {blob} into a list of numbers
@@ -1169,6 +1171,43 @@ autocmd_get([{opts}])                                    
*autocmd_get()*
                Return type: list<dict<any>>
 
 
+base64_decode({string})                                        
*base64_decode()*
+               Return a Blob containing the bytes decoded from the base64
+               characters in {string}.
+
+               The {string} argument should contain only base64-encoded
+               characters and should have a length that is a multiple of 4.
+
+               Returns an empty blob on error.
+
+               Examples: >
+                   " Write the decoded contents to a binary file
+                   call writefile(base64_decode(s), 'tools.bmp')
+                   " Decode a base64-encoded string
+                   echo list2str(blob2list(base64_decode(encodedstr)))
+<
+               Can also be used as a |method|: >
+                       GetEncodedString()->base64_decode()
+<
+               Return type: |Blob|
+
+
+base64_encode({blob})                                  *base64_encode()*
+               Return a base64-encoded String representing the bytes in
+               {blob}.  The base64 alphabet defined in RFC 4648 is used.
+
+               Examples: >
+                   " Encode the contents of a binary file
+                   echo base64_encode(readblob('somefile.bin'))
+                   " Encode a string
+                   echo base64_encode(list2blob(str2list(somestr)))
+<
+               Can also be used as a |method|: >
+                       GetBinaryData()->base64_encode()
+<
+               Return type: |String|
+
+
 balloon_gettext()                                      *balloon_gettext()*
                Return the current text in the balloon.  Only for the string,
                not used for the List.  Returns an empty string if balloon
diff --git a/runtime/doc/tags b/runtime/doc/tags
index b2aa06463..751a3f95d 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -6164,6 +6164,8 @@ balloon_show()    builtin.txt     /*balloon_show()*
 balloon_split()        builtin.txt     /*balloon_split()*
 bar    motion.txt      /*bar*
 bars   help.txt        /*bars*
+base64_decode()        builtin.txt     /*base64_decode()*
+base64_encode()        builtin.txt     /*base64_encode()*
 base_font_name_list    mbyte.txt       /*base_font_name_list*
 basic.vim      syntax.txt      /*basic.vim*
 beep   options.txt     /*beep*
diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt
index 342332ec2..48937b104 100644
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -1,4 +1,4 @@
-*todo.txt*      For Vim version 9.1.  Last change: 2024 Dec 16
+*todo.txt*      For Vim version 9.1.  Last change: 2024 Dec 30
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -4198,8 +4198,6 @@ Vim script language:
        char2hex()              convert char string to hex string.
        crypt()                 encrypt string
        decrypt()               decrypt string
-       base64enc()             base 64 encoding
-       base64dec()             base 64 decoding
        attributes()            return file protection flags "drwxrwxrwx"
        shorten(fname)          shorten a file name, like home_replace()
        perl(cmd)               call Perl and return string
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 36907d249..fdb993442 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1,4 +1,4 @@
-*usr_41.txt*   For Vim version 9.1.  Last change: 2024 Nov 11
+*usr_41.txt*   For Vim version 9.1.  Last change: 2024 Dec 30
 
                     VIM USER MANUAL - by Bram Moolenaar
 
@@ -1263,6 +1263,8 @@ Inter-process communication:                  
*channel-functions*
        json_decode()           decode a JSON string to Vim types
        js_encode()             encode an expression to a JSON string
        js_decode()             decode a JSON string to Vim types
+       base64_encode()         encode a blob into a base64 string
+       base64_decode()         decode a base64 string into a blob
        err_teapot()            give error 418 or 503
 
 Jobs:                                          *job-functions*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index bb39c4334..af637021c 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2024 Dec 29
+*version9.txt*  For Vim version 9.1.  Last change: 2024 Dec 30
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41629,13 +41629,15 @@ Various syntax, indent and other plugins were added.
 
 Functions: ~
 
+|base64_decode()|      decode a base64 string into a blob
+|base64_encode()|      encode a blob into a base64 string
 |bindtextdomain()|     set message lookup translation base path
 |diff()|               diff two Lists of strings
 |filecopy()|           copy a file {from} to {to}
 |foreach()|            apply function to List items
+|getcellpixels()|      get List of terminal cell pixel size
 |getcmdcomplpat()|     Shell command line completion
 |getcmdprompt()|       get prompt for input()/confirm()
-|getcellpixels()|      get List of terminal cell pixel size
 |getregion()|          get a region of text from a buffer
 |getregionpos()|       get a list of positions for a region
 |id()|                 get unique identifier for a Dict, List, Object,
diff --git a/src/evalfunc.c b/src/evalfunc.c
index b2905da2a..ec108b0cb 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -28,6 +28,8 @@ static void f_balloon_show(typval_T *argvars, typval_T 
*rettv);
 static void f_balloon_split(typval_T *argvars, typval_T *rettv);
 # endif
 #endif
+static void f_base64_encode(typval_T *argvars, typval_T *rettv);
+static void f_base64_decode(typval_T *argvars, typval_T *rettv);
 static void f_bindtextdomain(typval_T *argvars, typval_T *rettv);
 static void f_byte2line(typval_T *argvars, typval_T *rettv);
 static void f_call(typval_T *argvars, typval_T *rettv);
@@ -1834,6 +1836,10 @@ static funcentry_T global_functions[] =
            NULL
 #endif
                        },
+    {"base64_decode",  1, 1, FEARG_1,      arg1_string,
+                       ret_blob,           f_base64_decode},
+    {"base64_encode",  1, 1, FEARG_1,      arg1_blob,
+                       ret_string,         f_base64_encode},
     {"bindtextdomain", 2, 2, 0,            arg2_string,
                        ret_bool,           f_bindtextdomain},
     {"blob2list",      1, 1, FEARG_1,      arg1_blob,
@@ -3479,6 +3485,182 @@ 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
+ */
+    static void
+f_base64_decode(typval_T *argvars, typval_T *rettv)
+{
+    if (check_for_string_arg(argvars, 0) == FAIL)
+       return;
+
+    if (rettv_blob_alloc(rettv) == FAIL)
+       return;
+
+    char_u *str = tv_get_string_chk(&argvars[0]);
+    if (str != NULL)
+       base64_decode(str, rettv->vval.v_blob);
+}
+
+/*
+ * "base64_encode(blob)" function
+ */
+    static void
+f_base64_encode(typval_T *argvars, typval_T *rettv)
+{
+    if (check_for_blob_arg(argvars, 0) == FAIL)
+       return;
+
+    rettv->v_type = VAR_STRING;
+    rettv->vval.v_string = NULL;
+
+    blob_T *blob = argvars->vval.v_blob;
+    if (blob != NULL)
+       rettv->vval.v_string = base64_encode(blob);
+}
+
 /*
  * Get the buffer from "arg" and give an error and return NULL if it is not
  * valid.
diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim
index 8b2518f2b..f80754f0a 100644
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -4206,4 +4206,55 @@ func Test_getcellpixels_gui()
   endif
 endfunc
 
+func Str2Blob(s)
+  return list2blob(str2list(a:s))
+endfunc
+
+func Blob2Str(b)
+  return list2str(blob2list(a:b))
+endfunc
+
+" Test for the base64_encode() and base64_decode() functions
+func Test_base64_encoding()
+  let lines =<< trim END
+    #" Test for encoding/decoding the RFC-4648 alphabets
+    VAR s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+    for i in range(64)
+      call assert_equal($'{s[i]}A==', base64_encode(list2blob([i << 2])))
+      call assert_equal(list2blob([i << 2]), base64_decode($'{s[i]}A=='))
+    endfor
+
+    #" Test for encoding with padding
+    call assert_equal('TQ==', base64_encode(g:Str2Blob("M")))
+    call assert_equal('TWE=', base64_encode(g:Str2Blob("Ma")))
+    call assert_equal('TWFu', g:Str2Blob("Man")->base64_encode())
+    call assert_equal('', base64_encode(0z))
+    call assert_equal('', base64_encode(g:Str2Blob("")))
+
+    #" Test for decoding with padding
+    call assert_equal('light work.', 
g:Blob2Str(base64_decode("bGlnaHQgd29yay4=")))
+    call assert_equal('light work', 
g:Blob2Str(base64_decode("bGlnaHQgd29yaw==")))
+    call assert_equal('light wor', g:Blob2Str("bGlnaHQgd29y"->base64_decode()))
+    call assert_equal(0z00, base64_decode("===="))
+    call assert_equal(0z, base64_decode(""))
+
+    #" Test for invalid padding
+    call assert_equal('Hello', g:Blob2Str(base64_decode("SGVsbG8=")))
+    call assert_fails('call base64_decode("SGVsbG9=")', 'E475:')
+    call assert_fails('call base64_decode("SGVsbG9")', 'E475:')
+    call assert_equal('Hell', g:Blob2Str(base64_decode("SGVsbA==")))
+    call assert_fails('call base64_decode("SGVsbA=")', 'E475:')
+    call assert_fails('call base64_decode("SGVsbA")', 'E475:')
+    call assert_fails('call base64_decode("SGVsbA====")', 'E475:')
+
+    #" Error case
+    call assert_fails('call base64_decode("b")', 'E475: Invalid argument: b')
+    call assert_fails('call base64_decode("<<==")', 'E475: Invalid argument: 
<<==')
+
+    call assert_fails('call base64_encode([])', 'E1238: Blob required for 
argument 1')
+    call assert_fails('call base64_decode([])', 'E1174: String required for 
argument 1')
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index e63645d64..5f1649e0c 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    980,
 /**/
     979,
 /**/

-- 
-- 
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 vim_dev+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1tSCKd-00DTzV-TC%40256bit.org.

Raspunde prin e-mail lui