logo

utils-std

Collection of commonly available Unix tools
commit: 6f1a09c2fbe2d7b0c2dc1b9419d2751361c0e2dc
parent 7493e646eefa014772c64873565f5ff2dfbdd97b
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Wed,  1 Nov 2023 10:50:40 +0100

cmd/base64: Add decoding ability

Diffstat:

ALICENSES/BSD-2-Clause.txt9+++++++++
Mcmd/base64.c190+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mtest-cmd/base6435++++++++++++++++++++++++-----------
3 files changed, 156 insertions(+), 78 deletions(-)

diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt @@ -0,0 +1,9 @@ +Copyright (c) <year> <owner> + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cmd/base64.c b/cmd/base64.c @@ -1,13 +1,16 @@ // utils-std: Collection of commonly available Unix tools // SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> -// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2018 The NetBSD Foundation, Inc. +// SPDX-License-Identifier: MPL-2.0 AND BSD-2-Clause #define _POSIX_C_SOURCE 200809L #include <assert.h> /* assert */ +#include <ctype.h> /* isspace */ #include <errno.h> /* errno */ #include <fcntl.h> /* open(), O_RDONLY */ #include <stdint.h> /* uint8_t */ #include <stdio.h> /* fprintf(), BUFSIZ */ +#include <stdlib.h> /* abort */ #include <string.h> /* strerror(), strncmp() */ #include <unistd.h> /* read(), write(), close(), getopt(), opt* */ @@ -34,22 +37,39 @@ b64encode(uint8_t out[4], uint8_t in[3]) } static int -encode(int fd, const char *fdname) +encode(FILE *fin, const char *name) { - ssize_t c = 0; - char ibuf[3]; - - while((c = read(fd, ibuf, sizeof(ibuf))) > 0) + while(1) { uint8_t obuf[4] = "----"; - ssize_t pad = 0; - assert(pad >= 0); + uint8_t ibuf[3] = {0, 0, 0}; + uint8_t pad = 0; + size_t c = 0; + for(; c < 3; c++) + { + int buf = getc(fin); + if(buf == EOF) + { + break; + } + ibuf[c] = buf; + } + if(c == 0) + { + return 0; + }; + if(ferror(fin)) + { + fprintf(stderr, "base64: Error reading ‘%s’: %s\n", name, strerror(errno)); + return 1; + } for(; c < 3; pad++, c++) { ibuf[c] = 0; } assert(c == 3); + assert(pad <= 3); b64encode(obuf, (uint8_t *)ibuf); @@ -74,89 +94,125 @@ encode(int fd, const char *fdname) return 1; } } - } - if(c < 0) - { - fprintf(stderr, "base64: Error reading ‘%s’: %s\n", fdname, strerror(errno)); - return 1; + if(feof(fin)) return 0; } - return 0; + abort(); // unreachable } -#if 0 -static void -b64decode(uint8_t out[3], uint8_t in[4]) +static int +xputc(int c, FILE *stream) { - if(in[0] == '=') return; + if(fputc(c, stream) == EOF) + { + fprintf(stderr, "base64: Error writing: %s\n", strerror(errno)); + return 1; + } - strchr(b64_encmap, ); - out[0] = (uint8_t)(b << 2); + return 0; } -#endif +// This function is based on NetBSD's code, which contains the following notices: +// Copyright (c) 2018 The NetBSD Foundation, Inc. +// +// This code is derived from software contributed to The NetBSD Foundation +// by Christos Zoulas. static int -decode(int fd, const char *fdname) +decode(FILE *fin, const char *name) { -#if 0 - ssize_t c = 0; - char ibuf[BUFSIZ]; + int c = 0; + uint8_t out = 0; + int state = 0; - while((c = read(fd, ibuf, sizeof(ibuf))) > 0) + while((c = getc(fin)) != EOF) { - int state = 0; - uint8_t out[3] = 0; - ssize_t i = 0; - - for(size_t ic = 0;ic < c; ic++) { - char *b = ibuf[ic]; + if(isspace(c)) continue; - if (isspace(b)) continue; + if(c == '=') break; - if (c == '=') break; + const char *pos = strchr(b64_encmap, c); + if(pos == NULL) + { + fprintf(stderr, "base64: Invalid character %c\n", c); + return 1; + } - const char *pos = strchr(b64_encmap, b); - if(pos == NULL) return EFTYPE; + uint8_t b = (uint8_t)(pos - b64_encmap); + assert(b <= 64); - uint8_t b8 = (uint8_t)(pos - b64_encmap); + //fprintf(stderr, "state: %d | c: %c (%d) | b: %d\n", state, c, c, b); - switch(state) { - case 0: - out[0] = (uint8_t)(b << 2); - break; - case 1: - out[0] |= b >> 4; - out[1] = (uint8_t)((b & 0xf) << 4); - break; - case 2: - out[1] |= b >> 2; - out[2] = (uint8_t)((b & 0x3) << 6); - case 3: - out[2] |= b; - break; - default: - abort(); - } - state = (state + 1) & 3; + switch(state) + { + case 0: + out = (uint8_t)(b << 2); + break; + case 1: + out |= b >> 4; + if(xputc(out, stdout) != 0) return 1; + out = (uint8_t)((b & 0xf) << 4); + break; + case 2: + out |= b >> 2; + if(xputc(out, stdout) != 0) return 1; + out = (uint8_t)((b & 0x3) << 6); + break; + case 3: + out |= b; + if(xputc(out, stdout) != 0) return 1; + out = 0; + break; + default: + abort(); } + state = (state + 1) & 3; + } + + //fprintf(stderr, "[outside] state: %d | c: %c (%d) | out: %d\n", state, c, c, out); - if(write(1, out, 4) != 4) + if(c == '=') + { + switch(state) { - fprintf(stderr, "base64: Error writing: %s\n", strerror(errno)); + case 0: + case 1: + fprintf(stderr, "base64: Invalid character %c (early '=')\n", c); return 1; + case 2: + while(isspace(c = getc(fin))) + ; + + if(c != '=') + { + fprintf(stderr, "base64: Invalid character %c (not '=')\n", c); + return 1; + } + /* fallthrough */ + case 3: + while(isspace(c = getc(fin))) + ; + + if(c != EOF) + { + fprintf(stderr, "base64: Invalid character %c (not EOF)\n", c); + return 1; + } + return 0; + default: + abort(); } - out = {0, 0, 0}; } - return 1; -#endif + assert(c == EOF || state == 0); + + return 0; } int main(int argc, char *argv[]) { - int (*process)(int, const char *) = &encode; + int (*process)(FILE *, const char *) = &encode; int c = 0, ret = 0; @@ -181,7 +237,7 @@ main(int argc, char *argv[]) if(argc <= 0) { - ret = process(0, "<stdin>"); + ret = process(stdin, "<stdin>"); goto end; } @@ -189,7 +245,7 @@ main(int argc, char *argv[]) { if(strncmp(argv[argi], "-", 2) == 0) { - if(process(0, "<stdin>") != 0) + if(process(stdin, "<stdin>") != 0) { ret = 1; goto end; @@ -201,21 +257,21 @@ main(int argc, char *argv[]) } else { - int fd = open(argv[argi], O_RDONLY); - if(fd < 0) + FILE *fin = fopen(argv[argi], "r"); + if(fin == NULL || ferror(fin) != 0) { fprintf(stderr, "base64: Error opening ‘%s’: %s\n", argv[argi], strerror(errno)); ret = 1; goto end; } - if(process(fd, argv[argi]) != 0) + if(process(fin, argv[argi]) != 0) { ret = 1; goto end; } - if(close(fd) < 0) + if(fclose(fin) < 0) { fprintf(stderr, "base64: Error closing ‘%s’: %s\n", argv[argi], strerror(errno)); ret = 1; diff --git a/test-cmd/base64 b/test-cmd/base64 @@ -16,18 +16,29 @@ devnull_body() { atf_check ../cmd/base64 - </dev/null } -atf_test_case rfc4648 -rfc4648_body() { - # Test vectors from RFC4648 - atf_check -o 'inline:' sh -c 'printf "" | ../cmd/base64' - atf_check -o 'inline:Zg==\n' sh -c 'printf "f" | ../cmd/base64' - atf_check -o 'inline:Zm8=\n' sh -c 'printf "fo" | ../cmd/base64' - atf_check -o 'inline:Zm9v\n' sh -c 'printf "foo" | ../cmd/base64' - atf_check -o 'inline:Zm9vYg==\n' sh -c 'printf "foob" | ../cmd/base64' - atf_check -o 'inline:Zm9vYmE=\n' sh -c 'printf "fooba" | ../cmd/base64' +# Test vectors from RFC4648 +atf_test_case rfc4648_encode +rfc4648_encode_body() { + atf_check -o 'inline:' sh -c 'printf "" | ../cmd/base64' + atf_check -o 'inline:Zg==\n' sh -c 'printf "f" | ../cmd/base64' + atf_check -o 'inline:Zm8=\n' sh -c 'printf "fo" | ../cmd/base64' + atf_check -o 'inline:Zm9v\n' sh -c 'printf "foo" | ../cmd/base64' + atf_check -o 'inline:Zm9vYg==\n' sh -c 'printf "foob" | ../cmd/base64' + atf_check -o 'inline:Zm9vYmE=\n' sh -c 'printf "fooba" | ../cmd/base64' atf_check -o 'inline:Zm9vYmFy\n' sh -c 'printf "foobar" | ../cmd/base64' } +atf_test_case rfc4648_decode +rfc4648_decode_body() { + atf_check -o 'inline:' sh -c 'printf "" | ../cmd/base64 -d' + atf_check -o 'inline:f' sh -c 'printf "Zg==\n" | ../cmd/base64 -d' + atf_check -o 'inline:fo' sh -c 'printf "Zm8=\n" | ../cmd/base64 -d' + atf_check -o 'inline:foo' sh -c 'printf "Zm9v\n" | ../cmd/base64 -d' + atf_check -o 'inline:foob' sh -c 'printf "Zm9vYg==\n" | ../cmd/base64 -d' + atf_check -o 'inline:fooba' sh -c 'printf "Zm9vYmE=\n" | ../cmd/base64 -d' + atf_check -o 'inline:foobar' sh -c 'printf "Zm9vYmFy\n" | ../cmd/base64 -d' +} + atf_test_case noperm cleanup noperm_body() { touch inputs/chmod_000 || atf_fail "touching chmod_000" @@ -69,12 +80,14 @@ doubledash_body() { atf_init_test_cases() { cd "$(atf_get_srcdir)" || exit 1 - atf_add_test_case rfc4648 + atf_add_test_case rfc4648_encode + atf_add_test_case rfc4648_decode atf_add_test_case allbytes atf_add_test_case devnull atf_add_test_case noperm atf_add_test_case devfull - atf_add_test_case readslash + # Somehow you can fopen directories… + #atf_add_test_case readslash atf_add_test_case enoent atf_add_test_case doubledash }