Skip to content

Commit

Permalink
#135: bin/base64: add command line options for decoding
Browse files Browse the repository at this point in the history
Add the following command line options to the bin/base64 demo utility
that affect decoding:

  --ignore-garbage|-i

    Taken from GNU base64, this option ignores any characters in the
    input which are not part of the base64 alphabet.

  --no-strip-newlines|-n

    Turn off the recently introduced check for newlines in the input.
    This speeds up decoding for any inputs that are guaranteed not to
    have newlines in them.

Resolves #135.
  • Loading branch information
aklomp committed Jan 16, 2024
2 parents 8bdda2d + df1b19e commit 8f3212f
Showing 1 changed file with 83 additions and 17 deletions.
100 changes: 83 additions & 17 deletions bin/base64.c
Expand Up @@ -53,6 +53,12 @@ struct config {

// Whether to just print the help text and exit.
bool print_help;

// Whether to strip newlines from the input when decoding.
bool strip_newlines;

// Whether to ignore any character not in the base64 alphabet.
bool ignore_garbage;
};

// Input/output buffer structure.
Expand Down Expand Up @@ -340,6 +346,37 @@ encode (const struct config *config, struct buffer *buf)
return true;
}

static inline size_t
find_garbage (const char *p, const size_t avail)
{
// Use a lookup table to distinguish garbage from non-garbage.
static const char lut[256] = {
['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1,
['F'] = 1, ['G'] = 1, ['H'] = 1, ['I'] = 1, ['J'] = 1,
['K'] = 1, ['L'] = 1, ['M'] = 1, ['N'] = 1, ['O'] = 1,
['P'] = 1, ['Q'] = 1, ['R'] = 1, ['S'] = 1, ['T'] = 1,
['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1, ['Y'] = 1,
['Z'] = 1,
['a'] = 1, ['b'] = 1, ['c'] = 1, ['d'] = 1, ['e'] = 1,
['f'] = 1, ['g'] = 1, ['h'] = 1, ['i'] = 1, ['j'] = 1,
['k'] = 1, ['l'] = 1, ['m'] = 1, ['n'] = 1, ['o'] = 1,
['p'] = 1, ['q'] = 1, ['r'] = 1, ['s'] = 1, ['t'] = 1,
['u'] = 1, ['v'] = 1, ['w'] = 1, ['x'] = 1, ['y'] = 1,
['z'] = 1,
['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1,
['5'] = 1, ['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1,
['+'] = 1, ['/'] = 1, ['"'] = 1,
};

for (size_t len = 0; len < avail; len++) {
if (lut[(unsigned char) p[len]] == 0) {
return len;
}
}

return avail;
}

static inline size_t
find_newline (const char *p, const size_t avail)
{
Expand Down Expand Up @@ -373,10 +410,20 @@ decode (const struct config *config, struct buffer *buf)
// with GNU `base64'. That includes silently ignoring newlines
// in the input. Tokenize the input on newline characters.
while (avail > 0) {

// Find the offset of the next newline character, which
// is also the length of the next chunk.
size_t outlen, len = find_newline(start, avail);
size_t outlen, len;

// When stripping garbage or newlines in the input,
// find the offset of the next garbage/newline
// character, which is also the length of the next
// chunk. Otherwise treat the entire input as a single
// chunk.
if (config->ignore_garbage) {
len = find_garbage(start, avail);
} else if (config->strip_newlines) {
len = find_newline(start, avail);
} else {
len = avail;
}

// Ignore empty chunks.
if (len == 0) {
Expand Down Expand Up @@ -431,10 +478,17 @@ usage (FILE *fp, const struct config *config)
"If no FILE is given or is specified as '-', "
"read from standard input.\n"
"Options:\n"
" -d, --decode Decode a base64 stream.\n"
" -h, --help Print this help text.\n"
" -w, --wrap=COLS Wrap encoded lines at this column. "
"Default 76, 0 to disable.\n";
" -d, --decode Decode a base64 stream.\n"
" -h, --help Print this help text.\n"
" -i, --ignore-garbage When decoding, ignore any "
"non-base64 data.\n"
" -n, --no-strip-newlines When decoding, do not strip "
"newlines. Speeds up\n"
" decoding of inputs that do not "
"contain newlines.\n"
" -w, --wrap=COLS Wrap encoded lines at this "
"column. Default 76, 0 to\n"
" disable.\n";

fprintf(fp, usage, config->name);
}
Expand Down Expand Up @@ -471,17 +525,19 @@ parse_opts (int argc, char **argv, struct config *config)
{
int c;
static const struct option opts[] = {
{ "decode", no_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ "wrap", required_argument, NULL, 'w' },
{ "decode", no_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ "ignore-garbage", no_argument, NULL, 'i' },
{ "no-strip-newlines", no_argument, NULL, 'n' },
{ "wrap", required_argument, NULL, 'w' },
{ NULL }
};

// Remember the program's name.
config->name = *argv;

// Parse command line options.
while ((c = getopt_long(argc, argv, ":dhw:", opts, NULL)) != -1) {
while ((c = getopt_long(argc, argv, ":dhinw:", opts, NULL)) != -1) {
switch (c) {
case 'd':
config->decode = true;
Expand All @@ -491,6 +547,14 @@ parse_opts (int argc, char **argv, struct config *config)
config->print_help = true;
return true;

case 'i':
config->ignore_garbage = true;
break;

case 'n':
config->strip_newlines = false;
break;

case 'w':
if (get_wrap(config, optarg) == false) {
fprintf(stderr,
Expand Down Expand Up @@ -547,11 +611,13 @@ main (int argc, char **argv)
{
// Default program config.
struct config config = {
.file = "stdin",
.fp = stdin,
.wrap = 76,
.decode = false,
.print_help = false,
.file = "stdin",
.fp = stdin,
.wrap = 76,
.decode = false,
.print_help = false,
.strip_newlines = true,
.ignore_garbage = false,
};
struct buffer buf;

Expand Down

0 comments on commit 8f3212f

Please sign in to comment.