Bug 1814798 - pt 2. Add a PHCManager component to control PHC r=glandium,emilio
[gecko.git] / netwerk / protocol / res / PageThumbProtocolHandler.cpp
blob065fd7c36e949102f30084a83fb1074c4593401d
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"
16 #include "LoadInfo.h"
17 #include "nsContentUtils.h"
18 #include "nsServiceManagerUtils.h"
19 #include "nsIFile.h"
20 #include "nsIFileChannel.h"
21 #include "nsIFileStreams.h"
22 #include "nsIMIMEService.h"
23 #include "nsIURL.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"
31 #include "prio.h"
32 #include "SimpleChannel.h"
33 #include "nsICancelable.h"
35 #ifdef MOZ_PLACES
36 # include "nsIPlacesPreviewsHelperService.h"
37 #endif
39 #define PAGE_THUMB_HOST "thumbnails"
40 #define PLACES_PREVIEWS_HOST "places-previews"
41 #define PAGE_THUMB_SCHEME "moz-page-thumb"
43 namespace mozilla {
44 namespace net {
46 LazyLogModule gPageThumbProtocolLog("PageThumbProtocol");
48 #undef LOG
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() {
62 if (!sSingleton) {
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;
85 nsresult rv;
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)) ||
93 !isPageThumbScheme) {
94 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL,
95 __func__);
98 // We should never receive a URI that does not have "thumbnails" as the host.
99 nsAutoCString 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);
116 if (NS_FAILED(rv)) {
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);
127 if (NS_FAILED(rv)) {
128 return RemoteStreamPromise::CreateAndReject(rv, __func__);
131 nsCOMPtr<nsIURI> resolvedURI;
132 rv = ioService->NewURI(resolvedSpec, nullptr, nullptr,
133 getter_AddRefs(resolvedURI));
134 if (NS_FAILED(rv)) {
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);
146 if (NS_FAILED(rv)) {
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);
154 if (NS_FAILED(rv)) {
155 return RemoteStreamPromise::CreateAndReject(rv, __func__);
158 nsAutoCString contentType;
159 rv = mime->GetTypeFromURI(aChildURI, contentType);
160 if (NS_FAILED(rv)) {
161 return RemoteStreamPromise::CreateAndReject(rv, __func__);
164 rv = NS_DispatchBackgroundTask(
165 NS_NewRunnableFunction(
166 "PageThumbProtocolHandler::NewStream",
167 [contentType, channel, holder = std::move(promiseHolder)]() {
168 nsresult rv;
170 nsCOMPtr<nsIFileChannel> fileChannel =
171 do_QueryInterface(channel, &rv);
172 if (NS_FAILED(rv)) {
173 holder->Reject(rv, __func__);
174 return;
177 nsCOMPtr<nsIFile> requestedFile;
178 rv = fileChannel->GetFile(getter_AddRefs(requestedFile));
179 if (NS_FAILED(rv)) {
180 holder->Reject(rv, __func__);
181 return;
184 nsCOMPtr<nsIInputStream> inputStream;
185 rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
186 requestedFile, PR_RDONLY, -1);
187 if (NS_FAILED(rv)) {
188 holder->Reject(rv, __func__);
189 return;
192 RemoteStreamInfo info(inputStream, contentType, -1);
194 holder->Resolve(std::move(info), __func__);
196 NS_DISPATCH_EVENT_MAY_BLOCK);
198 if (NS_FAILED(rv)) {
199 return RemoteStreamPromise::CreateAndReject(rv, __func__);
202 return promise;
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.
215 return false;
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
229 // happy.
230 aResult.Append(aHost);
231 aResult.Append(aPath);
232 } else {
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))) {
238 return false;
241 aResult.Append(NS_ConvertUTF16toUTF8(thumbnailUrl));
244 return true;
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));
267 return NS_OK;
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);
276 #ifdef DEBUG
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"));
284 #endif /* DEBUG */
286 RefPtr<RemoteStreamGetter> streamGetter =
287 new RemoteStreamGetter(aURI, aLoadInfo);
289 NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal);
290 return Ok();
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
299 // from there.
300 int32_t queryIndex = aPath.FindChar('?');
301 if (queryIndex <= 0) {
302 return NS_ERROR_MALFORMED_URI;
305 // Extract URL from query string.
306 nsAutoString url;
307 bool found =
308 URLParams::Extract(Substring(aPath, queryIndex + 1), u"url"_ns, url);
309 if (!found || url.IsVoid()) {
310 return NS_ERROR_NOT_AVAILABLE;
313 nsresult rv;
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))) {
318 return rv;
320 // Use PageThumbsStorageService to get the local file path of the screenshot
321 // for the given URL.
322 rv = pageThumbsStorage->GetFilePathForURL(url, aThumbnailPath);
323 #ifdef MOZ_PLACES
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))) {
328 return rv;
330 rv = helper->GetFilePathForURL(url, aThumbnailPath);
331 #endif
332 } else {
333 MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host");
334 return NS_ERROR_UNEXPECTED;
336 if (NS_WARN_IF(NS_FAILED(rv))) {
337 return rv;
339 return NS_OK;
342 // static
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);
357 #undef LOG
359 } // namespace net
360 } // namespace mozilla