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/. */
7 #include "PageThumbProtocolHandler.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/ipc/URIParams.h"
11 #include "mozilla/ipc/URIUtils.h"
12 #include "mozilla/net/NeckoChild.h"
13 #include "mozilla/RefPtr.h"
14 #include "mozilla/ResultExtensions.h"
17 #include "nsContentUtils.h"
18 #include "nsServiceManagerUtils.h"
20 #include "nsIFileChannel.h"
21 #include "nsIFileStreams.h"
22 #include "nsIMIMEService.h"
24 #include "nsIChannel.h"
25 #include "nsIPageThumbsStorageService.h"
26 #include "nsIInputStreamPump.h"
27 #include "nsIStreamListener.h"
28 #include "nsIInputStream.h"
29 #include "nsNetUtil.h"
30 #include "nsURLHelper.h"
32 #include "SimpleChannel.h"
33 #include "nsICancelable.h"
36 # include "nsIPlacesPreviewsHelperService.h"
39 #define PAGE_THUMB_HOST "thumbnails"
40 #define PLACES_PREVIEWS_HOST "places-previews"
41 #define PAGE_THUMB_SCHEME "moz-page-thumb"
46 LazyLogModule
gPageThumbProtocolLog("PageThumbProtocol");
49 #define LOG(level, ...) \
50 MOZ_LOG(gPageThumbProtocolLog, LogLevel::level, (__VA_ARGS__))
52 StaticRefPtr
<PageThumbProtocolHandler
> PageThumbProtocolHandler::sSingleton
;
54 NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler
,
55 nsISubstitutingProtocolHandler
, nsIProtocolHandler
,
56 nsISupportsWeakReference
)
57 NS_IMPL_ADDREF_INHERITED(PageThumbProtocolHandler
, SubstitutingProtocolHandler
)
58 NS_IMPL_RELEASE_INHERITED(PageThumbProtocolHandler
, SubstitutingProtocolHandler
)
60 already_AddRefed
<PageThumbProtocolHandler
>
61 PageThumbProtocolHandler::GetSingleton() {
63 sSingleton
= new PageThumbProtocolHandler();
64 ClearOnShutdown(&sSingleton
);
67 return do_AddRef(sSingleton
);
70 // A moz-page-thumb URI is only loadable by chrome pages in the parent process,
71 // or privileged content running in the privileged about content process.
72 PageThumbProtocolHandler::PageThumbProtocolHandler()
73 : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME
) {}
75 RefPtr
<RemoteStreamPromise
> PageThumbProtocolHandler::NewStream(
76 nsIURI
* aChildURI
, bool* aTerminateSender
) {
77 MOZ_ASSERT(!IsNeckoChild());
78 MOZ_ASSERT(NS_IsMainThread());
80 if (!aChildURI
|| !aTerminateSender
) {
81 return RemoteStreamPromise::CreateAndReject(NS_ERROR_INVALID_ARG
, __func__
);
84 *aTerminateSender
= true;
87 // We should never receive a URI that isn't for a moz-page-thumb because
88 // these requests ordinarily come from the child's PageThumbProtocolHandler.
89 // Ensure this request is for a moz-page-thumb URI. A compromised child
90 // process could send us any URI.
91 bool isPageThumbScheme
= false;
92 if (NS_FAILED(aChildURI
->SchemeIs(PAGE_THUMB_SCHEME
, &isPageThumbScheme
)) ||
94 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL
,
98 // We should never receive a URI that does not have "thumbnails" as the host.
100 if (NS_FAILED(aChildURI
->GetAsciiHost(host
)) ||
101 !(host
.EqualsLiteral(PAGE_THUMB_HOST
) ||
102 host
.EqualsLiteral(PLACES_PREVIEWS_HOST
))) {
103 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED
, __func__
);
106 // For errors after this point, we want to propagate the error to
107 // the child, but we don't force the child process to be terminated.
108 *aTerminateSender
= false;
110 // Make sure the child URI resolves to a file URI. We will then get a file
111 // channel for the request. The resultant channel should be a file channel
112 // because we only request remote streams for resource loads where the URI
113 // resolves to a file.
114 nsAutoCString resolvedSpec
;
115 rv
= ResolveURI(aChildURI
, resolvedSpec
);
117 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
120 nsAutoCString resolvedScheme
;
121 rv
= net_ExtractURLScheme(resolvedSpec
, resolvedScheme
);
122 if (NS_FAILED(rv
) || !resolvedScheme
.EqualsLiteral("file")) {
123 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED
, __func__
);
126 nsCOMPtr
<nsIIOService
> ioService
= do_GetIOService(&rv
);
128 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
131 nsCOMPtr
<nsIURI
> resolvedURI
;
132 rv
= ioService
->NewURI(resolvedSpec
, nullptr, nullptr,
133 getter_AddRefs(resolvedURI
));
135 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
138 // We use the system principal to get a file channel for the request,
139 // but only after we've checked (above) that the child URI is of
140 // moz-page-thumb scheme and that the URI host matches PAGE_THUMB_HOST.
141 nsCOMPtr
<nsIChannel
> channel
;
142 rv
= NS_NewChannel(getter_AddRefs(channel
), resolvedURI
,
143 nsContentUtils::GetSystemPrincipal(),
144 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
145 nsIContentPolicy::TYPE_OTHER
);
147 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
150 auto promiseHolder
= MakeUnique
<MozPromiseHolder
<RemoteStreamPromise
>>();
151 RefPtr
<RemoteStreamPromise
> promise
= promiseHolder
->Ensure(__func__
);
153 nsCOMPtr
<nsIMIMEService
> mime
= do_GetService("@mozilla.org/mime;1", &rv
);
155 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
158 nsAutoCString contentType
;
159 rv
= mime
->GetTypeFromURI(aChildURI
, contentType
);
161 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
164 rv
= NS_DispatchBackgroundTask(
165 NS_NewRunnableFunction(
166 "PageThumbProtocolHandler::NewStream",
167 [contentType
, channel
, holder
= std::move(promiseHolder
)]() {
170 nsCOMPtr
<nsIFileChannel
> fileChannel
=
171 do_QueryInterface(channel
, &rv
);
173 holder
->Reject(rv
, __func__
);
177 nsCOMPtr
<nsIFile
> requestedFile
;
178 rv
= fileChannel
->GetFile(getter_AddRefs(requestedFile
));
180 holder
->Reject(rv
, __func__
);
184 nsCOMPtr
<nsIInputStream
> inputStream
;
185 rv
= NS_NewLocalFileInputStream(getter_AddRefs(inputStream
),
186 requestedFile
, PR_RDONLY
, -1);
188 holder
->Reject(rv
, __func__
);
192 RemoteStreamInfo
info(inputStream
, contentType
, -1);
194 holder
->Resolve(std::move(info
), __func__
);
196 NS_DISPATCH_EVENT_MAY_BLOCK
);
199 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
205 bool PageThumbProtocolHandler::ResolveSpecialCases(const nsACString
& aHost
,
206 const nsACString
& aPath
,
207 const nsACString
& aPathname
,
208 nsACString
& aResult
) {
209 // This should match the scheme in PageThumbs.jsm. We will only resolve
210 // URIs for thumbnails generated by PageThumbs here.
211 if (!aHost
.EqualsLiteral(PAGE_THUMB_HOST
) &&
212 !aHost
.EqualsLiteral(PLACES_PREVIEWS_HOST
)) {
213 // moz-page-thumb should always have a "thumbnails" host. We do not intend
214 // to allow substitution rules to be created for moz-page-thumb.
218 // Regardless of the outcome, the scheme will be resolved to file://.
219 aResult
.Assign("file://");
221 if (IsNeckoChild()) {
222 // We will resolve the URI in the parent if load is performed in the child
223 // because the child does not have access to the profile directory path.
224 // Technically we could retrieve the path from dom::ContentChild, but I
225 // would prefer to obtain the path from PageThumbsStorageService (which
226 // depends on OS.Path). Here, we resolve to the same URI, with the file://
227 // scheme. This won't ever be accessed directly by the content process,
228 // and is mainly used to keep the substitution protocol handler mechanism
230 aResult
.Append(aHost
);
231 aResult
.Append(aPath
);
233 // Resolve the URI in the parent to the thumbnail file URI since we will
234 // attempt to open the channel to load the file after this.
235 nsAutoString thumbnailUrl
;
236 nsresult rv
= GetThumbnailPath(aPath
, aHost
, thumbnailUrl
);
237 if (NS_WARN_IF(NS_FAILED(rv
))) {
241 aResult
.Append(NS_ConvertUTF16toUTF8(thumbnailUrl
));
247 nsresult
PageThumbProtocolHandler::SubstituteChannel(nsIURI
* aURI
,
248 nsILoadInfo
* aLoadInfo
,
249 nsIChannel
** aRetVal
) {
250 // Check if URI resolves to a file URI.
251 nsAutoCString resolvedSpec
;
252 MOZ_TRY(ResolveURI(aURI
, resolvedSpec
));
254 nsAutoCString scheme
;
255 MOZ_TRY(net_ExtractURLScheme(resolvedSpec
, scheme
));
257 if (!scheme
.EqualsLiteral("file")) {
258 NS_WARNING("moz-page-thumb URIs should only resolve to file URIs.");
259 return NS_ERROR_NO_INTERFACE
;
262 // Load the URI remotely if accessed from a child.
263 if (IsNeckoChild()) {
264 MOZ_TRY(SubstituteRemoteChannel(aURI
, aLoadInfo
, aRetVal
));
270 Result
<Ok
, nsresult
> PageThumbProtocolHandler::SubstituteRemoteChannel(
271 nsIURI
* aURI
, nsILoadInfo
* aLoadInfo
, nsIChannel
** aRetVal
) {
272 MOZ_ASSERT(IsNeckoChild());
273 MOZ_TRY(aURI
? NS_OK
: NS_ERROR_INVALID_ARG
);
274 MOZ_TRY(aLoadInfo
? NS_OK
: NS_ERROR_INVALID_ARG
);
277 nsAutoCString resolvedSpec
;
278 MOZ_TRY(ResolveURI(aURI
, resolvedSpec
));
280 nsAutoCString scheme
;
281 MOZ_TRY(net_ExtractURLScheme(resolvedSpec
, scheme
));
283 MOZ_ASSERT(scheme
.EqualsLiteral("file"));
286 RefPtr
<RemoteStreamGetter
> streamGetter
=
287 new RemoteStreamGetter(aURI
, aLoadInfo
);
289 NewSimpleChannel(aURI
, aLoadInfo
, streamGetter
, aRetVal
);
293 nsresult
PageThumbProtocolHandler::GetThumbnailPath(const nsACString
& aPath
,
294 const nsACString
& aHost
,
295 nsString
& aThumbnailPath
) {
296 MOZ_ASSERT(!IsNeckoChild());
298 // Ensures that the provided path has a query string. We will start parsing
300 int32_t queryIndex
= aPath
.FindChar('?');
301 if (queryIndex
<= 0) {
302 return NS_ERROR_MALFORMED_URI
;
305 // Extract URL from query string.
308 URLParams::Extract(Substring(aPath
, queryIndex
+ 1), u
"url"_ns
, url
);
309 if (!found
|| url
.IsVoid()) {
310 return NS_ERROR_NOT_AVAILABLE
;
314 if (aHost
.EqualsLiteral(PAGE_THUMB_HOST
)) {
315 nsCOMPtr
<nsIPageThumbsStorageService
> pageThumbsStorage
=
316 do_GetService("@mozilla.org/thumbnails/pagethumbs-service;1", &rv
);
317 if (NS_WARN_IF(NS_FAILED(rv
))) {
320 // Use PageThumbsStorageService to get the local file path of the screenshot
321 // for the given URL.
322 rv
= pageThumbsStorage
->GetFilePathForURL(url
, aThumbnailPath
);
324 } else if (aHost
.EqualsLiteral(PLACES_PREVIEWS_HOST
)) {
325 nsCOMPtr
<nsIPlacesPreviewsHelperService
> helper
=
326 do_GetService("@mozilla.org/places/previews-helper;1", &rv
);
327 if (NS_WARN_IF(NS_FAILED(rv
))) {
330 rv
= helper
->GetFilePathForURL(url
, aThumbnailPath
);
333 MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host");
334 return NS_ERROR_UNEXPECTED
;
336 if (NS_WARN_IF(NS_FAILED(rv
))) {
343 void PageThumbProtocolHandler::NewSimpleChannel(
344 nsIURI
* aURI
, nsILoadInfo
* aLoadinfo
, RemoteStreamGetter
* aStreamGetter
,
345 nsIChannel
** aRetVal
) {
346 nsCOMPtr
<nsIChannel
> channel
= NS_NewSimpleChannel(
347 aURI
, aLoadinfo
, aStreamGetter
,
348 [](nsIStreamListener
* listener
, nsIChannel
* simpleChannel
,
349 RemoteStreamGetter
* getter
) -> RequestOrReason
{
350 return getter
->GetAsync(listener
, simpleChannel
,
351 &NeckoChild::SendGetPageThumbStream
);
354 channel
.swap(*aRetVal
);
360 } // namespace mozilla