1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2020 Baptiste Daroussin <bapt@FreeBSD.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <ctype.h> 29 #include <getopt.h> 30 #include <stdbool.h> 31 #include <stdio.h> 32 #include <string.h> 33 #include <stdlib.h> 34 35 extern int main_quotedprintable(int, char *[]); 36 37 static int 38 hexval(int c) 39 { 40 if ('0' <= c && c <= '9') 41 return c - '0'; 42 return (10 + c - 'A'); 43 } 44 45 46 static int 47 decode_char(const char *s) 48 { 49 return (16 * hexval(toupper(s[1])) + hexval(toupper(s[2]))); 50 } 51 52 53 static void 54 decode_quoted_printable(const char *body, FILE *fpo, bool rfc2047) 55 { 56 while (*body != '\0') { 57 switch (*body) { 58 case '=': 59 if (strlen(body) < 2) { 60 fputc(*body, fpo); 61 break; 62 } 63 64 if (body[1] == '\r' && body[2] == '\n') { 65 body += 2; 66 break; 67 } 68 if (body[1] == '\n') { 69 body++; 70 break; 71 } 72 if (strchr("0123456789ABCDEFabcdef", body[1]) == NULL) { 73 fputc(*body, fpo); 74 break; 75 } 76 if (strchr("0123456789ABCDEFabcdef", body[2]) == NULL) { 77 fputc(*body, fpo); 78 break; 79 } 80 fputc(decode_char(body), fpo); 81 body += 2; 82 break; 83 case '_': 84 if (rfc2047) { 85 fputc(0x20, fpo); 86 break; 87 } 88 /* FALLTHROUGH */ 89 default: 90 fputc(*body, fpo); 91 break; 92 } 93 body++; 94 } 95 } 96 97 static void 98 encode_quoted_printable(const char *body, FILE *fpo, bool rfc2047) 99 { 100 const char *end = body + strlen(body); 101 size_t linelen = 0; 102 char prev = '\0'; 103 104 while (*body != '\0') { 105 if (linelen == 75) { 106 fputs("=\r\n", fpo); 107 linelen = 0; 108 } 109 if (!isascii(*body) || 110 *body == '=' || 111 (*body == '.' && body + 1 < end && 112 (body[1] == '\n' || body[1] == '\r'))) { 113 fprintf(fpo, "=%02X", (unsigned char)*body); 114 linelen += 2; 115 prev = *body; 116 } else if (*body < 33 && *body != '\n') { 117 if ((*body == ' ' || *body == '\t') && 118 body + 1 < end && 119 (body[1] != '\n' && body[1] != '\r')) { 120 if (*body == 0x20 && rfc2047) 121 fputc('_', fpo); 122 else 123 fputc(*body, fpo); 124 prev = *body; 125 } else { 126 fprintf(fpo, "=%02X", (unsigned char)*body); 127 linelen += 2; 128 prev = '_'; 129 } 130 } else if (*body == '\n') { 131 if (prev == ' ' || prev == '\t') { 132 fputc('=', fpo); 133 } 134 fputc('\n', fpo); 135 linelen = 0; 136 prev = 0; 137 } else { 138 fputc(*body, fpo); 139 prev = *body; 140 } 141 body++; 142 linelen++; 143 } 144 } 145 146 static void 147 qp(FILE *fp, FILE *fpo, bool encode, bool rfc2047) 148 { 149 char *line = NULL; 150 size_t linecap = 0; 151 void (*codec)(const char *line, FILE *f, bool rfc2047); 152 153 codec = encode ? encode_quoted_printable : decode_quoted_printable ; 154 155 while (getline(&line, &linecap, fp) > 0) 156 codec(line, fpo, rfc2047); 157 free(line); 158 } 159 160 static void 161 usage(void) 162 { 163 fprintf(stderr, 164 "usage: bintrans qp [-d] [-r] [-o outputfile] [file name]\n"); 165 } 166 167 int 168 main_quotedprintable(int argc, char *argv[]) 169 { 170 int ch; 171 bool encode = true; 172 bool rfc2047 = false; 173 FILE *fp = stdin; 174 FILE *fpo = stdout; 175 176 static const struct option opts[] = 177 { 178 { "decode", no_argument, NULL, 'd'}, 179 { "output", required_argument, NULL, 'o'}, 180 { "rfc2047", no_argument, NULL, 'r'}, 181 {NULL, no_argument, NULL, 0} 182 }; 183 184 while ((ch = getopt_long(argc, argv, "do:ru", opts, NULL)) != -1) { 185 switch(ch) { 186 case 'o': 187 fpo = fopen(optarg, "w"); 188 if (fpo == NULL) { 189 perror(optarg); 190 exit(EXIT_FAILURE); 191 } 192 break; 193 case 'u': 194 /* FALLTHROUGH for backward compatibility */ 195 case 'd': 196 encode = false; 197 break; 198 case 'r': 199 rfc2047 = true; 200 break; 201 default: 202 usage(); 203 exit(EXIT_FAILURE); 204 } 205 }; 206 argc -= optind; 207 argv += optind; 208 if (argc > 0) { 209 fp = fopen(argv[0], "r"); 210 if (fp == NULL) { 211 perror(argv[0]); 212 exit(EXIT_FAILURE); 213 } 214 } 215 qp(fp, fpo, encode, rfc2047); 216 217 return (EXIT_SUCCESS); 218 } 219