Line data Source code
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 "ExtensionProtocolHandler.h"
8 :
9 : #include "mozilla/AbstractThread.h"
10 : #include "mozilla/ClearOnShutdown.h"
11 : #include "mozilla/ExtensionPolicyService.h"
12 : #include "mozilla/FileUtils.h"
13 : #include "mozilla/ipc/IPCStreamUtils.h"
14 : #include "mozilla/ipc/URIParams.h"
15 : #include "mozilla/ipc/URIUtils.h"
16 : #include "mozilla/net/NeckoChild.h"
17 : #include "mozilla/RefPtr.h"
18 :
19 : #include "FileDescriptor.h"
20 : #include "FileDescriptorFile.h"
21 : #include "LoadInfo.h"
22 : #include "nsServiceManagerUtils.h"
23 : #include "nsIFile.h"
24 : #include "nsIFileChannel.h"
25 : #include "nsIFileStreams.h"
26 : #include "nsIFileURL.h"
27 : #include "nsIJARChannel.h"
28 : #include "nsIMIMEService.h"
29 : #include "nsIURL.h"
30 : #include "nsIChannel.h"
31 : #include "nsIInputStreamPump.h"
32 : #include "nsIJARURI.h"
33 : #include "nsIStreamListener.h"
34 : #include "nsIThread.h"
35 : #include "nsIInputStream.h"
36 : #include "nsIOutputStream.h"
37 : #include "nsIStreamConverterService.h"
38 : #include "nsNetUtil.h"
39 : #include "prio.h"
40 : #include "SimpleChannel.h"
41 :
42 : #if defined(XP_WIN)
43 : #include "nsILocalFileWin.h"
44 : #endif
45 :
46 : #if !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
47 : #include "mozilla/SandboxSettings.h"
48 : #endif
49 :
50 : #define EXTENSION_SCHEME "moz-extension"
51 : using mozilla::ipc::FileDescriptor;
52 : using OptionalIPCStream = mozilla::ipc::OptionalIPCStream;
53 :
54 : namespace mozilla {
55 :
56 : template <>
57 : class MOZ_MUST_USE_TYPE GenericErrorResult<nsresult>
58 : {
59 : nsresult mErrorValue;
60 :
61 : template<typename V, typename E2> friend class Result;
62 :
63 : public:
64 0 : explicit GenericErrorResult(nsresult aErrorValue) : mErrorValue(aErrorValue) {}
65 :
66 0 : operator nsresult() { return mErrorValue; }
67 : };
68 :
69 : namespace net {
70 :
71 : using extensions::URLInfo;
72 :
73 3 : StaticRefPtr<ExtensionProtocolHandler> ExtensionProtocolHandler::sSingleton;
74 :
75 : static inline Result<Ok, nsresult>
76 : WrapNSResult(PRStatus aRv)
77 : {
78 : if (aRv != PR_SUCCESS) {
79 : return Err(NS_ERROR_FAILURE);
80 : }
81 : return Ok();
82 : }
83 :
84 : static inline Result<Ok, nsresult>
85 0 : WrapNSResult(nsresult aRv)
86 : {
87 0 : if (NS_FAILED(aRv)) {
88 0 : return Err(aRv);
89 : }
90 0 : return Ok();
91 : }
92 :
93 : #define NS_TRY(expr) MOZ_TRY(WrapNSResult(expr))
94 :
95 : /**
96 : * Helper class used with SimpleChannel to asynchronously obtain an input
97 : * stream or file descriptor from the parent for a remote moz-extension load
98 : * from the child.
99 : */
100 : class ExtensionStreamGetter : public RefCounted<ExtensionStreamGetter>
101 : {
102 : public:
103 : // To use when getting a remote input stream for a resource
104 : // in an unpacked extension.
105 0 : ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
106 0 : : mURI(aURI)
107 : , mLoadInfo(aLoadInfo)
108 0 : , mIsJarChannel(false)
109 : {
110 0 : MOZ_ASSERT(aURI);
111 0 : MOZ_ASSERT(aLoadInfo);
112 0 : }
113 :
114 : // To use when getting an FD for a packed extension JAR file
115 : // in order to load a resource.
116 0 : ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo,
117 : already_AddRefed<nsIJARChannel>&& aJarChannel,
118 : nsIFile* aJarFile)
119 0 : : mURI(aURI)
120 : , mLoadInfo(aLoadInfo)
121 0 : , mJarChannel(Move(aJarChannel))
122 : , mJarFile(aJarFile)
123 0 : , mIsJarChannel(true)
124 : {
125 0 : MOZ_ASSERT(aURI);
126 0 : MOZ_ASSERT(aLoadInfo);
127 0 : MOZ_ASSERT(mJarChannel);
128 0 : MOZ_ASSERT(aJarFile);
129 0 : }
130 :
131 0 : ~ExtensionStreamGetter() {}
132 :
133 : // Get an input stream or file descriptor from the parent asynchronously.
134 : Result<Ok, nsresult> GetAsync(nsIStreamListener* aListener,
135 : nsIChannel* aChannel);
136 :
137 : // Handle an input stream being returned from the parent
138 : void OnStream(nsIInputStream* aStream);
139 :
140 : // Handle file descriptor being returned from the parent
141 : void OnFD(const FileDescriptor& aFD);
142 :
143 0 : MOZ_DECLARE_REFCOUNTED_TYPENAME(ExtensionStreamGetter)
144 :
145 : private:
146 : nsCOMPtr<nsIURI> mURI;
147 : nsCOMPtr<nsILoadInfo> mLoadInfo;
148 : nsCOMPtr<nsIJARChannel> mJarChannel;
149 : nsCOMPtr<nsIFile> mJarFile;
150 : nsCOMPtr<nsIStreamListener> mListener;
151 : nsCOMPtr<nsIChannel> mChannel;
152 : bool mIsJarChannel;
153 : };
154 :
155 : class ExtensionJARFileOpener : public nsISupports
156 : {
157 : public:
158 0 : ExtensionJARFileOpener(nsIFile* aFile,
159 0 : NeckoParent::GetExtensionFDResolver& aResolve) :
160 : mFile(aFile),
161 0 : mResolve(aResolve)
162 : {
163 0 : MOZ_ASSERT(aFile);
164 0 : MOZ_ASSERT(aResolve);
165 0 : }
166 :
167 0 : NS_IMETHOD OpenFile()
168 : {
169 0 : MOZ_ASSERT(!NS_IsMainThread());
170 0 : AutoFDClose prFileDesc;
171 :
172 : #if defined(XP_WIN)
173 : nsresult rv;
174 : nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(mFile, &rv);
175 : MOZ_ASSERT(winFile);
176 : if (NS_SUCCEEDED(rv)) {
177 : rv = winFile->OpenNSPRFileDescShareDelete(PR_RDONLY, 0,
178 : &prFileDesc.rwget());
179 : }
180 : #else
181 0 : nsresult rv = mFile->OpenNSPRFileDesc(PR_RDONLY, 0, &prFileDesc.rwget());
182 : #endif /* XP_WIN */
183 :
184 0 : if (NS_SUCCEEDED(rv)) {
185 0 : mFD = FileDescriptor(FileDescriptor::PlatformHandleType(
186 0 : PR_FileDesc2NativeHandle(prFileDesc)));
187 : }
188 :
189 : nsCOMPtr<nsIRunnable> event =
190 0 : mozilla::NewRunnableMethod("ExtensionJarFileFDResolver",
191 0 : this, &ExtensionJARFileOpener::SendBackFD);
192 :
193 0 : rv = NS_DispatchToMainThread(event, nsIEventTarget::DISPATCH_NORMAL);
194 0 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread");
195 0 : return NS_OK;
196 : }
197 :
198 0 : NS_IMETHOD SendBackFD()
199 : {
200 0 : MOZ_ASSERT(NS_IsMainThread());
201 0 : mResolve(mFD);
202 0 : return NS_OK;
203 : }
204 :
205 : NS_DECL_THREADSAFE_ISUPPORTS
206 :
207 : private:
208 0 : virtual ~ExtensionJARFileOpener() {}
209 :
210 : nsCOMPtr<nsIFile> mFile;
211 : NeckoParent::GetExtensionFDResolver mResolve;
212 : FileDescriptor mFD;
213 : };
214 :
215 0 : NS_IMPL_ISUPPORTS(ExtensionJARFileOpener, nsISupports)
216 :
217 : // The amount of time, in milliseconds, that the file opener thread will remain
218 : // allocated after it is used. This value chosen because to match other uses
219 : // of LazyIdleThread.
220 : #define DEFAULT_THREAD_TIMEOUT_MS 30000
221 :
222 : // Request an FD or input stream from the parent.
223 : Result<Ok, nsresult>
224 0 : ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener,
225 : nsIChannel* aChannel)
226 : {
227 0 : MOZ_ASSERT(IsNeckoChild());
228 :
229 0 : mListener = aListener;
230 0 : mChannel = aChannel;
231 :
232 : // Serialize the URI to send to parent
233 0 : mozilla::ipc::URIParams uri;
234 0 : SerializeURI(mURI, uri);
235 :
236 : // Serialize the LoadInfo to send to parent
237 0 : OptionalLoadInfoArgs loadInfo;
238 0 : NS_TRY(mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfo));
239 :
240 0 : RefPtr<ExtensionStreamGetter> self = this;
241 0 : if (mIsJarChannel) {
242 : // Request an FD for this moz-extension URI
243 0 : gNeckoChild->SendGetExtensionFD(uri, loadInfo)->Then(
244 0 : AbstractThread::MainThread(),
245 : __func__,
246 0 : [self] (const FileDescriptor& fd) {
247 0 : self->OnFD(fd);
248 0 : },
249 0 : [self] (const mozilla::ipc::PromiseRejectReason) {
250 0 : self->OnFD(FileDescriptor());
251 0 : }
252 0 : );
253 0 : return Ok();
254 : }
255 :
256 : // Request an input stream for this moz-extension URI
257 0 : gNeckoChild->SendGetExtensionStream(uri, loadInfo)->Then(
258 0 : AbstractThread::MainThread(),
259 : __func__,
260 0 : [self] (const OptionalIPCStream& stream) {
261 0 : nsCOMPtr<nsIInputStream> inputStream;
262 0 : if (stream.type() == OptionalIPCStream::OptionalIPCStream::TIPCStream) {
263 0 : inputStream = ipc::DeserializeIPCStream(stream);
264 : }
265 0 : self->OnStream(inputStream);
266 0 : },
267 0 : [self] (const mozilla::ipc::PromiseRejectReason) {
268 0 : self->OnStream(nullptr);
269 0 : }
270 0 : );
271 0 : return Ok();
272 : }
273 :
274 : // Handle an input stream sent from the parent.
275 : void
276 0 : ExtensionStreamGetter::OnStream(nsIInputStream* aStream)
277 : {
278 0 : MOZ_ASSERT(IsNeckoChild());
279 0 : MOZ_ASSERT(mListener);
280 :
281 : // We must keep an owning reference to the listener
282 : // until we pass it on to AsyncRead.
283 0 : nsCOMPtr<nsIStreamListener> listener = mListener.forget();
284 :
285 0 : MOZ_ASSERT(mChannel);
286 :
287 0 : if (!aStream) {
288 : // The parent didn't send us back a stream.
289 0 : listener->OnStartRequest(mChannel, nullptr);
290 0 : listener->OnStopRequest(mChannel, nullptr, NS_ERROR_FILE_ACCESS_DENIED);
291 0 : mChannel->Cancel(NS_BINDING_ABORTED);
292 0 : return;
293 : }
294 :
295 0 : nsCOMPtr<nsIInputStreamPump> pump;
296 0 : nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), aStream);
297 0 : if (NS_FAILED(rv)) {
298 0 : mChannel->Cancel(NS_BINDING_ABORTED);
299 0 : return;
300 : }
301 :
302 0 : rv = pump->AsyncRead(listener, nullptr);
303 0 : if (NS_FAILED(rv)) {
304 0 : mChannel->Cancel(NS_BINDING_ABORTED);
305 : }
306 : }
307 :
308 : // Handle an FD sent from the parent.
309 : void
310 0 : ExtensionStreamGetter::OnFD(const FileDescriptor& aFD)
311 : {
312 0 : MOZ_ASSERT(IsNeckoChild());
313 0 : MOZ_ASSERT(mListener);
314 0 : MOZ_ASSERT(mChannel);
315 :
316 0 : if (!aFD.IsValid()) {
317 0 : OnStream(nullptr);
318 0 : return;
319 : }
320 :
321 : // We must keep an owning reference to the listener
322 : // until we pass it on to AsyncOpen2.
323 0 : nsCOMPtr<nsIStreamListener> listener = mListener.forget();
324 :
325 0 : RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile);
326 0 : mJarChannel->SetJarFile(fdFile);
327 0 : nsresult rv = mJarChannel->AsyncOpen2(listener);
328 0 : if (NS_FAILED(rv)) {
329 0 : mChannel->Cancel(NS_BINDING_ABORTED);
330 : }
331 : }
332 :
333 0 : NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler, nsISubstitutingProtocolHandler,
334 : nsIProtocolHandler, nsIProtocolHandlerWithDynamicFlags,
335 : nsISupportsWeakReference)
336 0 : NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
337 0 : NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
338 :
339 : already_AddRefed<ExtensionProtocolHandler>
340 0 : ExtensionProtocolHandler::GetSingleton()
341 : {
342 0 : if (!sSingleton) {
343 0 : sSingleton = new ExtensionProtocolHandler();
344 0 : ClearOnShutdown(&sSingleton);
345 : }
346 0 : return do_AddRef(sSingleton.get());
347 : }
348 :
349 0 : ExtensionProtocolHandler::ExtensionProtocolHandler()
350 0 : : SubstitutingProtocolHandler(EXTENSION_SCHEME)
351 : #if !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
352 : , mAlreadyCheckedDevRepo(false)
353 : #endif
354 : {
355 0 : mUseRemoteFileChannels = IsNeckoChild() &&
356 0 : Preferences::GetBool("extensions.webextensions.protocol.remote");
357 0 : }
358 :
359 : static inline ExtensionPolicyService&
360 0 : EPS()
361 : {
362 0 : return ExtensionPolicyService::GetSingleton();
363 : }
364 :
365 : nsresult
366 0 : ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags)
367 : {
368 : // In general a moz-extension URI is only loadable by chrome, but a whitelisted
369 : // subset are web-accessible (and cross-origin fetchable). Check that whitelist.
370 0 : bool loadableByAnyone = false;
371 :
372 0 : URLInfo url(aURI);
373 0 : if (auto* policy = EPS().GetByURL(url)) {
374 0 : loadableByAnyone = policy->IsPathWebAccessible(url.FilePath());
375 : }
376 :
377 0 : *aFlags = URI_STD | URI_IS_LOCAL_RESOURCE | (loadableByAnyone ? (URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE) : URI_DANGEROUS_TO_LOAD);
378 0 : return NS_OK;
379 : }
380 :
381 : bool
382 0 : ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
383 : const nsACString& aPath,
384 : const nsACString& aPathname,
385 : nsACString& aResult)
386 : {
387 : // Create special moz-extension:-pages such as moz-extension://foo/_blank.html
388 : // for all registered extensions. We can't just do this as a substitution
389 : // because substitutions can only match on host.
390 0 : if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) {
391 0 : return false;
392 : }
393 :
394 0 : if (aPathname.EqualsLiteral("/_blank.html")) {
395 0 : aResult.AssignLiteral("about:blank");
396 0 : return true;
397 : }
398 :
399 0 : if (aPathname.EqualsLiteral("/_generated_background_page.html")) {
400 0 : Unused << EPS().GetGeneratedBackgroundPageUrl(aHost, aResult);
401 0 : return !aResult.IsEmpty();
402 : }
403 :
404 0 : return false;
405 : }
406 :
407 : // For file or JAR URI's, substitute in a remote channel.
408 : Result<Ok, nsresult>
409 0 : ExtensionProtocolHandler::SubstituteRemoteChannel(nsIURI* aURI,
410 : nsILoadInfo* aLoadInfo,
411 : nsIChannel** aRetVal)
412 : {
413 0 : MOZ_ASSERT(IsNeckoChild());
414 0 : NS_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
415 0 : NS_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
416 :
417 0 : nsAutoCString unResolvedSpec;
418 0 : NS_TRY(aURI->GetSpec(unResolvedSpec));
419 :
420 0 : nsAutoCString resolvedSpec;
421 0 : NS_TRY(ResolveURI(aURI, resolvedSpec));
422 :
423 : // Use the target URI scheme to determine if this is a packed or unpacked
424 : // extension URI. For unpacked extensions, we'll request an input stream
425 : // from the parent. For a packed extension, we'll request a file descriptor
426 : // for the JAR file.
427 0 : nsAutoCString scheme;
428 0 : NS_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
429 :
430 0 : if (scheme.EqualsLiteral("file")) {
431 : // Unpacked extension
432 0 : SubstituteRemoteFileChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
433 0 : return Ok();
434 : }
435 :
436 0 : if (scheme.EqualsLiteral("jar")) {
437 : // Packed extension
438 0 : return SubstituteRemoteJarChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
439 : }
440 :
441 : // Only unpacked resource files and JAR files are remoted.
442 : // No other moz-extension loads should be reading from the filesystem.
443 0 : return Ok();
444 : }
445 :
446 : nsresult
447 0 : ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
448 : nsILoadInfo* aLoadInfo,
449 : nsIChannel** result)
450 : {
451 : nsresult rv;
452 0 : nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
453 0 : NS_ENSURE_SUCCESS(rv, rv);
454 :
455 0 : if (mUseRemoteFileChannels) {
456 0 : MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, result));
457 : }
458 :
459 0 : nsAutoCString ext;
460 0 : rv = url->GetFileExtension(ext);
461 0 : NS_ENSURE_SUCCESS(rv, rv);
462 :
463 0 : if (!ext.LowerCaseEqualsLiteral("css")) {
464 0 : return NS_OK;
465 : }
466 :
467 : // Filter CSS files to replace locale message tokens with localized strings.
468 :
469 0 : bool haveLoadInfo = aLoadInfo;
470 0 : nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
471 : aURI, aLoadInfo, *result,
472 0 : [haveLoadInfo] (nsIStreamListener* listener, nsIChannel* channel, nsIChannel* origChannel) -> RequestOrReason {
473 : nsresult rv;
474 : nsCOMPtr<nsIStreamConverterService> convService =
475 0 : do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
476 0 : NS_TRY(rv);
477 :
478 0 : nsCOMPtr<nsIURI> uri;
479 0 : NS_TRY(channel->GetURI(getter_AddRefs(uri)));
480 :
481 0 : const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
482 0 : const char* kToType = "text/css";
483 :
484 0 : nsCOMPtr<nsIStreamListener> converter;
485 0 : NS_TRY(convService->AsyncConvertData(kFromType, kToType, listener,
486 : uri, getter_AddRefs(converter)));
487 0 : if (haveLoadInfo) {
488 0 : NS_TRY(origChannel->AsyncOpen2(converter));
489 : } else {
490 0 : NS_TRY(origChannel->AsyncOpen(converter, nullptr));
491 : }
492 :
493 0 : return RequestOrReason(origChannel);
494 0 : });
495 0 : NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY);
496 :
497 0 : if (aLoadInfo) {
498 : nsCOMPtr<nsILoadInfo> loadInfo =
499 0 : static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest();
500 0 : (*result)->SetLoadInfo(loadInfo);
501 : }
502 :
503 0 : channel.swap(*result);
504 :
505 0 : return NS_OK;
506 : }
507 :
508 : #if !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
509 : // The |aRequestedFile| argument must already be Normalize()'d
510 : Result<Ok, nsresult>
511 : ExtensionProtocolHandler::DevRepoContains(nsIFile* aRequestedFile,
512 : bool *aResult)
513 : {
514 : MOZ_ASSERT(!IsNeckoChild());
515 : MOZ_ASSERT(aResult);
516 : *aResult = false;
517 :
518 : // On the first invocation, set mDevRepo if this is a
519 : // development build with MOZ_DEVELOPER_REPO_DIR set.
520 : if (!mAlreadyCheckedDevRepo) {
521 : mAlreadyCheckedDevRepo = true;
522 : if (mozilla::IsDevelopmentBuild()) {
523 : char *developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
524 : if (developer_repo_dir) {
525 : NS_TRY(NS_NewLocalFile(NS_ConvertUTF8toUTF16(developer_repo_dir),
526 : false, getter_AddRefs(mDevRepo)));
527 : NS_TRY(mDevRepo->Normalize());
528 : }
529 : }
530 : }
531 :
532 : if (mDevRepo) {
533 : // This is a development build
534 : NS_TRY(mDevRepo->Contains(aRequestedFile, aResult));
535 : }
536 :
537 : return Ok();
538 : }
539 : #endif /* !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX) */
540 :
541 : Result<nsCOMPtr<nsIInputStream>, nsresult>
542 0 : ExtensionProtocolHandler::NewStream(nsIURI* aChildURI,
543 : nsILoadInfo* aChildLoadInfo,
544 : bool* aTerminateSender)
545 : {
546 0 : MOZ_ASSERT(!IsNeckoChild());
547 0 : NS_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
548 0 : NS_TRY(aChildLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
549 0 : NS_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
550 :
551 0 : *aTerminateSender = true;
552 : nsresult rv;
553 :
554 : // We should never receive a URI that isn't for a moz-extension because
555 : // these requests ordinarily come from the child's ExtensionProtocolHandler.
556 : // Ensure this request is for a moz-extension URI. A rogue child process
557 : // could send us any URI.
558 0 : bool isExtScheme = false;
559 0 : if (NS_FAILED(aChildURI->SchemeIs(EXTENSION_SCHEME, &isExtScheme)) ||
560 0 : !isExtScheme) {
561 0 : return Err(NS_ERROR_UNKNOWN_PROTOCOL);
562 : }
563 :
564 : // For errors after this point, we want to propagate the error to
565 : // the child, but we don't force the child to be terminated because
566 : // the error is likely to be due to a bug in the extension.
567 0 : *aTerminateSender = false;
568 :
569 : /*
570 : * Make sure there is a substitution installed for the host found
571 : * in the child's request URI and make sure the host resolves to
572 : * a directory.
573 : */
574 :
575 0 : nsAutoCString host;
576 0 : NS_TRY(aChildURI->GetAsciiHost(host));
577 :
578 : // Lookup the directory this host string resolves to
579 0 : nsCOMPtr<nsIURI> baseURI;
580 0 : NS_TRY(GetSubstitution(host, getter_AddRefs(baseURI)));
581 :
582 : // The result should be a file URL for the extension base dir
583 0 : nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(baseURI, &rv);
584 0 : NS_TRY(rv);
585 :
586 0 : nsCOMPtr<nsIFile> extensionDir;
587 0 : NS_TRY(fileURL->GetFile(getter_AddRefs(extensionDir)));
588 :
589 0 : bool isDirectory = false;
590 0 : NS_TRY(extensionDir->IsDirectory(&isDirectory));
591 0 : if (!isDirectory) {
592 : // The host should map to a directory for unpacked extensions
593 0 : return Err(NS_ERROR_FILE_NOT_DIRECTORY);
594 : }
595 :
596 : /*
597 : * Now get a channel for the resolved child URI and make sure the
598 : * channel is a file channel.
599 : */
600 :
601 0 : nsCOMPtr<nsIChannel> channel;
602 0 : NS_TRY(NS_NewChannelInternal(getter_AddRefs(channel),
603 : aChildURI,
604 : aChildLoadInfo));
605 :
606 : // Channel should be a file channel. It should never be a JAR
607 : // channel because we only request remote streams for unpacked
608 : // extension resource loads where the URI resolves to a file.
609 0 : nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel, &rv);
610 0 : NS_TRY(rv);
611 :
612 0 : nsCOMPtr<nsIFile> requestedFile;
613 0 : NS_TRY(fileChannel->GetFile(getter_AddRefs(requestedFile)));
614 :
615 : /*
616 : * Make sure the file we resolved to is within the extension directory.
617 : */
618 :
619 : // Normalize paths for sane comparisons. nsIFile::Contains depends on
620 : // it for reliable subpath checks.
621 0 : NS_TRY(extensionDir->Normalize());
622 0 : NS_TRY(requestedFile->Normalize());
623 :
624 0 : bool isResourceFromExtensionDir = false;
625 0 : NS_TRY(extensionDir->Contains(requestedFile, &isResourceFromExtensionDir));
626 0 : if (!isResourceFromExtensionDir) {
627 : #if defined(XP_WIN)
628 : return Err(NS_ERROR_FILE_ACCESS_DENIED);
629 : #elif defined(MOZ_CONTENT_SANDBOX)
630 : // On a dev build, we allow an unpacked resource that isn't
631 : // from the extension directory as long as it is from the repo.
632 : bool isResourceFromDevRepo = false;
633 : MOZ_TRY(DevRepoContains(requestedFile, &isResourceFromDevRepo));
634 : if (!isResourceFromDevRepo) {
635 : return Err(NS_ERROR_FILE_ACCESS_DENIED);
636 : }
637 : #endif /* defined(XP_WIN) */
638 : }
639 :
640 0 : nsCOMPtr<nsIInputStream> inputStream;
641 0 : NS_TRY(NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
642 : requestedFile,
643 : PR_RDONLY,
644 : -1,
645 : nsIFileInputStream::DEFER_OPEN));
646 :
647 0 : return inputStream;
648 : }
649 :
650 : Result<Ok, nsresult>
651 0 : ExtensionProtocolHandler::NewFD(nsIURI* aChildURI,
652 : nsILoadInfo* aChildLoadInfo,
653 : bool* aTerminateSender,
654 : NeckoParent::GetExtensionFDResolver& aResolve)
655 : {
656 0 : MOZ_ASSERT(!IsNeckoChild());
657 0 : NS_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
658 0 : NS_TRY(aChildLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
659 0 : NS_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
660 :
661 0 : *aTerminateSender = true;
662 : nsresult rv;
663 :
664 : // Ensure this is a moz-extension URI
665 0 : bool isExtScheme = false;
666 0 : if (NS_FAILED(aChildURI->SchemeIs(EXTENSION_SCHEME, &isExtScheme)) ||
667 0 : !isExtScheme) {
668 0 : return Err(NS_ERROR_UNKNOWN_PROTOCOL);
669 : }
670 :
671 : // For errors after this point, we want to propagate the error to
672 : // the child, but we don't force the child to be terminated.
673 0 : *aTerminateSender = false;
674 :
675 0 : nsAutoCString host;
676 0 : NS_TRY(aChildURI->GetAsciiHost(host));
677 :
678 : // We expect the host string to map to a JAR file because the URI
679 : // should refer to a web accessible resource for an enabled extension.
680 0 : nsCOMPtr<nsIURI> subURI;
681 0 : NS_TRY(GetSubstitution(host, getter_AddRefs(subURI)));
682 :
683 0 : nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(subURI, &rv);
684 0 : NS_TRY(rv);
685 :
686 0 : nsCOMPtr<nsIURI> innerFileURI;
687 0 : NS_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
688 :
689 0 : nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
690 0 : NS_TRY(rv);
691 :
692 0 : nsCOMPtr<nsIFile> jarFile;
693 0 : NS_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
694 :
695 0 : if (!mFileOpenerThread) {
696 : mFileOpenerThread =
697 : new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
698 0 : NS_LITERAL_CSTRING("ExtensionProtocolHandler"));
699 : }
700 :
701 : RefPtr<ExtensionJARFileOpener> fileOpener =
702 0 : new ExtensionJARFileOpener(jarFile, aResolve);
703 :
704 : nsCOMPtr<nsIRunnable> event =
705 0 : mozilla::NewRunnableMethod("ExtensionJarFileOpener",
706 0 : fileOpener, &ExtensionJARFileOpener::OpenFile);
707 :
708 0 : NS_TRY(mFileOpenerThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL));
709 :
710 0 : return Ok();
711 : }
712 :
713 : static void
714 0 : NewSimpleChannel(nsIURI* aURI,
715 : nsILoadInfo* aLoadinfo,
716 : ExtensionStreamGetter* aStreamGetter,
717 : nsIChannel** aRetVal)
718 : {
719 0 : nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
720 : aURI, aLoadinfo, aStreamGetter,
721 : [] (nsIStreamListener* listener, nsIChannel* channel,
722 0 : ExtensionStreamGetter* getter) -> RequestOrReason {
723 0 : MOZ_TRY(getter->GetAsync(listener, channel));
724 0 : return RequestOrReason(nullptr);
725 0 : });
726 :
727 : nsresult rv;
728 0 : nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
729 0 : if (NS_SUCCEEDED(rv)) {
730 0 : nsAutoCString contentType;
731 0 : rv = mime->GetTypeFromURI(aURI, contentType);
732 0 : if (NS_SUCCEEDED(rv)) {
733 0 : Unused << channel->SetContentType(contentType);
734 : }
735 : }
736 :
737 0 : channel.swap(*aRetVal);
738 0 : }
739 :
740 : void
741 0 : ExtensionProtocolHandler::SubstituteRemoteFileChannel(nsIURI* aURI,
742 : nsILoadInfo* aLoadinfo,
743 : nsACString& aResolvedFileSpec,
744 : nsIChannel** aRetVal)
745 : {
746 0 : MOZ_ASSERT(IsNeckoChild());
747 :
748 : RefPtr<ExtensionStreamGetter> streamGetter =
749 0 : new ExtensionStreamGetter(aURI, aLoadinfo);
750 :
751 0 : NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
752 0 : }
753 :
754 : Result<Ok, nsresult>
755 0 : ExtensionProtocolHandler::SubstituteRemoteJarChannel(nsIURI* aURI,
756 : nsILoadInfo* aLoadinfo,
757 : nsACString& aResolvedSpec,
758 : nsIChannel** aRetVal)
759 : {
760 0 : MOZ_ASSERT(IsNeckoChild());
761 : nsresult rv;
762 :
763 : // Build a JAR URI for this jar:file:// URI and use it to extract the
764 : // inner file URI.
765 0 : nsCOMPtr<nsIURI> uri;
766 0 : NS_TRY(NS_NewURI(getter_AddRefs(uri), aResolvedSpec));
767 :
768 0 : nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
769 0 : NS_TRY(rv);
770 :
771 0 : nsCOMPtr<nsIURI> innerFileURI;
772 0 : NS_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
773 :
774 0 : nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
775 0 : NS_TRY(rv);
776 :
777 0 : nsCOMPtr<nsIFile> jarFile;
778 0 : NS_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
779 :
780 0 : nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(*aRetVal, &rv);
781 0 : NS_TRY(rv);
782 :
783 : RefPtr<ExtensionStreamGetter> streamGetter =
784 0 : new ExtensionStreamGetter(aURI, aLoadinfo, jarChannel.forget(), jarFile);
785 :
786 0 : NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
787 0 : return Ok();
788 : }
789 :
790 : #undef NS_TRY
791 :
792 : } // namespace net
793 : } // namespace mozilla
|