logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: b1ce552adedbaa4b74341a1635fe12111aabbee9
parent 9d95b95eb003df990d5fabbc19ac8601c674f925
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Tue, 25 Mar 2025 05:25:52 +0100

cmd/uuencode: new

Diffstat:

Mcmd/base64.12++
Mcmd/base64.c250++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Acmd/uuencode2++
Acmd/uuencode.144++++++++++++++++++++++++++++++++++++++++++++
Mposix_utilities.txt4++--
Atest-cmd/outputs/uuencode/all_bytes8++++++++
Atest-cmd/outputs/uuencode/all_bytes.base647+++++++
Atest-cmd/uuencode.sh34++++++++++++++++++++++++++++++++++
8 files changed, 334 insertions(+), 17 deletions(-)

diff --git a/cmd/base64.1 b/cmd/base64.1 @@ -51,6 +51,8 @@ is 0, then no newlines are written. .El .Sh EXIT STATUS .Ex -std +.Sh SEE ALSO +.Xr uuencode 1 .Sh STANDARDS .Nm follows base64 as defined in RFC3548 and RFC4648. diff --git a/cmd/base64.c b/cmd/base64.c @@ -6,14 +6,15 @@ #define _POSIX_C_SOURCE 200809L #include "../lib/getopt_nolong.h" -#include <assert.h> /* assert */ -#include <ctype.h> /* isspace */ -#include <errno.h> /* errno */ -#include <stdint.h> /* uint8_t */ -#include <stdio.h> /* fopen(), fprintf(), BUFSIZ */ -#include <stdlib.h> /* abort */ -#include <string.h> /* strerror(), strncmp() */ -#include <unistd.h> /* read(), write(), close() */ +#include <assert.h> /* assert */ +#include <ctype.h> /* isspace */ +#include <errno.h> /* errno */ +#include <stdint.h> /* uint8_t */ +#include <stdio.h> /* fopen(), fprintf() */ +#include <stdlib.h> /* abort */ +#include <string.h> /* strerror(), strncmp() */ +#include <sys/stat.h> /* fstat */ +#include <unistd.h> /* read(), write(), close(), getopt() */ // 64(26+26+10+2) + NULL static const char *b64_encmap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -60,7 +61,7 @@ b64encode(uint8_t out[4], uint8_t in[3]) } static int -encode(FILE *fin, const char *name) +base64_encode(FILE *fin, const char *iname) { while(1) { @@ -85,7 +86,7 @@ encode(FILE *fin, const char *name) if(ferror(fin)) { fprintf( - stderr, "%s: error: Failed reading from file '%s': %s\n", argv0, name, strerror(errno)); + stderr, "%s: error: Failed reading from file '%s': %s\n", argv0, iname, strerror(errno)); errno = 0; return 1; } @@ -145,7 +146,7 @@ encode(FILE *fin, const char *name) // This code is derived from software contributed to The NetBSD Foundation // by Christos Zoulas. static int -decode(FILE *fin, const char *name) +base64_decode(FILE *fin, const char *iname) { int c = 0; uint8_t out = 0; @@ -254,10 +255,11 @@ decode(FILE *fin, const char *name) return 0; } -int -main(int argc, char *argv[]) +static int +base64_main(int argc, char *argv[]) { - int (*process)(FILE *, const char *) = &encode; + argv0 = "base64"; + int (*process)(FILE *, const char *) = &base64_encode; int ret = 0; @@ -266,7 +268,7 @@ main(int argc, char *argv[]) switch(c) { case 'd': - process = &decode; + process = &base64_decode; break; case 'w': errno = 0; @@ -363,3 +365,221 @@ end: return ret; } + +static int +uuencode(FILE *fin, const char *iname) +{ + while(1) + { + char output[60] = ""; + + size_t pos = 0; + int len = 0; + for(; len < 45; pos += 4, len += 3) + { + uint8_t ibuf[3] = {0, 0, 0}; + + size_t c = 0; + for(; c < 3; c++) + { + int buf = getc(fin); + if(buf == EOF) + { + break; + } + ibuf[c] = buf; + } + if(c == 0) + { + break; + } + if(ferror(fin)) + { + fprintf(stderr, + "%s: error: Failed reading from file '%s': %s\n", + argv0, + iname, + strerror(errno)); + errno = 0; + return 1; + } + + assert((3 + pos) < 60); + /* conversion math taken from POSIX.1-2024 specification */ + /* clang-format off */ + output[0+pos] = 0x20 + (( ibuf[0] >> 2 ) & 0x3F); + output[1+pos] = 0x20 + (((ibuf[0] << 4) | ((ibuf[1] >> 4) & 0x0F)) & 0x3F); + output[2+pos] = 0x20 + (((ibuf[1] << 2) | ((ibuf[2] >> 6) & 0x03)) & 0x3F); + output[3+pos] = 0x20 + (( ibuf[2] ) & 0x3F); + /* clang-format on */ + + for(int i = 0; i < 4; i++) + if(output[i + pos] == 0x20) output[i + pos] = 0x60; + } + + if(printf("%c", len == 0 ? 0x60 : 0x20 + len) < 0) + { + fprintf(stderr, "%s: error: Failed writing length: %s\n", argv0, strerror(errno)); + errno = 0; + return 1; + } + + if(pos > 0) + if(fwrite(output, pos, 1, stdout) <= 0) + { + fprintf(stderr, "%s: error: Failed writing data: %s\n", argv0, strerror(errno)); + errno = 0; + return 1; + } + + if(printf("\n") < 0) + { + fprintf(stderr, "%s: error: Failed writing newline: %s\n", argv0, strerror(errno)); + errno = 0; + return 1; + } + + if(feof(fin)) break; + } + + if(fflush(stdout) != 0) + { + fprintf(stderr, "%s: error: Failed writing (flush): %s\n", argv0, strerror(errno)); + errno = 0; + return 1; + } + int err = ferror(stdout); + if(err != 0) + { + fprintf(stderr, "%s: error: Failed writing (ferror): %s\n", argv0, strerror(errno)); + return 1; + } + + return 0; +} + +static int +uuencode_main(int argc, char *argv[]) +{ + argv0 = "uuencode"; + const char *begin_str = "begin"; + const char *end_str = "end\n"; + int (*process)(FILE *, const char *) = &uuencode; + + // get old, then reset to old. Yay to POSIX nonsense + mode_t mask = umask(0); + umask(mask); + + // negate to get a normal bitmask + mask ^= 0777; + + wrap_nl = 45; + + int ret = 0; + + for(int c = -1; (c = getopt_nolong(argc, argv, ":m")) != -1;) + { + switch(c) + { + case 'm': + process = &base64_encode; + begin_str = "begin-base64"; + end_str = "====\n"; + wrap_nl = 76; + break; + case ':': + fprintf(stderr, "%s: error: Missing operand for option '-%c'\n", argv0, optopt); + return 1; + case '?': + if(!got_long_opt) fprintf(stderr, "%s: error: Unrecognised option '-%c'\n", argv0, optopt); + return 1; + } + } + + argc -= optind; + argv += optind; + + if(argc > 2) + { + fprintf(stderr, "%s: error: Expected 2 arguments or less, got %d\n", argv0, argc); + return 1; + } + + FILE *fin = stdin; + const char *decode_path = "-"; + const char *iname = "<stdin>"; + mode_t mode = 0666 & mask; + + if(argc >= 1) decode_path = argv[argc - 1]; + + if(argc == 2) + { + iname = argv[0]; + + fin = fopen(iname, "rb"); + if(!fin) + { + fprintf( + stderr, "%s: error: Failed opening input file '%s': %s\n", argv0, iname, strerror(errno)); + return 1; + } + + struct stat fin_stat; + if(fstat(fileno(fin), &fin_stat) != 0) + { + fprintf(stderr, + "%s: warning: Failed getting status for file '%s': %s\n", + argv0, + iname, + strerror(errno)); + ret = 1; + } + + mode = fin_stat.st_mode & 0777; + } + + printf("%s %03o %s\n", begin_str, mode, decode_path); + if(process(fin, iname) != 0) + { + ret = 1; + goto end; + } + +end: + if(ret == 0) + { + if(wrap_nl != 0 && c_out > 0) printf("\n"); + + printf("%s", end_str); + } + + if(fclose(fin) != 0) + { + fprintf(stderr, "%s: error: Failed closing file \"%s\": %s\n", argv0, iname, strerror(errno)); + ret = 1; + } + + if(fclose(stdout) != 0) + { + fprintf(stderr, "%s: error: Failed closing file <stdout>: %s\n", argv0, strerror(errno)); + ret = 1; + } + + return ret; +} + +int +main(int argc, char *argv[]) +{ + const char *arg0 = argv[0]; + + const char *s = strrchr(arg0, '/') + 1; + if(s) arg0 = s; + + if(strcmp(arg0, "base64") == 0) return base64_main(argc, argv); + if(strcmp(arg0, "uuencode") == 0) return uuencode_main(argc, argv); + + fprintf(stderr, "%s: error: Unknown utility '%s' expected 'base64' or 'uuencode'\n", arg0, arg0); + + return -1; +} diff --git a/cmd/uuencode b/cmd/uuencode @@ -0,0 +1 @@ +base64 +\ No newline at end of file diff --git a/cmd/uuencode.1 b/cmd/uuencode.1 @@ -0,0 +1,44 @@ +.\" utils-std: Collection of commonly available Unix tools +.\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +.\" SPDX-License-Identifier: MPL-2.0 +.Dd March 25, 2025 +.Dt UUENCODE 1 +.Os +.Sh NAME +.Nm uuencode +.Nd encode data into uuencoding/base64 to standard output +.Sh SYNOPSIS +.Nm +.Op Fl m +.Op Oo Ar file Oc Ar decode_pathname +.Sh DESCRIPTION +.Nm +reads +.Ar file , +encode it in uuencoding or base64 and writes the results into standard output. +If no +.Ar file +is given, +.Nm +reads from the standard input. +If no +.Ar decode_pathname +is given for the path printed in the output, then +.Ql - +is used. +.Sh OPTIONS +.Bl -tag -width _m +.It Fl m +Encode data into base64 +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr base64 1 +.Sh STANDARDS +.Nm +should be compliant with the +IEEE Std 1003.1-2024 (“POSIX.1”) +specification. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me diff --git a/posix_utilities.txt b/posix_utilities.txt @@ -143,8 +143,8 @@ unget: no, SCCS XOPEN_UNIX uniq: done unlink: done uucp: no, XOPEN_UUCP -uudecode: no, external/obsolete -uuencode: no, external/obsolete +uudecode +uuencode: done uustat: no, XOPEN_UUCP uux: no, XOPEN_UUCP val: no, SCCS XOPEN_UNIX diff --git a/test-cmd/outputs/uuencode/all_bytes b/test-cmd/outputs/uuencode/all_bytes @@ -0,0 +1,8 @@ +begin 644 all_bytes +M``$"`P0%!@<("0H+#`T.#Q`1$A,4%187&!D:&QP='A\@(2(C)"4F)R@I*BLL +M+2XO,#$R,S0U-C<X.3H[/#T^/T!!0D-$149'2$E*2TQ-3D]045)35%565UA9 +M6EM<75Y?8&%B8V1E9F=H:6IK;&UN;W!Q<G-T=79W>'EZ>WQ]?G^`@8*#A(6& +MAXB)BHN,C8Z/D)&2DY25EI>8F9J;G)V>GZ"AHJ.DI::GJ*FJJZRMKJ^PL;*S +MM+6VM[BYNKN\O;Z_P,'"P\3%QL?(R<K+S,W.S]#1TM/4U=;7V-G:V]S=WM_@ +AX>+CY.7FY^CIZNOL[>[O\/'R\_3U]O?X^?K[_/W^_P`` +end diff --git a/test-cmd/outputs/uuencode/all_bytes.base64 b/test-cmd/outputs/uuencode/all_bytes.base64 @@ -0,0 +1,7 @@ +begin-base64 644 all_bytes +AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4 +OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx +cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq +q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj +5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w== +==== diff --git a/test-cmd/uuencode.sh b/test-cmd/uuencode.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 + +WD="$(dirname "$0")" +target="${WD}/../cmd/uuencode" +plans=6 +. "$(dirname "$0")/tap.sh" + +umask 022 + +t --input='' 'empty' 'empty' 'begin 644 empty +` +end +' + +t --input='Cat' 'Cat' '' 'begin 644 - +#0V%T +end +' + +t --input='CatCat' 'CatCat' '' 'begin 644 - +&0V%T0V%T +end +' + +t --input='CatCatCat' 'CatCatCat' '' 'begin 644 - +)0V%T0V%T0V%T +end +' + +t_file all_bytes "$WD/outputs/uuencode/all_bytes" "$WD/inputs/all_bytes" all_bytes + +t_file all_bytes.base64 "$WD/outputs/uuencode/all_bytes.base64" -m "$WD/inputs/all_bytes" all_bytes