logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

base64.c (11904B)


  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 "../config.h"
  7. #include "../libutils/getopt_nolong.h"
  8. #include <assert.h> /* assert */
  9. #include <ctype.h> /* isspace */
  10. #include <errno.h> /* errno */
  11. #include <stdint.h> /* uint8_t */
  12. #include <stdio.h> /* fopen(), fprintf() */
  13. #include <stdlib.h> /* abort */
  14. #include <string.h> /* strerror(), strncmp() */
  15. #include <sys/stat.h> /* fstat */
  16. #include <unistd.h> /* read(), write(), close(), getopt() */
  17. #ifdef HAS_GETOPT_LONG
  18. #include <getopt.h>
  19. #endif
  20. // 64(26+26+10+2) + NULL
  21. static const char *b64_encmap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  22. static size_t c_out = 0;
  23. // 76 is lowest of all base64 related RFCs
  24. static long wrap_nl = 76;
  25. const char *argv0 = "base64";
  26. static int
  27. xputc(int c, FILE *stream)
  28. {
  29. if(fputc(c, stream) == EOF)
  30. {
  31. fprintf(stderr, "%s: error: Failed writing: %s\n", argv0, strerror(errno));
  32. errno = 0;
  33. return 1;
  34. }
  35. int err = ferror(stream);
  36. if(err != 0)
  37. {
  38. fprintf(stderr, "%s: error: Failed writing: %s\n", argv0, strerror(err));
  39. errno = 0;
  40. return 1;
  41. }
  42. return 0;
  43. }
  44. static inline uint8_t
  45. b64_get(uint8_t pos)
  46. {
  47. assert(pos <= 64);
  48. return b64_encmap[pos];
  49. }
  50. static void
  51. b64encode(uint8_t out[4], uint8_t in[3])
  52. {
  53. out[0] = b64_get(in[0] >> 2);
  54. out[1] = b64_get((uint8_t)(((in[0] & 0x03) << 4) | (in[1] >> 4)));
  55. out[2] = b64_get((uint8_t)(((in[1] & 0x0f) << 2) | (in[2] >> 6)));
  56. out[3] = b64_get(in[2] & 0x3f);
  57. }
  58. static int
  59. base64_encode(FILE *fin, const char *iname)
  60. {
  61. while(1)
  62. {
  63. uint8_t obuf[4] = "----";
  64. uint8_t ibuf[3] = {0, 0, 0};
  65. uint8_t pad = 0;
  66. size_t c = 0;
  67. for(; c < 3; c++)
  68. {
  69. int buf = getc(fin);
  70. if(buf == EOF)
  71. {
  72. break;
  73. }
  74. ibuf[c] = buf;
  75. }
  76. if(c == 0)
  77. {
  78. return 0;
  79. };
  80. if(ferror(fin))
  81. {
  82. fprintf(
  83. stderr, "%s: error: Failed reading from file '%s': %s\n", argv0, iname, strerror(errno));
  84. errno = 0;
  85. return 1;
  86. }
  87. for(; c < 3; pad++, c++)
  88. {
  89. ibuf[c] = 0;
  90. }
  91. assert(c == 3);
  92. assert(pad <= 3);
  93. b64encode(obuf, (uint8_t *)ibuf);
  94. for(; pad > 0; pad--)
  95. {
  96. obuf[4 - pad] = '=';
  97. }
  98. if(fwrite((char *)obuf, 4, 1, stdout) <= 0)
  99. {
  100. fprintf(stderr, "%s: error: Failed writing: %s\n", argv0, strerror(errno));
  101. errno = 0;
  102. return 1;
  103. }
  104. c_out += 4;
  105. if(wrap_nl != 0 && (c_out + 4) > wrap_nl)
  106. {
  107. c_out = 0;
  108. if(xputc('\n', stdout) != 0) return 1;
  109. }
  110. if(feof(fin))
  111. {
  112. if(fflush(stdout))
  113. {
  114. fprintf(stderr, "%s: error: Failed writing: %s\n", argv0, strerror(errno));
  115. errno = 0;
  116. return 1;
  117. }
  118. int err = ferror(stdout);
  119. if(err != 0)
  120. {
  121. fprintf(stderr, "%s: error: Failed writing: %s\n", argv0, strerror(errno));
  122. return 1;
  123. }
  124. return 0;
  125. }
  126. }
  127. abort(); // unreachable
  128. }
  129. // This function is based on NetBSD's code, which contains the following notices:
  130. // Copyright (c) 2018 The NetBSD Foundation, Inc.
  131. //
  132. // This code is derived from software contributed to The NetBSD Foundation
  133. // by Christos Zoulas.
  134. static int
  135. base64_decode(FILE *fin, const char *iname)
  136. {
  137. int c = 0;
  138. uint8_t out = 0;
  139. int state = 0;
  140. while((c = getc(fin)) != EOF)
  141. {
  142. if(isspace(c)) continue;
  143. if(c == '=') break;
  144. uint8_t b = 65;
  145. for(uint8_t i = 0; i < 64; i++)
  146. {
  147. if(b64_encmap[i] == c)
  148. {
  149. b = i;
  150. break;
  151. }
  152. }
  153. if(b > 64)
  154. {
  155. fprintf(stderr, "%s: error: Invalid character '%c'\n", argv0, c);
  156. return 1;
  157. }
  158. //fprintf(stderr, "state: %d | c: %c (%d) | b: %d\n", state, c, c, b);
  159. switch(state)
  160. {
  161. case 0:
  162. out = (uint8_t)(b << 2);
  163. break;
  164. case 1:
  165. out |= b >> 4;
  166. if(xputc(out, stdout) != 0) return 1;
  167. out = (uint8_t)((b & 0xf) << 4);
  168. break;
  169. case 2:
  170. out |= b >> 2;
  171. if(xputc(out, stdout) != 0) return 1;
  172. out = (uint8_t)((b & 0x3) << 6);
  173. break;
  174. case 3:
  175. out |= b;
  176. if(xputc(out, stdout) != 0) return 1;
  177. out = 0;
  178. break;
  179. default:
  180. abort();
  181. }
  182. state = (state + 1) & 3;
  183. }
  184. //fprintf(stderr, "[outside] state: %d | c: %c (%d) | out: %d\n", state, c, c, out);
  185. if(c == '=')
  186. {
  187. switch(state)
  188. {
  189. case 0:
  190. case 1:
  191. fprintf(stderr, "%s: error: Invalid character '%c' (early '=')\n", argv0, c);
  192. return 1;
  193. case 2:
  194. while(isspace(c = getc(fin)))
  195. ;
  196. if(c != '=')
  197. {
  198. fprintf(stderr, "%s: error: Invalid character '%c' (not '=')\n", argv0, c);
  199. return 1;
  200. }
  201. /* fallthrough */
  202. case 3:
  203. while(isspace(c = getc(fin)))
  204. ;
  205. if(c != EOF)
  206. {
  207. fprintf(stderr, "%s: error: Invalid character '%c' (not EOF)\n", argv0, c);
  208. return 1;
  209. }
  210. return 0;
  211. default:
  212. abort();
  213. }
  214. }
  215. assert(c == EOF || state == 0);
  216. if(fflush(stdout))
  217. {
  218. fprintf(stderr, "%s: error: Failed writing: %s\n", argv0, strerror(errno));
  219. errno = 0;
  220. return 1;
  221. }
  222. int err = ferror(stdout);
  223. if(err != 0)
  224. {
  225. fprintf(stderr, "%s: error: Failed writing: %s\n", argv0, strerror(err));
  226. errno = 0;
  227. return 1;
  228. }
  229. return 0;
  230. }
  231. static int
  232. base64_main(int argc, char *argv[])
  233. {
  234. argv0 = "base64";
  235. int (*process)(FILE *, const char *) = &base64_encode;
  236. int ret = 0;
  237. #ifdef HAS_GETOPT_LONG
  238. // Strictly for GNUisms compatibility so no long-only options
  239. // clang-format off
  240. static struct option opts[] = {
  241. {"decode", no_argument, NULL, 'd'},
  242. {"wrap", required_argument, NULL, 'w'},
  243. {0, 0, 0, 0},
  244. };
  245. // clang-format on
  246. // Need + as first character to get POSIX-style option parsing
  247. for(int c = -1; (c = getopt_long(argc, argv, "+:dw:", opts, NULL)) != -1;)
  248. #else
  249. for(int c = -1; (c = getopt_nolong(argc, argv, ":dw:")) != -1;)
  250. #endif
  251. {
  252. switch(c)
  253. {
  254. case 'd':
  255. process = &base64_decode;
  256. break;
  257. case 'w':
  258. errno = 0;
  259. char *e = NULL;
  260. wrap_nl = strtol(optarg, &e, 10);
  261. // extraneous characters is invalid
  262. if(e && *e != 0) errno = EINVAL;
  263. if(errno != 0)
  264. {
  265. fprintf(stderr, "%s: error: Option '-w %s': %s\n", argv0, optarg, strerror(errno));
  266. return 1;
  267. }
  268. break;
  269. case ':':
  270. fprintf(stderr, "%s: error: Missing operand for option '-%c'\n", argv0, optopt);
  271. return 1;
  272. case '?':
  273. GETOPT_UNKNOWN_OPT
  274. return 1;
  275. }
  276. }
  277. argc -= optind;
  278. argv += optind;
  279. if(argc <= 0)
  280. {
  281. ret = process(stdin, "<stdin>");
  282. goto end;
  283. }
  284. for(int argi = 0; argi < argc; argi++)
  285. {
  286. if(strncmp(argv[argi], "-", 2) == 0)
  287. {
  288. if(process(stdin, "<stdin>") != 0)
  289. {
  290. ret = 1;
  291. goto end;
  292. }
  293. }
  294. else if(strncmp(argv[argi], "--", 3) == 0)
  295. {
  296. continue;
  297. }
  298. else
  299. {
  300. FILE *fin = fopen(argv[argi], "r");
  301. if(fin == NULL)
  302. {
  303. fprintf(stderr,
  304. "%s: error: Failed opening file '%s': %s\n",
  305. argv0,
  306. argv[argi],
  307. strerror(errno));
  308. ret = 1;
  309. goto end;
  310. }
  311. if(process(fin, argv[argi]) != 0) ret = 1;
  312. if(fclose(fin) < 0)
  313. {
  314. fprintf(stderr,
  315. "%s: error: Failed closing file '%s': %s\n",
  316. argv0,
  317. argv[argi],
  318. strerror(errno));
  319. ret = 1;
  320. goto end;
  321. }
  322. }
  323. }
  324. end:
  325. if(wrap_nl != 0 && c_out > 0)
  326. {
  327. printf("\n");
  328. }
  329. if(fclose(stdin) != 0)
  330. {
  331. fprintf(stderr, "%s: error: Failed closing file <stdin>: %s\n", argv0, strerror(errno));
  332. ret = 1;
  333. }
  334. if(fclose(stdout) != 0)
  335. {
  336. fprintf(stderr, "%s: error: Failed closing file <stdout>: %s\n", argv0, strerror(errno));
  337. ret = 1;
  338. }
  339. return ret;
  340. }
  341. static int
  342. uuencode(FILE *fin, const char *iname)
  343. {
  344. bool fin_eof = false;
  345. while(!fin_eof)
  346. {
  347. char output[60] = "";
  348. size_t pos = 0;
  349. int len = 0;
  350. for(; (len < 45) && !fin_eof; pos += 4, len += 3)
  351. {
  352. uint8_t ibuf[3] = {0, 0, 0};
  353. size_t c = 0;
  354. for(; c < 3; c++)
  355. {
  356. int buf = getc(fin);
  357. if(buf == EOF)
  358. {
  359. fin_eof = true;
  360. break;
  361. }
  362. ibuf[c] = buf;
  363. }
  364. if(c == 0)
  365. {
  366. break;
  367. }
  368. if(ferror(fin))
  369. {
  370. fprintf(stderr,
  371. "%s: error: Failed reading from file '%s': %s\n",
  372. argv0,
  373. iname,
  374. strerror(errno));
  375. errno = 0;
  376. return 1;
  377. }
  378. assert((3 + pos) < 60);
  379. /* conversion math taken from POSIX.1-2024 specification */
  380. /* clang-format off */
  381. output[0+pos] = 0x20 + (( ibuf[0] >> 2 ) & 0x3F);
  382. output[1+pos] = 0x20 + (((ibuf[0] << 4) | ((ibuf[1] >> 4) & 0x0F)) & 0x3F);
  383. output[2+pos] = 0x20 + (((ibuf[1] << 2) | ((ibuf[2] >> 6) & 0x03)) & 0x3F);
  384. output[3+pos] = 0x20 + (( ibuf[2] ) & 0x3F);
  385. /* clang-format on */
  386. for(int i = 0; i < 4; i++)
  387. if(output[i + pos] == 0x20) output[i + pos] = 0x60;
  388. }
  389. if(printf("%c", len == 0 ? 0x60 : 0x20 + len) < 0)
  390. {
  391. fprintf(stderr, "%s: error: Failed writing length: %s\n", argv0, strerror(errno));
  392. errno = 0;
  393. return 1;
  394. }
  395. if(pos > 0)
  396. if(fwrite(output, pos, 1, stdout) <= 0)
  397. {
  398. fprintf(stderr, "%s: error: Failed writing data: %s\n", argv0, strerror(errno));
  399. errno = 0;
  400. return 1;
  401. }
  402. if(printf("\n") < 0)
  403. {
  404. fprintf(stderr, "%s: error: Failed writing newline: %s\n", argv0, strerror(errno));
  405. errno = 0;
  406. return 1;
  407. }
  408. }
  409. if(fflush(stdout) != 0)
  410. {
  411. fprintf(stderr, "%s: error: Failed writing (flush): %s\n", argv0, strerror(errno));
  412. errno = 0;
  413. return 1;
  414. }
  415. int err = ferror(stdout);
  416. if(err != 0)
  417. {
  418. fprintf(stderr, "%s: error: Failed writing (ferror): %s\n", argv0, strerror(errno));
  419. return 1;
  420. }
  421. return 0;
  422. }
  423. static int
  424. uuencode_main(int argc, char *argv[])
  425. {
  426. argv0 = "uuencode";
  427. const char *begin_str = "begin";
  428. const char *end_str = "end\n";
  429. int (*process)(FILE *, const char *) = &uuencode;
  430. // get old, then reset to old. Yay to POSIX nonsense
  431. mode_t mask = umask(0);
  432. umask(mask);
  433. // negate to get a normal bitmask
  434. mask ^= 0777;
  435. wrap_nl = 45;
  436. int ret = 0;
  437. for(int c = -1; (c = getopt_nolong(argc, argv, ":m")) != -1;)
  438. {
  439. switch(c)
  440. {
  441. case 'm':
  442. process = &base64_encode;
  443. begin_str = "begin-base64";
  444. end_str = "====\n";
  445. wrap_nl = 76;
  446. break;
  447. case ':':
  448. fprintf(stderr, "%s: error: Missing operand for option '-%c'\n", argv0, optopt);
  449. return 1;
  450. case '?':
  451. GETOPT_UNKNOWN_OPT
  452. return 1;
  453. }
  454. }
  455. argc -= optind;
  456. argv += optind;
  457. if(argc > 2)
  458. {
  459. fprintf(stderr, "%s: error: Expected 2 arguments or less, got %d\n", argv0, argc);
  460. return 1;
  461. }
  462. FILE *fin = stdin;
  463. const char *decode_path = "-";
  464. const char *iname = "<stdin>";
  465. mode_t mode = 0666 & mask;
  466. if(argc >= 1) decode_path = argv[argc - 1];
  467. if(argc == 2)
  468. {
  469. iname = argv[0];
  470. fin = fopen(iname, "rb");
  471. if(!fin)
  472. {
  473. fprintf(
  474. stderr, "%s: error: Failed opening input file '%s': %s\n", argv0, iname, strerror(errno));
  475. return 1;
  476. }
  477. struct stat fin_stat;
  478. if(fstat(fileno(fin), &fin_stat) != 0)
  479. {
  480. fprintf(stderr,
  481. "%s: warning: Failed getting status for file '%s': %s\n",
  482. argv0,
  483. iname,
  484. strerror(errno));
  485. ret = 1;
  486. }
  487. mode = fin_stat.st_mode & 0777;
  488. }
  489. printf("%s %03o %s\n", begin_str, mode, decode_path);
  490. if(process(fin, iname) != 0)
  491. {
  492. ret = 1;
  493. goto end;
  494. }
  495. end:
  496. if(ret == 0)
  497. {
  498. if(wrap_nl != 0 && c_out > 0) printf("\n");
  499. printf("%s", end_str);
  500. }
  501. if(fclose(fin) != 0)
  502. {
  503. fprintf(stderr, "%s: error: Failed closing file \"%s\": %s\n", argv0, iname, strerror(errno));
  504. ret = 1;
  505. }
  506. if(fclose(stdout) != 0)
  507. {
  508. fprintf(stderr, "%s: error: Failed closing file <stdout>: %s\n", argv0, strerror(errno));
  509. ret = 1;
  510. }
  511. return ret;
  512. }
  513. int
  514. main(int argc, char *argv[])
  515. {
  516. const char *arg0 = argv[0];
  517. const char *s = strrchr(arg0, '/');
  518. if(s) arg0 = s + 1;
  519. if(strcmp(arg0, "base64") == 0) return base64_main(argc, argv);
  520. if(strcmp(arg0, "uuencode") == 0) return uuencode_main(argc, argv);
  521. fprintf(stderr, "%s: error: Unknown utility '%s' expected 'base64' or 'uuencode'\n", arg0, arg0);
  522. return -1;
  523. }