1
/*
2
    rfc2047e.c - encode a string as per RFC-2047
3
    Copyright (C) 2004  Matthias Andree
4
5
    This program is free software; you can redistribute it and/or modify
6
    it under the terms of the GNU General Public License as published by
7
    the Free Software Foundation; either version 2 of the License, or
8
    (at your option) any later version.
9
10
    This program is distributed in the hope that it will be useful,
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
    GNU General Public License for more details.
14
15
    You should have received a copy of the GNU General Public License
16
    along with this program; if not, write to the Free Software
17
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
*/
19
20
#ifndef _GNU_SOURCE
21
#define _GNU_SOURCE
22
#endif
23
#include "fetchmail.h"
24
25
#include <string.h>
26
#include <stdio.h>
27
#include <stdlib.h>
28
#include <assert.h>
29
30
static const char noenc[] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
31
static const char encchars[] = "!\"#$%&'*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~";
32
static const char ws[] = " \t\r\n";
33
34
#ifdef TEST
35
void report (FILE *fp, const char *format, ...) { (void)fp; (void)format;}
36
#endif
37
38
static int needs_enc(const char *string) {
39
    if (strspn(string, noenc) < strlen(string))
40
	return 1;
41
    if (strncmp(string, "=?", 2) == 0
42
	    && strcmp(string + strlen(string) - 2, "?=") == 0)
43
	return 1;
44
    return 0;
45
}
46
47
static char *encode_words(char *const *words, int nwords, const char *charset)
48
{
49
    char *out, *t, *v;
50
    size_t l = 0;
51
    int i;
52
53
    for (i = 0; i < nwords; i++)
54
	l += strlen(words[i]) * 3; /* worst case, encode everything */
55
    l += (strlen(charset) + 8) * (l/60 + 1);
56
57
    out = v = (char *)xmalloc(l);
58
    t = stpcpy(out, "=?");
59
    t = stpcpy(t, charset);
60
    t = stpcpy(t, "?Q?");
61
    for (i = 0; i < nwords; i++) {
62
	const char *u;
63
	for (u = words[i]; *u; u++) {
64
	    if (t - v >= 69) {
65
		t = stpcpy(t, "?=\r\n=?");
66
		v = t - 2;
67
		t = stpcpy(t, charset);
68
		t = stpcpy(t, "?Q?");
69
	    }
70
	    if (*u == ' ') { *t++ = '_'; continue; }
71
	    if (strchr(encchars, *u)) { *t++ = *u; continue; }
72
	    sprintf(t, "=%02X", (unsigned int)((unsigned char)*u));
73
	    t += 3;
74
	}
75
    }
76
    strcpy(t, "?=");
77
    return out;
78
}
79
80
/** RFC-2047 encode string with given charset. Only the Q encoding
81
 * (quoted-printable) supported at this time.
82
 * WARNING: this code returns a static buffer!
83
 */
84
char *rfc2047e(const char *string, const char *charset) {
85
    static char *out;
86
    char *t;
87
    const char *r;
88
    int count, minlen, idx, i;
89
    char **words = NULL;
90
    size_t l;
91
92
    assert(strlen(charset) < 40);
93
    if (out) {
94
	free(out);
95
	out = NULL;
96
    }
97
98
    /* phase 1: split original into words */
99
    /* 1a: count, 1b: copy */
100
    count = 0;
101
    r = string;
102
    while (*r) {
103
	count++;
104
	r += strcspn(r, ws);
105
	if (!*r) break;
106
	count++;
107
	r += strspn(r, ws);
108
    }
109
    words = (char **)xmalloc(sizeof(char *) * (count + 1));
110
111
    idx = 0;
112
    r = string;
113
    while (*r) {
114
	l = strcspn(r, ws);
115
	words[idx] = (char *)xmalloc(l+1);
116
	memcpy(words[idx], r, l);
117
	words[idx][l] = '\0';
118
	idx++;
119
	r += l;
120
	if (!*r) break;
121
	l = strspn(r, ws);
122
	words[idx] = (char *)xmalloc(l+1);
123
	memcpy(words[idx], r, l);
124
	words[idx][l] = '\0';
125
	idx++;
126
	r += l;
127
    }
128
129
    /* phase 2: encode words */
130
    /* a: find ranges of adjacent words to need encoding */
131
    /* b: encode ranges */
132
133
    idx = 0;
134
    while (idx < count) {
135
	int end; char *tmp;
136
137
	if (!needs_enc(words[idx])) {
138
	    idx += 2;
139
	    continue;
140
	}
141
	for (end = idx + 2; end < count; end += 2) {
142
	    if (!needs_enc(words[end]))
143
		break;
144
	}
145
	end -= 2;
146
	tmp = encode_words(&words[idx], end - idx + 1, charset);
147
	free(words[idx]);
148
	words[idx] = tmp;
149
	for (i = idx + 1; i <= end; i++)
150
	    words[i][0] = '\0';
151
	idx = end + 2;
152
    }
153
154
    l = 0;
155
    for (idx = 0; idx < count; idx++) {
156
	l += strlen(words[idx]);
157
    }
158
159
    /* phase 3: limit lengths */
160
    minlen = strlen(charset) + 7;
161
    /* allocate ample memory */
162
    out = (char *)xmalloc(l + (l / (72 - minlen) + 1) * (minlen + 2) + 1);
163
164
    if (count)
165
	t = stpcpy(out, words[0]);
166
    else
167
	t = out, *out = 0;
168
169
    l = strlen(out);
170
171
    for (i = 1; i < count; i+=2) {
172
	size_t m;
173
	char *tmp;
174
175
	m = strlen(words[i]);
176
	if (i + 1 < count)
177
	    m += strcspn(words[i+1], "\r\n");
178
	if (l + m > 74)
179
	    t = stpcpy(t, "\r\n");
180
	t = stpcpy(t, words[i]);
181
	if (i + 1 < count) {
182
	    t = stpcpy(t, words[i+1]);
183
	}
184
	tmp = strrchr(out, '\n');
185
	if (tmp == NULL)
186
	    tmp = out;
187
	else
188
	    tmp++;
189
	l = strlen(tmp);
190
    }
191
192
    /* free memory */
193
    for (i = 0; i < count; i++) free(words[i]);
194
    free(words);
195
    return out;
196
}
197
198
#ifdef TEST
199
int main(int argc, char **argv) {
200
    char *t;
201
202
    if (argc > 1) {
203
	t = rfc2047e(argv[1], argc > 2 ? argv[2] : "utf-8");
204
	printf( " input: \"%s\"\n"
205
		"output: \"%s\"\n", argv[1], t);
206
	free(t);
207
    }
208
    return EXIT_SUCCESS;
209
}
210
#endif