1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * 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 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7#include "nsContentUtils.h" 8#include "nsIconChannel.h" 9#include "mozilla/BasePrincipal.h" 10#include "mozilla/EndianUtils.h" 11#include "nsComponentManagerUtils.h" 12#include "nsIIconURI.h" 13#include "nsIInputStream.h" 14#include "nsIInterfaceRequestor.h" 15#include "nsIInterfaceRequestorUtils.h" 16#include "nsString.h" 17#include "nsMimeTypes.h" 18#include "nsMemory.h" 19#include "nsIURL.h" 20#include "nsNetCID.h" 21#include "nsIPipe.h" 22#include "nsIOutputStream.h" 23#include "nsCExternalHandlerService.h" 24#include "nsILocalFileMac.h" 25#include "nsIFileURL.h" 26#include "nsTArray.h" 27#include "nsObjCExceptions.h" 28#include "nsProxyRelease.h" 29#include "nsContentSecurityManager.h" 30#include "nsNetUtil.h" 31#include "mozilla/RefPtr.h" 32 33#include <Cocoa/Cocoa.h> 34 35using namespace mozilla; 36 37// nsIconChannel methods 38nsIconChannel::nsIconChannel() {} 39 40nsIconChannel::~nsIconChannel() {} 41 42NS_IMPL_ISUPPORTS(nsIconChannel, nsIChannel, nsIRequest, nsIRequestObserver, nsIStreamListener) 43 44nsresult nsIconChannel::Init(nsIURI* uri) { 45 NS_ASSERTION(uri, "no uri"); 46 mUrl = uri; 47 mOriginalURI = uri; 48 nsresult rv; 49 mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); 50 return rv; 51} 52 53//////////////////////////////////////////////////////////////////////////////// 54// nsIRequest methods: 55 56NS_IMETHODIMP 57nsIconChannel::GetName(nsACString& result) { return mUrl->GetSpec(result); } 58 59NS_IMETHODIMP 60nsIconChannel::IsPending(bool* result) { return mPump->IsPending(result); } 61 62NS_IMETHODIMP 63nsIconChannel::GetStatus(nsresult* status) { return mPump->GetStatus(status); } 64 65NS_IMETHODIMP 66nsIconChannel::Cancel(nsresult status) { 67 mCanceled = true; 68 return mPump->Cancel(status); 69} 70 71NS_IMETHODIMP 72nsIconChannel::GetCanceled(bool* result) { 73 *result = mCanceled; 74 return NS_OK; 75} 76 77NS_IMETHODIMP 78nsIconChannel::Suspend(void) { return mPump->Suspend(); } 79 80NS_IMETHODIMP 81nsIconChannel::Resume(void) { return mPump->Resume(); } 82 83// nsIRequestObserver methods 84NS_IMETHODIMP 85nsIconChannel::OnStartRequest(nsIRequest* aRequest) { 86 if (mListener) { 87 return mListener->OnStartRequest(this); 88 } 89 return NS_OK; 90} 91 92NS_IMETHODIMP 93nsIconChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { 94 if (mListener) { 95 mListener->OnStopRequest(this, aStatus); 96 mListener = nullptr; 97 } 98 99 // Remove from load group 100 if (mLoadGroup) { 101 mLoadGroup->RemoveRequest(this, nullptr, aStatus); 102 } 103 104 return NS_OK; 105} 106 107// nsIStreamListener methods 108NS_IMETHODIMP 109nsIconChannel::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream, uint64_t aOffset, 110 uint32_t aCount) { 111 if (mListener) { 112 return mListener->OnDataAvailable(this, aStream, aOffset, aCount); 113 } 114 return NS_OK; 115} 116 117//////////////////////////////////////////////////////////////////////////////// 118// nsIChannel methods: 119 120NS_IMETHODIMP 121nsIconChannel::GetOriginalURI(nsIURI** aURI) { 122 *aURI = mOriginalURI; 123 NS_ADDREF(*aURI); 124 return NS_OK; 125} 126 127NS_IMETHODIMP 128nsIconChannel::SetOriginalURI(nsIURI* aURI) { 129 NS_ENSURE_ARG_POINTER(aURI); 130 mOriginalURI = aURI; 131 return NS_OK; 132} 133 134NS_IMETHODIMP 135nsIconChannel::GetURI(nsIURI** aURI) { 136 *aURI = mUrl; 137 NS_IF_ADDREF(*aURI); 138 return NS_OK; 139} 140 141NS_IMETHODIMP 142nsIconChannel::Open(nsIInputStream** _retval) { 143 nsCOMPtr<nsIStreamListener> listener; 144 nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); 145 NS_ENSURE_SUCCESS(rv, rv); 146 147 return MakeInputStream(_retval, false); 148} 149 150nsresult nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile, uint32_t* aDesiredImageSize, 151 nsACString& aContentType, 152 nsACString& aFileExtension) { 153 nsresult rv = NS_OK; 154 nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv)); 155 NS_ENSURE_SUCCESS(rv, rv); 156 157 iconURI->GetImageSize(aDesiredImageSize); 158 iconURI->GetContentType(aContentType); 159 iconURI->GetFileExtension(aFileExtension); 160 161 nsCOMPtr<nsIURL> url; 162 rv = iconURI->GetIconURL(getter_AddRefs(url)); 163 if (NS_FAILED(rv) || !url) return NS_OK; 164 165 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv); 166 if (NS_FAILED(rv) || !fileURL) return NS_OK; 167 168 nsCOMPtr<nsIFile> file; 169 rv = fileURL->GetFile(getter_AddRefs(file)); 170 if (NS_FAILED(rv) || !file) return NS_OK; 171 172 nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(file, &rv)); 173 if (NS_FAILED(rv) || !localFileMac) return NS_OK; 174 175 *aLocalFile = file; 176 NS_IF_ADDREF(*aLocalFile); 177 178 return NS_OK; 179} 180 181NS_IMETHODIMP 182nsIconChannel::AsyncOpen(nsIStreamListener* aListener) { 183 nsCOMPtr<nsIStreamListener> listener = aListener; 184 nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); 185 if (NS_FAILED(rv)) { 186 mCallbacks = nullptr; 187 return rv; 188 } 189 190 MOZ_ASSERT(mLoadInfo->GetSecurityMode() == 0 || mLoadInfo->GetInitialSecurityCheckDone() || 191 (mLoadInfo->GetSecurityMode() == 192 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL && 193 mLoadInfo->GetLoadingPrincipal() && 194 mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()), 195 "security flags in loadInfo but doContentSecurityCheck() not called"); 196 197 nsCOMPtr<nsIInputStream> inStream; 198 rv = MakeInputStream(getter_AddRefs(inStream), true); 199 if (NS_FAILED(rv)) { 200 mCallbacks = nullptr; 201 return rv; 202 } 203 204 // Init our stream pump 205 nsCOMPtr<nsIEventTarget> target = 206 nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, mozilla::TaskCategory::Other); 207 rv = mPump->Init(inStream, 0, 0, false, target); 208 if (NS_FAILED(rv)) { 209 mCallbacks = nullptr; 210 return rv; 211 } 212 213 rv = mPump->AsyncRead(this); 214 if (NS_SUCCEEDED(rv)) { 215 // Store our real listener 216 mListener = aListener; 217 // Add ourself to the load group, if available 218 if (mLoadGroup) { 219 mLoadGroup->AddRequest(this, nullptr); 220 } 221 } else { 222 mCallbacks = nullptr; 223 } 224 225 return rv; 226} 227 228nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval, bool aNonBlocking) { 229 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 230 231 nsCString contentType; 232 nsAutoCString fileExt; 233 nsCOMPtr<nsIFile> fileloc; // file we want an icon for 234 uint32_t desiredImageSize; 235 nsresult rv = 236 ExtractIconInfoFromUrl(getter_AddRefs(fileloc), &desiredImageSize, contentType, fileExt); 237 NS_ENSURE_SUCCESS(rv, rv); 238 239 bool fileExists = false; 240 if (fileloc) { 241 fileloc->Exists(&fileExists); 242 } 243 244 NSImage* iconImage = nil; 245 246 // first try to get the icon from the file if it exists 247 if (fileExists) { 248 nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(fileloc, &rv)); 249 NS_ENSURE_SUCCESS(rv, rv); 250 251 CFURLRef macURL; 252 if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) { 253 iconImage = [[NSWorkspace sharedWorkspace] iconForFile:[(NSURL*)macURL path]]; 254 ::CFRelease(macURL); 255 } 256 } 257 258 // if we don't have an icon yet try to get one by extension 259 if (!iconImage && !fileExt.IsEmpty()) { 260 NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()]; 261 iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension]; 262 } 263 264 // If we still don't have an icon, get the generic document icon. 265 if (!iconImage) { 266 iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeUnknown]; 267 } 268 269 if (!iconImage) { 270 return NS_ERROR_FAILURE; 271 } 272 273 if (desiredImageSize > 255) { 274 // The Icon image format represents width and height as u8, so it does not 275 // allow for images sized 256 or more. 276 return NS_ERROR_FAILURE; 277 } 278 279 // Set the actual size to *twice* the requested size. 280 // We do this because our UI doesn't take the window's device pixel ratio into 281 // account when it requests these icons; e.g. it will request an icon with 282 // size 16, place it in a 16x16 CSS pixel sized image, and then display it in 283 // a window on a HiDPI screen where the icon now covers 32x32 physical screen 284 // pixels. So we just always double the size here in order to prevent blurriness. 285 uint32_t size = (desiredImageSize < 128) ? desiredImageSize * 2 : desiredImageSize; 286 uint32_t width = size; 287 uint32_t height = size; 288 289 // The "image format" we're outputting here (and which gets decoded by 290 // nsIconDecoder) has the following format: 291 // - 1 byte for the image width, as u8 292 // - 1 byte for the image height, as u8 293 // - the raw image data as BGRA, width * height * 4 bytes. 294 size_t bufferCapacity = 4 + width * height * 4; 295 UniquePtr<uint8_t[]> fileBuf = MakeUnique<uint8_t[]>(bufferCapacity); 296 fileBuf[0] = uint8_t(width); 297 fileBuf[1] = uint8_t(height); 298 fileBuf[2] = uint8_t(mozilla::gfx::SurfaceFormat::B8G8R8A8); 299 300 // Clear all bits to ensure in nsIconDecoder we assume we are already color 301 // managed and premultiplied. 302 fileBuf[3] = 0; 303 304 uint8_t* imageBuf = &fileBuf[4]; 305 306 // Create a CGBitmapContext around imageBuf and draw iconImage to it. 307 // This gives us the image data in the format we want: BGRA, four bytes per 308 // pixel, in host endianness, with premultiplied alpha. 309 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); 310 CGContextRef ctx = 311 CGBitmapContextCreate(imageBuf, width, height, 8 /* bitsPerComponent */, width * 4, cs, 312 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); 313 CGColorSpaceRelease(cs); 314 315 NSGraphicsContext* oldContext = [NSGraphicsContext currentContext]; 316 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx 317 flipped:NO]]; 318 319 [iconImage drawInRect:NSMakeRect(0, 0, width, height)]; 320 321 [NSGraphicsContext setCurrentContext:oldContext]; 322 323 CGContextRelease(ctx); 324 325 // Now, create a pipe and stuff our data into it 326 nsCOMPtr<nsIInputStream> inStream; 327 nsCOMPtr<nsIOutputStream> outStream; 328 rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream), bufferCapacity, 329 bufferCapacity, aNonBlocking); 330 331 if (NS_SUCCEEDED(rv)) { 332 uint32_t written; 333 rv = outStream->Write((char*)fileBuf.get(), bufferCapacity, &written); 334 if (NS_SUCCEEDED(rv)) { 335 NS_IF_ADDREF(*_retval = inStream); 336 } 337 } 338 339 // Drop notification callbacks to prevent cycles. 340 mCallbacks = nullptr; 341 342 return NS_OK; 343 344 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 345} 346 347NS_IMETHODIMP 348nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes) { 349 return mPump->GetLoadFlags(aLoadAttributes); 350} 351 352NS_IMETHODIMP 353nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes) { 354 return mPump->SetLoadFlags(aLoadAttributes); 355} 356 357NS_IMETHODIMP 358nsIconChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { return GetTRRModeImpl(aTRRMode); } 359 360NS_IMETHODIMP 361nsIconChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) { return SetTRRModeImpl(aTRRMode); } 362 363NS_IMETHODIMP 364nsIconChannel::GetIsDocument(bool* aIsDocument) { 365 return NS_GetIsDocumentChannel(this, aIsDocument); 366} 367 368NS_IMETHODIMP 369nsIconChannel::GetContentType(nsACString& aContentType) { 370 aContentType.AssignLiteral(IMAGE_ICON_MS); 371 return NS_OK; 372} 373 374NS_IMETHODIMP 375nsIconChannel::SetContentType(const nsACString& aContentType) { 376 // It doesn't make sense to set the content-type on this type 377 // of channel... 378 return NS_ERROR_FAILURE; 379} 380 381NS_IMETHODIMP 382nsIconChannel::GetContentCharset(nsACString& aContentCharset) { 383 aContentCharset.AssignLiteral(IMAGE_ICON_MS); 384 return NS_OK; 385} 386 387NS_IMETHODIMP 388nsIconChannel::SetContentCharset(const nsACString& aContentCharset) { 389 // It doesn't make sense to set the content-type on this type 390 // of channel... 391 return NS_ERROR_FAILURE; 392} 393 394NS_IMETHODIMP 395nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition) { 396 return NS_ERROR_NOT_AVAILABLE; 397} 398 399NS_IMETHODIMP 400nsIconChannel::SetContentDisposition(uint32_t aContentDisposition) { 401 return NS_ERROR_NOT_AVAILABLE; 402} 403 404NS_IMETHODIMP 405nsIconChannel::GetContentDispositionFilename(nsAString& aContentDispositionFilename) { 406 return NS_ERROR_NOT_AVAILABLE; 407} 408 409NS_IMETHODIMP 410nsIconChannel::SetContentDispositionFilename(const nsAString& aContentDispositionFilename) { 411 return NS_ERROR_NOT_AVAILABLE; 412} 413 414NS_IMETHODIMP 415nsIconChannel::GetContentDispositionHeader(nsACString& aContentDispositionHeader) { 416 return NS_ERROR_NOT_AVAILABLE; 417} 418 419NS_IMETHODIMP 420nsIconChannel::GetContentLength(int64_t* aContentLength) { 421 *aContentLength = 0; 422 return NS_ERROR_FAILURE; 423} 424 425NS_IMETHODIMP 426nsIconChannel::SetContentLength(int64_t aContentLength) { 427 MOZ_ASSERT_UNREACHABLE("nsIconChannel::SetContentLength"); 428 return NS_ERROR_NOT_IMPLEMENTED; 429} 430 431NS_IMETHODIMP 432nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) { 433 *aLoadGroup = mLoadGroup; 434 NS_IF_ADDREF(*aLoadGroup); 435 return NS_OK; 436} 437 438NS_IMETHODIMP 439nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) { 440 mLoadGroup = aLoadGroup; 441 return NS_OK; 442} 443 444NS_IMETHODIMP 445nsIconChannel::GetOwner(nsISupports** aOwner) { 446 *aOwner = mOwner.get(); 447 NS_IF_ADDREF(*aOwner); 448 return NS_OK; 449} 450 451NS_IMETHODIMP 452nsIconChannel::SetOwner(nsISupports* aOwner) { 453 mOwner = aOwner; 454 return NS_OK; 455} 456 457NS_IMETHODIMP 458nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) { 459 NS_IF_ADDREF(*aLoadInfo = mLoadInfo); 460 return NS_OK; 461} 462 463NS_IMETHODIMP 464nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) { 465 MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null"); 466 mLoadInfo = aLoadInfo; 467 return NS_OK; 468} 469 470NS_IMETHODIMP 471nsIconChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks) { 472 *aNotificationCallbacks = mCallbacks.get(); 473 NS_IF_ADDREF(*aNotificationCallbacks); 474 return NS_OK; 475} 476 477NS_IMETHODIMP 478nsIconChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks) { 479 mCallbacks = aNotificationCallbacks; 480 return NS_OK; 481} 482 483NS_IMETHODIMP 484nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo) { 485 *aSecurityInfo = nullptr; 486 return NS_OK; 487} 488