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