RemoteImageProtocolHandler.cpp (7613B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "RemoteImageProtocolHandler.h" 7 8 #include "imgITools.h" 9 #include "nsContentUtils.h" 10 #include "nsIPipe.h" 11 #include "nsIURI.h" 12 #include "nsMimeTypes.h" 13 #include "nsNetUtil.h" 14 #include "nsStreamUtils.h" 15 #include "nsURLHelper.h" 16 #include "mozilla/dom/ipc/IdType.h" 17 #include "mozilla/dom/ContentParent.h" 18 #include "mozilla/dom/ContentProcessManager.h" 19 #include "mozilla/gfx/2D.h" 20 21 namespace mozilla::image { 22 23 using mozilla::dom::ContentParent; 24 using mozilla::dom::ContentParentId; 25 using mozilla::dom::ContentProcessManager; 26 using mozilla::dom::UniqueContentParentKeepAlive; 27 28 StaticRefPtr<RemoteImageProtocolHandler> RemoteImageProtocolHandler::sSingleton; 29 30 NS_IMPL_ISUPPORTS(RemoteImageProtocolHandler, nsIProtocolHandler, 31 nsISupportsWeakReference); 32 33 NS_IMETHODIMP RemoteImageProtocolHandler::GetScheme(nsACString& aScheme) { 34 aScheme.AssignLiteral("moz-remote-image"); 35 return NS_OK; 36 } 37 38 NS_IMETHODIMP RemoteImageProtocolHandler::AllowPort(int32_t, const char*, 39 bool* aAllow) { 40 *aAllow = false; 41 return NS_OK; 42 } 43 44 static UniqueContentParentKeepAlive GetLaunchingContentParentForDecode( 45 const Maybe<ContentParentId>& aContentParentId) { 46 if (aContentParentId.isSome()) { 47 if (ContentProcessManager* cpm = ContentProcessManager::GetSingleton()) { 48 if (ContentParent* cp = cpm->GetContentProcessById(*aContentParentId)) { 49 return cp->TryAddKeepAlive(/* aBrowserId */ 0); 50 } 51 } 52 } 53 54 // We use the extension process as a fallback, because 55 // it is usually running, and should be OK to parse images. 56 return ContentParent::GetNewOrUsedLaunchingBrowserProcess( 57 EXTENSION_REMOTE_TYPE, 58 /* aGroup */ nullptr, 59 /* aPriority */ hal::PROCESS_PRIORITY_FOREGROUND, 60 /* aPreferUsed */ true); 61 } 62 63 static nsresult EncodeImage(const dom::IPCImage& aImage, 64 nsIAsyncOutputStream* aOutputStream) { 65 // TODO(Bug 1997538): Use the internal image/icon format for the 66 // moz-remote-image: protocol 67 nsresult rv; 68 nsCOMPtr<imgITools> imgTools = 69 do_GetService("@mozilla.org/image/tools;1", &rv); 70 MOZ_TRY(rv); 71 72 nsCOMPtr<imgIContainer> imgContainer = 73 nsContentUtils::IPCImageToImage(aImage); 74 if (!imgContainer) { 75 return NS_ERROR_FAILURE; 76 } 77 78 nsCOMPtr<nsIInputStream> stream; 79 MOZ_TRY(imgTools->EncodeImage(imgContainer, nsLiteralCString(IMAGE_PNG), 80 u"png-zlib-level=0"_ns, 81 getter_AddRefs(stream))); 82 83 nsCOMPtr<nsIEventTarget> target = 84 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); 85 MOZ_TRY(rv); 86 87 return NS_AsyncCopy(stream, aOutputStream, target); 88 } 89 90 static void AsyncReEncodeImage(nsIURI* aRemoteURI, ImageIntSize aSize, 91 const Maybe<ContentParentId> aContentParentId, 92 nsIAsyncOutputStream* aOutputStream) { 93 UniqueContentParentKeepAlive cp = 94 GetLaunchingContentParentForDecode(aContentParentId); 95 if (NS_WARN_IF(!cp)) { 96 aOutputStream->CloseWithStatus(NS_ERROR_FAILURE); 97 return; 98 } 99 100 cp->WaitForLaunchAsync() 101 ->Then( 102 GetCurrentSerialEventTarget(), __func__, 103 [remoteURI = nsCOMPtr{aRemoteURI}, 104 aSize](UniqueContentParentKeepAlive&& aCp) { 105 return aCp->SendDecodeImage(WrapNotNull(remoteURI), aSize); 106 }, 107 [](nsresult aError) { 108 return ContentParent::DecodeImagePromise::CreateAndReject( 109 ipc::ResponseRejectReason::SendError, __func__); 110 }) 111 ->Then( 112 GetCurrentSerialEventTarget(), __func__, 113 [cp = std::move(cp), outputStream = nsCOMPtr{aOutputStream}, 114 aSize](const std::tuple<nsresult, mozilla::Maybe<dom::IPCImage>>& 115 aResult) { 116 nsresult rv = std::get<0>(aResult); 117 const mozilla::Maybe<dom::IPCImage>& image = std::get<1>(aResult); 118 119 if (NS_FAILED(rv)) { 120 outputStream->CloseWithStatus(rv); 121 return; 122 } 123 124 if (image.isNothing()) { 125 outputStream->CloseWithStatus(NS_ERROR_UNEXPECTED); 126 return; 127 } 128 129 // Make sure the image size matches if a specific size was 130 // requested. 131 if (aSize.Width() && aSize.Height() && image->size() != aSize) { 132 outputStream->CloseWithStatus(NS_ERROR_UNEXPECTED); 133 return; 134 } 135 136 rv = EncodeImage(*image, outputStream); 137 if (NS_FAILED(rv)) { 138 outputStream->CloseWithStatus(rv); 139 } 140 }, 141 [outputStream = 142 nsCOMPtr{aOutputStream}](mozilla::ipc::ResponseRejectReason) { 143 outputStream->CloseWithStatus(NS_ERROR_FAILURE); 144 }); 145 } 146 147 // Parse out the relevant parts of the moz-remote-image URL 148 static nsresult ParseURI(nsIURI* aURI, nsIURI** aRemoteURI, ImageIntSize* aSize, 149 Maybe<ContentParentId>& aContentParentId) { 150 MOZ_ASSERT(aURI->SchemeIs("moz-remote-image")); 151 152 nsAutoCString query; 153 MOZ_TRY(aURI->GetQuery(query)); 154 155 bool hasURL; 156 int32_t width = 0; 157 int32_t height = 0; 158 159 bool ok = URLParams::Parse( 160 query, true, [&](const nsACString& aName, const nsACString& aValue) { 161 nsresult rv; 162 if (aName.EqualsLiteral("url")) { 163 hasURL = true; 164 rv = NS_NewURI(aRemoteURI, aValue); 165 if (NS_FAILED(rv)) { 166 return false; 167 } 168 } else if (aName.EqualsLiteral("width")) { 169 width = aValue.ToInteger(&rv); 170 if (NS_FAILED(rv) || width < 0) { 171 return false; 172 } 173 } else if (aName.EqualsLiteral("height")) { 174 height = aValue.ToInteger(&rv); 175 if (NS_FAILED(rv) || height < 0) { 176 return false; 177 } 178 } else if (aName.EqualsLiteral("contentParentId")) { 179 int64_t id = aValue.ToInteger(&rv); 180 if (NS_FAILED(rv) || id < 0) { 181 return false; 182 } 183 aContentParentId = Some(ContentParentId(uint64_t(id))); 184 } 185 return true; 186 }); 187 if (NS_WARN_IF(!ok || !hasURL)) { 188 return NS_ERROR_DOM_MALFORMED_URI; 189 } 190 191 *aSize = ImageIntSize(width, height); 192 return NS_OK; 193 } 194 195 NS_IMETHODIMP RemoteImageProtocolHandler::NewChannel(nsIURI* aURI, 196 nsILoadInfo* aLoadInfo, 197 nsIChannel** aOutChannel) { 198 if (!aLoadInfo->TriggeringPrincipal()->IsSystemPrincipal()) { 199 return NS_ERROR_UNEXPECTED; 200 } 201 202 nsCOMPtr<nsIURI> remoteURI; 203 ImageIntSize size; 204 Maybe<ContentParentId> contentParentId; 205 MOZ_TRY(ParseURI(aURI, getter_AddRefs(remoteURI), &size, contentParentId)); 206 207 nsCOMPtr<nsIAsyncInputStream> pipeIn; 208 nsCOMPtr<nsIAsyncOutputStream> pipeOut; 209 NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true); 210 211 nsCOMPtr<nsIChannel> channel; 212 MOZ_TRY(NS_NewInputStreamChannelInternal( 213 getter_AddRefs(channel), aURI, pipeIn.forget(), 214 /* aContentType */ nsLiteralCString(IMAGE_PNG), 215 /* aContentCharset */ ""_ns, aLoadInfo)); 216 217 AsyncReEncodeImage(remoteURI, size, contentParentId, pipeOut); 218 219 channel.forget(aOutChannel); 220 return NS_OK; 221 } 222 223 } // namespace mozilla::image