PageThumbProtocolHandler.cpp (9952B)
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 "PageThumbProtocolHandler.h" 8 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/net/NeckoParent.h" 15 #include "mozilla/RefPtr.h" 16 #include "mozilla/ResultExtensions.h" 17 #include "mozilla/Try.h" 18 19 #include "LoadInfo.h" 20 #include "nsContentUtils.h" 21 #include "nsServiceManagerUtils.h" 22 #include "nsIFile.h" 23 #include "nsIFileChannel.h" 24 #include "nsIFileStreams.h" 25 #include "nsIMIMEService.h" 26 #include "nsIURL.h" 27 #include "nsIChannel.h" 28 #include "nsIPageThumbsStorageService.h" 29 #include "nsIInputStreamPump.h" 30 #include "nsIStreamListener.h" 31 #include "nsIInputStream.h" 32 #include "nsNetUtil.h" 33 #include "nsURLHelper.h" 34 #include "prio.h" 35 #include "SimpleChannel.h" 36 #include "nsICancelable.h" 37 38 #ifdef MOZ_PLACES 39 # include "nsIPlacesPreviewsHelperService.h" 40 #endif 41 42 #define PAGE_THUMB_HOST "thumbnails" 43 #define PLACES_PREVIEWS_HOST "places-previews" 44 #define PAGE_THUMB_SCHEME "moz-page-thumb" 45 46 namespace mozilla { 47 namespace net { 48 49 LazyLogModule gPageThumbProtocolLog("PageThumbProtocol"); 50 51 #undef LOG 52 #define LOG(level, ...) \ 53 MOZ_LOG(gPageThumbProtocolLog, LogLevel::level, (__VA_ARGS__)) 54 55 StaticRefPtr<PageThumbProtocolHandler> PageThumbProtocolHandler::sSingleton; 56 57 NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler, 58 nsISubstitutingProtocolHandler, nsIProtocolHandler, 59 nsISupportsWeakReference) 60 NS_IMPL_ADDREF_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler) 61 NS_IMPL_RELEASE_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler) 62 63 already_AddRefed<PageThumbProtocolHandler> 64 PageThumbProtocolHandler::GetSingleton() { 65 if (!sSingleton) { 66 sSingleton = new PageThumbProtocolHandler(); 67 ClearOnShutdown(&sSingleton); 68 } 69 70 return do_AddRef(sSingleton); 71 } 72 73 // A moz-page-thumb URI is only loadable by chrome pages in the parent process, 74 // or privileged content running in the privileged about content process. 75 PageThumbProtocolHandler::PageThumbProtocolHandler() 76 : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME) {} 77 78 RefPtr<RemoteStreamPromise> PageThumbProtocolHandler::NewStream( 79 nsIURI* aChildURI, bool* aTerminateSender) { 80 MOZ_ASSERT(!IsNeckoChild()); 81 MOZ_ASSERT(NS_IsMainThread()); 82 83 if (!aChildURI || !aTerminateSender) { 84 return RemoteStreamPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__); 85 } 86 87 *aTerminateSender = true; 88 nsresult rv; 89 90 // We should never receive a URI that isn't for a moz-page-thumb because 91 // these requests ordinarily come from the child's PageThumbProtocolHandler. 92 // Ensure this request is for a moz-page-thumb URI. A compromised child 93 // process could send us any URI. 94 bool isPageThumbScheme = false; 95 if (NS_FAILED(aChildURI->SchemeIs(PAGE_THUMB_SCHEME, &isPageThumbScheme)) || 96 !isPageThumbScheme) { 97 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL, 98 __func__); 99 } 100 101 // We should never receive a URI that does not have "thumbnails" as the host. 102 nsAutoCString host; 103 if (NS_FAILED(aChildURI->GetAsciiHost(host)) || 104 !(host.EqualsLiteral(PAGE_THUMB_HOST) || 105 host.EqualsLiteral(PLACES_PREVIEWS_HOST))) { 106 return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__); 107 } 108 109 // For errors after this point, we want to propagate the error to 110 // the child, but we don't force the child process to be terminated. 111 *aTerminateSender = false; 112 113 // Make sure the child URI resolves to a file URI. We will then get a file 114 // channel for the request. The resultant channel should be a file channel 115 // because we only request remote streams for resource loads where the URI 116 // resolves to a file. 117 nsAutoCString resolvedSpec; 118 rv = ResolveURI(aChildURI, resolvedSpec); 119 if (NS_FAILED(rv)) { 120 return RemoteStreamPromise::CreateAndReject(rv, __func__); 121 } 122 123 return mozilla::net::NeckoParent::CreateRemoteStreamForResolvedURI( 124 aChildURI, resolvedSpec, ""_ns); 125 } 126 127 bool PageThumbProtocolHandler::ResolveSpecialCases(const nsACString& aHost, 128 const nsACString& aPath, 129 const nsACString& aPathname, 130 nsACString& aResult) { 131 // This should match the scheme in PageThumbs.sys.mjs. We will only resolve 132 // URIs for thumbnails generated by PageThumbs here. 133 if (!aHost.EqualsLiteral(PAGE_THUMB_HOST) && 134 !aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) { 135 // moz-page-thumb should always have a "thumbnails" host. We do not intend 136 // to allow substitution rules to be created for moz-page-thumb. 137 return false; 138 } 139 140 // Regardless of the outcome, the scheme will be resolved to file://. 141 aResult.Assign("file://"); 142 143 if (IsNeckoChild()) { 144 // We will resolve the URI in the parent if load is performed in the child 145 // because the child does not have access to the profile directory path. 146 // Technically we could retrieve the path from dom::ContentChild, but I 147 // would prefer to obtain the path from PageThumbsStorageService (which 148 // depends on OS.Path). Here, we resolve to the same URI, with the file:// 149 // scheme. This won't ever be accessed directly by the content process, 150 // and is mainly used to keep the substitution protocol handler mechanism 151 // happy. 152 aResult.Append(aHost); 153 aResult.Append(aPath); 154 } else { 155 // Resolve the URI in the parent to the thumbnail file URI since we will 156 // attempt to open the channel to load the file after this. 157 nsAutoString thumbnailUrl; 158 nsresult rv = GetThumbnailPath(aPath, aHost, thumbnailUrl); 159 if (NS_WARN_IF(NS_FAILED(rv))) { 160 return false; 161 } 162 163 aResult.Append(NS_ConvertUTF16toUTF8(thumbnailUrl)); 164 } 165 166 return true; 167 } 168 169 nsresult PageThumbProtocolHandler::SubstituteChannel(nsIURI* aURI, 170 nsILoadInfo* aLoadInfo, 171 nsIChannel** aRetVal) { 172 // Check if URI resolves to a file URI. 173 nsAutoCString resolvedSpec; 174 MOZ_TRY(ResolveURI(aURI, resolvedSpec)); 175 176 nsAutoCString scheme; 177 MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme)); 178 179 if (!scheme.EqualsLiteral("file")) { 180 NS_WARNING("moz-page-thumb URIs should only resolve to file URIs."); 181 return NS_ERROR_NO_INTERFACE; 182 } 183 184 // Load the URI remotely if accessed from a child. 185 if (IsNeckoChild()) { 186 MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, aRetVal)); 187 } 188 189 return NS_OK; 190 } 191 192 Result<Ok, nsresult> PageThumbProtocolHandler::SubstituteRemoteChannel( 193 nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) { 194 MOZ_ASSERT(IsNeckoChild()); 195 MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG); 196 MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG); 197 198 #ifdef DEBUG 199 nsAutoCString resolvedSpec; 200 MOZ_TRY(ResolveURI(aURI, resolvedSpec)); 201 202 nsAutoCString scheme; 203 MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme)); 204 205 MOZ_ASSERT(scheme.EqualsLiteral("file")); 206 #endif /* DEBUG */ 207 208 RefPtr<RemoteStreamGetter> streamGetter = 209 new RemoteStreamGetter(aURI, aLoadInfo); 210 211 NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal); 212 return Ok(); 213 } 214 215 nsresult PageThumbProtocolHandler::GetThumbnailPath(const nsACString& aPath, 216 const nsACString& aHost, 217 nsString& aThumbnailPath) { 218 MOZ_ASSERT(!IsNeckoChild()); 219 220 // Ensures that the provided path has a query string. We will start parsing 221 // from there. 222 int32_t queryIndex = aPath.FindChar('?'); 223 if (queryIndex <= 0) { 224 return NS_ERROR_MALFORMED_URI; 225 } 226 227 // Extract URL from query string. 228 nsAutoCString url; 229 bool found = 230 URLParams::Extract(Substring(aPath, queryIndex + 1), "url"_ns, url); 231 if (!found || url.IsVoid()) { 232 return NS_ERROR_NOT_AVAILABLE; 233 } 234 235 nsresult rv; 236 if (aHost.EqualsLiteral(PAGE_THUMB_HOST)) { 237 nsCOMPtr<nsIPageThumbsStorageService> pageThumbsStorage; 238 pageThumbsStorage = mozilla::components::PageThumbsStorage::Service(&rv); 239 if (NS_WARN_IF(NS_FAILED(rv))) { 240 return rv; 241 } 242 // Use PageThumbsStorageService to get the local file path of the screenshot 243 // for the given URL. 244 rv = pageThumbsStorage->GetFilePathForURL(NS_ConvertUTF8toUTF16(url), 245 aThumbnailPath); 246 #ifdef MOZ_PLACES 247 } else if (aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) { 248 nsCOMPtr<nsIPlacesPreviewsHelperService> helper; 249 helper = mozilla::components::PlacesPreviewsHelper::Service(&rv); 250 if (NS_WARN_IF(NS_FAILED(rv))) { 251 return rv; 252 } 253 rv = helper->GetFilePathForURL(NS_ConvertUTF8toUTF16(url), aThumbnailPath); 254 #endif 255 } else { 256 MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host"); 257 return NS_ERROR_UNEXPECTED; 258 } 259 if (NS_WARN_IF(NS_FAILED(rv))) { 260 return rv; 261 } 262 return NS_OK; 263 } 264 265 // static 266 void PageThumbProtocolHandler::NewSimpleChannel( 267 nsIURI* aURI, nsILoadInfo* aLoadinfo, RemoteStreamGetter* aStreamGetter, 268 nsIChannel** aRetVal) { 269 nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel( 270 aURI, aLoadinfo, aStreamGetter, 271 [](nsIStreamListener* listener, nsIChannel* simpleChannel, 272 RemoteStreamGetter* getter) -> RequestOrReason { 273 return getter->GetAsync(listener, simpleChannel, 274 &NeckoChild::SendGetPageThumbStream); 275 }); 276 277 channel.swap(*aRetVal); 278 } 279 280 #undef LOG 281 282 } // namespace net 283 } // namespace mozilla