tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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