0
0
mirror of https://github.com/vim/vim.git synced 2025-08-26 20:03:41 -04:00

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 <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yegappan Lakshmanan 2024-12-30 10:29:44 +01:00 committed by Christian Brabandt
parent 48fa3198b7
commit 810785c689
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
8 changed files with 285 additions and 7 deletions

View File

@ -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 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_gettext() String current text in the balloon
balloon_show({expr}) none show {expr} inside the balloon balloon_show({expr}) none show {expr} inside the balloon
balloon_split({msg}) List split {msg} as used for a 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}) bindtextdomain({package}, {path})
Bool bind text domain to specified path Bool bind text domain to specified path
blob2list({blob}) List convert {blob} into a list of numbers blob2list({blob}) List convert {blob} into a list of numbers
@ -1169,6 +1171,43 @@ autocmd_get([{opts}]) *autocmd_get()*
Return type: list<dict<any>> 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()* balloon_gettext() *balloon_gettext()*
Return the current text in the balloon. Only for the string, Return the current text in the balloon. Only for the string,
not used for the List. Returns an empty string if balloon not used for the List. Returns an empty string if balloon

View File

@ -6164,6 +6164,8 @@ balloon_show() builtin.txt /*balloon_show()*
balloon_split() builtin.txt /*balloon_split()* balloon_split() builtin.txt /*balloon_split()*
bar motion.txt /*bar* bar motion.txt /*bar*
bars help.txt /*bars* 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* base_font_name_list mbyte.txt /*base_font_name_list*
basic.vim syntax.txt /*basic.vim* basic.vim syntax.txt /*basic.vim*
beep options.txt /*beep* beep options.txt /*beep*

View File

@ -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 VIM REFERENCE MANUAL by Bram Moolenaar
@ -4198,8 +4198,6 @@ Vim script language:
char2hex() convert char string to hex string. char2hex() convert char string to hex string.
crypt() encrypt string crypt() encrypt string
decrypt() decrypt string decrypt() decrypt string
base64enc() base 64 encoding
base64dec() base 64 decoding
attributes() return file protection flags "drwxrwxrwx" attributes() return file protection flags "drwxrwxrwx"
shorten(fname) shorten a file name, like home_replace() shorten(fname) shorten a file name, like home_replace()
perl(cmd) call Perl and return string perl(cmd) call Perl and return string

View File

@ -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 VIM USER MANUAL - by Bram Moolenaar
@ -1263,6 +1263,8 @@ Inter-process communication: *channel-functions*
json_decode() decode a JSON string to Vim types json_decode() decode a JSON string to Vim types
js_encode() encode an expression to a JSON string js_encode() encode an expression to a JSON string
js_decode() decode a JSON string to Vim types 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 err_teapot() give error 418 or 503
Jobs: *job-functions* Jobs: *job-functions*

View File

@ -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 VIM REFERENCE MANUAL by Bram Moolenaar
@ -41629,13 +41629,15 @@ Various syntax, indent and other plugins were added.
Functions: ~ 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 |bindtextdomain()| set message lookup translation base path
|diff()| diff two Lists of strings |diff()| diff two Lists of strings
|filecopy()| copy a file {from} to {to} |filecopy()| copy a file {from} to {to}
|foreach()| apply function to List items |foreach()| apply function to List items
|getcellpixels()| get List of terminal cell pixel size
|getcmdcomplpat()| Shell command line completion |getcmdcomplpat()| Shell command line completion
|getcmdprompt()| get prompt for input()/confirm() |getcmdprompt()| get prompt for input()/confirm()
|getcellpixels()| get List of terminal cell pixel size
|getregion()| get a region of text from a buffer |getregion()| get a region of text from a buffer
|getregionpos()| get a list of positions for a region |getregionpos()| get a list of positions for a region
|id()| get unique identifier for a Dict, List, Object, |id()| get unique identifier for a Dict, List, Object,

View File

@ -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); static void f_balloon_split(typval_T *argvars, typval_T *rettv);
# endif # endif
#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_bindtextdomain(typval_T *argvars, typval_T *rettv);
static void f_byte2line(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); static void f_call(typval_T *argvars, typval_T *rettv);
@ -1834,6 +1836,10 @@ static funcentry_T global_functions[] =
NULL NULL
#endif #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, {"bindtextdomain", 2, 2, 0, arg2_string,
ret_bool, f_bindtextdomain}, ret_bool, f_bindtextdomain},
{"blob2list", 1, 1, FEARG_1, arg1_blob, {"blob2list", 1, 1, FEARG_1, arg1_blob,
@ -3479,6 +3485,182 @@ f_balloon_split(typval_T *argvars, typval_T *rettv UNUSED)
# endif # endif
#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 * Get the buffer from "arg" and give an error and return NULL if it is not
* valid. * valid.

View File

@ -4206,4 +4206,55 @@ func Test_getcellpixels_gui()
endif endif
endfunc 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 " vim: shiftwidth=2 sts=2 expandtab

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
980,
/**/ /**/
979, 979,
/**/ /**/