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