]> Pileus Git - ~andy/fetchmail/blob - rfc2047e.c
87c9c5c557f6fc0183285b75489e10b9d1ae7f7b
[~andy/fetchmail] / rfc2047e.c
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 #define _GNU_SOURCE
21 #include "fetchmail.h"
22
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <assert.h>
27
28 static const char noenc[] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
29 static const char encchars[] = "!\"#$%&'*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~";
30 static const char ws[] = " \t\r\n";
31
32 #ifdef TEST
33 void report (FILE *fp, const char *format, ...) { (void)fp; (void)format;}
34 #endif
35
36 static int needs_enc(const char *string) {
37     if (strspn(string, noenc) < strlen(string))
38         return 1;
39     if (strncmp(string, "=?", 2) == 0
40             && strcmp(string + strlen(string) - 2, "?=") == 0)
41         return 1;
42     return 0;
43 }
44
45 static char *encode_words(char *const *words, int nwords, const char *charset)
46 {
47     char *out, *t, *v;
48     size_t l = 0;
49     int i;
50
51     for (i = 0; i < nwords; i++)
52         l += strlen(words[i]) * 3; /* worst case, encode everything */
53     l += (strlen(charset) + 8) * (l/60 + 1);
54
55     out = v = (char *)xmalloc(l);
56     t = stpcpy(out, "=?");
57     t = stpcpy(t, charset);
58     t = stpcpy(t, "?Q?");
59     for (i = 0; i < nwords; i++) {
60         const char *u;
61         for (u = words[i]; *u; u++) {
62             if (t - v >= 69) {
63                 t = stpcpy(t, "?=\r\n=?");
64                 v = t - 2;
65                 t = stpcpy(t, charset);
66                 t = stpcpy(t, "?Q?");
67             }
68             if (*u == ' ') { *t++ = '_'; continue; }
69             if (strchr(encchars, *u)) { *t++ = *u; continue; }
70             sprintf(t, "=%02X", (unsigned int)((unsigned char)*u));
71             t += 3;
72         }
73     }
74     strcpy(t, "?=");
75     return out;
76 }
77
78 /** RFC-2047 encode string with given charset. Only the Q encoding
79  * (quoted-printable) supported at this time.
80  * WARNING: this code returns a static buffer!
81  */
82 char *rfc2047e(const char *string, const char *charset) {
83     static char *out;
84     char *t;
85     const char *r;
86     int count, minlen, idx, i;
87     char **words = NULL;
88     size_t l;
89
90     assert(strlen(charset) < 40);
91     if (out) {
92         free(out);
93         out = NULL;
94     }
95
96     /* phase 1: split original into words */
97     /* 1a: count, 1b: copy */
98     count = 0;
99     r = string;
100     while (*r) {
101         count++;
102         r += strcspn(r, ws);
103         if (!*r) break;
104         count++;
105         r += strspn(r, ws);
106     }
107     words = (char **)xmalloc(sizeof(char *) * (count + 1));
108
109     idx = 0;
110     r = string;
111     while (*r) {
112         l = strcspn(r, ws);
113         words[idx] = (char *)xmalloc(l+1);
114         memcpy(words[idx], r, l);
115         words[idx][l] = '\0';
116         idx++;
117         r += l;
118         if (!*r) break;
119         l = strspn(r, ws);
120         words[idx] = (char *)xmalloc(l+1);
121         memcpy(words[idx], r, l);
122         words[idx][l] = '\0';
123         idx++;
124         r += l;
125     }
126
127     /* phase 2: encode words */
128     /* a: find ranges of adjacent words to need encoding */
129     /* b: encode ranges */
130
131     idx = 0;
132     while (idx < count) {
133         int end; char *tmp;
134
135         if (!needs_enc(words[idx])) {
136             idx += 2;
137             continue;
138         }
139         for (end = idx + 2; end < count; end += 2) {
140             if (!needs_enc(words[end]))
141                 break;
142         }
143         end -= 2;
144         tmp = encode_words(&words[idx], end - idx + 1, charset);
145         free(words[idx]);
146         words[idx] = tmp;
147         for (i = idx + 1; i <= end; i++)
148             words[i][0] = '\0';
149         idx = end + 2;
150     }
151
152     l = 0;
153     for (idx = 0; idx < count; idx++) {
154         l += strlen(words[idx]);
155     }
156
157     /* phase 3: limit lengths */
158     minlen = strlen(charset) + 7;
159     /* allocate ample memory */
160     out = (char *)xmalloc(l + (l / (72 - minlen) + 1) * (minlen + 2) + 1);
161
162     if (count)
163         t = stpcpy(out, words[0]);
164     else
165         t = out, *out = 0;
166
167     l = strlen(out);
168
169     for (i = 1; i < count; i+=2) {
170         size_t m;
171         char *tmp;
172
173         m = strlen(words[i]);
174         if (i + 1 < count)
175             m += strcspn(words[i+1], "\r\n");
176         if (l + m > 74)
177             l = 0, t = stpcpy(t, "\r\n");
178         t = stpcpy(t, words[i]);
179         if (i + 1 < count) {
180             t = stpcpy(t, words[i+1]);
181         }
182         tmp = strrchr(out, '\n');
183         if (tmp == NULL)
184             tmp = out;
185         else
186             tmp++;
187         l = strlen(tmp);
188     }
189
190     /* free memory */
191     for (i = 0; i < count; i++) free(words[i]);
192     free(words);
193     return out;
194 }
195
196 #ifdef TEST
197 int main(int argc, char **argv) {
198     char *t;
199
200     if (argc > 1) {
201         t = rfc2047e(argv[1], argc > 2 ? argv[2] : "utf-8");
202         printf( " input: \"%s\"\n"
203                 "output: \"%s\"\n", argv[1], t);
204         free(t);
205     }
206     return EXIT_SUCCESS;
207 }
208 #endif