diff --git a/AUTHORS b/AUTHORS index da04d5c4d1..e64a2358f8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -58,6 +58,7 @@ Ryoichi KATO - doc fixes Rémy Léone - add .travis.yml Santiago Lapresta - join, arrays, all, any, other filters Sebastian Freundt - build +Shaun Guth - base64d Shay Elkin Simon Elsbrock - Debian Stefan Seemayer diff --git a/Makefile.am b/Makefile.am index c1eaf6deca..cf5d74dc5b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -115,7 +115,7 @@ endif ### Tests (make check) -TESTS = tests/optionaltest tests/mantest tests/jqtest tests/onigtest tests/shtest tests/utf8test +TESTS = tests/optionaltest tests/mantest tests/jqtest tests/onigtest tests/shtest tests/utf8test tests/base64test TESTS_ENVIRONMENT = NO_VALGRIND=$(NO_VALGRIND) diff --git a/appveyor.yml b/appveyor.yml index 719b4dce8e..e58981e7b2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ build_script: test_script: # tests/optionaltest and tests/shtest fail on Windows; run them # anyways but ignore their failures. Also, trace shtest. - - bash -lc "exec 0> (18 - j*6)) & 0x3f]; + buf[j] = BASE64_ENCODE_TABLE[(code >> (18 - j*6)) & 0x3f]; } if (n < 3) buf[3] = '='; if (n < 2) buf[2] = '='; @@ -568,6 +585,49 @@ static jv f_format(jq_state *jq, jv input, jv fmt) { } jv_free(input); return line; + } else if (!strcmp(fmt_s, "base64d")) { + jv_free(fmt); + input = f_tostring(jq, input); + const unsigned char* data = (const unsigned char*)jv_string_value(input); + int len = jv_string_length_bytes(jv_copy(input)); + size_t decoded_len = (3 * len) / 4; // 3 usable bytes for every 4 bytes of input + char *result = malloc(decoded_len * sizeof(char)); + memset(result, 0, decoded_len * sizeof(char)); + uint32_t ri = 0; + int input_bytes_read=0; + uint32_t code = 0; + for (int i=0; i> 16) & 0xFF; + result[ri++] = (code >> 8) & 0xFF; + result[ri++] = code & 0xFF; + input_bytes_read = 0; + code = 0; + } + } + if (input_bytes_read == 3) { + result[ri++] = (code >> 10) & 0xFF; + result[ri++] = (code >> 2) & 0xFF; + } else if (input_bytes_read == 2) { + result[ri++] = (code >> 4) & 0xFF; + } else if (input_bytes_read == 1) { + free(result); + return type_error(input, "trailing base64 byte found"); + } + + jv line = jv_string_sized(result, ri); + jv_free(input); + free(result); + return line; } else { jv_free(input); return jv_invalid_with_msg(jv_string_concat(fmt, jv_string(" is not a valid format"))); diff --git a/tests/base64.test b/tests/base64.test new file mode 100644 index 0000000000..0f82b0b71d --- /dev/null +++ b/tests/base64.test @@ -0,0 +1,35 @@ +# Tests are groups of three lines: program, input, expected output +# Blank lines and lines starting with # are ignored + +@base64 +"<>&'\"\t" +"PD4mJyIJ" + +# decoding encoded output results in same text +(@base64|@base64d) +"<>&'\"\t" +"<>&'\"\t" + +# regression test for #436 +@base64 +"foóbar\n" +"Zm/Ds2Jhcgo=" + +@base64d +"Zm/Ds2Jhcgo=" +"foóbar\n" + +# optional trailing equals padding (With padding, this is cWl4YmF6Cg==) +@base64d +"cWl4YmF6Cg" +"qixbaz\n" + +# invalid base64 characters (whitespace) +. | try @base64d catch . +"Not base64 data" +"string (\"Not base64...) is not valid base64 data" + +# invalid base64 (too many bytes, QUJD = "ABCD" +. | try @base64d catch . +"QUJDa" +"string (\"QUJDa\") trailing base64 byte found" diff --git a/tests/base64test b/tests/base64test new file mode 100755 index 0000000000..85fe64b719 --- /dev/null +++ b/tests/base64test @@ -0,0 +1,5 @@ +#!/bin/sh + +. "${0%/*}/setup" "$@" + +$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/base64.test diff --git a/tests/jq.test b/tests/jq.test index fa02b6d722..f510a917e5 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -61,7 +61,7 @@ null null "interpolation" -@text,@json,([1,.] | (@csv, @tsv)),@html,@uri,@sh,@base64 +@text,@json,([1,.] | (@csv, @tsv)),@html,@uri,@sh,@base64,(@base64 | @base64d) "<>&'\"\t" "<>&'\"\t" "\"<>&'\\\"\\t\"" @@ -71,12 +71,17 @@ null "%3C%3E%26'%22%09" "'<>&'\\''\"\t'" "PD4mJyIJ" +"<>&'\"\t" # regression test for #436 @base64 "foóbar\n" "Zm/Ds2Jhcgo=" +@base64d +"Zm/Ds2Jhcgo=" +"foóbar\n" + @uri "\u03bc" "%CE%BC"