1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=4 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include <string.h>
8 #include "prprf.h"
9 #include "plstr.h"
10 #include "plbase64.h"
11 #include "nsCRT.h"
12 #include "nsMemory.h"
13 #include "nsTArray.h"
14 #include "nsCOMPtr.h"
15 #include "nsEscape.h"
16 #include "nsIUTF8ConverterService.h"
17 #include "nsUConvCID.h"
18 #include "nsIServiceManager.h"
19 #include "nsMIMEHeaderParamImpl.h"
20 #include "nsReadableUtils.h"
21 #include "nsNativeCharsetUtils.h"
22 #include "nsError.h"
23 #include "mozilla/Encoding.h"
24
25 using mozilla::Encoding;
26
27 // static functions declared below are moved from mailnews/mime/src/comi18n.cpp
28
29 static char *DecodeQ(const char *, uint32_t);
30 static bool Is7bitNonAsciiString(const char *, uint32_t);
31 static void CopyRawHeader(const char *, uint32_t, const char *, nsACString &);
32 static nsresult DecodeRFC2047Str(const char *, const char *, bool,
33 nsACString &);
34 static nsresult internalDecodeParameter(const nsACString &, const char *,
35 const char *, bool, bool, nsACString &);
36
37 // XXX The chance of UTF-7 being used in the message header is really
38 // low, but in theory it's possible.
39 #define IS_7BIT_NON_ASCII_CHARSET(cset) \
40 (!nsCRT::strncasecmp((cset), "ISO-2022", 8) || \
41 !nsCRT::strncasecmp((cset), "HZ-GB", 5) || \
42 !nsCRT::strncasecmp((cset), "UTF-7", 5))
43
NS_IMPL_ISUPPORTS(nsMIMEHeaderParamImpl,nsIMIMEHeaderParam)44 NS_IMPL_ISUPPORTS(nsMIMEHeaderParamImpl, nsIMIMEHeaderParam)
45
46 NS_IMETHODIMP
47 nsMIMEHeaderParamImpl::GetParameter(const nsACString &aHeaderVal,
48 const char *aParamName,
49 const nsACString &aFallbackCharset,
50 bool aTryLocaleCharset, char **aLang,
51 nsAString &aResult) {
52 return DoGetParameter(aHeaderVal, aParamName, MIME_FIELD_ENCODING,
53 aFallbackCharset, aTryLocaleCharset, aLang, aResult);
54 }
55
56 NS_IMETHODIMP
GetParameterHTTP(const nsACString & aHeaderVal,const char * aParamName,const nsACString & aFallbackCharset,bool aTryLocaleCharset,char ** aLang,nsAString & aResult)57 nsMIMEHeaderParamImpl::GetParameterHTTP(const nsACString &aHeaderVal,
58 const char *aParamName,
59 const nsACString &aFallbackCharset,
60 bool aTryLocaleCharset, char **aLang,
61 nsAString &aResult) {
62 return DoGetParameter(aHeaderVal, aParamName, HTTP_FIELD_ENCODING,
63 aFallbackCharset, aTryLocaleCharset, aLang, aResult);
64 }
65
66 // XXX : aTryLocaleCharset is not yet effective.
DoGetParameter(const nsACString & aHeaderVal,const char * aParamName,ParamDecoding aDecoding,const nsACString & aFallbackCharset,bool aTryLocaleCharset,char ** aLang,nsAString & aResult)67 nsresult nsMIMEHeaderParamImpl::DoGetParameter(
68 const nsACString &aHeaderVal, const char *aParamName,
69 ParamDecoding aDecoding, const nsACString &aFallbackCharset,
70 bool aTryLocaleCharset, char **aLang, nsAString &aResult) {
71 aResult.Truncate();
72 nsresult rv;
73
74 // get parameter (decode RFC 2231/5987 when applicable, as specified by
75 // aDecoding (5987 being a subset of 2231) and return charset.)
76 nsCString med;
77 nsCString charset;
78 rv = DoParameterInternal(PromiseFlatCString(aHeaderVal).get(), aParamName,
79 aDecoding, getter_Copies(charset), aLang,
80 getter_Copies(med));
81 if (NS_FAILED(rv)) return rv;
82
83 // convert to UTF-8 after charset conversion and RFC 2047 decoding
84 // if necessary.
85
86 nsAutoCString str1;
87 rv = internalDecodeParameter(med, charset.get(), nullptr, false,
88 // was aDecoding == MIME_FIELD_ENCODING
89 // see bug 875615
90 true, str1);
91 NS_ENSURE_SUCCESS(rv, rv);
92
93 if (!aFallbackCharset.IsEmpty()) {
94 const Encoding *encoding = Encoding::ForLabel(aFallbackCharset);
95 nsAutoCString str2;
96 nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(
97 do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
98 if (cvtUTF8 && NS_SUCCEEDED(cvtUTF8->ConvertStringToUTF8(
99 str1, PromiseFlatCString(aFallbackCharset).get(), false,
100 encoding != UTF_8_ENCODING, 1, str2))) {
101 CopyUTF8toUTF16(str2, aResult);
102 return NS_OK;
103 }
104 }
105
106 if (IsUTF8(str1)) {
107 CopyUTF8toUTF16(str1, aResult);
108 return NS_OK;
109 }
110
111 if (aTryLocaleCharset && !NS_IsNativeUTF8())
112 return NS_CopyNativeToUnicode(str1, aResult);
113
114 CopyASCIItoUTF16(str1, aResult);
115 return NS_OK;
116 }
117
118 // remove backslash-encoded sequences from quoted-strings
119 // modifies string in place, potentially shortening it
RemoveQuotedStringEscapes(char * src)120 void RemoveQuotedStringEscapes(char *src) {
121 char *dst = src;
122
123 for (char *c = src; *c; ++c) {
124 if (c[0] == '\\' && c[1]) {
125 // skip backslash if not at end
126 ++c;
127 }
128 *dst++ = *c;
129 }
130 *dst = 0;
131 }
132
133 // true is character is a hex digit
IsHexDigit(char aChar)134 bool IsHexDigit(char aChar) {
135 char c = aChar;
136
137 return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ||
138 (c >= '0' && c <= '9');
139 }
140
141 // validate that a C String containing %-escapes is syntactically valid
IsValidPercentEscaped(const char * aValue,int32_t len)142 bool IsValidPercentEscaped(const char *aValue, int32_t len) {
143 for (int32_t i = 0; i < len; i++) {
144 if (aValue[i] == '%') {
145 if (!IsHexDigit(aValue[i + 1]) || !IsHexDigit(aValue[i + 2])) {
146 return false;
147 }
148 }
149 }
150 return true;
151 }
152
153 // Support for continuations (RFC 2231, Section 3)
154
155 // only a sane number supported
156 #define MAX_CONTINUATIONS 999
157
158 // part of a continuation
159
160 class Continuation {
161 public:
Continuation(const char * aValue,uint32_t aLength,bool aNeedsPercentDecoding,bool aWasQuotedString)162 Continuation(const char *aValue, uint32_t aLength, bool aNeedsPercentDecoding,
163 bool aWasQuotedString) {
164 value = aValue;
165 length = aLength;
166 needsPercentDecoding = aNeedsPercentDecoding;
167 wasQuotedString = aWasQuotedString;
168 }
Continuation()169 Continuation() {
170 // empty constructor needed for nsTArray
171 value = nullptr;
172 length = 0;
173 needsPercentDecoding = false;
174 wasQuotedString = false;
175 }
176 ~Continuation() = default;
177
178 const char *value;
179 uint32_t length;
180 bool needsPercentDecoding;
181 bool wasQuotedString;
182 };
183
184 // combine segments into a single string, returning the allocated string
185 // (or nullptr) while emptying the list
combineContinuations(nsTArray<Continuation> & aArray)186 char *combineContinuations(nsTArray<Continuation> &aArray) {
187 // Sanity check
188 if (aArray.Length() == 0) return nullptr;
189
190 // Get an upper bound for the length
191 uint32_t length = 0;
192 for (uint32_t i = 0; i < aArray.Length(); i++) {
193 length += aArray[i].length;
194 }
195
196 // Allocate
197 char *result = (char *)moz_xmalloc(length + 1);
198
199 // Concatenate
200 if (result) {
201 *result = '\0';
202
203 for (uint32_t i = 0; i < aArray.Length(); i++) {
204 Continuation cont = aArray[i];
205 if (!cont.value) break;
206
207 char *c = result + strlen(result);
208 strncat(result, cont.value, cont.length);
209 if (cont.needsPercentDecoding) {
210 nsUnescape(c);
211 }
212 if (cont.wasQuotedString) {
213 RemoveQuotedStringEscapes(c);
214 }
215 }
216
217 // return null if empty value
218 if (*result == '\0') {
219 free(result);
220 result = nullptr;
221 }
222 } else {
223 // Handle OOM
224 NS_WARNING("Out of memory\n");
225 }
226
227 return result;
228 }
229
230 // add a continuation, return false on error if segment already has been seen
addContinuation(nsTArray<Continuation> & aArray,uint32_t aIndex,const char * aValue,uint32_t aLength,bool aNeedsPercentDecoding,bool aWasQuotedString)231 bool addContinuation(nsTArray<Continuation> &aArray, uint32_t aIndex,
232 const char *aValue, uint32_t aLength,
233 bool aNeedsPercentDecoding, bool aWasQuotedString) {
234 if (aIndex < aArray.Length() && aArray[aIndex].value) {
235 NS_WARNING("duplicate RC2231 continuation segment #\n");
236 return false;
237 }
238
239 if (aIndex > MAX_CONTINUATIONS) {
240 NS_WARNING("RC2231 continuation segment # exceeds limit\n");
241 return false;
242 }
243
244 if (aNeedsPercentDecoding && aWasQuotedString) {
245 NS_WARNING(
246 "RC2231 continuation segment can't use percent encoding and quoted "
247 "string form at the same time\n");
248 return false;
249 }
250
251 Continuation cont(aValue, aLength, aNeedsPercentDecoding, aWasQuotedString);
252
253 if (aArray.Length() <= aIndex) {
254 aArray.SetLength(aIndex + 1);
255 }
256 aArray[aIndex] = cont;
257
258 return true;
259 }
260
261 // parse a segment number; return -1 on error
parseSegmentNumber(const char * aValue,int32_t aLen)262 int32_t parseSegmentNumber(const char *aValue, int32_t aLen) {
263 if (aLen < 1) {
264 NS_WARNING("segment number missing\n");
265 return -1;
266 }
267
268 if (aLen > 1 && aValue[0] == '0') {
269 NS_WARNING("leading '0' not allowed in segment number\n");
270 return -1;
271 }
272
273 int32_t segmentNumber = 0;
274
275 for (int32_t i = 0; i < aLen; i++) {
276 if (!(aValue[i] >= '0' && aValue[i] <= '9')) {
277 NS_WARNING("invalid characters in segment number\n");
278 return -1;
279 }
280
281 segmentNumber *= 10;
282 segmentNumber += aValue[i] - '0';
283 if (segmentNumber > MAX_CONTINUATIONS) {
284 NS_WARNING("Segment number exceeds sane size\n");
285 return -1;
286 }
287 }
288
289 return segmentNumber;
290 }
291
292 // validate a given octet sequence for compliance with the specified
293 // encoding
IsValidOctetSequenceForCharset(nsACString & aCharset,const char * aOctets)294 bool IsValidOctetSequenceForCharset(nsACString &aCharset, const char *aOctets) {
295 nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(
296 do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
297 if (!cvtUTF8) {
298 NS_WARNING("Can't get UTF8ConverterService\n");
299 return false;
300 }
301
302 nsAutoCString tmpRaw;
303 tmpRaw.Assign(aOctets);
304 nsAutoCString tmpDecoded;
305
306 nsresult rv = cvtUTF8->ConvertStringToUTF8(
307 tmpRaw, PromiseFlatCString(aCharset).get(), false, false, 1, tmpDecoded);
308
309 if (rv != NS_OK) {
310 // we can't decode; charset may be unsupported, or the octet sequence
311 // is broken (illegal or incomplete octet sequence contained)
312 NS_WARNING(
313 "RFC2231/5987 parameter value does not decode according to specified "
314 "charset\n");
315 return false;
316 }
317
318 return true;
319 }
320
321 // moved almost verbatim from mimehdrs.cpp
322 // char *
323 // MimeHeaders_get_parameter (const char *header_value, const char *parm_name,
324 // char **charset, char **language)
325 //
326 // The format of these header lines is
327 // <token> [ ';' <token> '=' <token-or-quoted-string> ]*
328 NS_IMETHODIMP
GetParameterInternal(const char * aHeaderValue,const char * aParamName,char ** aCharset,char ** aLang,char ** aResult)329 nsMIMEHeaderParamImpl::GetParameterInternal(const char *aHeaderValue,
330 const char *aParamName,
331 char **aCharset, char **aLang,
332 char **aResult) {
333 return DoParameterInternal(aHeaderValue, aParamName, MIME_FIELD_ENCODING,
334 aCharset, aLang, aResult);
335 }
336
DoParameterInternal(const char * aHeaderValue,const char * aParamName,ParamDecoding aDecoding,char ** aCharset,char ** aLang,char ** aResult)337 nsresult nsMIMEHeaderParamImpl::DoParameterInternal(
338 const char *aHeaderValue, const char *aParamName, ParamDecoding aDecoding,
339 char **aCharset, char **aLang, char **aResult) {
340 if (!aHeaderValue || !*aHeaderValue || !aResult) return NS_ERROR_INVALID_ARG;
341
342 *aResult = nullptr;
343
344 if (aCharset) *aCharset = nullptr;
345 if (aLang) *aLang = nullptr;
346
347 nsAutoCString charset;
348
349 // change to (aDecoding != HTTP_FIELD_ENCODING) when we want to disable
350 // them for HTTP header fields later on, see bug 776324
351 bool acceptContinuations = true;
352
353 const char *str = aHeaderValue;
354
355 // skip leading white space.
356 for (; *str && nsCRT::IsAsciiSpace(*str); ++str)
357 ;
358 const char *start = str;
359
360 // aParamName is empty. return the first (possibly) _unnamed_ 'parameter'
361 // For instance, return 'inline' in the following case:
362 // Content-Disposition: inline; filename=.....
363 if (!aParamName || !*aParamName) {
364 for (; *str && *str != ';' && !nsCRT::IsAsciiSpace(*str); ++str)
365 ;
366 if (str == start) return NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY;
367
368 *aResult = (char *)nsMemory::Clone(start, (str - start) + 1);
369 NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
370 (*aResult)[str - start] = '\0'; // null-terminate
371 return NS_OK;
372 }
373
374 /* Skip forward to first ';' */
375 for (; *str && *str != ';' && *str != ','; ++str)
376 ;
377 if (*str) str++;
378 /* Skip over following whitespace */
379 for (; *str && nsCRT::IsAsciiSpace(*str); ++str)
380 ;
381
382 // Some broken http servers just specify parameters
383 // like 'filename' without specifying disposition
384 // method. Rewind to the first non-white-space
385 // character.
386
387 if (!*str) str = start;
388
389 // RFC2231 - The legitimate parm format can be:
390 // A. title=ThisIsTitle
391 // B. title*=us-ascii'en-us'This%20is%20wierd.
392 // C. title*0*=us-ascii'en'This%20is%20wierd.%20We
393 // title*1*=have%20to%20support%20this.
394 // title*2="Else..."
395 // D. title*0="Hey, what you think you are doing?"
396 // title*1="There is no charset and lang info."
397 // RFC5987: only A and B
398
399 // collect results for the different algorithms (plain filename,
400 // RFC5987/2231-encoded filename, + continuations) separately and decide
401 // which to use at the end
402 char *caseAResult = nullptr;
403 char *caseBResult = nullptr;
404 char *caseCDResult = nullptr;
405
406 // collect continuation segments
407 nsTArray<Continuation> segments;
408
409 // our copies of the charset parameter, kept separately as they might
410 // differ for the two formats
411 nsDependentCSubstring charsetB, charsetCD;
412
413 nsDependentCSubstring lang;
414
415 int32_t paramLen = strlen(aParamName);
416
417 while (*str) {
418 // find name/value
419
420 const char *nameStart = str;
421 const char *nameEnd = nullptr;
422 const char *valueStart = str;
423 const char *valueEnd = nullptr;
424 bool isQuotedString = false;
425
426 NS_ASSERTION(!nsCRT::IsAsciiSpace(*str), "should be after whitespace.");
427
428 // Skip forward to the end of this token.
429 for (; *str && !nsCRT::IsAsciiSpace(*str) && *str != '=' && *str != ';';
430 str++)
431 ;
432 nameEnd = str;
433
434 int32_t nameLen = nameEnd - nameStart;
435
436 // Skip over whitespace, '=', and whitespace
437 while (nsCRT::IsAsciiSpace(*str)) ++str;
438 if (!*str) {
439 break;
440 }
441 if (*str != '=') {
442 // don't accept parameters without "="
443 goto increment_str;
444 }
445 // Skip over '=' only if it was actually there
446 str++;
447 while (nsCRT::IsAsciiSpace(*str)) ++str;
448
449 if (*str != '"') {
450 // The value is a token, not a quoted string.
451 valueStart = str;
452 for (valueEnd = str;
453 *valueEnd && !nsCRT::IsAsciiSpace(*valueEnd) && *valueEnd != ';';
454 valueEnd++)
455 ;
456 str = valueEnd;
457 } else {
458 isQuotedString = true;
459
460 ++str;
461 valueStart = str;
462 for (valueEnd = str; *valueEnd; ++valueEnd) {
463 if (*valueEnd == '\\' && *(valueEnd + 1))
464 ++valueEnd;
465 else if (*valueEnd == '"')
466 break;
467 }
468 str = valueEnd;
469 // *valueEnd != null means that *valueEnd is quote character.
470 if (*valueEnd) str++;
471 }
472
473 // See if this is the simplest case (case A above),
474 // a 'single' line value with no charset and lang.
475 // If so, copy it and return.
476 if (nameLen == paramLen &&
477 !nsCRT::strncasecmp(nameStart, aParamName, paramLen)) {
478 if (caseAResult) {
479 // we already have one caseA result, ignore subsequent ones
480 goto increment_str;
481 }
482
483 // if the parameter spans across multiple lines we have to strip out the
484 // line continuation -- jht 4/29/98
485 nsAutoCString tempStr(valueStart, valueEnd - valueStart);
486 tempStr.StripCRLF();
487 char *res = ToNewCString(tempStr);
488 NS_ENSURE_TRUE(res, NS_ERROR_OUT_OF_MEMORY);
489
490 if (isQuotedString) RemoveQuotedStringEscapes(res);
491
492 caseAResult = res;
493 // keep going, we may find a RFC 2231/5987 encoded alternative
494 }
495 // case B, C, and D
496 else if (nameLen > paramLen &&
497 !nsCRT::strncasecmp(nameStart, aParamName, paramLen) &&
498 *(nameStart + paramLen) == '*') {
499 // 1st char past '*'
500 const char *cp = nameStart + paramLen + 1;
501
502 // if param name ends in "*" we need do to RFC5987 "ext-value" decoding
503 bool needExtDecoding = *(nameEnd - 1) == '*';
504
505 bool caseB = nameLen == paramLen + 1;
506 bool caseCStart = (*cp == '0') && needExtDecoding;
507
508 // parse the segment number
509 int32_t segmentNumber = -1;
510 if (!caseB) {
511 int32_t segLen = (nameEnd - cp) - (needExtDecoding ? 1 : 0);
512 segmentNumber = parseSegmentNumber(cp, segLen);
513
514 if (segmentNumber == -1) {
515 acceptContinuations = false;
516 goto increment_str;
517 }
518 }
519
520 // CaseB and start of CaseC: requires charset and optional language
521 // in quotes (quotes required even if lang is blank)
522 if (caseB || (caseCStart && acceptContinuations)) {
523 // look for single quotation mark(')
524 const char *sQuote1 = PL_strchr(valueStart, 0x27);
525 const char *sQuote2 = sQuote1 ? PL_strchr(sQuote1 + 1, 0x27) : nullptr;
526
527 // Two single quotation marks must be present even in
528 // absence of charset and lang.
529 if (!sQuote1 || !sQuote2) {
530 NS_WARNING(
531 "Mandatory two single quotes are missing in header parameter\n");
532 }
533
534 const char *charsetStart = nullptr;
535 int32_t charsetLength = 0;
536 const char *langStart = nullptr;
537 int32_t langLength = 0;
538 const char *rawValStart = nullptr;
539 int32_t rawValLength = 0;
540
541 if (sQuote2 && sQuote1) {
542 // both delimiters present: charSet'lang'rawVal
543 rawValStart = sQuote2 + 1;
544 rawValLength = valueEnd - rawValStart;
545
546 langStart = sQuote1 + 1;
547 langLength = sQuote2 - langStart;
548
549 charsetStart = valueStart;
550 charsetLength = sQuote1 - charsetStart;
551 } else if (sQuote1) {
552 // one delimiter; assume charset'rawVal
553 rawValStart = sQuote1 + 1;
554 rawValLength = valueEnd - rawValStart;
555
556 charsetStart = valueStart;
557 charsetLength = sQuote1 - valueStart;
558 } else {
559 // no delimiter: just rawVal
560 rawValStart = valueStart;
561 rawValLength = valueEnd - valueStart;
562 }
563
564 if (langLength != 0) {
565 lang.Assign(langStart, langLength);
566 }
567
568 // keep the charset for later
569 if (caseB) {
570 charsetB.Assign(charsetStart, charsetLength);
571 } else {
572 // if caseCorD
573 charsetCD.Assign(charsetStart, charsetLength);
574 }
575
576 // non-empty value part
577 if (rawValLength > 0) {
578 if (!caseBResult && caseB) {
579 if (!IsValidPercentEscaped(rawValStart, rawValLength)) {
580 goto increment_str;
581 }
582
583 // allocate buffer for the raw value
584 char *tmpResult =
585 (char *)nsMemory::Clone(rawValStart, rawValLength + 1);
586 if (!tmpResult) {
587 goto increment_str;
588 }
589 *(tmpResult + rawValLength) = 0;
590
591 nsUnescape(tmpResult);
592 caseBResult = tmpResult;
593 } else {
594 // caseC
595 bool added = addContinuation(segments, 0, rawValStart, rawValLength,
596 needExtDecoding, isQuotedString);
597
598 if (!added) {
599 // continuation not added, stop processing them
600 acceptContinuations = false;
601 }
602 }
603 }
604 } // end of if-block : title*0*= or title*=
605 // caseD: a line of multiline param with no need for unescaping :
606 // title*[0-9]= or 2nd or later lines of a caseC param : title*[1-9]*=
607 else if (acceptContinuations && segmentNumber != -1) {
608 uint32_t valueLength = valueEnd - valueStart;
609
610 bool added =
611 addContinuation(segments, segmentNumber, valueStart, valueLength,
612 needExtDecoding, isQuotedString);
613
614 if (!added) {
615 // continuation not added, stop processing them
616 acceptContinuations = false;
617 }
618 } // end of if-block : title*[0-9]= or title*[1-9]*=
619 }
620
621 // str now points after the end of the value.
622 // skip over whitespace, ';', whitespace.
623 increment_str:
624 while (nsCRT::IsAsciiSpace(*str)) ++str;
625 if (*str == ';') {
626 ++str;
627 } else {
628 // stop processing the header field; either we are done or the
629 // separator was missing
630 break;
631 }
632 while (nsCRT::IsAsciiSpace(*str)) ++str;
633 }
634
635 caseCDResult = combineContinuations(segments);
636
637 if (caseBResult && !charsetB.IsEmpty()) {
638 // check that the 2231/5987 result decodes properly given the
639 // specified character set
640 if (!IsValidOctetSequenceForCharset(charsetB, caseBResult))
641 caseBResult = nullptr;
642 }
643
644 if (caseCDResult && !charsetCD.IsEmpty()) {
645 // check that the 2231/5987 result decodes properly given the
646 // specified character set
647 if (!IsValidOctetSequenceForCharset(charsetCD, caseCDResult))
648 caseCDResult = nullptr;
649 }
650
651 if (caseBResult) {
652 // prefer simple 5987 format over 2231 with continuations
653 *aResult = caseBResult;
654 caseBResult = nullptr;
655 charset.Assign(charsetB);
656 } else if (caseCDResult) {
657 // prefer 2231/5987 with or without continuations over plain format
658 *aResult = caseCDResult;
659 caseCDResult = nullptr;
660 charset.Assign(charsetCD);
661 } else if (caseAResult) {
662 *aResult = caseAResult;
663 caseAResult = nullptr;
664 }
665
666 // free unused stuff
667 free(caseAResult);
668 free(caseBResult);
669 free(caseCDResult);
670
671 // if we have a result
672 if (*aResult) {
673 // then return charset and lang as well
674 if (aLang && !lang.IsEmpty()) {
675 uint32_t len = lang.Length();
676 *aLang = (char *)nsMemory::Clone(lang.BeginReading(), len + 1);
677 if (*aLang) {
678 *(*aLang + len) = 0;
679 }
680 }
681 if (aCharset && !charset.IsEmpty()) {
682 uint32_t len = charset.Length();
683 *aCharset = (char *)nsMemory::Clone(charset.BeginReading(), len + 1);
684 if (*aCharset) {
685 *(*aCharset + len) = 0;
686 }
687 }
688 }
689
690 return *aResult ? NS_OK : NS_ERROR_INVALID_ARG;
691 }
692
internalDecodeRFC2047Header(const char * aHeaderVal,const char * aDefaultCharset,bool aOverrideCharset,bool aEatContinuations,nsACString & aResult)693 nsresult internalDecodeRFC2047Header(const char *aHeaderVal,
694 const char *aDefaultCharset,
695 bool aOverrideCharset,
696 bool aEatContinuations,
697 nsACString &aResult) {
698 aResult.Truncate();
699 if (!aHeaderVal) return NS_ERROR_INVALID_ARG;
700 if (!*aHeaderVal) return NS_OK;
701
702 // If aHeaderVal is RFC 2047 encoded or is not a UTF-8 string but
703 // aDefaultCharset is specified, decodes RFC 2047 encoding and converts
704 // to UTF-8. Otherwise, just strips away CRLF.
705 if (PL_strstr(aHeaderVal, "=?") ||
706 (aDefaultCharset &&
707 (!IsUTF8(nsDependentCString(aHeaderVal)) ||
708 Is7bitNonAsciiString(aHeaderVal, strlen(aHeaderVal))))) {
709 DecodeRFC2047Str(aHeaderVal, aDefaultCharset, aOverrideCharset, aResult);
710 } else if (aEatContinuations &&
711 (PL_strchr(aHeaderVal, '\n') || PL_strchr(aHeaderVal, '\r'))) {
712 aResult = aHeaderVal;
713 } else {
714 aEatContinuations = false;
715 aResult = aHeaderVal;
716 }
717
718 if (aEatContinuations) {
719 nsAutoCString temp(aResult);
720 temp.ReplaceSubstring("\n\t", " ");
721 temp.ReplaceSubstring("\r\t", " ");
722 temp.StripCRLF();
723 aResult = temp;
724 }
725
726 return NS_OK;
727 }
728
729 NS_IMETHODIMP
DecodeRFC2047Header(const char * aHeaderVal,const char * aDefaultCharset,bool aOverrideCharset,bool aEatContinuations,nsACString & aResult)730 nsMIMEHeaderParamImpl::DecodeRFC2047Header(const char *aHeaderVal,
731 const char *aDefaultCharset,
732 bool aOverrideCharset,
733 bool aEatContinuations,
734 nsACString &aResult) {
735 return internalDecodeRFC2047Header(aHeaderVal, aDefaultCharset,
736 aOverrideCharset, aEatContinuations,
737 aResult);
738 }
739
740 // true if the character is allowed in a RFC 5987 value
741 // see RFC 5987, Section 3.2.1, "attr-char"
IsRFC5987AttrChar(char aChar)742 bool IsRFC5987AttrChar(char aChar) {
743 char c = aChar;
744
745 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
746 (c >= '0' && c <= '9') ||
747 (c == '!' || c == '#' || c == '$' || c == '&' || c == '+' ||
748 c == '-' || c == '.' || c == '^' || c == '_' || c == '`' ||
749 c == '|' || c == '~');
750 }
751
752 // percent-decode a value
753 // returns false on failure
PercentDecode(nsACString & aValue)754 bool PercentDecode(nsACString &aValue) {
755 char *c = (char *)moz_xmalloc(aValue.Length() + 1);
756 if (!c) {
757 return false;
758 }
759
760 strcpy(c, PromiseFlatCString(aValue).get());
761 nsUnescape(c);
762 aValue.Assign(c);
763 free(c);
764
765 return true;
766 }
767
768 // Decode a parameter value using the encoding defined in RFC 5987
769 //
770 // charset "'" [ language ] "'" value-chars
771 NS_IMETHODIMP
DecodeRFC5987Param(const nsACString & aParamVal,nsACString & aLang,nsAString & aResult)772 nsMIMEHeaderParamImpl::DecodeRFC5987Param(const nsACString &aParamVal,
773 nsACString &aLang,
774 nsAString &aResult) {
775 nsAutoCString charset;
776 nsAutoCString language;
777 nsAutoCString value;
778
779 uint32_t delimiters = 0;
780 const nsCString &encoded = PromiseFlatCString(aParamVal);
781 const char *c = encoded.get();
782
783 while (*c) {
784 char tc = *c++;
785
786 if (tc == '\'') {
787 // single quote
788 delimiters++;
789 } else if (((unsigned char)tc) >= 128) {
790 // fail early, not ASCII
791 NS_WARNING("non-US-ASCII character in RFC5987-encoded param");
792 return NS_ERROR_INVALID_ARG;
793 } else {
794 if (delimiters == 0) {
795 // valid characters are checked later implicitly
796 charset.Append(tc);
797 } else if (delimiters == 1) {
798 // no value checking for now
799 language.Append(tc);
800 } else if (delimiters == 2) {
801 if (IsRFC5987AttrChar(tc)) {
802 value.Append(tc);
803 } else if (tc == '%') {
804 if (!IsHexDigit(c[0]) || !IsHexDigit(c[1])) {
805 // we expect two more characters
806 NS_WARNING("broken %-escape in RFC5987-encoded param");
807 return NS_ERROR_INVALID_ARG;
808 }
809 value.Append(tc);
810 // we consume two more
811 value.Append(*c++);
812 value.Append(*c++);
813 } else {
814 // character not allowed here
815 NS_WARNING("invalid character in RFC5987-encoded param");
816 return NS_ERROR_INVALID_ARG;
817 }
818 }
819 }
820 }
821
822 if (delimiters != 2) {
823 NS_WARNING("missing delimiters in RFC5987-encoded param");
824 return NS_ERROR_INVALID_ARG;
825 }
826
827 // abort early for unsupported encodings
828 if (!charset.LowerCaseEqualsLiteral("utf-8")) {
829 NS_WARNING("unsupported charset in RFC5987-encoded param");
830 return NS_ERROR_INVALID_ARG;
831 }
832
833 // percent-decode
834 if (!PercentDecode(value)) {
835 return NS_ERROR_OUT_OF_MEMORY;
836 }
837
838 // return the encoding
839 aLang.Assign(language);
840
841 // finally convert octet sequence to UTF-8 and be done
842 nsresult rv = NS_OK;
843 nsCOMPtr<nsIUTF8ConverterService> cvtUTF8 =
844 do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID, &rv);
845 NS_ENSURE_SUCCESS(rv, rv);
846
847 nsAutoCString utf8;
848 rv = cvtUTF8->ConvertStringToUTF8(value, charset.get(), true, false, 1, utf8);
849 NS_ENSURE_SUCCESS(rv, rv);
850
851 CopyUTF8toUTF16(utf8, aResult);
852 return NS_OK;
853 }
854
internalDecodeParameter(const nsACString & aParamValue,const char * aCharset,const char * aDefaultCharset,bool aOverrideCharset,bool aDecode2047,nsACString & aResult)855 nsresult internalDecodeParameter(const nsACString &aParamValue,
856 const char *aCharset,
857 const char *aDefaultCharset,
858 bool aOverrideCharset, bool aDecode2047,
859 nsACString &aResult) {
860 aResult.Truncate();
861 // If aCharset is given, aParamValue was obtained from RFC2231/5987
862 // encoding and we're pretty sure that it's in aCharset.
863 if (aCharset && *aCharset) {
864 nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(
865 do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
866 if (cvtUTF8)
867 return cvtUTF8->ConvertStringToUTF8(aParamValue, aCharset, true, true, 1,
868 aResult);
869 }
870
871 const nsCString ¶m = PromiseFlatCString(aParamValue);
872 nsAutoCString unQuoted;
873 nsACString::const_iterator s, e;
874 param.BeginReading(s);
875 param.EndReading(e);
876
877 // strip '\' when used to quote CR, LF, '"' and '\'
878 for (; s != e; ++s) {
879 if ((*s == '\\')) {
880 if (++s == e) {
881 --s; // '\' is at the end. move back and append '\'.
882 } else if (*s != nsCRT::CR && *s != nsCRT::LF && *s != '"' &&
883 *s != '\\') {
884 --s; // '\' is not foll. by CR,LF,'"','\'. move back and append '\'
885 }
886 // else : skip '\' and append the quoted character.
887 }
888 unQuoted.Append(*s);
889 }
890
891 aResult = unQuoted;
892 nsresult rv = NS_OK;
893
894 if (aDecode2047) {
895 nsAutoCString decoded;
896
897 // Try RFC 2047 encoding, instead.
898 rv = internalDecodeRFC2047Header(unQuoted.get(), aDefaultCharset,
899 aOverrideCharset, true, decoded);
900
901 if (NS_SUCCEEDED(rv) && !decoded.IsEmpty()) aResult = decoded;
902 }
903
904 return rv;
905 }
906
907 NS_IMETHODIMP
DecodeParameter(const nsACString & aParamValue,const char * aCharset,const char * aDefaultCharset,bool aOverrideCharset,nsACString & aResult)908 nsMIMEHeaderParamImpl::DecodeParameter(const nsACString &aParamValue,
909 const char *aCharset,
910 const char *aDefaultCharset,
911 bool aOverrideCharset,
912 nsACString &aResult) {
913 return internalDecodeParameter(aParamValue, aCharset, aDefaultCharset,
914 aOverrideCharset, true, aResult);
915 }
916
917 #define ISHEXCHAR(c) \
918 ((0x30 <= uint8_t(c) && uint8_t(c) <= 0x39) || \
919 (0x41 <= uint8_t(c) && uint8_t(c) <= 0x46) || \
920 (0x61 <= uint8_t(c) && uint8_t(c) <= 0x66))
921
922 // Decode Q encoding (RFC 2047).
923 // static
DecodeQ(const char * in,uint32_t length)924 char *DecodeQ(const char *in, uint32_t length) {
925 char *out, *dest = nullptr;
926
927 out = dest = (char *)calloc(length + 1, sizeof(char));
928 if (dest == nullptr) return nullptr;
929 while (length > 0) {
930 unsigned c = 0;
931 switch (*in) {
932 case '=':
933 // check if |in| in the form of '=hh' where h is [0-9a-fA-F].
934 if (length < 3 || !ISHEXCHAR(in[1]) || !ISHEXCHAR(in[2]))
935 goto badsyntax;
936 PR_sscanf(in + 1, "%2X", &c);
937 *out++ = (char)c;
938 in += 3;
939 length -= 3;
940 break;
941
942 case '_':
943 *out++ = ' ';
944 in++;
945 length--;
946 break;
947
948 default:
949 if (*in & 0x80) goto badsyntax;
950 *out++ = *in++;
951 length--;
952 }
953 }
954 *out++ = '\0';
955
956 for (out = dest; *out; ++out) {
957 if (*out == '\t') *out = ' ';
958 }
959
960 return dest;
961
962 badsyntax:
963 free(dest);
964 return nullptr;
965 }
966
967 // check if input is HZ (a 7bit encoding for simplified Chinese : RFC 1842))
968 // or has ESC which may be an indication that it's in one of many ISO
969 // 2022 7bit encodings (e.g. ISO-2022-JP(-2)/CN : see RFC 1468, 1922, 1554).
970 // static
Is7bitNonAsciiString(const char * input,uint32_t len)971 bool Is7bitNonAsciiString(const char *input, uint32_t len) {
972 int32_t c;
973
974 enum {
975 hz_initial, // No HZ seen yet
976 hz_escaped, // Inside an HZ ~{ escape sequence
977 hz_seen, // Have seen at least one complete HZ sequence
978 hz_notpresent // Have seen something that is not legal HZ
979 } hz_state;
980
981 hz_state = hz_initial;
982 while (len) {
983 c = uint8_t(*input++);
984 len--;
985 if (c & 0x80) return false;
986 if (c == 0x1B) return true;
987 if (c == '~') {
988 switch (hz_state) {
989 case hz_initial:
990 case hz_seen:
991 if (*input == '{') {
992 hz_state = hz_escaped;
993 } else if (*input == '~') {
994 // ~~ is the HZ encoding of ~. Skip over second ~ as well
995 hz_state = hz_seen;
996 input++;
997 len--;
998 } else {
999 hz_state = hz_notpresent;
1000 }
1001 break;
1002
1003 case hz_escaped:
1004 if (*input == '}') hz_state = hz_seen;
1005 break;
1006 default:
1007 break;
1008 }
1009 }
1010 }
1011 return hz_state == hz_seen;
1012 }
1013
1014 #define REPLACEMENT_CHAR "\357\277\275" // EF BF BD (UTF-8 encoding of U+FFFD)
1015
1016 // copy 'raw' sequences of octets in aInput to aOutput.
1017 // If aDefaultCharset is specified, the input is assumed to be in the
1018 // charset and converted to UTF-8. Otherwise, a blind copy is made.
1019 // If aDefaultCharset is specified, but the conversion to UTF-8
1020 // is not successful, each octet is replaced by Unicode replacement
1021 // chars. *aOutput is advanced by the number of output octets.
1022 // static
CopyRawHeader(const char * aInput,uint32_t aLen,const char * aDefaultCharset,nsACString & aOutput)1023 void CopyRawHeader(const char *aInput, uint32_t aLen,
1024 const char *aDefaultCharset, nsACString &aOutput) {
1025 int32_t c;
1026
1027 // If aDefaultCharset is not specified, make a blind copy.
1028 if (!aDefaultCharset || !*aDefaultCharset) {
1029 aOutput.Append(aInput, aLen);
1030 return;
1031 }
1032
1033 // Copy as long as it's US-ASCII. An ESC may indicate ISO 2022
1034 // A ~ may indicate it is HZ
1035 while (aLen && (c = uint8_t(*aInput++)) != 0x1B && c != '~' && !(c & 0x80)) {
1036 aOutput.Append(char(c));
1037 aLen--;
1038 }
1039 if (!aLen) {
1040 return;
1041 }
1042 aInput--;
1043
1044 // skip ASCIIness/UTF8ness test if aInput is supected to be a 7bit non-ascii
1045 // string and aDefaultCharset is a 7bit non-ascii charset.
1046 bool skipCheck =
1047 (c == 0x1B || c == '~') && IS_7BIT_NON_ASCII_CHARSET(aDefaultCharset);
1048
1049 // If not UTF-8, treat as default charset
1050 nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(
1051 do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
1052 nsAutoCString utf8Text;
1053 if (cvtUTF8 && NS_SUCCEEDED(cvtUTF8->ConvertStringToUTF8(
1054 Substring(aInput, aInput + aLen), aDefaultCharset,
1055 skipCheck, true, 1, utf8Text))) {
1056 aOutput.Append(utf8Text);
1057 } else { // replace each octet with Unicode replacement char in UTF-8.
1058 for (uint32_t i = 0; i < aLen; i++) {
1059 c = uint8_t(*aInput++);
1060 if (c & 0x80)
1061 aOutput.Append(REPLACEMENT_CHAR);
1062 else
1063 aOutput.Append(char(c));
1064 }
1065 }
1066 }
1067
DecodeQOrBase64Str(const char * aEncoded,size_t aLen,char aQOrBase64,const char * aCharset,nsACString & aResult)1068 nsresult DecodeQOrBase64Str(const char *aEncoded, size_t aLen, char aQOrBase64,
1069 const char *aCharset, nsACString &aResult) {
1070 char *decodedText;
1071 NS_ASSERTION(aQOrBase64 == 'Q' || aQOrBase64 == 'B', "Should be 'Q' or 'B'");
1072 if (aQOrBase64 == 'Q')
1073 decodedText = DecodeQ(aEncoded, aLen);
1074 else if (aQOrBase64 == 'B') {
1075 decodedText = PL_Base64Decode(aEncoded, aLen, nullptr);
1076 } else {
1077 return NS_ERROR_INVALID_ARG;
1078 }
1079
1080 if (!decodedText) {
1081 return NS_ERROR_INVALID_ARG;
1082 }
1083
1084 nsresult rv;
1085 nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(
1086 do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID, &rv));
1087 nsAutoCString utf8Text;
1088 if (NS_SUCCEEDED(rv)) {
1089 // skip ASCIIness/UTF8ness test if aCharset is 7bit non-ascii charset.
1090 rv = cvtUTF8->ConvertStringToUTF8(nsDependentCString(decodedText), aCharset,
1091 IS_7BIT_NON_ASCII_CHARSET(aCharset), true,
1092 1, utf8Text);
1093 }
1094 free(decodedText);
1095 if (NS_FAILED(rv)) {
1096 return rv;
1097 }
1098 aResult.Append(utf8Text);
1099
1100 return NS_OK;
1101 }
1102
1103 static const char especials[] = R"(()<>@,;:\"/[]?.=)";
1104
1105 // |decode_mime_part2_str| taken from comi18n.c
1106 // Decode RFC2047-encoded words in the input and convert the result to UTF-8.
1107 // If aOverrideCharset is true, charset in RFC2047-encoded words is
1108 // ignored and aDefaultCharset is assumed, instead. aDefaultCharset
1109 // is also used to convert raw octets (without RFC 2047 encoding) to UTF-8.
1110 // static
DecodeRFC2047Str(const char * aHeader,const char * aDefaultCharset,bool aOverrideCharset,nsACString & aResult)1111 nsresult DecodeRFC2047Str(const char *aHeader, const char *aDefaultCharset,
1112 bool aOverrideCharset, nsACString &aResult) {
1113 const char *p, *q = nullptr, *r;
1114 const char *begin; // tracking pointer for where we are in the input buffer
1115 int32_t isLastEncodedWord = 0;
1116 const char *charsetStart, *charsetEnd;
1117 nsAutoCString prevCharset, curCharset;
1118 nsAutoCString encodedText;
1119 char prevEncoding = '\0', curEncoding;
1120 nsresult rv;
1121
1122 begin = aHeader;
1123
1124 // To avoid buffer realloc, if possible, set capacity in advance. No
1125 // matter what, more than 3x expansion can never happen for all charsets
1126 // supported by Mozilla. SCSU/BCSU with the sliding window set to a
1127 // non-BMP block may be exceptions, but Mozilla does not support them.
1128 // Neither any known mail/news program use them. Even if there's, we're
1129 // safe because we don't use a raw *char any more.
1130 aResult.SetCapacity(3 * strlen(aHeader));
1131
1132 while ((p = PL_strstr(begin, "=?")) != nullptr) {
1133 if (isLastEncodedWord) {
1134 // See if it's all whitespace.
1135 for (q = begin; q < p; ++q) {
1136 if (!PL_strchr(" \t\r\n", *q)) break;
1137 }
1138 }
1139
1140 if (!isLastEncodedWord || q < p) {
1141 if (!encodedText.IsEmpty()) {
1142 rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
1143 prevEncoding, prevCharset.get(), aResult);
1144 if (NS_FAILED(rv)) {
1145 aResult.Append(encodedText);
1146 }
1147 encodedText.Truncate();
1148 prevCharset.Truncate();
1149 prevEncoding = '\0';
1150 }
1151 // copy the part before the encoded-word
1152 CopyRawHeader(begin, p - begin, aDefaultCharset, aResult);
1153 begin = p;
1154 }
1155
1156 p += 2;
1157
1158 // Get charset info
1159 charsetStart = p;
1160 charsetEnd = nullptr;
1161 for (q = p; *q != '?'; q++) {
1162 if (*q <= ' ' || PL_strchr(especials, *q)) {
1163 goto badsyntax;
1164 }
1165
1166 // RFC 2231 section 5
1167 if (!charsetEnd && *q == '*') {
1168 charsetEnd = q;
1169 }
1170 }
1171 if (!charsetEnd) {
1172 charsetEnd = q;
1173 }
1174
1175 q++;
1176 curEncoding = nsCRT::ToUpper(*q);
1177 if (curEncoding != 'Q' && curEncoding != 'B') goto badsyntax;
1178
1179 if (q[1] != '?') goto badsyntax;
1180
1181 // loop-wise, keep going until we hit "?=". the inner check handles the
1182 // nul terminator should the string terminate before we hit the right
1183 // marker. (And the r[1] will never reach beyond the end of the string
1184 // because *r != '?' is true if r is the nul character.)
1185 for (r = q + 2; *r != '?' || r[1] != '='; r++) {
1186 if (*r < ' ') goto badsyntax;
1187 }
1188 if (r == q + 2) {
1189 // it's empty, skip
1190 begin = r + 2;
1191 isLastEncodedWord = 1;
1192 continue;
1193 }
1194
1195 curCharset.Assign(charsetStart, charsetEnd - charsetStart);
1196 // Override charset if requested. Never override labeled UTF-8.
1197 // Use default charset instead of UNKNOWN-8BIT
1198 if ((aOverrideCharset &&
1199 0 != nsCRT::strcasecmp(curCharset.get(), "UTF-8")) ||
1200 (aDefaultCharset &&
1201 0 == nsCRT::strcasecmp(curCharset.get(), "UNKNOWN-8BIT"))) {
1202 curCharset = aDefaultCharset;
1203 }
1204
1205 const char *R;
1206 R = r;
1207 if (curEncoding == 'B') {
1208 // bug 227290. ignore an extraneous '=' at the end.
1209 // (# of characters in B-encoded part has to be a multiple of 4)
1210 int32_t n = r - (q + 2);
1211 R -= (n % 4 == 1 && !PL_strncmp(r - 3, "===", 3)) ? 1 : 0;
1212 }
1213 // Bug 493544. Don't decode the encoded text until it ends
1214 if (R[-1] != '=' &&
1215 (prevCharset.IsEmpty() ||
1216 (curCharset == prevCharset && curEncoding == prevEncoding))) {
1217 encodedText.Append(q + 2, R - (q + 2));
1218 prevCharset = curCharset;
1219 prevEncoding = curEncoding;
1220
1221 begin = r + 2;
1222 isLastEncodedWord = 1;
1223 continue;
1224 }
1225
1226 bool bDecoded; // If the current line has been decoded.
1227 bDecoded = false;
1228 if (!encodedText.IsEmpty()) {
1229 if (curCharset == prevCharset && curEncoding == prevEncoding) {
1230 encodedText.Append(q + 2, R - (q + 2));
1231 bDecoded = true;
1232 }
1233 rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
1234 prevEncoding, prevCharset.get(), aResult);
1235 if (NS_FAILED(rv)) {
1236 aResult.Append(encodedText);
1237 }
1238 encodedText.Truncate();
1239 prevCharset.Truncate();
1240 prevEncoding = '\0';
1241 }
1242 if (!bDecoded) {
1243 rv = DecodeQOrBase64Str(q + 2, R - (q + 2), curEncoding, curCharset.get(),
1244 aResult);
1245 if (NS_FAILED(rv)) {
1246 aResult.Append(encodedText);
1247 }
1248 }
1249
1250 begin = r + 2;
1251 isLastEncodedWord = 1;
1252 continue;
1253
1254 badsyntax:
1255 if (!encodedText.IsEmpty()) {
1256 rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
1257 prevEncoding, prevCharset.get(), aResult);
1258 if (NS_FAILED(rv)) {
1259 aResult.Append(encodedText);
1260 }
1261 encodedText.Truncate();
1262 prevCharset.Truncate();
1263 }
1264 // copy the part before the encoded-word
1265 aResult.Append(begin, p - begin);
1266 begin = p;
1267 isLastEncodedWord = 0;
1268 }
1269
1270 if (!encodedText.IsEmpty()) {
1271 rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
1272 prevEncoding, prevCharset.get(), aResult);
1273 if (NS_FAILED(rv)) {
1274 aResult.Append(encodedText);
1275 }
1276 }
1277
1278 // put the tail back
1279 CopyRawHeader(begin, strlen(begin), aDefaultCharset, aResult);
1280
1281 nsAutoCString tempStr(aResult);
1282 tempStr.ReplaceChar('\t', ' ');
1283 aResult = tempStr;
1284
1285 return NS_OK;
1286 }
1287