logo

utils

~/.local/bin tools and git-hooks git clone https://hacktivis.me/git/utils.git
commit: 7574df9ac786b3be7115588d0d73f4e44776fea8
parent a33474573598a9985c20e47da5a81e8e57c19780
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Fri, 28 Jul 2023 12:55:02 +0200

cmd/base64: New command

Diffstat:

Acmd/base64.c171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest-cmd/Kyuafile1+
Atest-cmd/base6480+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 252 insertions(+), 0 deletions(-)

diff --git a/cmd/base64.c b/cmd/base64.c @@ -0,0 +1,171 @@ +// Collection of Unix tools, comparable to coreutils +// SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only + +#define _POSIX_C_SOURCE 200809L +#include <assert.h> /* assert */ +#include <errno.h> /* errno */ +#include <fcntl.h> /* open(), O_RDONLY */ +#include <stdint.h> /* uint8_t */ +#include <stdio.h> /* fprintf(), BUFSIZ */ +#include <string.h> /* strerror(), strncmp() */ +#include <unistd.h> /* read(), write(), close(), getopt(), opt* */ + +// 64(26+26+10+2) +static char b64_encmap[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static size_t c_out = 0; +// TODO: -w option ; 76 is lowest of all base64 related RFCs +static size_t wrap_nl = 76; + +static inline uint8_t +b64_get(uint8_t pos) +{ + assert(pos <= 64); + return b64_encmap[pos]; +} + +static void +b64encode(uint8_t out[4], uint8_t in[3]) +{ + out[0] = b64_get(in[0] >> 2); + out[1] = b64_get((uint8_t)(((in[0] & 0x03) << 4) | (in[1] >> 4))); + out[2] = b64_get((uint8_t)(((in[1] & 0x0f) << 2) | (in[2] >> 6))); + out[3] = b64_get(in[2] & 0x3f); +} + +static int +encode(int fd, const char *fdname) +{ + ssize_t c = 0; + char ibuf[3]; + + while((c = read(fd, ibuf, sizeof(ibuf))) > 0) + { + uint8_t obuf[4] = "----"; + ssize_t pad = 0; + assert(pad >= 0); + + for(; c < 3; pad++, c++) + { + ibuf[c] = 0; + } + assert(c == 3); + + b64encode(obuf, (uint8_t *)ibuf); + + for(; pad > 0; pad--) + { + obuf[4 - pad] = '='; + } + + if(write(1, (char *)obuf, 4) != 4) + { + fprintf(stderr, "base64: Error writing: %s\n", strerror(errno)); + return 1; + } + c_out += 4; + + if(wrap_nl != 0 && c_out >= wrap_nl) + { + c_out = 0; + if(write(1, "\n", 1) != 1) + { + fprintf(stderr, "base64: Error writing: %s\n", strerror(errno)); + return 1; + } + } + } + + if(c < 0) + { + fprintf(stderr, "base64: Error reading ‘%s’: %s\n", fdname, strerror(errno)); + return 1; + } + + return 0; +} + +static int +decode(int fd, const char *fdname) +{ + return 1; +} + +int +main(int argc, char *argv[]) +{ + int (*process)(int, const char *) = &encode; + + int c = 0, ret = 0; + + while((c = getopt(argc, argv, ":d")) != -1) + { + switch(c) + { + case 'd': + process = &decode; + break; + case ':': + fprintf(stderr, "base64: Error: Missing operand for option: ‘-%c’\n", optopt); + return 1; + case '?': + fprintf(stderr, "base64: Error: Unrecognised option: ‘-%c’\n", optopt); + return 1; + } + } + + argc -= optind; + argv += optind; + + if(argc <= 0) + { + ret = process(0, "<stdin>"); + goto end; + } + + for(int argi = 0; argi < argc; argi++) + { + if(strncmp(argv[argi], "-", 2) == 0) + { + if(process(0, "<stdin>") != 0) + { + ret = 1; + goto end; + } + } + else if(strncmp(argv[argi], "--", 3) == 0) + { + continue; + } + else + { + int fd = open(argv[argi], O_RDONLY); + if(fd < 0) + { + fprintf(stderr, "base64: Error opening ‘%s’: %s\n", argv[argi], strerror(errno)); + ret = 1; + goto end; + } + + if(process(fd, argv[argi]) != 0) + { + ret = 1; + goto end; + } + + if(close(fd) < 0) + { + fprintf(stderr, "base64: Error closing ‘%s’: %s\n", argv[argi], strerror(errno)); + ret = 1; + goto end; + } + } + } + +end: + if(c_out > 0) + { + printf("\n"); + } + return ret; +} diff --git a/test-cmd/Kyuafile b/test-cmd/Kyuafile @@ -10,6 +10,7 @@ basedir = fs.dirname(fs.dirname(current_kyuafile())) -- atf_test_program{name="pat", required_files=basedir.."/cmd/pat", timeout=1} atf_test_program{name="args", required_files=basedir.."/cmd/args", timeout=1} atf_test_program{name="basename", required_files=basedir.."/cmd/basename", timeout=1} +atf_test_program{name="base64", required_files=basedir.."/cmd/base64", timeout=1} atf_test_program{name="cat", required_files=basedir.."/cmd/cat", timeout=1} atf_test_program{name="date", required_files=basedir.."/cmd/date", timeout=1} atf_test_program{name="del", required_files=basedir.."/cmd/del", timeout=1} diff --git a/test-cmd/base64 b/test-cmd/base64 @@ -0,0 +1,80 @@ +#!/usr/bin/env atf-sh +# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only + +atf_test_case allbytes +allbytes_body() { + atf_check -o file:outputs/base64/all_bytes ../cmd/base64 inputs/all_bytes + atf_check -o file:outputs/base64/all_bytes ../cmd/base64 <inputs/all_bytes + atf_check -o file:outputs/base64/all_bytes ../cmd/base64 - <inputs/all_bytes +} + +atf_test_case devnull +devnull_body() { + atf_check ../cmd/base64 /dev/null + atf_check ../cmd/base64 </dev/null + 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' + atf_check -o 'inline:Zm9vYmFy\n' sh -c 'printf "foobar" | ../cmd/base64' +} + +atf_test_case noperm cleanup +noperm_body() { + touch inputs/chmod_000 || atf_fail "touching chmod_000" + chmod 0000 inputs/chmod_000 || atf_fail "chmod 0000 chmod_000" + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:base64: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/base64 inputs/chmod_000 +} +noperm_cleanup() { + chmod 0600 inputs/chmod_000 || atf_fail "chmod 0600 chmod_000" + rm inputs/chmod_000 || atf_fail "rm chmod_000" +} + +atf_test_case devfull +devfull_body() { + atf_check -s exit:1 -e 'inline:base64: Error writing: No space left on device\n' sh -c '../cmd/base64 inputs/all_bytes >/dev/full' + atf_check -s exit:1 -e 'inline:base64: Error writing: No space left on device\n' sh -c '../cmd/base64 <inputs/all_bytes >/dev/full' + atf_check -s exit:1 -e 'inline:base64: Error writing: No space left on device\n' sh -c '../cmd/base64 - <inputs/all_bytes >/dev/full' +} + +atf_test_case readslash +readslash_body() { + [ "$(uname -s)" = "NetBSD" ] && atf_skip "NetBSD allows to read directories" + + atf_check -s exit:1 -e "inline:base64: Error reading ‘/’: Is a directory\n" ../cmd/base64 / +} + +atf_test_case enoent +enoent_body() { + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:base64: Error opening ‘/var/empty/e/no/ent’: No such file or directory\n' ../cmd/base64 /var/empty/e/no/ent +} + +atf_test_case doubledash +doubledash_body() { + atf_check -o file:outputs/base64/all_bytes -- ../cmd/base64 -- inputs/all_bytes + # shellcheck disable=SC1112 + atf_check -s exit:1 -e "inline:base64: Error: Unrecognised option: ‘--’\n" -o empty -- ../cmd/base64 --- inputs/all_bytes +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case rfc4648 + 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 + atf_add_test_case enoent + atf_add_test_case doubledash +}