tor-browser

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

SubstitutingProtocolHandler.cpp (21915B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/ModuleUtils.h"
      8 #include "mozilla/chrome/RegistryMessageUtils.h"
      9 #include "mozilla/dom/ContentParent.h"
     10 #include "mozilla/ipc/URIUtils.h"
     11 
     12 #include "SubstitutingProtocolHandler.h"
     13 #include "SubstitutingURL.h"
     14 #include "SubstitutingJARURI.h"
     15 #include "nsIChannel.h"
     16 #include "nsIIOService.h"
     17 #include "nsIFile.h"
     18 #include "nsNetCID.h"
     19 #include "nsNetUtil.h"
     20 #include "nsReadableUtils.h"
     21 #include "nsURLHelper.h"
     22 #include "nsEscape.h"
     23 #include "nsIObjectInputStream.h"
     24 #include "nsIObjectOutputStream.h"
     25 #include "nsIClassInfoImpl.h"
     26 
     27 using mozilla::dom::ContentParent;
     28 
     29 namespace mozilla {
     30 namespace net {
     31 
     32 // Log module for Substituting Protocol logging. We keep the pre-existing module
     33 // name of "nsResProtocol" to avoid disruption.
     34 static LazyLogModule gResLog("nsResProtocol");
     35 
     36 static NS_DEFINE_CID(kSubstitutingJARURIImplCID,
     37                     NS_SUBSTITUTINGJARURI_IMPL_CID);
     38 
     39 //---------------------------------------------------------------------------------
     40 // SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile
     41 // resolution
     42 //---------------------------------------------------------------------------------
     43 
     44 // The list of interfaces should be in sync with nsStandardURL
     45 // Queries this list of interfaces. If none match, it queries mURI.
     46 NS_IMPL_NSIURIMUTATOR_ISUPPORTS(SubstitutingURL::Mutator, nsIURISetters,
     47                                nsIURIMutator, nsIStandardURLMutator,
     48                                nsIURLMutator, nsIFileURLMutator,
     49                                nsISerializable)
     50 
     51 NS_IMPL_CLASSINFO(SubstitutingURL, nullptr, nsIClassInfo::THREADSAFE,
     52                  NS_SUBSTITUTINGURL_CID)
     53 // Empty CI getter. We only need nsIClassInfo for Serialization
     54 NS_IMPL_CI_INTERFACE_GETTER0(SubstitutingURL)
     55 
     56 NS_IMPL_ADDREF_INHERITED(SubstitutingURL, nsStandardURL)
     57 NS_IMPL_RELEASE_INHERITED(SubstitutingURL, nsStandardURL)
     58 NS_IMPL_QUERY_INTERFACE_CI_INHERITED0(SubstitutingURL, nsStandardURL)
     59 
     60 nsresult SubstitutingURL::EnsureFile() {
     61  nsAutoCString ourScheme;
     62  nsresult rv = GetScheme(ourScheme);
     63  NS_ENSURE_SUCCESS(rv, rv);
     64 
     65  // Get the handler associated with this scheme. It would be nice to just
     66  // pass this in when constructing SubstitutingURLs, but we need a generic
     67  // factory constructor.
     68  nsCOMPtr<nsIIOService> io = do_GetIOService(&rv);
     69  nsCOMPtr<nsIProtocolHandler> handler;
     70  rv = io->GetProtocolHandler(ourScheme.get(), getter_AddRefs(handler));
     71  NS_ENSURE_SUCCESS(rv, rv);
     72  nsCOMPtr<nsISubstitutingProtocolHandler> substHandler =
     73      do_QueryInterface(handler);
     74  if (!substHandler) {
     75    return NS_ERROR_NO_INTERFACE;
     76  }
     77 
     78  nsAutoCString spec;
     79  rv = substHandler->ResolveURI(this, spec);
     80  if (NS_FAILED(rv)) return rv;
     81 
     82  nsAutoCString scheme;
     83  rv = net_ExtractURLScheme(spec, scheme);
     84  if (NS_FAILED(rv)) return rv;
     85 
     86  // Bug 585869:
     87  // In most cases, the scheme is jar if it's not file.
     88  // Regardless, net_GetFileFromURLSpec should be avoided
     89  // when the scheme isn't file.
     90  if (!scheme.EqualsLiteral("file")) return NS_ERROR_NO_INTERFACE;
     91 
     92  return net_GetFileFromURLSpec(spec, getter_AddRefs(mFile));
     93 }
     94 
     95 /* virtual */
     96 nsStandardURL* SubstitutingURL::StartClone() {
     97  SubstitutingURL* clone = new SubstitutingURL();
     98  return clone;
     99 }
    100 
    101 void SubstitutingURL::Serialize(ipc::URIParams& aParams) {
    102  nsStandardURL::Serialize(aParams);
    103  aParams.get_StandardURLParams().isSubstituting() = true;
    104 }
    105 
    106 // SubstitutingJARURI
    107 
    108 SubstitutingJARURI::SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved)
    109    : mSource(source), mResolved(resolved) {}
    110 
    111 // SubstitutingJARURI::nsIURI
    112 
    113 NS_IMETHODIMP
    114 SubstitutingJARURI::Equals(nsIURI* aOther, bool* aResult) {
    115  return EqualsInternal(aOther, eHonorRef, aResult);
    116 }
    117 
    118 NS_IMETHODIMP
    119 SubstitutingJARURI::EqualsExceptRef(nsIURI* aOther, bool* aResult) {
    120  return EqualsInternal(aOther, eIgnoreRef, aResult);
    121 }
    122 
    123 nsresult SubstitutingJARURI::EqualsInternal(nsIURI* aOther,
    124                                            RefHandlingEnum aRefHandlingMode,
    125                                            bool* aResult) {
    126  *aResult = false;
    127  if (!aOther) {
    128    return NS_OK;
    129  }
    130 
    131  nsresult rv;
    132  RefPtr<SubstitutingJARURI> other;
    133  rv =
    134      aOther->QueryInterface(kSubstitutingJARURIImplCID, getter_AddRefs(other));
    135  if (NS_FAILED(rv)) {
    136    return NS_OK;
    137  }
    138 
    139  // We only need to check the source as the resolved URI is the same for a
    140  // given source
    141  return aRefHandlingMode == eHonorRef
    142             ? mSource->Equals(other->mSource, aResult)
    143             : mSource->EqualsExceptRef(other->mSource, aResult);
    144 }
    145 
    146 NS_IMETHODIMP
    147 SubstitutingJARURI::Mutate(nsIURIMutator** aMutator) {
    148  RefPtr<SubstitutingJARURI::Mutator> mutator =
    149      new SubstitutingJARURI::Mutator();
    150  nsresult rv = mutator->InitFromURI(this);
    151  if (NS_FAILED(rv)) {
    152    return rv;
    153  }
    154  mutator.forget(aMutator);
    155  return NS_OK;
    156 }
    157 
    158 void SubstitutingJARURI::Serialize(mozilla::ipc::URIParams& aParams) {
    159  using namespace mozilla::ipc;
    160 
    161  SubstitutingJARURIParams params;
    162  URIParams source;
    163  URIParams resolved;
    164 
    165  mSource->Serialize(source);
    166  mResolved->Serialize(resolved);
    167  params.source() = source;
    168  params.resolved() = resolved;
    169  aParams = params;
    170 }
    171 
    172 size_t SubstitutingJARURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
    173  // We don't need to calcaulte this unless it shows up in DMD.
    174  return 0;
    175 };
    176 
    177 // SubstitutingJARURI::nsISerializable
    178 
    179 NS_IMETHODIMP
    180 SubstitutingJARURI::Read(nsIObjectInputStream* aStream) {
    181  MOZ_ASSERT(!mSource);
    182  MOZ_ASSERT(!mResolved);
    183  NS_ENSURE_ARG_POINTER(aStream);
    184 
    185  nsresult rv;
    186  nsCOMPtr<nsISupports> source;
    187  rv = aStream->ReadObject(true, getter_AddRefs(source));
    188  NS_ENSURE_SUCCESS(rv, rv);
    189 
    190  mSource = do_QueryInterface(source, &rv);
    191  NS_ENSURE_SUCCESS(rv, rv);
    192 
    193  nsCOMPtr<nsISupports> resolved;
    194  rv = aStream->ReadObject(true, getter_AddRefs(resolved));
    195  NS_ENSURE_SUCCESS(rv, rv);
    196 
    197  mResolved = do_QueryInterface(resolved, &rv);
    198  NS_ENSURE_SUCCESS(rv, rv);
    199 
    200  return NS_OK;
    201 }
    202 
    203 NS_IMETHODIMP
    204 SubstitutingJARURI::Write(nsIObjectOutputStream* aStream) {
    205  NS_ENSURE_ARG_POINTER(aStream);
    206 
    207  nsresult rv;
    208  rv = aStream->WriteCompoundObject(mSource, NS_GET_IID(nsISupports), true);
    209  NS_ENSURE_SUCCESS(rv, rv);
    210 
    211  rv = aStream->WriteCompoundObject(mResolved, NS_GET_IID(nsISupports), true);
    212  NS_ENSURE_SUCCESS(rv, rv);
    213 
    214  return NS_OK;
    215 }
    216 
    217 nsresult SubstitutingJARURI::Clone(nsIURI** aURI) {
    218  RefPtr<SubstitutingJARURI> uri = new SubstitutingJARURI();
    219  // SubstitutingJARURI's mSource/mResolved isn't mutable.
    220  uri->mSource = mSource;
    221  uri->mResolved = mResolved;
    222  uri.forget(aURI);
    223 
    224  return NS_OK;
    225 }
    226 
    227 nsresult SubstitutingJARURI::SetUserPass(const nsACString& aInput) {
    228  // If setting same value in mSource, return NS_OK;
    229  if (!mSource) {
    230    return NS_ERROR_NULL_POINTER;
    231  }
    232 
    233  nsAutoCString sourceUserPass;
    234  nsresult rv = mSource->GetUserPass(sourceUserPass);
    235  if (NS_FAILED(rv)) {
    236    return rv;
    237  }
    238  if (aInput.Equals(sourceUserPass)) {
    239    return NS_OK;
    240  }
    241  return NS_ERROR_FAILURE;
    242 }
    243 
    244 nsresult SubstitutingJARURI::SetPort(int32_t aPort) {
    245  // If setting same value in mSource, return NS_OK;
    246  if (!mSource) {
    247    return NS_ERROR_NULL_POINTER;
    248  }
    249 
    250  int32_t sourcePort = -1;
    251  nsresult rv = mSource->GetPort(&sourcePort);
    252  if (NS_FAILED(rv)) {
    253    return rv;
    254  }
    255  if (aPort == sourcePort) {
    256    return NS_OK;
    257  }
    258  return NS_ERROR_FAILURE;
    259 }
    260 
    261 bool SubstitutingJARURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
    262  using namespace mozilla::ipc;
    263 
    264  if (aParams.type() != URIParams::TSubstitutingJARURIParams) {
    265    NS_ERROR("Received unknown parameters from the other process!");
    266    return false;
    267  }
    268 
    269  const SubstitutingJARURIParams& jarUriParams =
    270      aParams.get_SubstitutingJARURIParams();
    271 
    272  nsCOMPtr<nsIURI> source = DeserializeURI(jarUriParams.source());
    273  nsresult rv;
    274  mSource = do_QueryInterface(source, &rv);
    275  if (NS_FAILED(rv)) {
    276    return false;
    277  }
    278  nsCOMPtr<nsIURI> jarUri = DeserializeURI(jarUriParams.resolved());
    279  mResolved = do_QueryInterface(jarUri, &rv);
    280  return NS_SUCCEEDED(rv);
    281 }
    282 
    283 nsresult SubstitutingJARURI::ReadPrivate(nsIObjectInputStream* aStream) {
    284  return Read(aStream);
    285 }
    286 
    287 NS_IMPL_CLASSINFO(SubstitutingJARURI, nullptr, 0, NS_SUBSTITUTINGJARURI_CID)
    288 
    289 NS_IMPL_ADDREF(SubstitutingJARURI)
    290 NS_IMPL_RELEASE(SubstitutingJARURI)
    291 
    292 NS_INTERFACE_MAP_BEGIN(SubstitutingJARURI)
    293  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI)
    294  NS_INTERFACE_MAP_ENTRY(nsIJARURI)
    295  NS_INTERFACE_MAP_ENTRY(nsIURL)
    296  NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
    297  NS_INTERFACE_MAP_ENTRY(nsISerializable)
    298  if (aIID.Equals(kSubstitutingJARURIImplCID)) {
    299    foundInterface = static_cast<nsIURI*>(this);
    300  } else
    301    NS_INTERFACE_MAP_ENTRY(nsIURI)
    302  NS_IMPL_QUERY_CLASSINFO(SubstitutingJARURI)
    303 NS_INTERFACE_MAP_END
    304 
    305 NS_IMPL_CI_INTERFACE_GETTER(SubstitutingJARURI, nsIURI, nsIJARURI, nsIURL,
    306                            nsIStandardURL, nsISerializable)
    307 
    308 NS_IMPL_NSIURIMUTATOR_ISUPPORTS(SubstitutingJARURI::Mutator, nsIURISetters,
    309                                nsIURIMutator, nsISerializable)
    310 
    311 SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme,
    312                                                         bool aEnforceFileOrJar)
    313    : mScheme(aScheme),
    314      mSubstitutionsLock("SubstitutingProtocolHandler::mSubstitutions"),
    315      mSubstitutions(16),
    316      mEnforceFileOrJar(aEnforceFileOrJar) {
    317  ConstructInternal();
    318 }
    319 
    320 void SubstitutingProtocolHandler::ConstructInternal() {
    321  nsresult rv;
    322  mIOService = do_GetIOService(&rv);
    323  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOService);
    324 }
    325 
    326 //
    327 // IPC marshalling.
    328 //
    329 
    330 nsresult SubstitutingProtocolHandler::CollectSubstitutions(
    331    nsTArray<SubstitutionMapping>& aMappings) {
    332  AutoReadLock lock(mSubstitutionsLock);
    333  for (const auto& substitutionEntry : mSubstitutions) {
    334    const SubstitutionEntry& entry = substitutionEntry.GetData();
    335    nsCOMPtr<nsIURI> uri = entry.baseURI;
    336    SerializedURI serialized;
    337    if (uri) {
    338      nsresult rv = uri->GetSpec(serialized.spec);
    339      NS_ENSURE_SUCCESS(rv, rv);
    340    }
    341    SubstitutionMapping substitution = {mScheme,
    342                                        nsCString(substitutionEntry.GetKey()),
    343                                        serialized, entry.flags};
    344    aMappings.AppendElement(substitution);
    345  }
    346 
    347  return NS_OK;
    348 }
    349 
    350 nsresult SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot,
    351                                                       nsIURI* aBaseURI,
    352                                                       uint32_t aFlags) {
    353  if (GeckoProcessType_Content == XRE_GetProcessType()) {
    354    return NS_OK;
    355  }
    356 
    357  nsTArray<ContentParent*> parents;
    358  ContentParent::GetAll(parents);
    359  if (!parents.Length()) {
    360    return NS_OK;
    361  }
    362 
    363  SubstitutionMapping mapping;
    364  mapping.scheme = mScheme;
    365  mapping.path = aRoot;
    366  if (aBaseURI) {
    367    nsresult rv = aBaseURI->GetSpec(mapping.resolvedURI.spec);
    368    NS_ENSURE_SUCCESS(rv, rv);
    369  }
    370  mapping.flags = aFlags;
    371 
    372  for (uint32_t i = 0; i < parents.Length(); i++) {
    373    (void)parents[i]->SendRegisterChromeItem(mapping);
    374  }
    375 
    376  return NS_OK;
    377 }
    378 
    379 //----------------------------------------------------------------------------
    380 // nsIProtocolHandler
    381 //----------------------------------------------------------------------------
    382 
    383 nsresult SubstitutingProtocolHandler::GetScheme(nsACString& result) {
    384  result = mScheme;
    385  return NS_OK;
    386 }
    387 
    388 nsresult SubstitutingProtocolHandler::NewURI(const nsACString& aSpec,
    389                                             const char* aCharset,
    390                                             nsIURI* aBaseURI,
    391                                             nsIURI** aResult) {
    392  // unescape any %2f and %2e to make sure nsStandardURL coalesces them.
    393  // Later net_GetFileFromURLSpec() will do a full unescape and we want to
    394  // treat them the same way the file system will. (bugs 380994, 394075)
    395  nsresult rv;
    396  nsAutoCString spec;
    397  const char* src = aSpec.BeginReading();
    398  const char* end = aSpec.EndReading();
    399  const char* last = src;
    400 
    401  spec.SetCapacity(aSpec.Length() + 1);
    402  for (; src < end; ++src) {
    403    if (*src == '%' && (src < end - 2) && *(src + 1) == '2') {
    404      char ch = '\0';
    405      if (*(src + 2) == 'f' || *(src + 2) == 'F') {
    406        ch = '/';
    407      } else if (*(src + 2) == 'e' || *(src + 2) == 'E') {
    408        ch = '.';
    409      }
    410 
    411      if (ch) {
    412        if (last < src) {
    413          spec.Append(last, src - last);
    414        }
    415        spec.Append(ch);
    416        src += 2;
    417        last = src + 1;  // src will be incremented by the loop
    418      }
    419    }
    420    if (*src == '?' || *src == '#') {
    421      break;  // Don't escape %2f and %2e in the query or ref parts of the URI
    422    }
    423  }
    424 
    425  if (last < end) {
    426    spec.Append(last, end - last);
    427  }
    428 
    429  nsCOMPtr<nsIURI> base(aBaseURI);
    430  nsCOMPtr<nsIURL> uri;
    431  rv =
    432      NS_MutateURI(new SubstitutingURL::Mutator())
    433          .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
    434                 -1, spec, aCharset, base, nullptr)
    435          .Finalize(uri);
    436  if (NS_FAILED(rv)) return rv;
    437 
    438  nsAutoCString host;
    439  rv = uri->GetHost(host);
    440  if (NS_FAILED(rv)) return rv;
    441 
    442  // "android" is the only root that would return the RESOLVE_JAR_URI flag
    443  // see nsResProtocolHandler::GetSubstitutionInternal
    444  if (GetJARFlags(host) & nsISubstitutingProtocolHandler::RESOLVE_JAR_URI) {
    445    return ResolveJARURI(uri, aResult);
    446  }
    447 
    448  uri.forget(aResult);
    449  return NS_OK;
    450 }
    451 
    452 nsresult SubstitutingProtocolHandler::ResolveJARURI(nsIURL* aURL,
    453                                                    nsIURI** aResult) {
    454  nsAutoCString spec;
    455  nsresult rv = ResolveURI(aURL, spec);
    456  NS_ENSURE_SUCCESS(rv, rv);
    457 
    458  nsCOMPtr<nsIURI> resolvedURI;
    459  rv = NS_NewURI(getter_AddRefs(resolvedURI), spec);
    460  NS_ENSURE_SUCCESS(rv, rv);
    461 
    462  nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(resolvedURI);
    463  nsAutoCString scheme;
    464  innermostURI->GetScheme(scheme);
    465 
    466  // We only ever want to resolve to a local jar.
    467  NS_ENSURE_TRUE(scheme.EqualsLiteral("file"), NS_ERROR_UNEXPECTED);
    468 
    469  nsCOMPtr<nsIJARURI> jarURI(do_QueryInterface(resolvedURI));
    470  if (!jarURI) {
    471    // This substitution does not resolve to a jar: URL, so we just
    472    // return the plain SubstitutionURL
    473    nsCOMPtr<nsIURI> url = aURL;
    474    url.forget(aResult);
    475    return NS_OK;
    476  }
    477 
    478  nsCOMPtr<nsIJARURI> result = new SubstitutingJARURI(aURL, jarURI);
    479  result.forget(aResult);
    480 
    481  return rv;
    482 }
    483 
    484 nsresult SubstitutingProtocolHandler::NewChannel(nsIURI* uri,
    485                                                 nsILoadInfo* aLoadInfo,
    486                                                 nsIChannel** result) {
    487  NS_ENSURE_ARG_POINTER(uri);
    488  NS_ENSURE_ARG_POINTER(aLoadInfo);
    489 
    490  nsAutoCString spec;
    491  nsresult rv = ResolveURI(uri, spec);
    492  NS_ENSURE_SUCCESS(rv, rv);
    493 
    494  nsCOMPtr<nsIURI> newURI;
    495  rv = NS_NewURI(getter_AddRefs(newURI), spec);
    496  NS_ENSURE_SUCCESS(rv, rv);
    497 
    498  // We don't want to allow the inner protocol handler to modify the result
    499  // principal URI since we want either |uri| or anything pre-set by upper
    500  // layers to prevail.
    501  nsCOMPtr<nsIURI> savedResultPrincipalURI;
    502  rv =
    503      aLoadInfo->GetResultPrincipalURI(getter_AddRefs(savedResultPrincipalURI));
    504  NS_ENSURE_SUCCESS(rv, rv);
    505 
    506  rv = NS_NewChannelInternal(result, newURI, aLoadInfo);
    507  NS_ENSURE_SUCCESS(rv, rv);
    508 
    509  rv = aLoadInfo->SetResultPrincipalURI(savedResultPrincipalURI);
    510  NS_ENSURE_SUCCESS(rv, rv);
    511  rv = (*result)->SetOriginalURI(uri);
    512  NS_ENSURE_SUCCESS(rv, rv);
    513 
    514  return SubstituteChannel(uri, aLoadInfo, result);
    515 }
    516 
    517 nsresult SubstitutingProtocolHandler::AllowPort(int32_t port,
    518                                                const char* scheme,
    519                                                bool* _retval) {
    520  // don't override anything.
    521  *_retval = false;
    522  return NS_OK;
    523 }
    524 
    525 //----------------------------------------------------------------------------
    526 // nsISubstitutingProtocolHandler
    527 //----------------------------------------------------------------------------
    528 
    529 nsresult SubstitutingProtocolHandler::SetSubstitution(const nsACString& root,
    530                                                      nsIURI* baseURI) {
    531  // Add-ons use this API but they should not be able to make anything
    532  // content-accessible.
    533  return SetSubstitutionWithFlags(root, baseURI, 0);
    534 }
    535 
    536 nsresult SubstitutingProtocolHandler::SetSubstitutionWithFlags(
    537    const nsACString& origRoot, nsIURI* baseURI, uint32_t flags) {
    538  nsAutoCString root;
    539  ToLowerCase(origRoot, root);
    540 
    541  if (!baseURI) {
    542    {
    543      AutoWriteLock lock(mSubstitutionsLock);
    544      mSubstitutions.Remove(root);
    545    }
    546 
    547    return SendSubstitution(root, baseURI, flags);
    548  }
    549 
    550  // If baseURI isn't a same-scheme URI, we can set the substitution
    551  // immediately.
    552  nsAutoCString scheme;
    553  nsresult rv = baseURI->GetScheme(scheme);
    554  NS_ENSURE_SUCCESS(rv, rv);
    555  if (!scheme.Equals(mScheme)) {
    556    if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") &&
    557        !scheme.EqualsLiteral("jar") && !scheme.EqualsLiteral("app") &&
    558        !scheme.EqualsLiteral("resource")) {
    559      NS_WARNING("Refusing to create substituting URI to non-file:// target");
    560      return NS_ERROR_INVALID_ARG;
    561    }
    562 
    563    {
    564      AutoWriteLock lock(mSubstitutionsLock);
    565      mSubstitutions.InsertOrUpdate(root, SubstitutionEntry{baseURI, flags});
    566    }
    567 
    568    return SendSubstitution(root, baseURI, flags);
    569  }
    570 
    571  // baseURI is a same-type substituting URI, let's resolve it first.
    572  nsAutoCString newBase;
    573  rv = ResolveURI(baseURI, newBase);
    574  if (NS_FAILED(rv)) return rv;
    575 
    576  nsCOMPtr<nsIURI> newBaseURI;
    577  rv =
    578      mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
    579  NS_ENSURE_SUCCESS(rv, rv);
    580 
    581  {
    582    AutoWriteLock lock(mSubstitutionsLock);
    583    mSubstitutions.InsertOrUpdate(root, SubstitutionEntry{newBaseURI, flags});
    584  }
    585 
    586  return SendSubstitution(root, newBaseURI, flags);
    587 }
    588 
    589 nsresult SubstitutingProtocolHandler::GetSubstitution(
    590    const nsACString& origRoot, nsIURI** result) {
    591  NS_ENSURE_ARG_POINTER(result);
    592 
    593  nsAutoCString root;
    594  ToLowerCase(origRoot, root);
    595 
    596  {
    597    AutoReadLock lock(mSubstitutionsLock);
    598    SubstitutionEntry entry;
    599    if (mSubstitutions.Get(root, &entry)) {
    600      nsCOMPtr<nsIURI> baseURI = entry.baseURI;
    601      baseURI.forget(result);
    602      return NS_OK;
    603    }
    604  }
    605 
    606  return GetSubstitutionInternal(root, result);
    607 }
    608 
    609 nsresult SubstitutingProtocolHandler::GetSubstitutionFlags(
    610    const nsACString& root, uint32_t* flags) {
    611 #ifdef DEBUG
    612  nsAutoCString lcRoot;
    613  ToLowerCase(root, lcRoot);
    614  MOZ_ASSERT(root.Equals(lcRoot),
    615             "GetSubstitutionFlags should never receive mixed-case root name");
    616 #endif
    617 
    618  *flags = 0;
    619 
    620  {
    621    AutoReadLock lock(mSubstitutionsLock);
    622 
    623    SubstitutionEntry entry;
    624    if (mSubstitutions.Get(root, &entry)) {
    625      *flags = entry.flags;
    626      return NS_OK;
    627    }
    628  }
    629 
    630  nsCOMPtr<nsIURI> baseURI;
    631  *flags = GetJARFlags(root);
    632  return GetSubstitutionInternal(root, getter_AddRefs(baseURI));
    633 }
    634 
    635 nsresult SubstitutingProtocolHandler::HasSubstitution(
    636    const nsACString& origRoot, bool* result) {
    637  NS_ENSURE_ARG_POINTER(result);
    638 
    639  nsAutoCString root;
    640  ToLowerCase(origRoot, root);
    641 
    642  *result = HasSubstitution(root);
    643  return NS_OK;
    644 }
    645 
    646 nsresult SubstitutingProtocolHandler::ResolveURI(nsIURI* uri,
    647                                                 nsACString& result) {
    648  nsresult rv;
    649 
    650  nsAutoCString host;
    651  nsAutoCString path;
    652  nsAutoCString pathname;
    653 
    654  nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
    655  if (!url) {
    656    return NS_ERROR_MALFORMED_URI;
    657  }
    658 
    659  rv = uri->GetAsciiHost(host);
    660  if (NS_FAILED(rv)) return rv;
    661 
    662  rv = uri->GetPathQueryRef(path);
    663  if (NS_FAILED(rv)) return rv;
    664 
    665  rv = url->GetFilePath(pathname);
    666  if (NS_FAILED(rv)) return rv;
    667 
    668  if (ResolveSpecialCases(host, path, pathname, result)) {
    669    return NS_OK;
    670  }
    671 
    672  nsCOMPtr<nsIURI> baseURI;
    673  rv = GetSubstitution(host, getter_AddRefs(baseURI));
    674  if (NS_FAILED(rv)) return rv;
    675 
    676  // Unescape the path so we can perform some checks on it.
    677  NS_UnescapeURL(pathname);
    678  if (pathname.FindChar('\\') != -1) {
    679    return NS_ERROR_MALFORMED_URI;
    680  }
    681 
    682  // Some code relies on an empty path resolving to a file rather than a
    683  // directory.
    684  NS_ASSERTION(path.CharAt(0) == '/', "Path must begin with '/'");
    685  if (path.Length() == 1) {
    686    rv = baseURI->GetSpec(result);
    687  } else {
    688    // Make sure we always resolve the path as file-relative to our target URI.
    689    // When the baseURI is a nsIFileURL, and the directory it points to doesn't
    690    // exist, it doesn't end with a /. In that case, a file-relative resolution
    691    // is going to pick something in the parent directory, so we resolve using
    692    // an absolute path derived from the full path in that case.
    693    nsCOMPtr<nsIFileURL> baseDir = do_QueryInterface(baseURI);
    694    if (baseDir) {
    695      nsAutoCString basePath;
    696      rv = baseURI->GetFilePath(basePath);
    697      if (NS_SUCCEEDED(rv) && !StringEndsWith(basePath, "/"_ns)) {
    698        // Cf. the assertion above, path already starts with a /, so prefixing
    699        // with a string that doesn't end with one will leave us wit the right
    700        // amount of /.
    701        path.Insert(basePath, 0);
    702      } else {
    703        // Allow to fall through below.
    704        baseDir = nullptr;
    705      }
    706    }
    707    if (!baseDir) {
    708      path.Insert('.', 0);
    709    }
    710    rv = baseURI->Resolve(path, result);
    711  }
    712 
    713  if (NS_WARN_IF(NS_FAILED(rv))) {
    714    return rv;
    715  }
    716 
    717  if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) {
    718    nsAutoCString spec;
    719    uri->GetAsciiSpec(spec);
    720    MOZ_LOG(gResLog, LogLevel::Debug,
    721            ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get()));
    722  }
    723  return rv;
    724 }
    725 
    726 }  // namespace net
    727 }  // namespace mozilla