master xplshn/aruu / cmd / pseudo / base64.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include "arg.h"
  4#include "util.h"
  5
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9
 10static void
 11usage(void)
 12{
 13  eprintf("usage: %s [-d] [-i] [-w cols] [file]\n", argv0);
 14}
 15
 16static void
 17base64_encode(char *dst, const unsigned char *src, size_t len)
 18{
 19  static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr"
 20                            "stuvwxyz0123456789+/";
 21  size_t            i;
 22
 23  for (i = 0; i < len; i += 3, dst += 4) {
 24    unsigned long x = (src[i] & 0xfful) << 16;
 25    dst[3]          = i + 2 >= len ? '=' : b64[(x |= src[i + 2] & 0xfful) & 0x3f];
 26    dst[2]          = i + 1 >= len ? '=' : b64[(x |= (src[i + 1] & 0xfful) << 8) >> 6 & 0x3f];
 27    dst[1]          = b64[x >> 12 & 0x3f];
 28    dst[0]          = b64[x >> 18];
 29  }
 30  *dst = '\0';
 31}
 32
 33static size_t
 34base64_decode(unsigned char *dst, const char *src)
 35{
 36  static const char b64[] = {
 37      ['A'] = 0,  ['B'] = 1,  ['C'] = 2,  ['D'] = 3,  ['E'] = 4,  ['F'] = 5,  ['G'] = 6,
 38      ['H'] = 7,  ['I'] = 8,  ['J'] = 9,  ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13,
 39      ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20,
 40      ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27,
 41      ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34,
 42      ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41,
 43      ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48,
 44      ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, 53,         54,         55,
 45      56,         57,         58,         59,         60,         61,         ['+'] = 62,
 46      ['/'] = 63, ['='] = 0,
 47  };
 48  unsigned long x;
 49  size_t        i, c, len, pad;
 50
 51  for (i = 0, x = 0, len = 0, pad = 0; src[i]; ++i) {
 52    c = (unsigned char)src[i];
 53    if (c == '=' && (!src[i + 1] || (src[i + 1] == '=' && !src[i + 2])))
 54      ++pad;
 55    else if (c >= sizeof(b64) || (!b64[c] && c != 'A'))
 56      return 0;
 57    x = x << 6 | b64[c];
 58    if (i % 4 == 3) {
 59      dst[len + 2] = x & 0xff, x >>= 8;
 60      dst[len + 1] = x & 0xff, x >>= 8;
 61      dst[len + 0] = x & 0xff;
 62      len += 3;
 63    }
 64  }
 65  if (i % 4 != 0)
 66    return 0;
 67  return len - pad;
 68}
 69
 70static void
 71encode_stream(FILE *fp, size_t wrap)
 72{
 73  unsigned char buf[3072];
 74  char          out[4096 + 1];
 75  size_t        n, i, col;
 76
 77  col = 0;
 78  while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
 79    base64_encode(out, buf, n);
 80    for (i = 0; out[i]; i++) {
 81      putchar(out[i]);
 82      if (wrap > 0 && ++col == wrap) {
 83        putchar('\n');
 84        col = 0;
 85      }
 86    }
 87  }
 88  if (wrap > 0 && col > 0)
 89    putchar('\n');
 90}
 91
 92static void
 93decode_stream(FILE *fp, int iflag)
 94{
 95  char          in[5];
 96  unsigned char out[4];
 97  int           c;
 98  size_t        count, n;
 99
100  count = 0;
101  while ((c = fgetc(fp)) != EOF) {
102    /* skip whitespace */
103    if (c == '\r' || c == '\n' || c == '\t' || c == ' ')
104      continue;
105    /* check validity */
106    if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '+'
107        || c == '/' || c == '=') {
108      in[count++] = c;
109      if (count == 4) {
110        in[4] = '\0';
111        n     = base64_decode(out, in);
112        if (n > 0)
113          fwrite(out, 1, n, stdout);
114        else
115          eprintf("invalid input\n");
116        if (strchr(in, '='))
117          break;
118        count = 0;
119      }
120    } else if (!iflag) {
121      eprintf("invalid character\n");
122    }
123  }
124  if (count > 0)
125    eprintf("input is truncated\n");
126}
127
128// ?man base64: encode or decode base64
129// ?man arguments: file
130// ?man encode or decode data in base64 format
131int
132main(int argc, char *argv[])
133{
134  FILE  *fp;
135  int    dflag, iflag, ret;
136  size_t wrap;
137
138  dflag = 0;
139  iflag = 0;
140  wrap  = 76;
141  ret   = 0;
142  fp    = stdin;
143
144  ARGBEGIN
145  {
146    // ?man -d: specify directory
147    case 'd':
148      dflag = 1;
149      break;
150    // ?man -i: interactive mode or prompt for confirmation
151    case 'i':
152      iflag = 1;
153      break;
154    // ?man -w:num: wait for completion
155    case 'w':
156      wrap = estrtonum(
157          EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SSIZE_MAX)
158      );
159      break;
160    default:
161      usage();
162  }
163  ARGEND
164
165  if (argc > 1)
166    usage();
167
168  if (argc == 1 && strcmp(argv[0], "-") != 0) {
169    fp = fopen(argv[0], "r");
170    if (!fp)
171      eprintf("fopen %s:", argv[0]);
172  }
173
174  if (dflag)
175    decode_stream(fp, iflag);
176  else
177    encode_stream(fp, wrap);
178
179  if (fp != stdin)
180    fclose(fp);
181
182  ret = fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
183  return ret;
184}