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/Components.h"
11 #include "mozilla/ipc/URIParams.h"
12 #include "mozilla/ipc/URIUtils.h"
13 #include "mozilla/net/NeckoChild.h"
14 #include "mozilla/RefPtr.h"
15 #include "mozilla/ResultExtensions.h"
16 #include "mozilla/Try.h"
19 #include "nsContentUtils.h"
20 #include "nsServiceManagerUtils.h"
22 #include "nsIFileChannel.h"
23 #include "nsIFileStreams.h"
24 #include "nsIMIMEService.h"
26 #include "nsIChannel.h"
27 #include "nsIPageThumbsStorageService.h"
28 #include "nsIInputStreamPump.h"
29 #include "nsIStreamListener.h"
30 #include "nsIInputStream.h"
31 #include "nsNetUtil.h"
32 #include "nsURLHelper.h"
34 #include "SimpleChannel.h"
35 #include "nsICancelable.h"
38 # include "nsIPlacesPreviewsHelperService.h"
41 #define PAGE_THUMB_HOST "thumbnails"
42 #define PLACES_PREVIEWS_HOST "places-previews"
43 #define PAGE_THUMB_SCHEME "moz-page-thumb"
48 LazyLogModule
gPageThumbProtocolLog("PageThumbProtocol");
51 #define LOG(level, ...) \
52 MOZ_LOG(gPageThumbProtocolLog, LogLevel::level, (__VA_ARGS__))
54 StaticRefPtr
<PageThumbProtocolHandler
> PageThumbProtocolHandler::sSingleton
;
56 NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler
,
57 nsISubstitutingProtocolHandler
, nsIProtocolHandler
,
58 nsISupportsWeakReference
)
59 NS_IMPL_ADDREF_INHERITED(PageThumbProtocolHandler
, SubstitutingProtocolHandler
)
60 NS_IMPL_RELEASE_INHERITED(PageThumbProtocolHandler
, SubstitutingProtocolHandler
)
62 already_AddRefed
<PageThumbProtocolHandler
>
63 PageThumbProtocolHandler::GetSingleton() {
65 sSingleton
= new PageThumbProtocolHandler();
66 ClearOnShutdown(&sSingleton
);
69 return do_AddRef(sSingleton
);
72 // A moz-page-thumb URI is only loadable by chrome pages in the parent process,
73 // or privileged content running in the privileged about content process.
74 PageThumbProtocolHandler::PageThumbProtocolHandler()
75 : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME
) {}
77 RefPtr
<RemoteStreamPromise
> PageThumbProtocolHandler::NewStream(
78 nsIURI
* aChildURI
, bool* aTerminateSender
) {
79 MOZ_ASSERT(!IsNeckoChild());
80 MOZ_ASSERT(NS_IsMainThread());
82 if (!aChildURI
|| !aTerminateSender
) {
83 return RemoteStreamPromise::CreateAndReject(NS_ERROR_INVALID_ARG
, __func__
);
86 *aTerminateSender
= true;
89 // We should never receive a URI that isn't for a moz-page-thumb because
90 // these requests ordinarily come from the child's PageThumbProtocolHandler.
91 // Ensure this request is for a moz-page-thumb URI. A compromised child
92 // process could send us any URI.
93 bool isPageThumbScheme
= false;
94 if (NS_FAILED(aChildURI
->SchemeIs(PAGE_THUMB_SCHEME
, &isPageThumbScheme
)) ||
96 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL
,
100 // We should never receive a URI that does not have "thumbnails" as the host.
102 if (NS_FAILED(aChildURI
->GetAsciiHost(host
)) ||
103 !(host
.EqualsLiteral(PAGE_THUMB_HOST
) ||
104 host
.EqualsLiteral(PLACES_PREVIEWS_HOST
))) {
105 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED
, __func__
);
108 // For errors after this point, we want to propagate the error to
109 // the child, but we don't force the child process to be terminated.
110 *aTerminateSender
= false;
112 // Make sure the child URI resolves to a file URI. We will then get a file
113 // channel for the request. The resultant channel should be a file channel
114 // because we only request remote streams for resource loads where the URI
115 // resolves to a file.
116 nsAutoCString resolvedSpec
;
117 rv
= ResolveURI(aChildURI
, resolvedSpec
);
119 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
122 nsAutoCString resolvedScheme
;
123 rv
= net_ExtractURLScheme(resolvedSpec
, resolvedScheme
);
124 if (NS_FAILED(rv
) || !resolvedScheme
.EqualsLiteral("file")) {
125 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED
, __func__
);
128 nsCOMPtr
<nsIIOService
> ioService
= do_GetIOService(&rv
);
130 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
133 nsCOMPtr
<nsIURI
> resolvedURI
;
134 rv
= ioService
->NewURI(resolvedSpec
, nullptr, nullptr,
135 getter_AddRefs(resolvedURI
));
137 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
140 // We use the system principal to get a file channel for the request,
141 // but only after we've checked (above) that the child URI is of
142 // moz-page-thumb scheme and that the URI host matches PAGE_THUMB_HOST.
143 nsCOMPtr
<nsIChannel
> channel
;
144 rv
= NS_NewChannel(getter_AddRefs(channel
), resolvedURI
,
145 nsContentUtils::GetSystemPrincipal(),
146 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
147 nsIContentPolicy::TYPE_OTHER
);
149 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
152 auto promiseHolder
= MakeUnique
<MozPromiseHolder
<RemoteStreamPromise
>>();
153 RefPtr
<RemoteStreamPromise
> promise
= promiseHolder
->Ensure(__func__
);
155 nsCOMPtr
<nsIMIMEService
> mime
= do_GetService("@mozilla.org/mime;1", &rv
);
157 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
160 nsAutoCString contentType
;
161 rv
= mime
->GetTypeFromURI(aChildURI
, contentType
);
163 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
166 rv
= NS_DispatchBackgroundTask(
167 NS_NewRunnableFunction(
168 "PageThumbProtocolHandler::NewStream",
169 [contentType
, channel
, holder
= std::move(promiseHolder
)]() {
172 nsCOMPtr
<nsIFileChannel
> fileChannel
=
173 do_QueryInterface(channel
, &rv
);
175 holder
->Reject(rv
, __func__
);
179 nsCOMPtr
<nsIFile
> requestedFile
;
180 rv
= fileChannel
->GetFile(getter_AddRefs(requestedFile
));
182 holder
->Reject(rv
, __func__
);
186 nsCOMPtr
<nsIInputStream
> inputStream
;
187 rv
= NS_NewLocalFileInputStream(getter_AddRefs(inputStream
),
188 requestedFile
, PR_RDONLY
, -1);
190 holder
->Reject(rv
, __func__
);
194 RemoteStreamInfo
info(inputStream
, contentType
, -1);
196 holder
->Resolve(std::move(info
), __func__
);
198 NS_DISPATCH_EVENT_MAY_BLOCK
);
201 return RemoteStreamPromise::CreateAndReject(rv
, __func__
);
207 bool PageThumbProtocolHandler::ResolveSpecialCases(const nsACString
& aHost
,
208 const nsACString
& aPath
,
209 const nsACString
& aPathname
,
210 nsACString
& aResult
) {
211 // This should match the scheme in PageThumbs.sys.mjs. We will only resolve
212 // URIs for thumbnails generated by PageThumbs here.
213 if (!aHost
.EqualsLiteral(PAGE_THUMB_HOST
) &&
214 !aHost
.EqualsLiteral(PLACES_PREVIEWS_HOST
)) {
215 // moz-page-thumb should always have a "thumbnails" host. We do not intend
216 // to allow substitution rules to be created for moz-page-thumb.
220 // Regardless of the outcome, the scheme will be resolved to file://.
221 aResult
.Assign("file://");
223 if (IsNeckoChild()) {
224 // We will resolve the URI in the parent if load is performed in the child
225 // because the child does not have access to the profile directory path.
226 // Technically we could retrieve the path from dom::ContentChild, but I
227 // would prefer to obtain the path from PageThumbsStorageService (which
228 // depends on OS.Path). Here, we resolve to the same URI, with the file://
229 // scheme. This won't ever be accessed directly by the content process,
230 // and is mainly used to keep the substitution protocol handler mechanism
232 aResult
.Append(aHost
);
233 aResult
.Append(aPath
);
235 // Resolve the URI in the parent to the thumbnail file URI since we will
236 // attempt to open the channel to load the file after this.
237 nsAutoString thumbnailUrl
;
238 nsresult rv
= GetThumbnailPath(aPath
, aHost
, thumbnailUrl
);
239 if (NS_WARN_IF(NS_FAILED(rv
))) {
243 aResult
.Append(NS_ConvertUTF16toUTF8(thumbnailUrl
));
249 nsresult
PageThumbProtocolHandler::SubstituteChannel(nsIURI
* aURI
,
250 nsILoadInfo
* aLoadInfo
,
251 nsIChannel
** aRetVal
) {
252 // Check if URI resolves to a file URI.
253 nsAutoCString resolvedSpec
;
254 MOZ_TRY(ResolveURI(aURI
, resolvedSpec
));
256 nsAutoCString scheme
;
257 MOZ_TRY(net_ExtractURLScheme(resolvedSpec
, scheme
));
259 if (!scheme
.EqualsLiteral("file")) {
260 NS_WARNING("moz-page-thumb URIs should only resolve to file URIs.");
261 return NS_ERROR_NO_INTERFACE
;
264 // Load the URI remotely if accessed from a child.
265 if (IsNeckoChild()) {
266 MOZ_TRY(SubstituteRemoteChannel(aURI
, aLoadInfo
, aRetVal
));
272 Result
<Ok
, nsresult
> PageThumbProtocolHandler::SubstituteRemoteChannel(
273 nsIURI
* aURI
, nsILoadInfo
* aLoadInfo
, nsIChannel
** aRetVal
) {
274 MOZ_ASSERT(IsNeckoChild());
275 MOZ_TRY(aURI
? NS_OK
: NS_ERROR_INVALID_ARG
);
276 MOZ_TRY(aLoadInfo
? NS_OK
: NS_ERROR_INVALID_ARG
);
279 nsAutoCString resolvedSpec
;
280 MOZ_TRY(ResolveURI(aURI
, resolvedSpec
));
282 nsAutoCString scheme
;
283 MOZ_TRY(net_ExtractURLScheme(resolvedSpec
, scheme
));
285 MOZ_ASSERT(scheme
.EqualsLiteral("file"));
288 RefPtr
<RemoteStreamGetter
> streamGetter
=
289 new RemoteStreamGetter(aURI
, aLoadInfo
);
291 NewSimpleChannel(aURI
, aLoadInfo
, streamGetter
, aRetVal
);
295 nsresult
PageThumbProtocolHandler::GetThumbnailPath(const nsACString
& aPath
,
296 const nsACString
& aHost
,
297 nsString
& aThumbnailPath
) {
298 MOZ_ASSERT(!IsNeckoChild());
300 // Ensures that the provided path has a query string. We will start parsing
302 int32_t queryIndex
= aPath
.FindChar('?');
303 if (queryIndex
<= 0) {
304 return NS_ERROR_MALFORMED_URI
;
307 // Extract URL from query string.
310 URLParams::Extract(Substring(aPath
, queryIndex
+ 1), "url"_ns
, url
);
311 if (!found
|| url
.IsVoid()) {
312 return NS_ERROR_NOT_AVAILABLE
;
316 if (aHost
.EqualsLiteral(PAGE_THUMB_HOST
)) {
317 nsCOMPtr
<nsIPageThumbsStorageService
> pageThumbsStorage
;
318 pageThumbsStorage
= mozilla::components::PageThumbsStorage::Service(&rv
);
319 if (NS_WARN_IF(NS_FAILED(rv
))) {
322 // Use PageThumbsStorageService to get the local file path of the screenshot
323 // for the given URL.
324 rv
= pageThumbsStorage
->GetFilePathForURL(NS_ConvertUTF8toUTF16(url
),
327 } else if (aHost
.EqualsLiteral(PLACES_PREVIEWS_HOST
)) {
328 nsCOMPtr
<nsIPlacesPreviewsHelperService
> helper
;
329 helper
= mozilla::components::PlacesPreviewsHelper::Service(&rv
);
330 if (NS_WARN_IF(NS_FAILED(rv
))) {
333 rv
= helper
->GetFilePathForURL(NS_ConvertUTF8toUTF16(url
), aThumbnailPath
);
336 MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host");
337 return NS_ERROR_UNEXPECTED
;
339 if (NS_WARN_IF(NS_FAILED(rv
))) {
346 void PageThumbProtocolHandler::NewSimpleChannel(
347 nsIURI
* aURI
, nsILoadInfo
* aLoadinfo
, RemoteStreamGetter
* aStreamGetter
,
348 nsIChannel
** aRetVal
) {
349 nsCOMPtr
<nsIChannel
> channel
= NS_NewSimpleChannel(
350 aURI
, aLoadinfo
, aStreamGetter
,
351 [](nsIStreamListener
* listener
, nsIChannel
* simpleChannel
,
352 RemoteStreamGetter
* getter
) -> RequestOrReason
{
353 return getter
->GetAsync(listener
, simpleChannel
,
354 &NeckoChild::SendGetPageThumbStream
);
357 channel
.swap(*aRetVal
);
363 } // namespace mozilla