]> Pileus Git - ~andy/fetchmail/blob - rfc2047e.c
Fix typo repsonsible -> responsible.
[~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 #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