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 <stdbool.h>
30#include <stdio.h>
31#include <string.h>
32#include <stdlib.h>
33
34extern int main_quotedprintable(int, char *[]);
35
36static int
37hexval(int c)
38{
39	if ('0' <= c && c <= '9')
40		return c - '0';
41	return (10 + c - 'A');
42}
43
44
45static int
46decode_char(const char *s)
47{
48	return (16 * hexval(toupper(s[1])) + hexval(toupper(s[2])));
49}
50
51
52static void
53decode_quoted_printable(const char *body, FILE *fpo)
54{
55	while (*body != '\0') {
56		switch (*body) {
57		case '=':
58			if (strlen(body) < 2) {
59				fputc(*body, fpo);
60				break;
61			}
62
63			if (body[1] == '\r' && body[2] == '\n') {
64				body += 2;
65				break;
66			}
67			if (body[1] == '\n') {
68				body++;
69				break;
70			}
71			if (strchr("0123456789ABCDEFabcdef", body[1]) == NULL) {
72				fputc(*body, fpo);
73				break;
74			}
75			if (strchr("0123456789ABCDEFabcdef", body[2]) == NULL) {
76				fputc(*body, fpo);
77				break;
78			}
79			fputc(decode_char(body), fpo);
80			body += 2;
81			break;
82		default:
83			fputc(*body, fpo);
84			break;
85		}
86		body++;
87	}
88}
89
90static void
91encode_quoted_printable(const char *body, FILE *fpo)
92{
93	const char *end = body + strlen(body);
94	size_t linelen = 0;
95	char prev = '\0';
96
97	while (*body != '\0') {
98		if (linelen == 75) {
99			fputs("=\r\n", fpo);
100			linelen = 0;
101		}
102		if (!isascii(*body) ||
103		    *body == '=' ||
104		    (*body == '.' && body + 1 < end &&
105		      (body[1] == '\n' || body[1] == '\r'))) {
106			fprintf(fpo, "=%02X", (unsigned char)*body);
107			linelen += 2;
108			prev = *body;
109		} else if (*body < 33 && *body != '\n') {
110			if ((*body == ' ' || *body == '\t') &&
111			    body + 1 < end &&
112			    (body[1] != '\n' && body[1] != '\r')) {
113				fputc(*body, fpo);
114				prev = *body;
115			} else {
116				fprintf(fpo, "=%02X", (unsigned char)*body);
117				linelen += 2;
118				prev = '_';
119			}
120		} else if (*body == '\n') {
121			if (prev == ' ' || prev == '\t') {
122				fputc('=', fpo);
123			}
124			fputc('\n', fpo);
125			linelen = 0;
126			prev = 0;
127		} else {
128			fputc(*body, fpo);
129			prev = *body;
130		}
131		body++;
132		linelen++;
133	}
134}
135
136static void
137qp(FILE *fp, FILE *fpo, bool encode)
138{
139	char *line = NULL;
140	size_t linecap = 0;
141	void (*codec)(const char *line, FILE *f);
142
143	codec = encode ? encode_quoted_printable : decode_quoted_printable ;
144
145	while (getline(&line, &linecap, fp) > 0)
146		codec(line, fpo);
147	free(line);
148}
149
150static void
151usage(void)
152{
153	fprintf(stderr,
154	   "usage: bintrans qp [-u] [-o outputfile] [file name]\n");
155}
156
157int
158main_quotedprintable(int argc, char *argv[])
159{
160	int i;
161	bool encode = true;
162	FILE *fp = stdin;
163	FILE *fpo = stdout;
164
165	for (i = 1; i < argc; ++i) {
166		if (argv[i][0] == '-') {
167			switch (argv[i][1]) {
168			case 'o':
169				if (++i >= argc) {
170					fprintf(stderr, "qp: -o requires a file name.\n");
171					exit(EXIT_FAILURE);
172				}
173				fpo = fopen(argv[i], "w");
174				if (fpo == NULL) {
175					perror(argv[i]);
176					exit(EXIT_FAILURE);
177				}
178				break;
179			case 'u':
180				encode = false;
181				break;
182			default:
183				usage();
184				exit(EXIT_FAILURE);
185			}
186		} else {
187			fp = fopen(argv[i], "r");
188			if (fp == NULL) {
189				perror(argv[i]);
190				exit(EXIT_FAILURE);
191			}
192		}
193	}
194	qp(fp, fpo, encode);
195
196	return (EXIT_SUCCESS);
197}
198