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