| 1 |
/* |
| 2 |
* MIME mail decoding. |
| 3 |
* |
| 4 |
* This module contains decoding routines for converting |
| 5 |
* quoted-printable data into pure 8-bit data, in MIME |
| 6 |
* formatted messages. |
| 7 |
* |
| 8 |
* By Henrik Storner <storner@image.dk> |
| 9 |
* |
| 10 |
* Configuration file support for fetchmail 4.3.8 by |
| 11 |
* Frank Damgaard <frda@post3.tele.dk> |
| 12 |
* |
| 13 |
* For license terms, see the file COPYING in this directory. |
| 14 |
*/ |
| 15 |
|
| 16 |
#include "config.h" |
| 17 |
#include <string.h> |
| 18 |
#include <stdlib.h> |
| 19 |
#include <stdio.h> |
| 20 |
#include <ctype.h> |
| 21 |
#include "fetchmail.h" |
| 22 |
#include "i18n.h" |
| 23 |
|
| 24 |
static unsigned char unhex(unsigned char c) |
| 25 |
{ |
| 26 |
if ((c >= '0') && (c <= '9')) |
| 27 |
return (c - '0'); |
| 28 |
else if ((c >= 'A') && (c <= 'F')) |
| 29 |
return (c - 'A' + 10); |
| 30 |
else if ((c >= 'a') && (c <= 'f')) |
| 31 |
return (c - 'a' + 10); |
| 32 |
else |
| 33 |
return 16; /* invalid hex character */ |
| 34 |
} |
| 35 |
|
| 36 |
static int qp_char(unsigned char c1, unsigned char c2, char *c_out) |
| 37 |
{ |
| 38 |
c1 = unhex(c1); |
| 39 |
c2 = unhex(c2); |
| 40 |
|
| 41 |
if ((c1 > 15) || (c2 > 15)) |
| 42 |
return 1; |
| 43 |
else { |
| 44 |
*c_out = 16*c1+c2; |
| 45 |
return 0; |
| 46 |
} |
| 47 |
} |
| 48 |
|
| 49 |
|
| 50 |
/* |
| 51 |
* Routines to decode MIME QP-encoded headers, as per RFC 2047. |
| 52 |
*/ |
| 53 |
|
| 54 |
/* States of the decoding state machine */ |
| 55 |
#define S_COPY_PLAIN 0 /* Just copy, but watch for the QP flag */ |
| 56 |
#define S_SKIP_MIMEINIT 1 /* Get the encoding, and skip header */ |
| 57 |
#define S_COPY_MIME 2 /* Decode a sequence of coded characters */ |
| 58 |
|
| 59 |
static const char MIMEHDR_INIT[] = "=?"; /* Start of coded sequence */ |
| 60 |
static const char MIMEHDR_END[] = "?="; /* End of coded sequence */ |
| 61 |
|
| 62 |
void UnMimeHeader(char *hdr) |
| 63 |
{ |
| 64 |
/* Decode a buffer containing data encoded according to RFC |
| 65 |
* 2047. This only handles content-transfer-encoding; conversion |
| 66 |
* between character sets is not implemented. In other words: We |
| 67 |
* assume the charsets used can be displayed by your mail program |
| 68 |
* without problems. |
| 69 |
*/ |
| 70 |
|
| 71 |
/* Note: Decoding is done "in-situ", i.e. without using an |
| 72 |
* additional buffer for temp. storage. This is possible, since the |
| 73 |
* decoded string will always be shorter than the encoded string, |
| 74 |
* due to the encoding scheme. |
| 75 |
*/ |
| 76 |
|
| 77 |
int state = S_COPY_PLAIN; |
| 78 |
char *p_in, *p_out, *p; |
| 79 |
char enc = '\0'; /* initialization pacifies -Wall */ |
| 80 |
int i; |
| 81 |
|
| 82 |
/* Speed up in case this is not a MIME-encoded header */ |
| 83 |
p = strstr(hdr, MIMEHDR_INIT); |
| 84 |
if (p == NULL) |
| 85 |
return; /* No MIME header */ |
| 86 |
|
| 87 |
/* Loop through the buffer. |
| 88 |
* p_in : Next char to be processed. |
| 89 |
* p_out: Where to put the next processed char |
| 90 |
* enc : Encoding used (usually, 'q' = quoted-printable) |
| 91 |
*/ |
| 92 |
for (p_out = p_in = hdr; (*p_in); ) { |
| 93 |
switch (state) { |
| 94 |
case S_COPY_PLAIN: |
| 95 |
p = strstr(p_in, MIMEHDR_INIT); |
| 96 |
if (p == NULL) { |
| 97 |
/* |
| 98 |
* No more coded data in buffer, |
| 99 |
* just move remainder into place. |
| 100 |
*/ |
| 101 |
i = strlen(p_in); /* How much left */ |
| 102 |
memmove(p_out, p_in, i); |
| 103 |
p_in += i; p_out += i; |
| 104 |
} |
| 105 |
else { |
| 106 |
/* MIME header init found at location p */ |
| 107 |
if (p > p_in) { |
| 108 |
/* There are some uncoded chars at the beginning. */ |
| 109 |
i = (p - p_in); |
| 110 |
memmove(p_out, p_in, i); |
| 111 |
p_out += i; |
| 112 |
} |
| 113 |
p_in = (p + 2); |
| 114 |
state = S_SKIP_MIMEINIT; |
| 115 |
} |
| 116 |
break; |
| 117 |
|
| 118 |
case S_SKIP_MIMEINIT: |
| 119 |
/* Mime type definition: "charset?encoding?" */ |
| 120 |
p = strchr(p_in, '?'); |
| 121 |
if (p != NULL) { |
| 122 |
/* p_in .. (p-1) holds the charset */ |
| 123 |
|
| 124 |
/* *(p+1) is the transfer encoding, *(p+2) must be a '?' */ |
| 125 |
if (*(p+2) == '?') { |
| 126 |
enc = tolower((unsigned char)*(p+1)); |
| 127 |
p_in = p+3; |
| 128 |
state = S_COPY_MIME; |
| 129 |
} |
| 130 |
else |
| 131 |
state = S_COPY_PLAIN; |
| 132 |
} |
| 133 |
else |
| 134 |
state = S_COPY_PLAIN; /* Invalid data */ |
| 135 |
break; |
| 136 |
|
| 137 |
case S_COPY_MIME: |
| 138 |
p = strstr(p_in, MIMEHDR_END); /* Find end of coded data */ |
| 139 |
if (p == NULL) p = p_in + strlen(p_in); |
| 140 |
for (; (p_in < p); ) { |
| 141 |
/* Decode all encoded data */ |
| 142 |
if (enc == 'q') { |
| 143 |
if (*p_in == '=') { |
| 144 |
/* Decode one char qp-coded at (p_in+1) and (p_in+2) */ |
| 145 |
if (qp_char(*(p_in+1), *(p_in+2), p_out) == 0) |
| 146 |
p_in += 3; |
| 147 |
else { |
| 148 |
/* Invalid QP data - pass through unchanged. */ |
| 149 |
*p_out = *p_in; |
| 150 |
p_in++; |
| 151 |
} |
| 152 |
} |
| 153 |
else if (*p_in == '_') { |
| 154 |
/* |
| 155 |
* RFC 2047: '_' inside encoded word represents 0x20. |
| 156 |
* NOT a space - always the value 0x20. |
| 157 |
*/ |
| 158 |
*p_out = 0x20; |
| 159 |
p_in++; |
| 160 |
} |
| 161 |
else { |
| 162 |
/* Copy unchanged */ |
| 163 |
*p_out = *p_in; |
| 164 |
p_in++; |
| 165 |
} |
| 166 |
p_out++; |
| 167 |
} |
| 168 |
else if (enc == 'b') { |
| 169 |
/* Decode base64 encoded data */ |
| 170 |
char delimsave; |
| 171 |
int decoded_count; |
| 172 |
|
| 173 |
delimsave = *p; *p = '\r'; |
| 174 |
decoded_count = from64tobits(p_out, p_in, 0); |
| 175 |
*p = delimsave; |
| 176 |
if (decoded_count > 0) |
| 177 |
p_out += decoded_count; |
| 178 |
p_in = p; |
| 179 |
} |
| 180 |
else { |
| 181 |
/* Copy unchanged */ |
| 182 |
*p_out = *p_in; |
| 183 |
p_in++; |
| 184 |
p_out++; |
| 185 |
} |
| 186 |
} |
| 187 |
if (*p_in) |
| 188 |
p_in += 2; /* Skip the MIMEHDR_END delimiter */ |
| 189 |
|
| 190 |
/* |
| 191 |
* We've completed decoding one encoded sequence. But another |
| 192 |
* may follow immediately, in which case whitespace before the |
| 193 |
* new MIMEHDR_INIT delimiter must be discarded. |
| 194 |
* See if that is the case |
| 195 |
*/ |
| 196 |
p = strstr(p_in, MIMEHDR_INIT); |
| 197 |
state = S_COPY_PLAIN; |
| 198 |
if (p != NULL) { |
| 199 |
/* |
| 200 |
* There is more MIME data later on. Is there |
| 201 |
* whitespace only before the delimiter? |
| 202 |
*/ |
| 203 |
char *q; |
| 204 |
int wsp_only = 1; |
| 205 |
|
| 206 |
for (q=p_in; (wsp_only && (q < p)); q++) |
| 207 |
wsp_only = isspace((unsigned char)*q); |
| 208 |
|
| 209 |
if (wsp_only) { |
| 210 |
/* |
| 211 |
* Whitespace-only before the MIME delimiter. OK, |
| 212 |
* just advance p_in to past the new MIMEHDR_INIT, |
| 213 |
* and prepare to process the new MIME charset/encoding |
| 214 |
* header. |
| 215 |
*/ |
| 216 |
p_in = p + sizeof(MIMEHDR_INIT) - 1; |
| 217 |
state = S_SKIP_MIMEINIT; |
| 218 |
} |
| 219 |
} |
| 220 |
break; |
| 221 |
} |
| 222 |
} |
| 223 |
|
| 224 |
*p_out = '\0'; |
| 225 |
} |
| 226 |
|
| 227 |
|
| 228 |
|
| 229 |
/* |
| 230 |
* Routines for decoding body-parts of a message. |
| 231 |
* |
| 232 |
* Since the "fetch" part of fetchmail gets a message body |
| 233 |
* one line at a time, we need to maintain some state variables |
| 234 |
* across multiple invokations of the UnMimeBodyline() routine. |
| 235 |
* The driver routine should call MimeBodyType() when all |
| 236 |
* headers have been received, and then UnMimeBodyline() for |
| 237 |
* every line in the message body. |
| 238 |
* |
| 239 |
*/ |
| 240 |
#define S_BODY_DATA 0 |
| 241 |
#define S_BODY_HDR 1 |
| 242 |
|
| 243 |
/* |
| 244 |
* Flag indicating if we are currently processing |
| 245 |
* the headers or the body of a (multipart) message. |
| 246 |
*/ |
| 247 |
static int BodyState = S_BODY_DATA; |
| 248 |
|
| 249 |
/* |
| 250 |
* Flag indicating if we are in the process of decoding |
| 251 |
* a quoted-printable body part. |
| 252 |
*/ |
| 253 |
static int CurrEncodingIsQP = 0; |
| 254 |
static int CurrTypeNeedsDecode = 0; |
| 255 |
|
| 256 |
/* |
| 257 |
* Delimiter for multipart messages. RFC 2046 states that this must |
| 258 |
* NEVER be longer than 70 characters. Add 3 for the two hyphens |
| 259 |
* at the beginning, and a terminating null. |
| 260 |
*/ |
| 261 |
#define MAX_DELIM_LEN 70 |
| 262 |
static char MultipartDelimiter[MAX_DELIM_LEN+3]; |
| 263 |
|
| 264 |
|
| 265 |
/* This string replaces the "Content-Transfer-Encoding: quoted-printable" |
| 266 |
* string in all headers, including those in body-parts. The replacement |
| 267 |
* must be no longer than the original string. |
| 268 |
*/ |
| 269 |
static const char ENC8BIT[] = "Content-Transfer-Encoding: 8bit"; |
| 270 |
static void SetEncoding8bit(char *XferEncOfs) |
| 271 |
{ |
| 272 |
char *p; |
| 273 |
|
| 274 |
if (XferEncOfs != NULL) { |
| 275 |
memcpy(XferEncOfs, ENC8BIT, sizeof(ENC8BIT) - 1); |
| 276 |
|
| 277 |
/* If anything left, in this header, replace with whitespace */ |
| 278 |
for (p=XferEncOfs+sizeof(ENC8BIT)-1; ((unsigned char)*p >= ' '); p++) |
| 279 |
*p=' '; |
| 280 |
} |
| 281 |
} |
| 282 |
|
| 283 |
static char *GetBoundary(char *CntType) |
| 284 |
{ |
| 285 |
char *p1, *p2; |
| 286 |
int flag; |
| 287 |
|
| 288 |
/* Find the "boundary" delimiter. It must be preceded with a ';' |
| 289 |
* and optionally some whitespace. |
| 290 |
*/ |
| 291 |
p1 = CntType; |
| 292 |
do { |
| 293 |
p2 = strchr(p1, ';'); |
| 294 |
if (p2) |
| 295 |
for (p2++; isspace((unsigned char)*p2); p2++) { } |
| 296 |
|
| 297 |
p1 = p2; |
| 298 |
} while ((p1) && (strncasecmp(p1, "boundary", 8) != 0)); |
| 299 |
|
| 300 |
if (p1 == NULL) |
| 301 |
/* No boundary delimiter */ |
| 302 |
return NULL; |
| 303 |
|
| 304 |
/* Skip "boundary", whitespace and '='; check that we do have a '=' */ |
| 305 |
for (p1+=8, flag=0; (isspace((unsigned char)*p1) || (*p1 == '=')); p1++) |
| 306 |
flag |= (*p1 == '='); |
| 307 |
if (!flag) |
| 308 |
return NULL; |
| 309 |
|
| 310 |
/* Find end of boundary delimiter string */ |
| 311 |
if (*p1 == '\"') { |
| 312 |
/* The delimiter is inside quotes */ |
| 313 |
p1++; |
| 314 |
p2 = strchr(p1, '\"'); |
| 315 |
if (p2 == NULL) |
| 316 |
return NULL; /* No closing '"' !?! */ |
| 317 |
} |
| 318 |
else { |
| 319 |
/* There might be more text after the "boundary" string. */ |
| 320 |
p2 = strchr(p1, ';'); /* Safe - delimiter with ';' must be in quotes */ |
| 321 |
} |
| 322 |
|
| 323 |
/* Zero-terminate the boundary string */ |
| 324 |
if (p2 != NULL) |
| 325 |
*p2 = '\0'; |
| 326 |
|
| 327 |
return (p1 && strlen(p1)) ? p1 : NULL; |
| 328 |
} |
| 329 |
|
| 330 |
|
| 331 |
static int CheckContentType(char *CntType) |
| 332 |
{ |
| 333 |
/* |
| 334 |
* Static array of Content-Type's for which we will do |
| 335 |
* quoted-printable decoding, if requested. |
| 336 |
* It is probably wise to do this only on known text-only types; |
| 337 |
* be really careful if you change this. |
| 338 |
*/ |
| 339 |
|
| 340 |
static const char *DecodedTypes[] = { |
| 341 |
"text/", /* Will match ALL content-type's starting with 'text/' */ |
| 342 |
"message/rfc822", |
| 343 |
NULL |
| 344 |
}; |
| 345 |
|
| 346 |
char *p = CntType; |
| 347 |
int i; |
| 348 |
|
| 349 |
/* If no Content-Type header, it isn't MIME - don't touch it */ |
| 350 |
if (CntType == NULL) return 0; |
| 351 |
|
| 352 |
/* Skip whitespace, if any */ |
| 353 |
for (; isspace((unsigned char)*p); p++) ; |
| 354 |
|
| 355 |
for (i=0; |
| 356 |
(DecodedTypes[i] && |
| 357 |
(strncasecmp(p, DecodedTypes[i], strlen(DecodedTypes[i])))); |
| 358 |
i++) ; |
| 359 |
|
| 360 |
return (DecodedTypes[i] != NULL); |
| 361 |
} |
| 362 |
|
| 363 |
|
| 364 |
/* |
| 365 |
* This routine does three things: |
| 366 |
* 1) It determines - based on the message headers - whether the |
| 367 |
* message body is a MIME message that may hold 8 bit data. |
| 368 |
* - A message that has a "quoted-printable" or "8bit" transfer |
| 369 |
* encoding is assumed to contain 8-bit data (when decoded). |
| 370 |
* - A multipart message is assumed to contain 8-bit data |
| 371 |
* when decoded (there might be quoted-printable body-parts). |
| 372 |
* - All other messages are assumed NOT to include 8-bit data. |
| 373 |
* 2) It determines the delimiter-string used in multi-part message |
| 374 |
* bodies. |
| 375 |
* 3) It sets the initial values of the CurrEncodingIsQP, |
| 376 |
* CurrTypeNeedsDecode, and BodyState variables, from the header |
| 377 |
* contents. |
| 378 |
* |
| 379 |
* The return value is a bitmask. |
| 380 |
*/ |
| 381 |
int MimeBodyType(char *hdrs, int WantDecode) |
| 382 |
{ |
| 383 |
char *NxtHdr = hdrs; |
| 384 |
char *XferEnc, *XferEncOfs, *CntType, *MimeVer, *p; |
| 385 |
int HdrsFound = 0; /* We only look for three headers */ |
| 386 |
int BodyType; /* Return value */ |
| 387 |
|
| 388 |
/* Setup for a standard (no MIME, no QP, 7-bit US-ASCII) message */ |
| 389 |
MultipartDelimiter[0] = '\0'; |
| 390 |
CurrEncodingIsQP = CurrTypeNeedsDecode = 0; |
| 391 |
BodyState = S_BODY_DATA; |
| 392 |
BodyType = 0; |
| 393 |
|
| 394 |
/* Just in case ... */ |
| 395 |
if (hdrs == NULL) |
| 396 |
return BodyType; |
| 397 |
|
| 398 |
XferEnc = XferEncOfs = CntType = MimeVer = NULL; |
| 399 |
|
| 400 |
do { |
| 401 |
if (strncasecmp("Content-Transfer-Encoding:", NxtHdr, 26) == 0) { |
| 402 |
XferEncOfs = NxtHdr; |
| 403 |
p = nxtaddr(NxtHdr); |
| 404 |
if (p != NULL) { |
| 405 |
xfree(XferEnc); |
| 406 |
XferEnc = xstrdup(p); |
| 407 |
HdrsFound++; |
| 408 |
} |
| 409 |
} |
| 410 |
else if (strncasecmp("Content-Type:", NxtHdr, 13) == 0) { |
| 411 |
/* |
| 412 |
* This one is difficult. We cannot use the standard |
| 413 |
* nxtaddr() routine, since the boundary-delimiter is |
| 414 |
* (probably) enclosed in quotes - and thus appears |
| 415 |
* as an rfc822 comment, and nxtaddr() "eats" up any |
| 416 |
* spaces in the delimiter. So, we have to do this |
| 417 |
* by hand. |
| 418 |
*/ |
| 419 |
|
| 420 |
/* Skip the "Content-Type:" part and whitespace after it */ |
| 421 |
for (NxtHdr += 13; ((*NxtHdr == ' ') || (*NxtHdr == '\t')); NxtHdr++) { } |
| 422 |
|
| 423 |
/* |
| 424 |
* Get the full value of the Content-Type header; |
| 425 |
* it might span multiple lines. So search for |
| 426 |
* a newline char, but ignore those that have a |
| 427 |
* have a TAB or space just after the NL (continued |
| 428 |
* lines). |
| 429 |
*/ |
| 430 |
p = NxtHdr-1; |
| 431 |
do { |
| 432 |
p=strchr((p+1),'\n'); |
| 433 |
} while ( (p != NULL) && ((*(p+1) == '\t') || (*(p+1) == ' ')) ); |
| 434 |
if (p == NULL) p = NxtHdr + strlen(NxtHdr); |
| 435 |
|
| 436 |
xfree(CntType); |
| 437 |
CntType = (char *)xmalloc(p-NxtHdr+1); |
| 438 |
strlcpy(CntType, NxtHdr, p-NxtHdr+1); |
| 439 |
HdrsFound++; |
| 440 |
} |
| 441 |
else if (strncasecmp("MIME-Version:", NxtHdr, 13) == 0) { |
| 442 |
p = nxtaddr(NxtHdr); |
| 443 |
if (p != NULL) { |
| 444 |
xfree(MimeVer); |
| 445 |
MimeVer = xstrdup(p); |
| 446 |
HdrsFound++; |
| 447 |
} |
| 448 |
} |
| 449 |
|
| 450 |
NxtHdr = (strchr(NxtHdr, '\n')); |
| 451 |
if (NxtHdr != NULL) NxtHdr++; |
| 452 |
} while ((NxtHdr != NULL) && (*NxtHdr) && (HdrsFound != 3)); |
| 453 |
|
| 454 |
|
| 455 |
/* Done looking through the headers, now check what they say */ |
| 456 |
if ((MimeVer != NULL) && (strcmp(MimeVer, "1.0") == 0)) { |
| 457 |
|
| 458 |
CurrTypeNeedsDecode = CheckContentType(CntType); |
| 459 |
|
| 460 |
/* Check Content-Type to see if this is a multipart message */ |
| 461 |
if ( (CntType != NULL) && |
| 462 |
((strncasecmp(CntType, "multipart/mixed", 16) == 0) || |
| 463 |
(strncasecmp(CntType, "message/", 8) == 0)) ) { |
| 464 |
|
| 465 |
char *p1 = GetBoundary(CntType); |
| 466 |
|
| 467 |
if (p1 != NULL) { |
| 468 |
/* The actual delimiter is "--" followed by |
| 469 |
the boundary string */ |
| 470 |
strcpy(MultipartDelimiter, "--"); |
| 471 |
strlcat(MultipartDelimiter, p1, sizeof(MultipartDelimiter)); |
| 472 |
MultipartDelimiter[sizeof(MultipartDelimiter)-1] = '\0'; |
| 473 |
BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE); |
| 474 |
} |
| 475 |
} |
| 476 |
|
| 477 |
/* |
| 478 |
* Check Content-Transfer-Encoding, but |
| 479 |
* ONLY for non-multipart messages (BodyType == 0). |
| 480 |
*/ |
| 481 |
if ((XferEnc != NULL) && (BodyType == 0)) { |
| 482 |
if (strcasecmp(XferEnc, "quoted-printable") == 0) { |
| 483 |
CurrEncodingIsQP = 1; |
| 484 |
BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE); |
| 485 |
if (WantDecode && CurrTypeNeedsDecode) { |
| 486 |
SetEncoding8bit(XferEncOfs); |
| 487 |
} |
| 488 |
} |
| 489 |
else if (strcasecmp(XferEnc, "7bit") == 0) { |
| 490 |
CurrEncodingIsQP = 0; |
| 491 |
BodyType = (MSG_IS_7BIT); |
| 492 |
} |
| 493 |
else if (strcasecmp(XferEnc, "8bit") == 0) { |
| 494 |
CurrEncodingIsQP = 0; |
| 495 |
BodyType = (MSG_IS_8BIT); |
| 496 |
} |
| 497 |
} |
| 498 |
|
| 499 |
} |
| 500 |
|
| 501 |
xfree(XferEnc); |
| 502 |
xfree(CntType); |
| 503 |
xfree(MimeVer); |
| 504 |
|
| 505 |
return BodyType; |
| 506 |
} |
| 507 |
|
| 508 |
|
| 509 |
/* |
| 510 |
* Decode one line of data containing QP data. |
| 511 |
* Return flag set if this line ends with a soft line-break. |
| 512 |
* 'bufp' is modified to point to the end of the output buffer. |
| 513 |
*/ |
| 514 |
static int DoOneQPLine(char **bufp, flag delimited, flag issoftline) |
| 515 |
{ |
| 516 |
char *buf = *bufp; |
| 517 |
char *p_in, *p_out, *p; |
| 518 |
int n; |
| 519 |
int ret = 0; |
| 520 |
|
| 521 |
/* |
| 522 |
* Special case: line consists of a single =2E and messages are |
| 523 |
* dot-terminated. Line has to be dot-stuffed after decoding. |
| 524 |
*/ |
| 525 |
if (delimited && !issoftline && buf[0]=='=' && !strncmp(*bufp, "=2E\r\n", 5)) |
| 526 |
{ |
| 527 |
strcpy(buf, "..\r\n"); |
| 528 |
*bufp += 5; |
| 529 |
return(FALSE); |
| 530 |
} |
| 531 |
|
| 532 |
p_in = buf; |
| 533 |
if (delimited && issoftline && (strncmp(buf, "..", 2) == 0)) |
| 534 |
p_in++; |
| 535 |
|
| 536 |
for (p_out = buf; (*p_in); ) { |
| 537 |
p = strchr(p_in, '='); |
| 538 |
if (p == NULL) { |
| 539 |
/* No more QP data, just move remainder into place */ |
| 540 |
n = strlen(p_in); |
| 541 |
memmove(p_out, p_in, n); |
| 542 |
p_in += n; p_out += n; |
| 543 |
} |
| 544 |
else { |
| 545 |
if (p > p_in) { |
| 546 |
/* There are some uncoded chars at the beginning. */ |
| 547 |
n = (p - p_in); |
| 548 |
memmove(p_out, p_in, n); |
| 549 |
p_out += n; |
| 550 |
} |
| 551 |
|
| 552 |
switch (*(p+1)) { |
| 553 |
case '\0': case '\r': case '\n': |
| 554 |
/* Soft line break, skip '=' */ |
| 555 |
p_in = p+1; |
| 556 |
if (*p_in == '\r') p_in++; |
| 557 |
if (*p_in == '\n') p_in++; |
| 558 |
ret = 1; |
| 559 |
break; |
| 560 |
|
| 561 |
default: |
| 562 |
/* There is a QP encoded byte */ |
| 563 |
if (qp_char(*(p+1), *(p+2), p_out) == 0) { |
| 564 |
p_in = p+3; |
| 565 |
} |
| 566 |
else { |
| 567 |
/* Invalid QP data - pass through unchanged. */ |
| 568 |
*p_out = '='; |
| 569 |
p_in = p+1; |
| 570 |
} |
| 571 |
p_out++; |
| 572 |
break; |
| 573 |
} |
| 574 |
} |
| 575 |
} |
| 576 |
|
| 577 |
*p_out = '\0'; |
| 578 |
*bufp = p_out; |
| 579 |
return ret; |
| 580 |
} |
| 581 |
|
| 582 |
|
| 583 |
/* This is called once per line in the message body. We need to scan |
| 584 |
* all lines in the message body for the multipart delimiter string, |
| 585 |
* and handle any body-part headers in such messages (these can toggle |
| 586 |
* qp-decoding on and off). |
| 587 |
* |
| 588 |
* Note: Messages that are NOT multipart-messages go through this |
| 589 |
* routine quickly, since BodyState will always be S_BODY_DATA, |
| 590 |
* and MultipartDelimiter is NULL. |
| 591 |
* |
| 592 |
* Return flag set if this line ends with a soft line-break. |
| 593 |
* 'bufp' is modified to point to the end of the output buffer. |
| 594 |
*/ |
| 595 |
|
| 596 |
int UnMimeBodyline(char **bufp, flag delimited, flag softline) |
| 597 |
{ |
| 598 |
char *buf = *bufp; |
| 599 |
int ret = 0; |
| 600 |
|
| 601 |
switch (BodyState) { |
| 602 |
case S_BODY_HDR: |
| 603 |
UnMimeHeader(buf); /* Headers in body-parts can be encoded, too! */ |
| 604 |
if ((*buf == '\0') || (*buf == '\n') || (strcmp(buf, "\r\n") == 0)) { |
| 605 |
BodyState = S_BODY_DATA; |
| 606 |
} |
| 607 |
else if (strncasecmp("Content-Transfer-Encoding:", buf, 26) == 0) { |
| 608 |
char *XferEnc; |
| 609 |
|
| 610 |
XferEnc = nxtaddr(buf); |
| 611 |
if ((XferEnc != NULL) && (strcasecmp(XferEnc, "quoted-printable") == 0)) { |
| 612 |
CurrEncodingIsQP = 1; |
| 613 |
|
| 614 |
/* |
| 615 |
* Hmm ... we cannot be really sure that CurrTypeNeedsDecode |
| 616 |
* has been set - we may not have seen the Content-Type header |
| 617 |
* yet. But *usually* the Content-Type header comes first, so |
| 618 |
* this will work. And there is really no way of doing it |
| 619 |
* "right" as long as we stick with the line-by-line processing. |
| 620 |
*/ |
| 621 |
if (CurrTypeNeedsDecode) |
| 622 |
SetEncoding8bit(buf); |
| 623 |
} |
| 624 |
} |
| 625 |
else if (strncasecmp("Content-Type:", buf, 13) == 0) { |
| 626 |
CurrTypeNeedsDecode = CheckContentType(nxtaddr(buf)); |
| 627 |
} |
| 628 |
|
| 629 |
*bufp = (buf + strlen(buf)); |
| 630 |
break; |
| 631 |
|
| 632 |
case S_BODY_DATA: |
| 633 |
if ((*MultipartDelimiter) && |
| 634 |
(strncmp(buf, MultipartDelimiter, strlen(MultipartDelimiter)) == 0)) { |
| 635 |
BodyState = S_BODY_HDR; |
| 636 |
CurrEncodingIsQP = CurrTypeNeedsDecode = 0; |
| 637 |
} |
| 638 |
|
| 639 |
if (CurrEncodingIsQP && CurrTypeNeedsDecode) |
| 640 |
ret = DoOneQPLine(bufp, delimited, softline); |
| 641 |
else |
| 642 |
*bufp = (buf + strlen(buf)); |
| 643 |
break; |
| 644 |
} |
| 645 |
|
| 646 |
return ret; |
| 647 |
} |
| 648 |
|
| 649 |
|
| 650 |
#ifdef STANDALONE |
| 651 |
#include <stdio.h> |
| 652 |
#include <unistd.h> |
| 653 |
|
| 654 |
const char *program_name = "unmime"; |
| 655 |
int outlevel = 0; |
| 656 |
|
| 657 |
#define BUFSIZE_INCREMENT 4096 |
| 658 |
|
| 659 |
#ifdef DEBUG |
| 660 |
#define DBG_FWRITE(B,L,BS,FD) do { if (fwrite((B), (L), (BS), (FD))) { } } while(0) |
| 661 |
#else |
| 662 |
#define DBG_FWRITE(B,L,BS,FD) |
| 663 |
#endif |
| 664 |
|
| 665 |
int main(int argc, char *argv[]) |
| 666 |
{ |
| 667 |
unsigned int BufSize; |
| 668 |
char *buffer, *buf_p; |
| 669 |
int nl_count, i, bodytype; |
| 670 |
|
| 671 |
/* quench warnings about unused arguments */ |
| 672 |
(void)argc; |
| 673 |
(void)argv; |
| 674 |
|
| 675 |
#ifdef DEBUG |
| 676 |
pid_t pid; |
| 677 |
FILE *fd_orig, *fd_conv; |
| 678 |
char fnam[100]; |
| 679 |
|
| 680 |
/* we don't need snprintf here, but for consistency, we'll use it */ |
| 681 |
pid = getpid(); |
| 682 |
snprintf(fnam, sizeof(fnam), "/tmp/i_unmime.%lx", (long)pid); |
| 683 |
fd_orig = fopen(fnam, "w"); |
| 684 |
snprintf(fnam, sizeof(fnam), "/tmp/o_unmime.%lx", (long)pid); |
| 685 |
fd_conv = fopen(fnam, "w"); |
| 686 |
#endif |
| 687 |
|
| 688 |
BufSize = BUFSIZE_INCREMENT; /* Initial size of buffer */ |
| 689 |
buf_p = buffer = (char *) xmalloc(BufSize); |
| 690 |
nl_count = 0; |
| 691 |
|
| 692 |
do { |
| 693 |
i = fread(buf_p, 1, 1, stdin); |
| 694 |
switch (*buf_p) { |
| 695 |
case '\n': |
| 696 |
nl_count++; |
| 697 |
break; |
| 698 |
|
| 699 |
case '\r': |
| 700 |
break; |
| 701 |
|
| 702 |
default: |
| 703 |
nl_count = 0; |
| 704 |
break; |
| 705 |
} |
| 706 |
|
| 707 |
buf_p++; |
| 708 |
if ((unsigned)(buf_p - buffer) == BufSize) { |
| 709 |
/* Buffer is full! Get more room. */ |
| 710 |
buffer = (char *)xrealloc(buffer, BufSize+BUFSIZE_INCREMENT); |
| 711 |
buf_p = buffer + BufSize; |
| 712 |
BufSize += BUFSIZE_INCREMENT; |
| 713 |
} |
| 714 |
} while ((i > 0) && (nl_count < 2)); |
| 715 |
|
| 716 |
*buf_p = '\0'; |
| 717 |
DBG_FWRITE(buffer, strlen(buffer), 1, fd_orig); |
| 718 |
|
| 719 |
UnMimeHeader(buffer); |
| 720 |
bodytype = MimeBodyType(buffer, 1); |
| 721 |
|
| 722 |
i = strlen(buffer); |
| 723 |
DBG_FWRITE(buffer, i, 1, fd_conv); |
| 724 |
if (fwrite(buffer, i, 1, stdout) < 1) { |
| 725 |
perror("fwrite"); |
| 726 |
goto barf; |
| 727 |
} |
| 728 |
|
| 729 |
do { |
| 730 |
buf_p = (buffer - 1); |
| 731 |
do { |
| 732 |
buf_p++; |
| 733 |
i = fread(buf_p, 1, 1, stdin); |
| 734 |
} while ((i == 1) && (*buf_p != '\n')); |
| 735 |
if (i == 1) buf_p++; |
| 736 |
*buf_p = '\0'; |
| 737 |
DBG_FWRITE(buf, (buf_p - buffer), 1, fd_orig); |
| 738 |
|
| 739 |
if (buf_p > buffer) { |
| 740 |
if (bodytype & MSG_NEEDS_DECODE) { |
| 741 |
buf_p = buffer; |
| 742 |
UnMimeBodyline(&buf_p, 0, 0); |
| 743 |
} |
| 744 |
DBG_FWRITE(buffer, (buf_p - buffer), 1, fd_conv); |
| 745 |
if (fwrite(buffer, (buf_p - buffer), 1, stdout) < 1) { |
| 746 |
perror("fwrite"); |
| 747 |
goto barf; |
| 748 |
} |
| 749 |
} |
| 750 |
} while (buf_p > buffer); |
| 751 |
|
| 752 |
barf: |
| 753 |
free(buffer); |
| 754 |
if (EOF == fflush(stdout)) perror("fflush"); |
| 755 |
|
| 756 |
#ifdef DEBUG |
| 757 |
fclose(fd_orig); |
| 758 |
fclose(fd_conv); |
| 759 |
#endif |
| 760 |
|
| 761 |
return 0; |
| 762 |
} |
| 763 |
#endif |