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