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.