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:
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
+}