logo

utils-std

Collection of commonly available Unix tools

base64.c (6555B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-FileCopyrightText: 2018 The NetBSD Foundation, Inc.
  4. // SPDX-License-Identifier: MPL-2.0 AND BSD-2-Clause
  5. #define _POSIX_C_SOURCE 200809L
  6. #include <assert.h> /* assert */
  7. #include <ctype.h> /* isspace */
  8. #include <errno.h> /* errno */
  9. #include <fcntl.h> /* open(), O_RDONLY */
  10. #include <stdint.h> /* uint8_t */
  11. #include <stdio.h> /* fprintf(), BUFSIZ */
  12. #include <stdlib.h> /* abort */
  13. #include <string.h> /* strerror(), strncmp() */
  14. #include <unistd.h> /* read(), write(), close(), getopt(), opt* */
  15. // 64(26+26+10+2)
  16. static char b64_encmap[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  17. static size_t c_out = 0;
  18. // 76 is lowest of all base64 related RFCs
  19. static long wrap_nl = 76;
  20. static int
  21. xputc(int c, FILE *stream)
  22. {
  23. assert(errno == 0);
  24. if(fputc(c, stream) == EOF)
  25. {
  26. fprintf(stderr, "base64: Error writing: %s\n", strerror(errno));
  27. errno = 0;
  28. return 1;
  29. }
  30. assert(errno == 0);
  31. int err = ferror(stream);
  32. if(err != 0)
  33. {
  34. fprintf(stderr, "base64: Error writing: %s\n", strerror(err));
  35. errno = 0;
  36. return 1;
  37. }
  38. return 0;
  39. }
  40. static inline uint8_t
  41. b64_get(uint8_t pos)
  42. {
  43. assert(pos <= 64);
  44. return b64_encmap[pos];
  45. }
  46. static void
  47. b64encode(uint8_t out[4], uint8_t in[3])
  48. {
  49. out[0] = b64_get(in[0] >> 2);
  50. out[1] = b64_get((uint8_t)(((in[0] & 0x03) << 4) | (in[1] >> 4)));
  51. out[2] = b64_get((uint8_t)(((in[1] & 0x0f) << 2) | (in[2] >> 6)));
  52. out[3] = b64_get(in[2] & 0x3f);
  53. }
  54. static int
  55. encode(FILE *fin, const char *name)
  56. {
  57. while(1)
  58. {
  59. uint8_t obuf[4] = "----";
  60. uint8_t ibuf[3] = {0, 0, 0};
  61. uint8_t pad = 0;
  62. size_t c = 0;
  63. for(; c < 3; c++)
  64. {
  65. int buf = getc(fin);
  66. if(buf == EOF)
  67. {
  68. break;
  69. }
  70. ibuf[c] = buf;
  71. }
  72. if(c == 0)
  73. {
  74. return 0;
  75. };
  76. assert(errno == 0);
  77. if(ferror(fin))
  78. {
  79. fprintf(stderr, "base64: Error reading ‘%s’: %s\n", name, strerror(errno));
  80. errno = 0;
  81. return 1;
  82. }
  83. for(; c < 3; pad++, c++)
  84. {
  85. ibuf[c] = 0;
  86. }
  87. assert(c == 3);
  88. assert(pad <= 3);
  89. b64encode(obuf, (uint8_t *)ibuf);
  90. for(; pad > 0; pad--)
  91. {
  92. obuf[4 - pad] = '=';
  93. }
  94. assert(errno == 0);
  95. fwrite((char *)obuf, 4, 1, stdout);
  96. if(errno != 0)
  97. {
  98. fprintf(stderr, "base64: Error writing: %s\n", strerror(errno));
  99. errno = 0;
  100. return 1;
  101. }
  102. c_out += 4;
  103. if(wrap_nl != 0 && (c_out + 4) > wrap_nl)
  104. {
  105. c_out = 0;
  106. if(xputc('\n', stdout) != 0) return 1;
  107. }
  108. if(feof(fin))
  109. {
  110. assert(errno == 0);
  111. if(fflush(stdout))
  112. {
  113. fprintf(stderr, "base64: Error writing: %s\n", strerror(errno));
  114. errno = 0;
  115. return 1;
  116. }
  117. int err = ferror(stdout);
  118. if(err != 0)
  119. {
  120. fprintf(stderr, "base64: Error writing: %s\n", strerror(err));
  121. return 1;
  122. }
  123. return 0;
  124. }
  125. }
  126. abort(); // unreachable
  127. }
  128. // This function is based on NetBSD's code, which contains the following notices:
  129. // Copyright (c) 2018 The NetBSD Foundation, Inc.
  130. //
  131. // This code is derived from software contributed to The NetBSD Foundation
  132. // by Christos Zoulas.
  133. static int
  134. decode(FILE *fin, const char *name)
  135. {
  136. int c = 0;
  137. uint8_t out = 0;
  138. int state = 0;
  139. while((c = getc(fin)) != EOF)
  140. {
  141. if(isspace(c)) continue;
  142. if(c == '=') break;
  143. const char *pos = strchr(b64_encmap, c);
  144. if(pos == NULL)
  145. {
  146. fprintf(stderr, "base64: Invalid character %c\n", c);
  147. return 1;
  148. }
  149. uint8_t b = (uint8_t)(pos - b64_encmap);
  150. assert(b <= 64);
  151. //fprintf(stderr, "state: %d | c: %c (%d) | b: %d\n", state, c, c, b);
  152. switch(state)
  153. {
  154. case 0:
  155. out = (uint8_t)(b << 2);
  156. break;
  157. case 1:
  158. out |= b >> 4;
  159. if(xputc(out, stdout) != 0) return 1;
  160. out = (uint8_t)((b & 0xf) << 4);
  161. break;
  162. case 2:
  163. out |= b >> 2;
  164. if(xputc(out, stdout) != 0) return 1;
  165. out = (uint8_t)((b & 0x3) << 6);
  166. break;
  167. case 3:
  168. out |= b;
  169. if(xputc(out, stdout) != 0) return 1;
  170. out = 0;
  171. break;
  172. default:
  173. abort();
  174. }
  175. state = (state + 1) & 3;
  176. }
  177. //fprintf(stderr, "[outside] state: %d | c: %c (%d) | out: %d\n", state, c, c, out);
  178. if(c == '=')
  179. {
  180. switch(state)
  181. {
  182. case 0:
  183. case 1:
  184. fprintf(stderr, "base64: Invalid character %c (early '=')\n", c);
  185. return 1;
  186. case 2:
  187. while(isspace(c = getc(fin)))
  188. ;
  189. if(c != '=')
  190. {
  191. fprintf(stderr, "base64: Invalid character %c (not '=')\n", c);
  192. return 1;
  193. }
  194. /* fallthrough */
  195. case 3:
  196. while(isspace(c = getc(fin)))
  197. ;
  198. if(c != EOF)
  199. {
  200. fprintf(stderr, "base64: Invalid character %c (not EOF)\n", c);
  201. return 1;
  202. }
  203. return 0;
  204. default:
  205. abort();
  206. }
  207. }
  208. assert(c == EOF || state == 0);
  209. assert(errno == 0);
  210. if(fflush(stdout))
  211. {
  212. fprintf(stderr, "base64: Error writing: %s\n", strerror(errno));
  213. errno = 0;
  214. return 1;
  215. }
  216. assert(errno == 0);
  217. int err = ferror(stdout);
  218. if(err != 0)
  219. {
  220. fprintf(stderr, "base64: Error writing: %s\n", strerror(err));
  221. errno = 0;
  222. return 1;
  223. }
  224. return 0;
  225. }
  226. int
  227. main(int argc, char *argv[])
  228. {
  229. int (*process)(FILE *, const char *) = &encode;
  230. int c = 0, ret = 0;
  231. while((c = getopt(argc, argv, ":dw:")) != -1)
  232. {
  233. switch(c)
  234. {
  235. case 'd':
  236. process = &decode;
  237. break;
  238. case 'w':
  239. errno = 0;
  240. char *e = "";
  241. wrap_nl = strtol(optarg, &e, 10);
  242. // extraneous characters is invalid
  243. if(*e != 0) errno = EINVAL;
  244. if(errno != 0)
  245. {
  246. fprintf(stderr, "base64: Option `-w %s`: %s\n", optarg, strerror(errno));
  247. return 1;
  248. }
  249. break;
  250. case ':':
  251. fprintf(stderr, "base64: Error: Missing operand for option: ‘-%c’\n", optopt);
  252. return 1;
  253. case '?':
  254. fprintf(stderr, "base64: Error: Unrecognised option: ‘-%c’\n", optopt);
  255. return 1;
  256. }
  257. }
  258. argc -= optind;
  259. argv += optind;
  260. if(argc <= 0)
  261. {
  262. ret = process(stdin, "<stdin>");
  263. goto end;
  264. }
  265. for(int argi = 0; argi < argc; argi++)
  266. {
  267. if(strncmp(argv[argi], "-", 2) == 0)
  268. {
  269. if(process(stdin, "<stdin>") != 0)
  270. {
  271. ret = 1;
  272. goto end;
  273. }
  274. }
  275. else if(strncmp(argv[argi], "--", 3) == 0)
  276. {
  277. continue;
  278. }
  279. else
  280. {
  281. FILE *fin = fopen(argv[argi], "r");
  282. if(fin == NULL || ferror(fin) != 0)
  283. {
  284. fprintf(stderr, "base64: Error opening ‘%s’: %s\n", argv[argi], strerror(errno));
  285. ret = 1;
  286. goto end;
  287. }
  288. if(process(fin, argv[argi]) != 0)
  289. {
  290. ret = 1;
  291. goto end;
  292. }
  293. if(fclose(fin) < 0)
  294. {
  295. fprintf(stderr, "base64: Error closing ‘%s’: %s\n", argv[argi], strerror(errno));
  296. ret = 1;
  297. goto end;
  298. }
  299. }
  300. }
  301. end:
  302. if(wrap_nl != 0 && c_out > 0)
  303. {
  304. printf("\n");
  305. }
  306. return ret;
  307. }