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