nsURLHelper.cpp (42749B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=4 sw=2 sts=2 et cindent: */ 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 "nsURLHelper.h" 8 9 #include "mozilla/AppShutdown.h" 10 #include "mozilla/CompactPair.h" 11 #include "mozilla/Encoding.h" 12 #include "mozilla/Mutex.h" 13 #include "mozilla/TextUtils.h" 14 15 #include <algorithm> 16 #include <iterator> 17 18 #include "nsASCIIMask.h" 19 #include "nsIFile.h" 20 #include "nsIURLParser.h" 21 #include "nsCOMPtr.h" 22 #include "nsCRT.h" 23 #include "nsNetCID.h" 24 #include "mozilla/Preferences.h" 25 #include "prnetdb.h" 26 #include "mozilla/StaticPrefs_network.h" 27 #include "mozilla/Tokenizer.h" 28 #include "nsEscape.h" 29 #include "nsDOMString.h" 30 #include "mozilla/net/rust_helper.h" 31 #include "mozilla/net/DNS.h" 32 33 using namespace mozilla; 34 35 //---------------------------------------------------------------------------- 36 // Init/Shutdown 37 //---------------------------------------------------------------------------- 38 39 // We protect only the initialization with the mutex, such that we cannot 40 // annotate the following static variables. 41 static StaticMutex gInitLock MOZ_UNANNOTATED; 42 // The relaxed memory ordering is fine here as we write this only when holding 43 // gInitLock and only ever set it true once during EnsureGlobalsAreInited. 44 static Atomic<bool, MemoryOrdering::Relaxed> gInitialized(false); 45 static StaticRefPtr<nsIURLParser> gNoAuthURLParser; 46 static StaticRefPtr<nsIURLParser> gAuthURLParser; 47 static StaticRefPtr<nsIURLParser> gStdURLParser; 48 49 static void EnsureGlobalsAreInited() { 50 if (!gInitialized) { 51 StaticMutexAutoLock lock(gInitLock); 52 // Taking the lock will sync us with any other thread's write in case we 53 // saw a stale value above, thus we need to check again. 54 if (gInitialized) { 55 return; 56 } 57 58 nsCOMPtr<nsIURLParser> parser; 59 60 parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID); 61 NS_ASSERTION(parser, "failed getting 'noauth' url parser"); 62 if (parser) { 63 gNoAuthURLParser = parser.forget(); 64 } 65 66 parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID); 67 NS_ASSERTION(parser, "failed getting 'auth' url parser"); 68 if (parser) { 69 gAuthURLParser = parser.forget(); 70 } 71 72 parser = do_GetService(NS_STDURLPARSER_CONTRACTID); 73 NS_ASSERTION(parser, "failed getting 'std' url parser"); 74 if (parser) { 75 gStdURLParser = parser.forget(); 76 } 77 78 gInitialized = true; 79 } 80 } 81 82 void net_ShutdownURLHelper() { 83 if (gInitialized) { 84 // We call this late in XPCOM shutdown when there is only the main thread 85 // left, so we can safely release the static pointers here. 86 MOZ_ASSERT(AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)); 87 gNoAuthURLParser = nullptr; 88 gAuthURLParser = nullptr; 89 gStdURLParser = nullptr; 90 // We keep gInitialized true to protect us from resurrection. 91 } 92 } 93 94 //---------------------------------------------------------------------------- 95 // nsIURLParser getters 96 //---------------------------------------------------------------------------- 97 98 already_AddRefed<nsIURLParser> net_GetAuthURLParser() { 99 EnsureGlobalsAreInited(); 100 RefPtr<nsIURLParser> keepMe = gAuthURLParser; 101 return keepMe.forget(); 102 } 103 104 already_AddRefed<nsIURLParser> net_GetNoAuthURLParser() { 105 EnsureGlobalsAreInited(); 106 RefPtr<nsIURLParser> keepMe = gNoAuthURLParser; 107 return keepMe.forget(); 108 } 109 110 already_AddRefed<nsIURLParser> net_GetStdURLParser() { 111 EnsureGlobalsAreInited(); 112 RefPtr<nsIURLParser> keepMe = gStdURLParser; 113 return keepMe.forget(); 114 } 115 116 //--------------------------------------------------------------------------- 117 // GetFileFromURLSpec implementations 118 //--------------------------------------------------------------------------- 119 nsresult net_GetURLSpecFromDir(nsIFile* aFile, nsACString& result) { 120 nsAutoCString escPath; 121 nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath); 122 if (NS_FAILED(rv)) return rv; 123 124 if (escPath.Last() != '/') { 125 escPath += '/'; 126 } 127 128 result = escPath; 129 return NS_OK; 130 } 131 132 nsresult net_GetURLSpecFromFile(nsIFile* aFile, nsACString& result) { 133 nsAutoCString escPath; 134 nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath); 135 if (NS_FAILED(rv)) return rv; 136 137 // if this file references a directory, then we need to ensure that the 138 // URL ends with a slash. this is important since it affects the rules 139 // for relative URL resolution when this URL is used as a base URL. 140 // if the file does not exist, then we make no assumption about its type, 141 // and simply leave the URL unmodified. 142 if (escPath.Last() != '/') { 143 bool dir; 144 rv = aFile->IsDirectory(&dir); 145 if (NS_SUCCEEDED(rv) && dir) escPath += '/'; 146 } 147 148 result = escPath; 149 return NS_OK; 150 } 151 152 //---------------------------------------------------------------------------- 153 // file:// URL parsing 154 //---------------------------------------------------------------------------- 155 156 nsresult net_ParseFileURL(const nsACString& inURL, nsACString& outDirectory, 157 nsACString& outFileBaseName, 158 nsACString& outFileExtension) { 159 nsresult rv; 160 161 if (inURL.Length() > 162 (uint32_t)StaticPrefs::network_standard_url_max_length()) { 163 return NS_ERROR_MALFORMED_URI; 164 } 165 166 outDirectory.Truncate(); 167 outFileBaseName.Truncate(); 168 outFileExtension.Truncate(); 169 170 const nsPromiseFlatCString& flatURL = PromiseFlatCString(inURL); 171 const char* url = flatURL.get(); 172 173 nsAutoCString scheme; 174 rv = net_ExtractURLScheme(flatURL, scheme); 175 if (NS_FAILED(rv)) return rv; 176 177 if (!scheme.EqualsLiteral("file")) { 178 NS_ERROR("must be a file:// url"); 179 return NS_ERROR_UNEXPECTED; 180 } 181 182 nsCOMPtr<nsIURLParser> parser = net_GetNoAuthURLParser(); 183 NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED); 184 185 uint32_t pathPos, filepathPos, directoryPos, basenamePos, extensionPos; 186 int32_t pathLen, filepathLen, directoryLen, basenameLen, extensionLen; 187 188 // invoke the parser to extract the URL path 189 rv = parser->ParseURL(url, flatURL.Length(), nullptr, 190 nullptr, // don't care about scheme 191 nullptr, nullptr, // don't care about authority 192 &pathPos, &pathLen); 193 if (NS_FAILED(rv)) return rv; 194 195 // invoke the parser to extract filepath from the path 196 rv = parser->ParsePath(url + pathPos, pathLen, &filepathPos, &filepathLen, 197 nullptr, nullptr, // don't care about query 198 nullptr, nullptr); // don't care about ref 199 if (NS_FAILED(rv)) return rv; 200 201 filepathPos += pathPos; 202 203 // invoke the parser to extract the directory and filename from filepath 204 rv = parser->ParseFilePath(url + filepathPos, filepathLen, &directoryPos, 205 &directoryLen, &basenamePos, &basenameLen, 206 &extensionPos, &extensionLen); 207 if (NS_FAILED(rv)) return rv; 208 209 if (directoryLen > 0) { 210 outDirectory = Substring(inURL, filepathPos + directoryPos, directoryLen); 211 } 212 if (basenameLen > 0) { 213 outFileBaseName = Substring(inURL, filepathPos + basenamePos, basenameLen); 214 } 215 if (extensionLen > 0) { 216 outFileExtension = 217 Substring(inURL, filepathPos + extensionPos, extensionLen); 218 } 219 // since we are using a no-auth url parser, there will never be a host 220 // XXX not strictly true... file://localhost/foo/bar.html is a valid URL 221 222 return NS_OK; 223 } 224 225 //---------------------------------------------------------------------------- 226 // path manipulation functions 227 //---------------------------------------------------------------------------- 228 229 // Replace all /./ with a / while resolving URLs 230 // But only till #? 231 mozilla::Maybe<mozilla::CompactPair<uint32_t, uint32_t>> net_CoalesceDirs( 232 char* path) { 233 /* Stolen from the old netlib's mkparse.c. 234 * 235 * modifies a url of the form /foo/../foo1 -> /foo1 236 * and /foo/./foo1 -> /foo/foo1 237 * and /foo/foo1/.. -> /foo/ 238 */ 239 char* fwdPtr = path; 240 char* urlPtr = path; 241 242 MOZ_ASSERT(*path == '/', "We expect the path to begin with /"); 243 if (*path != '/') { 244 return Nothing(); 245 } 246 247 // This function checks if the character terminates the path segment, 248 // meaning it is / or ? or # or null. 249 auto isSegmentEnd = [](char aChar) { 250 return aChar == '/' || aChar == '?' || aChar == '#' || aChar == '\0'; 251 }; 252 253 // replace all %2E, %2e, %2e%2e, %2e%2E, %2E%2e, %2E%2E, etc with . or .. 254 // respectively if between two "/"s or "/" and NULL terminator 255 constexpr int PERCENT_2E_LENGTH = sizeof("%2e") - 1; 256 constexpr uint32_t PERCENT_2E_WITH_PERIOD_LENGTH = PERCENT_2E_LENGTH + 1; 257 258 for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#'); ++fwdPtr) { 259 // Assuming that we are currently at '/' 260 if (*fwdPtr == '/' && 261 nsCRT::strncasecmp(fwdPtr + 1, "%2e", PERCENT_2E_LENGTH) == 0 && 262 isSegmentEnd(*(fwdPtr + PERCENT_2E_LENGTH + 1))) { 263 *urlPtr++ = '/'; 264 *urlPtr++ = '.'; 265 fwdPtr += PERCENT_2E_LENGTH; 266 } 267 // If the remaining pathname is "%2e%2e" between "/"s, add ".." 268 else if (*fwdPtr == '/' && 269 nsCRT::strncasecmp(fwdPtr + 1, "%2e%2e", PERCENT_2E_LENGTH * 2) == 270 0 && 271 isSegmentEnd(*(fwdPtr + PERCENT_2E_LENGTH * 2 + 1))) { 272 *urlPtr++ = '/'; 273 *urlPtr++ = '.'; 274 *urlPtr++ = '.'; 275 fwdPtr += PERCENT_2E_LENGTH * 2; 276 } 277 // If the remaining pathname is "%2e." or ".%2e" between "/"s, add ".." 278 else if (*fwdPtr == '/' && 279 (nsCRT::strncasecmp(fwdPtr + 1, "%2e.", 280 PERCENT_2E_WITH_PERIOD_LENGTH) == 0 || 281 nsCRT::strncasecmp(fwdPtr + 1, ".%2e", 282 PERCENT_2E_WITH_PERIOD_LENGTH) == 0) && 283 isSegmentEnd(*(fwdPtr + PERCENT_2E_WITH_PERIOD_LENGTH + 1))) { 284 *urlPtr++ = '/'; 285 *urlPtr++ = '.'; 286 *urlPtr++ = '.'; 287 fwdPtr += PERCENT_2E_WITH_PERIOD_LENGTH; 288 } else { 289 *urlPtr++ = *fwdPtr; 290 } 291 } 292 // Copy remaining stuff past the #?; 293 for (; *fwdPtr != '\0'; ++fwdPtr) { 294 *urlPtr++ = *fwdPtr; 295 } 296 *urlPtr = '\0'; // terminate the url 297 298 // start again, this time for real 299 fwdPtr = path; 300 urlPtr = path; 301 302 for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#'); ++fwdPtr) { 303 if (*fwdPtr == '/' && *(fwdPtr + 1) == '.' && *(fwdPtr + 2) == '/') { 304 // remove . followed by slash 305 ++fwdPtr; 306 } else if (*fwdPtr == '/' && *(fwdPtr + 1) == '.' && *(fwdPtr + 2) == '.' && 307 isSegmentEnd(*(fwdPtr + 3))) { 308 // This will take care of something like foo/bar/..#sometag 309 // remove foo/.. 310 // reverse the urlPtr to the previous slash if possible 311 // if url does not allow relative root then drop .. above root 312 // otherwise retain them in the path 313 if (urlPtr != path) urlPtr--; // we must be going back at least by one 314 for (; *urlPtr != '/' && urlPtr != path; urlPtr--) { 315 ; // null body 316 } 317 // forward the fwdPtr past the ../ 318 fwdPtr += 2; 319 // special case if we have reached the end 320 // to preserve the last / 321 if (*fwdPtr == '.' && (*(fwdPtr + 1) == '\0' || *(fwdPtr + 1) == '?' || 322 *(fwdPtr + 1) == '#')) { 323 ++urlPtr; 324 } 325 } else { 326 // copy the url incrementaly 327 *urlPtr++ = *fwdPtr; 328 } 329 } 330 331 /* 332 * Now lets remove trailing . case 333 * /foo/foo1/. -> /foo/foo1/ 334 */ 335 336 if ((urlPtr > (path + 1)) && (*(urlPtr - 1) == '.') && 337 (*(urlPtr - 2) == '/')) { 338 urlPtr--; 339 } 340 341 // Before we start copying past ?#, we must make sure we don't overwrite 342 // the first / character. If fwdPtr is also unchanged, just copy everything 343 // (this shouldn't happen unless we could get in here without a leading 344 // slash). 345 if (urlPtr == path && fwdPtr != path) { 346 urlPtr++; 347 } 348 349 // Copy remaining stuff past the #?; 350 for (; *fwdPtr != '\0'; ++fwdPtr) { 351 *urlPtr++ = *fwdPtr; 352 } 353 *urlPtr = '\0'; // terminate the url 354 355 uint32_t lastSlash = 0; 356 uint32_t endOfBasename = 0; 357 358 // find the last slash before # or ? 359 // find the end of basename (i.e. hash, query, or end of string) 360 for (; (*(path + endOfBasename) != '\0') && 361 (*(path + endOfBasename) != '?') && (*(path + endOfBasename) != '#'); 362 ++endOfBasename) { 363 } 364 365 // Now find the last slash starting from the end 366 lastSlash = endOfBasename; 367 if (lastSlash != 0 && *(path + lastSlash) == '\0') { 368 --lastSlash; 369 } 370 // search the slash 371 for (; lastSlash != 0 && *(path + lastSlash) != '/'; --lastSlash) { 372 } 373 374 return Some(mozilla::MakeCompactPair(lastSlash, endOfBasename)); 375 } 376 377 //---------------------------------------------------------------------------- 378 // scheme fu 379 //---------------------------------------------------------------------------- 380 381 static bool net_IsValidSchemeChar(const char aChar) { 382 return mozilla::net::rust_net_is_valid_scheme_char(aChar); 383 } 384 385 /* Extract URI-Scheme if possible */ 386 nsresult net_ExtractURLScheme(const nsACString& inURI, nsACString& scheme) { 387 nsACString::const_iterator start, end; 388 inURI.BeginReading(start); 389 inURI.EndReading(end); 390 391 // Strip C0 and space from begining 392 while (start != end) { 393 if ((uint8_t)*start > 0x20) { 394 break; 395 } 396 start++; 397 } 398 399 Tokenizer p(Substring(start, end), "\r\n\t"); 400 p.Record(); 401 if (!p.CheckChar(IsAsciiAlpha)) { 402 // First char must be alpha 403 return NS_ERROR_MALFORMED_URI; 404 } 405 406 while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) { 407 // Skip valid scheme characters or \r\n\t 408 } 409 410 if (!p.CheckChar(':')) { 411 return NS_ERROR_MALFORMED_URI; 412 } 413 414 p.Claim(scheme); 415 scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); 416 ToLowerCase(scheme); 417 return NS_OK; 418 } 419 420 bool net_IsValidScheme(const nsACString& scheme) { 421 return mozilla::net::rust_net_is_valid_scheme(&scheme); 422 } 423 424 bool net_IsAbsoluteURL(const nsACString& uri) { 425 nsACString::const_iterator start, end; 426 uri.BeginReading(start); 427 uri.EndReading(end); 428 429 // Strip C0 and space from begining 430 while (start != end) { 431 if ((uint8_t)*start > 0x20) { 432 break; 433 } 434 start++; 435 } 436 437 Tokenizer p(Substring(start, end), "\r\n\t"); 438 439 // First char must be alpha 440 if (!p.CheckChar(IsAsciiAlpha)) { 441 return false; 442 } 443 444 while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) { 445 // Skip valid scheme characters or \r\n\t 446 } 447 if (!p.CheckChar(':')) { 448 return false; 449 } 450 p.SkipWhites(); 451 452 if (!p.CheckChar('/')) { 453 return false; 454 } 455 p.SkipWhites(); 456 457 if (p.CheckChar('/')) { 458 // aSpec is really absolute. Ignore aBaseURI in this case 459 return true; 460 } 461 return false; 462 } 463 464 void net_FilterURIString(const nsACString& input, nsACString& result) { 465 result.Truncate(); 466 467 const auto* start = input.BeginReading(); 468 const auto* end = input.EndReading(); 469 470 // Trim off leading and trailing invalid chars. 471 auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; }; 472 const auto* newStart = std::find_if(start, end, charFilter); 473 const auto* newEnd = 474 std::find_if(std::reverse_iterator<decltype(end)>(end), 475 std::reverse_iterator<decltype(newStart)>(newStart), 476 charFilter) 477 .base(); 478 479 // Check if chars need to be stripped. 480 bool needsStrip = false; 481 const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab(); 482 for (const auto* itr = start; itr != end; ++itr) { 483 if (ASCIIMask::IsMasked(mask, *itr)) { 484 needsStrip = true; 485 break; 486 } 487 } 488 489 // Just use the passed in string rather than creating new copies if no 490 // changes are necessary. 491 if (newStart == start && newEnd == end && !needsStrip) { 492 result = input; 493 return; 494 } 495 496 result.Assign(Substring(newStart, newEnd)); 497 if (needsStrip) { 498 result.StripTaggedASCII(mask); 499 } 500 } 501 502 nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags, 503 const ASCIIMaskArray& aFilterMask, 504 nsACString& aResult) { 505 aResult.Truncate(); 506 507 const auto* start = aInput.BeginReading(); 508 const auto* end = aInput.EndReading(); 509 510 // Trim off leading and trailing invalid chars. 511 auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; }; 512 const auto* newStart = std::find_if(start, end, charFilter); 513 const auto* newEnd = 514 std::find_if(std::reverse_iterator<decltype(end)>(end), 515 std::reverse_iterator<decltype(newStart)>(newStart), 516 charFilter) 517 .base(); 518 519 return NS_EscapeAndFilterURL(Substring(newStart, newEnd), aFlags, 520 &aFilterMask, aResult, fallible); 521 } 522 523 #if defined(XP_WIN) 524 bool net_NormalizeFileURL(const nsACString& aURL, nsCString& aResultBuf) { 525 bool writing = false; 526 527 nsACString::const_iterator beginIter, endIter; 528 aURL.BeginReading(beginIter); 529 aURL.EndReading(endIter); 530 531 const char *s, *begin = beginIter.get(); 532 533 for (s = begin; s != endIter.get(); ++s) { 534 if (*s == '\\') { 535 writing = true; 536 if (s > begin) aResultBuf.Append(begin, s - begin); 537 aResultBuf += '/'; 538 begin = s + 1; 539 } 540 if (*s == '#') { 541 // Don't normalize any backslashes following the hash. 542 s = endIter.get(); 543 break; 544 } 545 } 546 if (writing && s > begin) aResultBuf.Append(begin, s - begin); 547 548 return writing; 549 } 550 #endif 551 552 //---------------------------------------------------------------------------- 553 // miscellaneous (i.e., stuff that should really be elsewhere) 554 //---------------------------------------------------------------------------- 555 556 static inline void ToLower(char& c) { 557 if ((unsigned)(c - 'A') <= (unsigned)('Z' - 'A')) c += 'a' - 'A'; 558 } 559 560 void net_ToLowerCase(char* str, uint32_t length) { 561 for (char* end = str + length; str < end; ++str) ToLower(*str); 562 } 563 564 void net_ToLowerCase(char* str) { 565 for (; *str; ++str) ToLower(*str); 566 } 567 568 char* net_FindCharInSet(const char* iter, const char* stop, const char* set) { 569 for (; iter != stop && *iter; ++iter) { 570 for (const char* s = set; *s; ++s) { 571 if (*iter == *s) return (char*)iter; 572 } 573 } 574 return (char*)iter; 575 } 576 577 char* net_FindCharNotInSet(const char* iter, const char* stop, 578 const char* set) { 579 repeat: 580 for (const char* s = set; *s; ++s) { 581 if (*iter == *s) { 582 if (++iter == stop) break; 583 goto repeat; 584 } 585 } 586 return (char*)iter; 587 } 588 589 char* net_RFindCharNotInSet(const char* stop, const char* iter, 590 const char* set) { 591 --iter; 592 --stop; 593 594 if (iter == stop) return (char*)iter; 595 596 repeat: 597 for (const char* s = set; *s; ++s) { 598 if (*iter == *s) { 599 if (--iter == stop) break; 600 goto repeat; 601 } 602 } 603 return (char*)iter; 604 } 605 606 #define HTTP_LWS " \t" 607 608 // Return the index of the closing quote of the string, if any 609 static uint32_t net_FindStringEnd(const nsCString& flatStr, 610 uint32_t stringStart, char stringDelim) { 611 NS_ASSERTION(stringStart < flatStr.Length() && 612 flatStr.CharAt(stringStart) == stringDelim && 613 (stringDelim == '"' || stringDelim == '\''), 614 "Invalid stringStart"); 615 616 const char set[] = {stringDelim, '\\', '\0'}; 617 do { 618 // stringStart points to either the start quote or the last 619 // escaped char (the char following a '\\') 620 621 // Write to searchStart here, so that when we get back to the 622 // top of the loop right outside this one we search from the 623 // right place. 624 uint32_t stringEnd = flatStr.FindCharInSet(set, stringStart + 1); 625 if (stringEnd == uint32_t(kNotFound)) return flatStr.Length(); 626 627 if (flatStr.CharAt(stringEnd) == '\\') { 628 // Hit a backslash-escaped char. Need to skip over it. 629 stringStart = stringEnd + 1; 630 if (stringStart == flatStr.Length()) return stringStart; 631 632 // Go back to looking for the next escape or the string end 633 continue; 634 } 635 636 return stringEnd; 637 638 } while (true); 639 640 MOZ_ASSERT_UNREACHABLE("How did we get here?"); 641 return flatStr.Length(); 642 } 643 644 static uint32_t net_FindMediaDelimiter(const nsCString& flatStr, 645 uint32_t searchStart, char delimiter) { 646 do { 647 // searchStart points to the spot from which we should start looking 648 // for the delimiter. 649 const char delimStr[] = {delimiter, '"', '\0'}; 650 uint32_t curDelimPos = flatStr.FindCharInSet(delimStr, searchStart); 651 if (curDelimPos == uint32_t(kNotFound)) return flatStr.Length(); 652 653 char ch = flatStr.CharAt(curDelimPos); 654 if (ch == delimiter) { 655 // Found delimiter 656 return curDelimPos; 657 } 658 659 // We hit the start of a quoted string. Look for its end. 660 searchStart = net_FindStringEnd(flatStr, curDelimPos, ch); 661 if (searchStart == flatStr.Length()) return searchStart; 662 663 ++searchStart; 664 665 // searchStart now points to the first char after the end of the 666 // string, so just go back to the top of the loop and look for 667 // |delimiter| again. 668 } while (true); 669 670 MOZ_ASSERT_UNREACHABLE("How did we get here?"); 671 return flatStr.Length(); 672 } 673 674 // aOffset should be added to aCharsetStart and aCharsetEnd if this 675 // function sets them. 676 static void net_ParseMediaType(const nsACString& aMediaTypeStr, 677 nsACString& aContentType, 678 nsACString& aContentCharset, int32_t aOffset, 679 bool* aHadCharset, int32_t* aCharsetStart, 680 int32_t* aCharsetEnd, bool aStrict) { 681 const nsCString& flatStr = PromiseFlatCString(aMediaTypeStr); 682 const char* start = flatStr.get(); 683 const char* end = start + flatStr.Length(); 684 685 // Trim LWS leading and trailing whitespace from type. 686 const char* type = net_FindCharNotInSet(start, end, HTTP_LWS); 687 const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";"); 688 689 const char* charset = ""; 690 const char* charsetEnd = charset; 691 int32_t charsetParamStart = 0; 692 int32_t charsetParamEnd = 0; 693 694 uint32_t consumed = typeEnd - type; 695 696 // Iterate over parameters 697 bool typeHasCharset = false; 698 uint32_t paramStart = flatStr.FindChar(';', typeEnd - start); 699 if (paramStart != uint32_t(kNotFound)) { 700 // We have parameters. Iterate over them. 701 uint32_t curParamStart = paramStart + 1; 702 do { 703 uint32_t curParamEnd = 704 net_FindMediaDelimiter(flatStr, curParamStart, ';'); 705 706 const char* paramName = net_FindCharNotInSet( 707 start + curParamStart, start + curParamEnd, HTTP_LWS); 708 static const char charsetStr[] = "charset="; 709 if (nsCRT::strncasecmp(paramName, charsetStr, sizeof(charsetStr) - 1) == 710 0) { 711 charset = paramName + sizeof(charsetStr) - 1; 712 charsetEnd = start + curParamEnd; 713 typeHasCharset = true; 714 charsetParamStart = curParamStart - 1; 715 charsetParamEnd = curParamEnd; 716 } 717 718 consumed = curParamEnd; 719 curParamStart = curParamEnd + 1; 720 } while (curParamStart < flatStr.Length()); 721 } 722 723 bool charsetNeedsQuotedStringUnescaping = false; 724 if (typeHasCharset) { 725 // Trim LWS leading and trailing whitespace from charset. 726 charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS); 727 if (*charset == '"') { 728 charsetNeedsQuotedStringUnescaping = true; 729 charsetEnd = 730 start + net_FindStringEnd(flatStr, charset - start, *charset); 731 charset++; 732 NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing"); 733 } else { 734 charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";"); 735 } 736 } 737 738 // if the server sent "*/*", it is meaningless, so do not store it. 739 // also, if type is the same as aContentType, then just update the 740 // charset. however, if charset is empty and aContentType hasn't 741 // changed, then don't wipe-out an existing aContentCharset. We 742 // also want to reject a mime-type if it does not include a slash. 743 // some servers give junk after the charset parameter, which may 744 // include a comma, so this check makes us a bit more tolerant. 745 746 if (type != typeEnd && memchr(type, '/', typeEnd - type) != nullptr && 747 (aStrict ? (net_FindCharNotInSet(start + consumed, end, HTTP_LWS) == end) 748 : (strncmp(type, "*/*", typeEnd - type) != 0))) { 749 // Common case here is that aContentType is empty 750 bool eq = !aContentType.IsEmpty() && 751 aContentType.Equals(Substring(type, typeEnd), 752 nsCaseInsensitiveCStringComparator); 753 if (!eq) { 754 aContentType.Assign(type, typeEnd - type); 755 ToLowerCase(aContentType); 756 } 757 758 if ((!eq && *aHadCharset) || typeHasCharset) { 759 *aHadCharset = true; 760 if (charsetNeedsQuotedStringUnescaping) { 761 // parameters using the "quoted-string" syntax need 762 // backslash-escapes to be unescaped (see RFC 2616 Section 2.2) 763 aContentCharset.Truncate(); 764 for (const char* c = charset; c != charsetEnd; c++) { 765 if (*c == '\\' && c + 1 != charsetEnd) { 766 // eat escape 767 c++; 768 } 769 aContentCharset.Append(*c); 770 } 771 } else { 772 aContentCharset.Assign(charset, charsetEnd - charset); 773 } 774 if (typeHasCharset) { 775 *aCharsetStart = charsetParamStart + aOffset; 776 *aCharsetEnd = charsetParamEnd + aOffset; 777 } 778 } 779 // Only set a new charset position if this is a different type 780 // from the last one we had and it doesn't already have a 781 // charset param. If this is the same type, we probably want 782 // to leave the charset position on its first occurrence. 783 if (!eq && !typeHasCharset) { 784 int32_t charsetStart = int32_t(paramStart); 785 if (charsetStart == kNotFound) charsetStart = flatStr.Length(); 786 787 *aCharsetEnd = *aCharsetStart = charsetStart + aOffset; 788 } 789 } 790 } 791 792 #undef HTTP_LWS 793 794 void net_ParseContentType(const nsACString& aHeaderStr, 795 nsACString& aContentType, nsACString& aContentCharset, 796 bool* aHadCharset) { 797 int32_t dummy1, dummy2; 798 net_ParseContentType(aHeaderStr, aContentType, aContentCharset, aHadCharset, 799 &dummy1, &dummy2); 800 } 801 802 void net_ParseContentType(const nsACString& aHeaderStr, 803 nsACString& aContentType, nsACString& aContentCharset, 804 bool* aHadCharset, int32_t* aCharsetStart, 805 int32_t* aCharsetEnd) { 806 // 807 // Augmented BNF (from RFC 2616 section 3.7): 808 // 809 // header-value = media-type *( LWS "," LWS media-type ) 810 // media-type = type "/" subtype *( LWS ";" LWS parameter ) 811 // type = token 812 // subtype = token 813 // parameter = attribute "=" value 814 // attribute = token 815 // value = token | quoted-string 816 // 817 // 818 // Examples: 819 // 820 // text/html 821 // text/html, text/html 822 // text/html,text/html; charset=ISO-8859-1 823 // text/html,text/html; charset="ISO-8859-1" 824 // text/html;charset=ISO-8859-1, text/html 825 // text/html;charset='ISO-8859-1', text/html 826 // application/octet-stream 827 // 828 829 *aHadCharset = false; 830 const nsCString& flatStr = PromiseFlatCString(aHeaderStr); 831 832 // iterate over media-types. Note that ',' characters can happen 833 // inside quoted strings, so we need to watch out for that. 834 uint32_t curTypeStart = 0; 835 do { 836 // curTypeStart points to the start of the current media-type. We want 837 // to look for its end. 838 uint32_t curTypeEnd = net_FindMediaDelimiter(flatStr, curTypeStart, ','); 839 840 // At this point curTypeEnd points to the spot where the media-type 841 // starting at curTypeEnd ends. Time to parse that! 842 net_ParseMediaType( 843 Substring(flatStr, curTypeStart, curTypeEnd - curTypeStart), 844 aContentType, aContentCharset, curTypeStart, aHadCharset, aCharsetStart, 845 aCharsetEnd, false); 846 847 // And let's move on to the next media-type 848 curTypeStart = curTypeEnd + 1; 849 } while (curTypeStart < flatStr.Length()); 850 } 851 852 void net_ParseRequestContentType(const nsACString& aHeaderStr, 853 nsACString& aContentType, 854 nsACString& aContentCharset, 855 bool* aHadCharset) { 856 // 857 // Augmented BNF (from RFC 7231 section 3.1.1.1): 858 // 859 // media-type = type "/" subtype *( OWS ";" OWS parameter ) 860 // type = token 861 // subtype = token 862 // parameter = token "=" ( token / quoted-string ) 863 // 864 // Examples: 865 // 866 // text/html 867 // text/html; charset=ISO-8859-1 868 // text/html; charset="ISO-8859-1" 869 // application/octet-stream 870 // 871 872 aContentType.Truncate(); 873 aContentCharset.Truncate(); 874 *aHadCharset = false; 875 const nsCString& flatStr = PromiseFlatCString(aHeaderStr); 876 877 // At this point curTypeEnd points to the spot where the media-type 878 // starting at curTypeEnd ends. Time to parse that! 879 nsAutoCString contentType, contentCharset; 880 bool hadCharset = false; 881 int32_t dummy1, dummy2; 882 uint32_t typeEnd = net_FindMediaDelimiter(flatStr, 0, ','); 883 if (typeEnd != flatStr.Length()) { 884 // We have some stuff left at the end, so this is not a valid 885 // request Content-Type header. 886 return; 887 } 888 net_ParseMediaType(flatStr, contentType, contentCharset, 0, &hadCharset, 889 &dummy1, &dummy2, true); 890 891 aContentType = contentType; 892 aContentCharset = contentCharset; 893 *aHadCharset = hadCharset; 894 } 895 896 bool net_IsValidDNSHost(const nsACString& host) { 897 // The host name is limited to 253 ascii characters. 898 if (host.Length() > 253) { 899 return false; 900 } 901 902 const char* end = host.EndReading(); 903 // Use explicit whitelists to select which characters we are 904 // willing to send to lower-level DNS logic. This is more 905 // self-documenting, and can also be slightly faster than the 906 // blacklist approach, since DNS names are the common case, and 907 // the commonest characters will tend to be near the start of 908 // the list. 909 910 // Whitelist for DNS names (RFC 1035) with extra characters added 911 // for pragmatic reasons "$+_" 912 // see https://bugzilla.mozilla.org/show_bug.cgi?id=355181#c2 913 if (net_FindCharNotInSet(host.BeginReading(), end, 914 "abcdefghijklmnopqrstuvwxyz" 915 ".-0123456789" 916 "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end) { 917 return true; 918 } 919 920 // Might be a valid IPv6 link-local address containing a percent sign 921 return mozilla::net::HostIsIPLiteral(host); 922 } 923 924 bool net_IsValidIPv4Addr(const nsACString& aAddr) { 925 return mozilla::net::rust_net_is_valid_ipv4_addr(&aAddr); 926 } 927 928 bool net_IsValidIPv6Addr(const nsACString& aAddr) { 929 return mozilla::net::rust_net_is_valid_ipv6_addr(&aAddr); 930 } 931 932 bool net_GetDefaultStatusTextForCode(uint16_t aCode, nsACString& aOutText) { 933 switch (aCode) { 934 // start with the most common 935 case 200: 936 aOutText.AssignLiteral("OK"); 937 break; 938 case 404: 939 aOutText.AssignLiteral("Not Found"); 940 break; 941 case 301: 942 aOutText.AssignLiteral("Moved Permanently"); 943 break; 944 case 304: 945 aOutText.AssignLiteral("Not Modified"); 946 break; 947 case 307: 948 aOutText.AssignLiteral("Temporary Redirect"); 949 break; 950 case 500: 951 aOutText.AssignLiteral("Internal Server Error"); 952 break; 953 954 // also well known 955 case 100: 956 aOutText.AssignLiteral("Continue"); 957 break; 958 case 101: 959 aOutText.AssignLiteral("Switching Protocols"); 960 break; 961 case 201: 962 aOutText.AssignLiteral("Created"); 963 break; 964 case 202: 965 aOutText.AssignLiteral("Accepted"); 966 break; 967 case 203: 968 aOutText.AssignLiteral("Non Authoritative"); 969 break; 970 case 204: 971 aOutText.AssignLiteral("No Content"); 972 break; 973 case 205: 974 aOutText.AssignLiteral("Reset Content"); 975 break; 976 case 206: 977 aOutText.AssignLiteral("Partial Content"); 978 break; 979 case 207: 980 aOutText.AssignLiteral("Multi-Status"); 981 break; 982 case 208: 983 aOutText.AssignLiteral("Already Reported"); 984 break; 985 case 300: 986 aOutText.AssignLiteral("Multiple Choices"); 987 break; 988 case 302: 989 aOutText.AssignLiteral("Found"); 990 break; 991 case 303: 992 aOutText.AssignLiteral("See Other"); 993 break; 994 case 305: 995 aOutText.AssignLiteral("Use Proxy"); 996 break; 997 case 308: 998 aOutText.AssignLiteral("Permanent Redirect"); 999 break; 1000 case 400: 1001 aOutText.AssignLiteral("Bad Request"); 1002 break; 1003 case 401: 1004 aOutText.AssignLiteral("Unauthorized"); 1005 break; 1006 case 402: 1007 aOutText.AssignLiteral("Payment Required"); 1008 break; 1009 case 403: 1010 aOutText.AssignLiteral("Forbidden"); 1011 break; 1012 case 405: 1013 aOutText.AssignLiteral("Method Not Allowed"); 1014 break; 1015 case 406: 1016 aOutText.AssignLiteral("Not Acceptable"); 1017 break; 1018 case 407: 1019 aOutText.AssignLiteral("Proxy Authentication Required"); 1020 break; 1021 case 408: 1022 aOutText.AssignLiteral("Request Timeout"); 1023 break; 1024 case 409: 1025 aOutText.AssignLiteral("Conflict"); 1026 break; 1027 case 410: 1028 aOutText.AssignLiteral("Gone"); 1029 break; 1030 case 411: 1031 aOutText.AssignLiteral("Length Required"); 1032 break; 1033 case 412: 1034 aOutText.AssignLiteral("Precondition Failed"); 1035 break; 1036 case 413: 1037 aOutText.AssignLiteral("Request Entity Too Large"); 1038 break; 1039 case 414: 1040 aOutText.AssignLiteral("Request URI Too Long"); 1041 break; 1042 case 415: 1043 aOutText.AssignLiteral("Unsupported Media Type"); 1044 break; 1045 case 416: 1046 aOutText.AssignLiteral("Requested Range Not Satisfiable"); 1047 break; 1048 case 417: 1049 aOutText.AssignLiteral("Expectation Failed"); 1050 break; 1051 case 418: 1052 aOutText.AssignLiteral("I'm a teapot"); 1053 break; 1054 case 421: 1055 aOutText.AssignLiteral("Misdirected Request"); 1056 break; 1057 case 422: 1058 aOutText.AssignLiteral("Unprocessable Entity"); 1059 break; 1060 case 423: 1061 aOutText.AssignLiteral("Locked"); 1062 break; 1063 case 424: 1064 aOutText.AssignLiteral("Failed Dependency"); 1065 break; 1066 case 425: 1067 aOutText.AssignLiteral("Too Early"); 1068 break; 1069 case 426: 1070 aOutText.AssignLiteral("Upgrade Required"); 1071 break; 1072 case 428: 1073 aOutText.AssignLiteral("Precondition Required"); 1074 break; 1075 case 429: 1076 aOutText.AssignLiteral("Too Many Requests"); 1077 break; 1078 case 431: 1079 aOutText.AssignLiteral("Request Header Fields Too Large"); 1080 break; 1081 case 451: 1082 aOutText.AssignLiteral("Unavailable For Legal Reasons"); 1083 break; 1084 case 501: 1085 aOutText.AssignLiteral("Not Implemented"); 1086 break; 1087 case 502: 1088 aOutText.AssignLiteral("Bad Gateway"); 1089 break; 1090 case 503: 1091 aOutText.AssignLiteral("Service Unavailable"); 1092 break; 1093 case 504: 1094 aOutText.AssignLiteral("Gateway Timeout"); 1095 break; 1096 case 505: 1097 aOutText.AssignLiteral("HTTP Version Unsupported"); 1098 break; 1099 case 506: 1100 aOutText.AssignLiteral("Variant Also Negotiates"); 1101 break; 1102 case 507: 1103 aOutText.AssignLiteral("Insufficient Storage "); 1104 break; 1105 case 508: 1106 aOutText.AssignLiteral("Loop Detected"); 1107 break; 1108 case 510: 1109 aOutText.AssignLiteral("Not Extended"); 1110 break; 1111 case 511: 1112 aOutText.AssignLiteral("Network Authentication Required"); 1113 break; 1114 default: 1115 aOutText.AssignLiteral("No Reason Phrase"); 1116 return false; 1117 } 1118 return true; 1119 } 1120 1121 static auto MakeNameMatcher(const nsACString& aName) { 1122 return [&aName](const auto& param) { return param.mKey.Equals(aName); }; 1123 } 1124 1125 static void AssignMaybeInvalidUTF8String(const nsACString& aSource, 1126 nsACString& aDest) { 1127 if (NS_FAILED(UTF_8_ENCODING->DecodeWithoutBOMHandling(aSource, aDest))) { 1128 MOZ_CRASH("Out of memory when converting URL params."); 1129 } 1130 } 1131 1132 namespace mozilla { 1133 1134 bool URLParams::Has(const nsACString& aName) { 1135 return std::any_of(mParams.cbegin(), mParams.cend(), MakeNameMatcher(aName)); 1136 } 1137 1138 bool URLParams::Has(const nsACString& aName, const nsACString& aValue) { 1139 return std::any_of( 1140 mParams.cbegin(), mParams.cend(), [&aName, &aValue](const auto& param) { 1141 return param.mKey.Equals(aName) && param.mValue.Equals(aValue); 1142 }); 1143 } 1144 1145 void URLParams::Get(const nsACString& aName, nsACString& aRetval) { 1146 aRetval.SetIsVoid(true); 1147 1148 const auto end = mParams.cend(); 1149 const auto it = std::find_if(mParams.cbegin(), end, MakeNameMatcher(aName)); 1150 if (it != end) { 1151 aRetval.Assign(it->mValue); 1152 } 1153 } 1154 1155 void URLParams::GetAll(const nsACString& aName, nsTArray<nsCString>& aRetval) { 1156 aRetval.Clear(); 1157 1158 for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) { 1159 if (mParams[i].mKey.Equals(aName)) { 1160 aRetval.AppendElement(mParams[i].mValue); 1161 } 1162 } 1163 } 1164 1165 void URLParams::Append(const nsACString& aName, const nsACString& aValue) { 1166 Param* param = mParams.AppendElement(); 1167 param->mKey = aName; 1168 param->mValue = aValue; 1169 } 1170 1171 void URLParams::Set(const nsACString& aName, const nsACString& aValue) { 1172 Param* param = nullptr; 1173 for (uint32_t i = 0, len = mParams.Length(); i < len;) { 1174 if (!mParams[i].mKey.Equals(aName)) { 1175 ++i; 1176 continue; 1177 } 1178 if (!param) { 1179 param = &mParams[i]; 1180 ++i; 1181 continue; 1182 } 1183 // Remove duplicates. 1184 mParams.RemoveElementAt(i); 1185 --len; 1186 } 1187 1188 if (!param) { 1189 param = mParams.AppendElement(); 1190 param->mKey = aName; 1191 } 1192 1193 param->mValue = aValue; 1194 } 1195 1196 void URLParams::Delete(const nsACString& aName) { 1197 mParams.RemoveElementsBy( 1198 [&aName](const auto& param) { return param.mKey.Equals(aName); }); 1199 } 1200 1201 void URLParams::Delete(const nsACString& aName, const nsACString& aValue) { 1202 mParams.RemoveElementsBy([&aName, &aValue](const auto& param) { 1203 return param.mKey.Equals(aName) && param.mValue.Equals(aValue); 1204 }); 1205 } 1206 1207 /* static */ 1208 void URLParams::DecodeString(const nsACString& aInput, nsACString& aOutput) { 1209 const char* const end = aInput.EndReading(); 1210 for (const char* iter = aInput.BeginReading(); iter != end;) { 1211 // replace '+' with U+0020 1212 if (*iter == '+') { 1213 aOutput.Append(' '); 1214 ++iter; 1215 continue; 1216 } 1217 1218 // Percent decode algorithm 1219 if (*iter == '%') { 1220 const char* const first = iter + 1; 1221 const char* const second = first + 1; 1222 1223 const auto asciiHexDigit = [](char x) { 1224 return (x >= 0x41 && x <= 0x46) || (x >= 0x61 && x <= 0x66) || 1225 (x >= 0x30 && x <= 0x39); 1226 }; 1227 1228 const auto hexDigit = [](char x) { 1229 return x >= 0x30 && x <= 0x39 1230 ? x - 0x30 1231 : (x >= 0x41 && x <= 0x46 ? x - 0x37 : x - 0x57); 1232 }; 1233 1234 if (first != end && second != end && asciiHexDigit(*first) && 1235 asciiHexDigit(*second)) { 1236 aOutput.Append(hexDigit(*first) * 16 + hexDigit(*second)); 1237 iter = second + 1; 1238 } else { 1239 aOutput.Append('%'); 1240 ++iter; 1241 } 1242 1243 continue; 1244 } 1245 1246 aOutput.Append(*iter); 1247 ++iter; 1248 } 1249 AssignMaybeInvalidUTF8String(aOutput, aOutput); 1250 } 1251 1252 /* static */ 1253 bool URLParams::ParseNextInternal(const char*& aStart, const char* const aEnd, 1254 bool aShouldDecode, nsACString* aOutputName, 1255 nsACString* aOutputValue) { 1256 nsDependentCSubstring string; 1257 1258 const char* const iter = std::find(aStart, aEnd, '&'); 1259 if (iter != aEnd) { 1260 string.Rebind(aStart, iter); 1261 aStart = iter + 1; 1262 } else { 1263 string.Rebind(aStart, aEnd); 1264 aStart = aEnd; 1265 } 1266 1267 if (string.IsEmpty()) { 1268 return false; 1269 } 1270 1271 const auto* const eqStart = string.BeginReading(); 1272 const auto* const eqEnd = string.EndReading(); 1273 const auto* const eqIter = std::find(eqStart, eqEnd, '='); 1274 1275 nsDependentCSubstring name; 1276 nsDependentCSubstring value; 1277 1278 if (eqIter != eqEnd) { 1279 name.Rebind(eqStart, eqIter); 1280 value.Rebind(eqIter + 1, eqEnd); 1281 } else { 1282 name.Rebind(string, 0); 1283 } 1284 1285 if (aShouldDecode) { 1286 DecodeString(name, *aOutputName); 1287 DecodeString(value, *aOutputValue); 1288 return true; 1289 } 1290 1291 AssignMaybeInvalidUTF8String(name, *aOutputName); 1292 AssignMaybeInvalidUTF8String(value, *aOutputValue); 1293 return true; 1294 } 1295 1296 /* static */ 1297 bool URLParams::Extract(const nsACString& aInput, const nsACString& aName, 1298 nsACString& aValue) { 1299 aValue.SetIsVoid(true); 1300 return !URLParams::Parse( 1301 aInput, true, 1302 [&aName, &aValue](const nsACString& name, nsCString&& value) { 1303 if (aName == name) { 1304 aValue = std::move(value); 1305 return false; 1306 } 1307 return true; 1308 }); 1309 } 1310 1311 void URLParams::ParseInput(const nsACString& aInput) { 1312 // Remove all the existing data before parsing a new input. 1313 DeleteAll(); 1314 1315 URLParams::Parse(aInput, true, [this](nsCString&& name, nsCString&& value) { 1316 mParams.AppendElement(Param{std::move(name), std::move(value)}); 1317 return true; 1318 }); 1319 } 1320 1321 void URLParams::SerializeString(const nsACString& aInput, nsACString& aValue) { 1322 const unsigned char* p = (const unsigned char*)aInput.BeginReading(); 1323 const unsigned char* end = p + aInput.Length(); 1324 1325 while (p != end) { 1326 // ' ' to '+' 1327 if (*p == 0x20) { 1328 aValue.Append(0x2B); 1329 // Percent Encode algorithm 1330 } else if (*p == 0x2A || *p == 0x2D || *p == 0x2E || 1331 (*p >= 0x30 && *p <= 0x39) || (*p >= 0x41 && *p <= 0x5A) || 1332 *p == 0x5F || (*p >= 0x61 && *p <= 0x7A)) { 1333 aValue.Append(*p); 1334 } else { 1335 aValue.AppendPrintf("%%%.2X", *p); 1336 } 1337 1338 ++p; 1339 } 1340 } 1341 1342 void URLParams::Serialize(nsACString& aValue, bool aEncode) const { 1343 aValue.Truncate(); 1344 bool first = true; 1345 1346 for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) { 1347 if (first) { 1348 first = false; 1349 } else { 1350 aValue.Append('&'); 1351 } 1352 1353 // XXX Actually, it's not necessary to build a new string object. Generally, 1354 // such cases could just convert each codepoint one-by-one. 1355 if (aEncode) { 1356 SerializeString(mParams[i].mKey, aValue); 1357 aValue.Append('='); 1358 SerializeString(mParams[i].mValue, aValue); 1359 } else { 1360 aValue.Append(mParams[i].mKey); 1361 aValue.Append('='); 1362 aValue.Append(mParams[i].mValue); 1363 } 1364 } 1365 } 1366 1367 void URLParams::Sort() { 1368 mParams.StableSort([](const Param& lhs, const Param& rhs) { 1369 // FIXME(emilio, bug 1888901): The URLSearchParams.sort() spec requires 1370 // comparing by utf-16 code points... That's a bit unfortunate, maybe we 1371 // can optimize the string conversions here? 1372 return Compare(NS_ConvertUTF8toUTF16(lhs.mKey), 1373 NS_ConvertUTF8toUTF16(rhs.mKey)); 1374 }); 1375 } 1376 1377 } // namespace mozilla