Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / netwerk / protocol / res / PageThumbProtocolHandler.cpp
blob7a55675156362df0316f38ac4f942af7257d1b92
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"
18 #include "LoadInfo.h"
19 #include "nsContentUtils.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsIFile.h"
22 #include "nsIFileChannel.h"
23 #include "nsIFileStreams.h"
24 #include "nsIMIMEService.h"
25 #include "nsIURL.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"
33 #include "prio.h"
34 #include "SimpleChannel.h"
35 #include "nsICancelable.h"
37 #ifdef MOZ_PLACES
38 # include "nsIPlacesPreviewsHelperService.h"
39 #endif
41 #define PAGE_THUMB_HOST "thumbnails"
42 #define PLACES_PREVIEWS_HOST "places-previews"
43 #define PAGE_THUMB_SCHEME "moz-page-thumb"
45 namespace mozilla {
46 namespace net {
48 LazyLogModule gPageThumbProtocolLog("PageThumbProtocol");
50 #undef LOG
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() {
64 if (!sSingleton) {
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;
87 nsresult rv;
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)) ||
95 !isPageThumbScheme) {
96 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL,
97 __func__);
100 // We should never receive a URI that does not have "thumbnails" as the host.
101 nsAutoCString 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);
118 if (NS_FAILED(rv)) {
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);
129 if (NS_FAILED(rv)) {
130 return RemoteStreamPromise::CreateAndReject(rv, __func__);
133 nsCOMPtr<nsIURI> resolvedURI;
134 rv = ioService->NewURI(resolvedSpec, nullptr, nullptr,
135 getter_AddRefs(resolvedURI));
136 if (NS_FAILED(rv)) {
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);
148 if (NS_FAILED(rv)) {
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);
156 if (NS_FAILED(rv)) {
157 return RemoteStreamPromise::CreateAndReject(rv, __func__);
160 nsAutoCString contentType;
161 rv = mime->GetTypeFromURI(aChildURI, contentType);
162 if (NS_FAILED(rv)) {
163 return RemoteStreamPromise::CreateAndReject(rv, __func__);
166 rv = NS_DispatchBackgroundTask(
167 NS_NewRunnableFunction(
168 "PageThumbProtocolHandler::NewStream",
169 [contentType, channel, holder = std::move(promiseHolder)]() {
170 nsresult rv;
172 nsCOMPtr<nsIFileChannel> fileChannel =
173 do_QueryInterface(channel, &rv);
174 if (NS_FAILED(rv)) {
175 holder->Reject(rv, __func__);
176 return;
179 nsCOMPtr<nsIFile> requestedFile;
180 rv = fileChannel->GetFile(getter_AddRefs(requestedFile));
181 if (NS_FAILED(rv)) {
182 holder->Reject(rv, __func__);
183 return;
186 nsCOMPtr<nsIInputStream> inputStream;
187 rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
188 requestedFile, PR_RDONLY, -1);
189 if (NS_FAILED(rv)) {
190 holder->Reject(rv, __func__);
191 return;
194 RemoteStreamInfo info(inputStream, contentType, -1);
196 holder->Resolve(std::move(info), __func__);
198 NS_DISPATCH_EVENT_MAY_BLOCK);
200 if (NS_FAILED(rv)) {
201 return RemoteStreamPromise::CreateAndReject(rv, __func__);
204 return promise;
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.
217 return false;
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
231 // happy.
232 aResult.Append(aHost);
233 aResult.Append(aPath);
234 } else {
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))) {
240 return false;
243 aResult.Append(NS_ConvertUTF16toUTF8(thumbnailUrl));
246 return true;
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));
269 return NS_OK;
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);
278 #ifdef DEBUG
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"));
286 #endif /* DEBUG */
288 RefPtr<RemoteStreamGetter> streamGetter =
289 new RemoteStreamGetter(aURI, aLoadInfo);
291 NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal);
292 return Ok();
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
301 // from there.
302 int32_t queryIndex = aPath.FindChar('?');
303 if (queryIndex <= 0) {
304 return NS_ERROR_MALFORMED_URI;
307 // Extract URL from query string.
308 nsAutoCString url;
309 bool found =
310 URLParams::Extract(Substring(aPath, queryIndex + 1), "url"_ns, url);
311 if (!found || url.IsVoid()) {
312 return NS_ERROR_NOT_AVAILABLE;
315 nsresult rv;
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))) {
320 return 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),
325 aThumbnailPath);
326 #ifdef MOZ_PLACES
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))) {
331 return rv;
333 rv = helper->GetFilePathForURL(NS_ConvertUTF8toUTF16(url), aThumbnailPath);
334 #endif
335 } else {
336 MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host");
337 return NS_ERROR_UNEXPECTED;
339 if (NS_WARN_IF(NS_FAILED(rv))) {
340 return rv;
342 return NS_OK;
345 // static
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);
360 #undef LOG
362 } // namespace net
363 } // namespace mozilla