xref: /freebsd/usr.bin/bintrans/qp.c (revision dd21556857e8d40f66bf5ad54754d9d52669ebf7)
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