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