/* rfc2047e.c - encode a string as per RFC-2047 Copyright (C) 2004 Matthias Andree This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE #include "fetchmail.h" #include #include #include #include static const char noenc[] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; static const char encchars[] = "!\"#$%&'*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~"; static const char ws[] = " \t\r\n"; #ifdef TEST void report (FILE *fp, const char *format, ...) { (void)fp; (void)format;} #endif static int needs_enc(const char *string) { if (strspn(string, noenc) < strlen(string)) return 1; if (strncmp(string, "=?", 2) == 0 && strcmp(string + strlen(string) - 2, "?=") == 0) return 1; return 0; } static char *encode_words(char *const *words, int nwords, const char *charset) { char *out, *t, *v; size_t l = 0; int i; for (i = 0; i < nwords; i++) l += strlen(words[i]) * 3; /* worst case, encode everything */ l += (strlen(charset) + 8) * (l/60 + 1); out = v = xmalloc(l); t = stpcpy(out, "=?"); t = stpcpy(t, charset); t = stpcpy(t, "?Q?"); for (i = 0; i < nwords; i++) { const char *u; for (u = words[i]; *u; u++) { if (t - v >= 69) { t = stpcpy(t, "?=\r\n=?"); v = t - 2; t = stpcpy(t, charset); t = stpcpy(t, "?Q?"); } if (*u == ' ') { *t++ = '_'; continue; } if (strchr(encchars, *u)) { *t++ = *u; continue; } sprintf(t, "=%02X", (unsigned char)*u); t += 3; } } strcpy(t, "?="); return out; } /** RFC-2047 encode string with given charset. Only the Q encoding * (quoted-printable) supported at this time. * WARNING: this code returns a static buffer! */ char *rfc2047e(const char *string, const char *charset) { static char *out; char *t; const char *r; int count, minlen, idx, i; char **words = NULL; size_t l; assert(strlen(charset) < 40); if (out) { free(out); out = NULL; } /* phase 1: split original into words */ /* 1a: count, 1b: copy */ count = 0; r = string; while (*r) { count++; r += strcspn(r, ws); if (!*r) break; count++; r += strspn(r, ws); } words = xmalloc(sizeof(char *) * (count + 1)); idx = 0; r = string; while (*r) { l = strcspn(r, ws); words[idx] = xmalloc(l+1); memcpy(words[idx], r, l); words[idx][l] = 0; idx++; r += l; if (!*r) break; l = strspn(r, ws); words[idx] = xmalloc(l+1); memcpy(words[idx], r, l); words[idx][l] = 0; idx++; r += l; } /* phase 2: encode words */ /* a: find ranges of adjacent words to need encoding */ /* b: encode ranges */ idx = 0; while (idx < count) { int end; char *tmp; if (!needs_enc(words[idx])) { idx += 2; continue; } for (end = idx + 2; end < count; end += 2) { if (!needs_enc(words[end])) break; } end -= 2; tmp = encode_words(&words[idx], end - idx + 1, charset); free(words[idx]); words[idx] = tmp; for (i = idx + 1; i <= end; i++) words[i][0] = 0; idx = end + 2; } for (idx = l = 0; idx < count; idx++) { l += strlen(words[idx]); } /* phase 3: limit lengths */ minlen = strlen(charset) + 7; /* allocate ample memory */ out = xmalloc(l + (l / (72 - minlen) + 1) * (minlen + 2) + 1); if (count) t = stpcpy(out, words[0]); else t = out, *out = 0; l = strlen(out); for (i = 1; i < count; i+=2) { size_t m; char *tmp; m = strlen(words[i]); if (i + 1 < count) m += strcspn(words[i+1], "\r\n"); if (l + m > 74) l = 0, t = stpcpy(t, "\r\n"); t = stpcpy(t, words[i]); if (i + 1 < count) { t = stpcpy(t, words[i+1]); } tmp = strrchr(out, '\n'); if (!tmp) tmp = out; else tmp++; l = strlen(tmp); } /* free memory */ for (i = 0; i < count; i++) free(words[i]); free(words); return out; } #ifdef TEST int main(int argc, char **argv) { char *t; if (argc > 1) { t = rfc2047e(argv[1], argc > 2 ? argv[2] : "utf-8"); printf( " input: \"%s\"\n" "output: \"%s\"\n", argv[1], t); free(t); } return EXIT_SUCCESS; } #endif