Skip to content

Commit 810785c

Browse files
yegappanchrisbra
authored andcommitted
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>
1 parent 48fa319 commit 810785c

8 files changed

Lines changed: 285 additions & 7 deletions

File tree

runtime/doc/builtin.txt

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*builtin.txt* For Vim version 9.1. Last change: 2024 Dec 03
1+
*builtin.txt* For Vim version 9.1. Last change: 2024 Dec 30
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -67,6 +67,8 @@ autocmd_get([{opts}]) List return a list of autocmds
6767
balloon_gettext() String current text in the balloon
6868
balloon_show({expr}) none show {expr} inside the balloon
6969
balloon_split({msg}) List split {msg} as used for a balloon
70+
base64_decode({string}) Blob base-64 decode {string} characters
71+
base64_encode({blob}) String base-64 encode the bytes in {blob}
7072
bindtextdomain({package}, {path})
7173
Bool bind text domain to specified path
7274
blob2list({blob}) List convert {blob} into a list of numbers
@@ -1169,6 +1171,43 @@ autocmd_get([{opts}]) *autocmd_get()*
11691171
Return type: list<dict<any>>
11701172

11711173

1174+
base64_decode({string}) *base64_decode()*
1175+
Return a Blob containing the bytes decoded from the base64
1176+
characters in {string}.
1177+
1178+
The {string} argument should contain only base64-encoded
1179+
characters and should have a length that is a multiple of 4.
1180+
1181+
Returns an empty blob on error.
1182+
1183+
Examples: >
1184+
" Write the decoded contents to a binary file
1185+
call writefile(base64_decode(s), 'tools.bmp')
1186+
" Decode a base64-encoded string
1187+
echo list2str(blob2list(base64_decode(encodedstr)))
1188+
<
1189+
Can also be used as a |method|: >
1190+
GetEncodedString()->base64_decode()
1191+
<
1192+
Return type: |Blob|
1193+
1194+
1195+
base64_encode({blob}) *base64_encode()*
1196+
Return a base64-encoded String representing the bytes in
1197+
{blob}. The base64 alphabet defined in RFC 4648 is used.
1198+
1199+
Examples: >
1200+
" Encode the contents of a binary file
1201+
echo base64_encode(readblob('somefile.bin'))
1202+
" Encode a string
1203+
echo base64_encode(list2blob(str2list(somestr)))
1204+
<
1205+
Can also be used as a |method|: >
1206+
GetBinaryData()->base64_encode()
1207+
<
1208+
Return type: |String|
1209+
1210+
11721211
balloon_gettext() *balloon_gettext()*
11731212
Return the current text in the balloon. Only for the string,
11741213
not used for the List. Returns an empty string if balloon

runtime/doc/tags

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6164,6 +6164,8 @@ balloon_show() builtin.txt /*balloon_show()*
61646164
balloon_split() builtin.txt /*balloon_split()*
61656165
bar motion.txt /*bar*
61666166
bars help.txt /*bars*
6167+
base64_decode() builtin.txt /*base64_decode()*
6168+
base64_encode() builtin.txt /*base64_encode()*
61676169
base_font_name_list mbyte.txt /*base_font_name_list*
61686170
basic.vim syntax.txt /*basic.vim*
61696171
beep options.txt /*beep*

runtime/doc/todo.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*todo.txt* For Vim version 9.1. Last change: 2024 Dec 16
1+
*todo.txt* For Vim version 9.1. Last change: 2024 Dec 30
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -4198,8 +4198,6 @@ Vim script language:
41984198
char2hex() convert char string to hex string.
41994199
crypt() encrypt string
42004200
decrypt() decrypt string
4201-
base64enc() base 64 encoding
4202-
base64dec() base 64 decoding
42034201
attributes() return file protection flags "drwxrwxrwx"
42044202
shorten(fname) shorten a file name, like home_replace()
42054203
perl(cmd) call Perl and return string

runtime/doc/usr_41.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*usr_41.txt* For Vim version 9.1. Last change: 2024 Nov 11
1+
*usr_41.txt* For Vim version 9.1. Last change: 2024 Dec 30
22

33
VIM USER MANUAL - by Bram Moolenaar
44

@@ -1263,6 +1263,8 @@ Inter-process communication: *channel-functions*
12631263
json_decode() decode a JSON string to Vim types
12641264
js_encode() encode an expression to a JSON string
12651265
js_decode() decode a JSON string to Vim types
1266+
base64_encode() encode a blob into a base64 string
1267+
base64_decode() decode a base64 string into a blob
12661268
err_teapot() give error 418 or 503
12671269

12681270
Jobs: *job-functions*

runtime/doc/version9.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*version9.txt* For Vim version 9.1. Last change: 2024 Dec 29
1+
*version9.txt* For Vim version 9.1. Last change: 2024 Dec 30
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41629,13 +41629,15 @@ Various syntax, indent and other plugins were added.
4162941629

4163041630
Functions: ~
4163141631

41632+
|base64_decode()| decode a base64 string into a blob
41633+
|base64_encode()| encode a blob into a base64 string
4163241634
|bindtextdomain()| set message lookup translation base path
4163341635
|diff()| diff two Lists of strings
4163441636
|filecopy()| copy a file {from} to {to}
4163541637
|foreach()| apply function to List items
41638+
|getcellpixels()| get List of terminal cell pixel size
4163641639
|getcmdcomplpat()| Shell command line completion
4163741640
|getcmdprompt()| get prompt for input()/confirm()
41638-
|getcellpixels()| get List of terminal cell pixel size
4163941641
|getregion()| get a region of text from a buffer
4164041642
|getregionpos()| get a list of positions for a region
4164141643
|id()| get unique identifier for a Dict, List, Object,

src/evalfunc.c

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ static void f_balloon_show(typval_T *argvars, typval_T *rettv);
2828
static void f_balloon_split(typval_T *argvars, typval_T *rettv);
2929
# endif
3030
#endif
31+
static void f_base64_encode(typval_T *argvars, typval_T *rettv);
32+
static void f_base64_decode(typval_T *argvars, typval_T *rettv);
3133
static void f_bindtextdomain(typval_T *argvars, typval_T *rettv);
3234
static void f_byte2line(typval_T *argvars, typval_T *rettv);
3335
static void f_call(typval_T *argvars, typval_T *rettv);
@@ -1834,6 +1836,10 @@ static funcentry_T global_functions[] =
18341836
NULL
18351837
#endif
18361838
},
1839+
{"base64_decode", 1, 1, FEARG_1, arg1_string,
1840+
ret_blob, f_base64_decode},
1841+
{"base64_encode", 1, 1, FEARG_1, arg1_blob,
1842+
ret_string, f_base64_encode},
18371843
{"bindtextdomain", 2, 2, 0, arg2_string,
18381844
ret_bool, f_bindtextdomain},
18391845
{"blob2list", 1, 1, FEARG_1, arg1_blob,
@@ -3479,6 +3485,182 @@ f_balloon_split(typval_T *argvars, typval_T *rettv UNUSED)
34793485
# endif
34803486
#endif
34813487

3488+
// Base64 character set
3489+
static const char_u base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3490+
3491+
// Base64 decoding table (initialized in init_base64_dec_table() below)
3492+
static char_u base64_dec_table[256];
3493+
3494+
/*
3495+
* Initialize the base64 decoding table
3496+
*/
3497+
static void
3498+
init_base64_dec_table(void)
3499+
{
3500+
static int base64_dec_tbl_initialized = FALSE;
3501+
3502+
if (base64_dec_tbl_initialized)
3503+
return;
3504+
3505+
// Unsupported characters are set to 0xFF
3506+
vim_memset(base64_dec_table, 0xFF, sizeof(base64_dec_table));
3507+
3508+
// Initialize the index for the base64 alphabets
3509+
for (size_t i = 0; i < sizeof(base64_table) - 1; i++)
3510+
base64_dec_table[(char_u)base64_table[i]] = (char_u)i;
3511+
3512+
// base64 padding character
3513+
base64_dec_table['='] = 0;
3514+
3515+
base64_dec_tbl_initialized = TRUE;
3516+
}
3517+
3518+
/*
3519+
* Encode the bytes in "blob" using base-64 encoding.
3520+
*/
3521+
static char_u *
3522+
base64_encode(blob_T *blob)
3523+
{
3524+
size_t input_len = blob->bv_ga.ga_len;
3525+
size_t encoded_len = ((input_len + 2) / 3) * 4;
3526+
char_u *data = blob->bv_ga.ga_data;
3527+
3528+
char_u *encoded = alloc(encoded_len + 1);
3529+
if (encoded == NULL)
3530+
return NULL;
3531+
3532+
size_t i, j;
3533+
for (i = 0, j = 0; i < input_len;)
3534+
{
3535+
int_u octet_a = i < input_len ? data[i++] : 0;
3536+
int_u octet_b = i < input_len ? data[i++] : 0;
3537+
int_u octet_c = i < input_len ? data[i++] : 0;
3538+
3539+
int_u triple = (octet_a << 16) | (octet_b << 8) | octet_c;
3540+
3541+
encoded[j++] = base64_table[(triple >> 18) & 0x3F];
3542+
encoded[j++] = base64_table[(triple >> 12) & 0x3F];
3543+
encoded[j++] = (!octet_b && i >= input_len) ? '='
3544+
: base64_table[(triple >> 6) & 0x3F];
3545+
encoded[j++] = (!octet_c && i >= input_len) ? '='
3546+
: base64_table[triple & 0x3F];
3547+
}
3548+
encoded[j] = NUL;
3549+
3550+
return encoded;
3551+
}
3552+
3553+
/*
3554+
* Decode the string "data" using base-64 encoding.
3555+
*/
3556+
static void
3557+
base64_decode(const char_u *data, blob_T *blob)
3558+
{
3559+
size_t input_len = STRLEN(data);
3560+
3561+
if (input_len == 0)
3562+
return;
3563+
3564+
if (input_len % 4 != 0)
3565+
{
3566+
// Invalid input length
3567+
semsg(_(e_invalid_argument_str), data);
3568+
return;
3569+
}
3570+
3571+
init_base64_dec_table();
3572+
3573+
size_t decoded_len = (input_len / 4) * 3;
3574+
if (data[input_len - 1] == '=')
3575+
decoded_len--;
3576+
if (data[input_len - 2] == '=')
3577+
decoded_len--;
3578+
3579+
size_t i, j;
3580+
for (i = 0, j = 0; i < input_len;)
3581+
{
3582+
int_u sextet_a = base64_dec_table[(char_u)data[i++]];
3583+
int_u sextet_b = base64_dec_table[(char_u)data[i++]];
3584+
int_u sextet_c = base64_dec_table[(char_u)data[i++]];
3585+
int_u sextet_d = base64_dec_table[(char_u)data[i++]];
3586+
3587+
if (sextet_a == 0xFF || sextet_b == 0xFF || sextet_c == 0xFF
3588+
|| sextet_d == 0xFF)
3589+
{
3590+
// Invalid character
3591+
semsg(_(e_invalid_argument_str), data);
3592+
ga_clear(&blob->bv_ga);
3593+
return;
3594+
}
3595+
3596+
int_u triple = (sextet_a << 18) | (sextet_b << 12)
3597+
| (sextet_c << 6) | sextet_d;
3598+
3599+
if (j < decoded_len)
3600+
{
3601+
ga_append(&blob->bv_ga, (triple >> 16) & 0xFF);
3602+
j++;
3603+
}
3604+
if (j < decoded_len)
3605+
{
3606+
ga_append(&blob->bv_ga, (triple >> 8) & 0xFF);
3607+
j++;
3608+
}
3609+
if (j < decoded_len)
3610+
{
3611+
ga_append(&blob->bv_ga, triple & 0xFF);
3612+
j++;
3613+
}
3614+
3615+
if (j == decoded_len)
3616+
{
3617+
// Check for invalid padding bytes (based on the
3618+
// "Base64 Malleability in Practice" ACM paper).
3619+
if ((data[input_len - 2] == '=' && ((sextet_b & 0xF) != 0))
3620+
|| ((data[input_len - 1] == '=') && ((sextet_c & 0x3) != 0)))
3621+
{
3622+
semsg(_(e_invalid_argument_str), data);
3623+
ga_clear(&blob->bv_ga);
3624+
return;
3625+
}
3626+
}
3627+
}
3628+
}
3629+
3630+
/*
3631+
* "base64_decode(string)" function
3632+
*/
3633+
static void
3634+
f_base64_decode(typval_T *argvars, typval_T *rettv)
3635+
{
3636+
if (check_for_string_arg(argvars, 0) == FAIL)
3637+
return;
3638+
3639+
if (rettv_blob_alloc(rettv) == FAIL)
3640+
return;
3641+
3642+
char_u *str = tv_get_string_chk(&argvars[0]);
3643+
if (str != NULL)
3644+
base64_decode(str, rettv->vval.v_blob);
3645+
}
3646+
3647+
/*
3648+
* "base64_encode(blob)" function
3649+
*/
3650+
static void
3651+
f_base64_encode(typval_T *argvars, typval_T *rettv)
3652+
{
3653+
if (check_for_blob_arg(argvars, 0) == FAIL)
3654+
return;
3655+
3656+
rettv->v_type = VAR_STRING;
3657+
rettv->vval.v_string = NULL;
3658+
3659+
blob_T *blob = argvars->vval.v_blob;
3660+
if (blob != NULL)
3661+
rettv->vval.v_string = base64_encode(blob);
3662+
}
3663+
34823664
/*
34833665
* Get the buffer from "arg" and give an error and return NULL if it is not
34843666
* valid.

src/testdir/test_functions.vim

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4206,4 +4206,55 @@ func Test_getcellpixels_gui()
42064206
endif
42074207
endfunc
42084208

4209+
func Str2Blob(s)
4210+
return list2blob(str2list(a:s))
4211+
endfunc
4212+
4213+
func Blob2Str(b)
4214+
return list2str(blob2list(a:b))
4215+
endfunc
4216+
4217+
" Test for the base64_encode() and base64_decode() functions
4218+
func Test_base64_encoding()
4219+
let lines =<< trim END
4220+
#" Test for encoding/decoding the RFC-4648 alphabets
4221+
VAR s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
4222+
for i in range(64)
4223+
call assert_equal($'{s[i]}A==', base64_encode(list2blob([i << 2])))
4224+
call assert_equal(list2blob([i << 2]), base64_decode($'{s[i]}A=='))
4225+
endfor
4226+
4227+
#" Test for encoding with padding
4228+
call assert_equal('TQ==', base64_encode(g:Str2Blob("M")))
4229+
call assert_equal('TWE=', base64_encode(g:Str2Blob("Ma")))
4230+
call assert_equal('TWFu', g:Str2Blob("Man")->base64_encode())
4231+
call assert_equal('', base64_encode(0z))
4232+
call assert_equal('', base64_encode(g:Str2Blob("")))
4233+
4234+
#" Test for decoding with padding
4235+
call assert_equal('light work.', g:Blob2Str(base64_decode("bGlnaHQgd29yay4=")))
4236+
call assert_equal('light work', g:Blob2Str(base64_decode("bGlnaHQgd29yaw==")))
4237+
call assert_equal('light wor', g:Blob2Str("bGlnaHQgd29y"->base64_decode()))
4238+
call assert_equal(0z00, base64_decode("===="))
4239+
call assert_equal(0z, base64_decode(""))
4240+
4241+
#" Test for invalid padding
4242+
call assert_equal('Hello', g:Blob2Str(base64_decode("SGVsbG8=")))
4243+
call assert_fails('call base64_decode("SGVsbG9=")', 'E475:')
4244+
call assert_fails('call base64_decode("SGVsbG9")', 'E475:')
4245+
call assert_equal('Hell', g:Blob2Str(base64_decode("SGVsbA==")))
4246+
call assert_fails('call base64_decode("SGVsbA=")', 'E475:')
4247+
call assert_fails('call base64_decode("SGVsbA")', 'E475:')
4248+
call assert_fails('call base64_decode("SGVsbA====")', 'E475:')
4249+
4250+
#" Error case
4251+
call assert_fails('call base64_decode("b")', 'E475: Invalid argument: b')
4252+
call assert_fails('call base64_decode("<<==")', 'E475: Invalid argument: <<==')
4253+
4254+
call assert_fails('call base64_encode([])', 'E1238: Blob required for argument 1')
4255+
call assert_fails('call base64_decode([])', 'E1174: String required for argument 1')
4256+
END
4257+
call v9.CheckLegacyAndVim9Success(lines)
4258+
endfunc
4259+
42094260
" vim: shiftwidth=2 sts=2 expandtab

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,8 @@ static char *(features[]) =
704704

705705
static int included_patches[] =
706706
{ /* Add new patch number below this line */
707+
/**/
708+
980,
707709
/**/
708710
979,
709711
/**/

0 commit comments

Comments
 (0)