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