tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsIconChannelCocoa.mm (14803B)


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