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
hexval(int c)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
decode_char(const char * s)47 decode_char(const char *s)
48 {
49 return (16 * hexval(toupper(s[1])) + hexval(toupper(s[2])));
50 }
51
52
53 static void
decode_quoted_printable(const char * body,FILE * fpo,bool rfc2047)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
encode_quoted_printable(const char * body,FILE * fpo,bool rfc2047)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
qp(FILE * fp,FILE * fpo,bool encode,bool rfc2047)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
usage(void)161 usage(void)
162 {
163 fprintf(stderr,
164 "usage: bintrans qp [-d] [-r] [-o outputfile] [file name]\n");
165 }
166
167 int
main_quotedprintable(int argc,char * argv[])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