1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 /**
8  * This is the favicon service, which stores favicons for web pages with your
9  * history as you browse. It is also used to save the favicons for bookmarks.
10  *
11  * DANGER: The history query system makes assumptions about the favicon storage
12  * so that icons can be quickly generated for history/bookmark result sets. If
13  * you change the database layout at all, you will have to update both services.
14  */
15 
16 #include "nsFaviconService.h"
17 
18 #include "nsNavHistory.h"
19 #include "nsPlacesMacros.h"
20 #include "Helpers.h"
21 
22 #include "nsNetUtil.h"
23 #include "nsReadableUtils.h"
24 #include "nsStreamUtils.h"
25 #include "nsStringStream.h"
26 #include "plbase64.h"
27 #include "nsIClassInfoImpl.h"
28 #include "mozilla/ArrayUtils.h"
29 #include "mozilla/LoadInfo.h"
30 #include "mozilla/Preferences.h"
31 #include "nsILoadInfo.h"
32 #include "nsIContentPolicy.h"
33 #include "nsContentUtils.h"
34 #include "nsNullPrincipal.h"
35 
36 // For large favicons optimization.
37 #include "imgITools.h"
38 #include "imgIContainer.h"
39 
40 // The target dimension, in pixels, for favicons we optimize.
41 #define OPTIMIZED_FAVICON_DIMENSION 32
42 
43 #define MAX_FAILED_FAVICONS 256
44 #define FAVICON_CACHE_REDUCE_COUNT 64
45 
46 #define UNASSOCIATED_FAVICONS_LENGTH 32
47 
48 // When replaceFaviconData is called, we store the icons in an in-memory cache
49 // instead of in storage. Icons in the cache are expired according to this
50 // interval.
51 #define UNASSOCIATED_ICON_EXPIRY_INTERVAL 60000
52 
53 // The MIME type of the default favicon and favicons created by
54 // OptimizeFaviconImage.
55 #define DEFAULT_MIME_TYPE "image/png"
56 
57 using namespace mozilla;
58 using namespace mozilla::places;
59 
60 /**
61  * Used to notify a topic to system observers on async execute completion.
62  * Will throw on error.
63  */
64 class ExpireFaviconsStatementCallbackNotifier : public AsyncStatementCallback
65 {
66 public:
67   ExpireFaviconsStatementCallbackNotifier();
68   NS_IMETHOD HandleCompletion(uint16_t aReason);
69 };
70 
71 
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService,gFaviconService)72 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
73 
74 NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID)
75 NS_IMPL_ISUPPORTS_CI(
76   nsFaviconService
77 , nsIFaviconService
78 , mozIAsyncFavicons
79 , nsITimerCallback
80 )
81 
82 nsFaviconService::nsFaviconService()
83   : mFailedFaviconSerial(0)
84   , mFailedFavicons(MAX_FAILED_FAVICONS / 2)
85   , mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH)
86 {
87   NS_ASSERTION(!gFaviconService,
88                "Attempting to create two instances of the service!");
89   gFaviconService = this;
90 }
91 
92 
~nsFaviconService()93 nsFaviconService::~nsFaviconService()
94 {
95   NS_ASSERTION(gFaviconService == this,
96                "Deleting a non-singleton instance of the service");
97   if (gFaviconService == this)
98     gFaviconService = nullptr;
99 }
100 
101 
102 nsresult
Init()103 nsFaviconService::Init()
104 {
105   mDB = Database::GetDatabase();
106   NS_ENSURE_STATE(mDB);
107 
108   mExpireUnassociatedIconsTimer = do_CreateInstance("@mozilla.org/timer;1");
109   NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
110 
111   return NS_OK;
112 }
113 
114 NS_IMETHODIMP
ExpireAllFavicons()115 nsFaviconService::ExpireAllFavicons()
116 {
117   nsCOMPtr<mozIStorageAsyncStatement> unlinkIconsStmt = mDB->GetAsyncStatement(
118     "UPDATE moz_places "
119     "SET favicon_id = NULL "
120     "WHERE favicon_id NOT NULL"
121   );
122   NS_ENSURE_STATE(unlinkIconsStmt);
123   nsCOMPtr<mozIStorageAsyncStatement> removeIconsStmt = mDB->GetAsyncStatement(
124     "DELETE FROM moz_favicons WHERE id NOT IN ("
125       "SELECT favicon_id FROM moz_places WHERE favicon_id NOT NULL "
126     ")"
127   );
128   NS_ENSURE_STATE(removeIconsStmt);
129 
130   mozIStorageBaseStatement* stmts[] = {
131     unlinkIconsStmt.get()
132   , removeIconsStmt.get()
133   };
134   nsCOMPtr<mozIStoragePendingStatement> ps;
135   RefPtr<ExpireFaviconsStatementCallbackNotifier> callback =
136     new ExpireFaviconsStatementCallbackNotifier();
137   nsresult rv = mDB->MainConn()->ExecuteAsync(
138     stmts, ArrayLength(stmts), callback, getter_AddRefs(ps)
139   );
140   NS_ENSURE_SUCCESS(rv, rv);
141 
142   return NS_OK;
143 }
144 
145 ////////////////////////////////////////////////////////////////////////////////
146 //// nsITimerCallback
147 
148 NS_IMETHODIMP
Notify(nsITimer * timer)149 nsFaviconService::Notify(nsITimer* timer)
150 {
151   if (timer != mExpireUnassociatedIconsTimer.get()) {
152     return NS_ERROR_INVALID_ARG;
153   }
154 
155   PRTime now = PR_Now();
156   for (auto iter = mUnassociatedIcons.Iter(); !iter.Done(); iter.Next()) {
157     UnassociatedIconHashKey* iconKey = iter.Get();
158     if (now - iconKey->created >= UNASSOCIATED_ICON_EXPIRY_INTERVAL) {
159       iter.Remove();
160     }
161   }
162 
163   // Re-init the expiry timer if the cache isn't empty.
164   if (mUnassociatedIcons.Count() > 0) {
165     mExpireUnassociatedIconsTimer->InitWithCallback(
166       this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
167   }
168 
169   return NS_OK;
170 }
171 
172 ////////////////////////////////////////////////////////////////////////////////
173 //// nsIFaviconService
174 
175 NS_IMETHODIMP
GetDefaultFavicon(nsIURI ** _retval)176 nsFaviconService::GetDefaultFavicon(nsIURI** _retval)
177 {
178   NS_ENSURE_ARG_POINTER(_retval);
179 
180   // not found, use default
181   if (!mDefaultIcon) {
182     nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
183                             NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
184     NS_ENSURE_SUCCESS(rv, rv);
185   }
186   return mDefaultIcon->Clone(_retval);
187 }
188 
189 void
SendFaviconNotifications(nsIURI * aPageURI,nsIURI * aFaviconURI,const nsACString & aGUID)190 nsFaviconService::SendFaviconNotifications(nsIURI* aPageURI,
191                                            nsIURI* aFaviconURI,
192                                            const nsACString& aGUID)
193 {
194   nsAutoCString faviconSpec;
195   nsNavHistory* history = nsNavHistory::GetHistoryService();
196   if (history && NS_SUCCEEDED(aFaviconURI->GetSpec(faviconSpec))) {
197     history->SendPageChangedNotification(aPageURI,
198                                          nsINavHistoryObserver::ATTRIBUTE_FAVICON,
199                                          NS_ConvertUTF8toUTF16(faviconSpec),
200                                          aGUID);
201   }
202 }
203 
204 NS_IMETHODIMP
SetAndFetchFaviconForPage(nsIURI * aPageURI,nsIURI * aFaviconURI,bool aForceReload,uint32_t aFaviconLoadType,nsIFaviconDataCallback * aCallback,nsIPrincipal * aLoadingPrincipal,mozIPlacesPendingOperation ** _canceler)205 nsFaviconService::SetAndFetchFaviconForPage(nsIURI* aPageURI,
206                                             nsIURI* aFaviconURI,
207                                             bool aForceReload,
208                                             uint32_t aFaviconLoadType,
209                                             nsIFaviconDataCallback* aCallback,
210                                             nsIPrincipal* aLoadingPrincipal,
211                                             mozIPlacesPendingOperation **_canceler)
212 {
213   MOZ_ASSERT(NS_IsMainThread());
214   NS_ENSURE_ARG(aPageURI);
215   NS_ENSURE_ARG(aFaviconURI);
216   NS_ENSURE_ARG_POINTER(_canceler);
217 
218   // If a favicon is in the failed cache, only load it during a forced reload.
219   bool previouslyFailed;
220   nsresult rv = IsFailedFavicon(aFaviconURI, &previouslyFailed);
221   NS_ENSURE_SUCCESS(rv, rv);
222   if (previouslyFailed) {
223     if (aForceReload)
224       RemoveFailedFavicon(aFaviconURI);
225     else
226       return NS_OK;
227   }
228 
229   nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
230   MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
231   if (!loadingPrincipal) {
232     // Let's default to the nullPrincipal if no loadingPrincipal is provided.
233     const char16_t* params[] = {
234       u"nsFaviconService::setAndFetchFaviconForPage()",
235       u"nsFaviconService::setAndFetchFaviconForPage(..., [optional aLoadingPrincipal])"
236     };
237     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
238                                     NS_LITERAL_CSTRING("Security by Default"),
239                                     nullptr, // aDocument
240                                     nsContentUtils::eNECKO_PROPERTIES,
241                                     "APIDeprecationWarning",
242                                     params, ArrayLength(params));
243     loadingPrincipal = nsNullPrincipal::Create();
244   }
245   NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
246 
247   // Check if the icon already exists and fetch it from the network, if needed.
248   // Finally associate the icon to the requested page if not yet associated.
249   bool loadPrivate = aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE;
250 
251   PageData page;
252   rv = aPageURI->GetSpec(page.spec);
253   NS_ENSURE_SUCCESS(rv, rv);
254   // URIs can arguably miss a host.
255   (void)GetReversedHostname(aPageURI, page.revHost);
256   bool canAddToHistory;
257   nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
258   NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
259   rv = navHistory->CanAddURI(aPageURI, &canAddToHistory);
260   NS_ENSURE_SUCCESS(rv, rv);
261   page.canAddToHistory = !!canAddToHistory && !loadPrivate;
262 
263   IconData icon;
264   UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(aFaviconURI);
265   if (iconKey) {
266     icon = iconKey->iconData;
267     mUnassociatedIcons.RemoveEntry(iconKey);
268   } else {
269     icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
270     rv = aFaviconURI->GetSpec(icon.spec);
271     NS_ENSURE_SUCCESS(rv, rv);
272   }
273 
274   // If the page url points to an image, the icon's url will be the same.
275   // In future evaluate to store a resample of the image.  For now avoid that
276   // for database size concerns.
277   // Don't store favicons for error pages too.
278   if (icon.spec.Equals(page.spec) ||
279       icon.spec.Equals(FAVICON_ERRORPAGE_URL)) {
280     return NS_OK;
281   }
282 
283   RefPtr<AsyncFetchAndSetIconForPage> event =
284     new AsyncFetchAndSetIconForPage(icon, page, loadPrivate,
285                                     aCallback, aLoadingPrincipal);
286 
287   // Get the target thread and start the work.
288   // DB will be updated and observers notified when data has finished loading.
289   RefPtr<Database> DB = Database::GetDatabase();
290   NS_ENSURE_STATE(DB);
291   DB->DispatchToAsyncThread(event);
292 
293   // Return this event to the caller to allow aborting an eventual fetch.
294   event.forget(_canceler);
295 
296   return NS_OK;
297 }
298 
299 NS_IMETHODIMP
ReplaceFaviconData(nsIURI * aFaviconURI,const uint8_t * aData,uint32_t aDataLen,const nsACString & aMimeType,PRTime aExpiration)300 nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
301                                     const uint8_t* aData,
302                                     uint32_t aDataLen,
303                                     const nsACString& aMimeType,
304                                     PRTime aExpiration)
305 {
306   MOZ_ASSERT(NS_IsMainThread());
307   NS_ENSURE_ARG(aFaviconURI);
308   NS_ENSURE_ARG(aData);
309   NS_ENSURE_TRUE(aDataLen > 0, NS_ERROR_INVALID_ARG);
310   NS_ENSURE_TRUE(aMimeType.Length() > 0, NS_ERROR_INVALID_ARG);
311   if (aExpiration == 0) {
312     aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
313   }
314 
315   UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(aFaviconURI);
316   if (!iconKey) {
317     return NS_ERROR_OUT_OF_MEMORY;
318   }
319 
320   iconKey->created = PR_Now();
321 
322   // If the cache contains unassociated icons, an expiry timer should already exist, otherwise
323   // there may be a timer left hanging around, so make sure we fire a new one.
324   int32_t unassociatedCount = mUnassociatedIcons.Count();
325   if (unassociatedCount == 1) {
326     mExpireUnassociatedIconsTimer->Cancel();
327     mExpireUnassociatedIconsTimer->InitWithCallback(
328       this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
329   }
330 
331   IconData* iconData = &(iconKey->iconData);
332   iconData->expiration = aExpiration;
333   iconData->status = ICON_STATUS_CACHED;
334   iconData->fetchMode = FETCH_NEVER;
335   nsresult rv = aFaviconURI->GetSpec(iconData->spec);
336   NS_ENSURE_SUCCESS(rv, rv);
337 
338   // If the page provided a large image for the favicon (eg, a highres image
339   // or a multiresolution .ico file), we don't want to store more data than
340   // needed.
341   if (aDataLen > MAX_FAVICON_FILESIZE) {
342     rv = OptimizeFaviconImage(aData, aDataLen, aMimeType, iconData->data, iconData->mimeType);
343     NS_ENSURE_SUCCESS(rv, rv);
344 
345     if (iconData->data.Length() > nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
346       // We cannot optimize this favicon size and we are over the maximum size
347       // allowed, so we will not save data to the db to avoid bloating it.
348       mUnassociatedIcons.RemoveEntry(aFaviconURI);
349       return NS_ERROR_FAILURE;
350     }
351   } else {
352     iconData->mimeType.Assign(aMimeType);
353     iconData->data.Assign(TO_CHARBUFFER(aData), aDataLen);
354   }
355 
356   // If the database contains an icon at the given url, we will update the
357   // database immediately so that the associated pages are kept in sync.
358   // Otherwise, do nothing and let the icon be picked up from the memory hash.
359   RefPtr<AsyncReplaceFaviconData> event = new AsyncReplaceFaviconData(*iconData);
360   RefPtr<Database> DB = Database::GetDatabase();
361   NS_ENSURE_STATE(DB);
362   DB->DispatchToAsyncThread(event);
363 
364   return NS_OK;
365 }
366 
367 NS_IMETHODIMP
ReplaceFaviconDataFromDataURL(nsIURI * aFaviconURI,const nsAString & aDataURL,PRTime aExpiration,nsIPrincipal * aLoadingPrincipal)368 nsFaviconService::ReplaceFaviconDataFromDataURL(nsIURI* aFaviconURI,
369                                                const nsAString& aDataURL,
370                                                PRTime aExpiration,
371                                                nsIPrincipal* aLoadingPrincipal)
372 {
373   NS_ENSURE_ARG(aFaviconURI);
374   NS_ENSURE_TRUE(aDataURL.Length() > 0, NS_ERROR_INVALID_ARG);
375   if (aExpiration == 0) {
376     aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
377   }
378 
379   nsCOMPtr<nsIURI> dataURI;
380   nsresult rv = NS_NewURI(getter_AddRefs(dataURI), aDataURL);
381   NS_ENSURE_SUCCESS(rv, rv);
382 
383   // Use the data: protocol handler to convert the data.
384   nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
385   NS_ENSURE_SUCCESS(rv, rv);
386   nsCOMPtr<nsIProtocolHandler> protocolHandler;
387   rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler));
388   NS_ENSURE_SUCCESS(rv, rv);
389 
390   nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
391   MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
392   if (!loadingPrincipal) {
393     // Let's default to the nullPrincipal if no loadingPrincipal is provided.
394     const char16_t* params[] = {
395       u"nsFaviconService::ReplaceFaviconDataFromDataURL()",
396       u"nsFaviconService::ReplaceFaviconDataFromDataURL(..., [optional aLoadingPrincipal])"
397     };
398     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
399                                     NS_LITERAL_CSTRING("Security by Default"),
400                                     nullptr, // aDocument
401                                     nsContentUtils::eNECKO_PROPERTIES,
402                                     "APIDeprecationWarning",
403                                     params, ArrayLength(params));
404 
405     loadingPrincipal = nsNullPrincipal::Create();
406   }
407   NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
408 
409   nsCOMPtr<nsILoadInfo> loadInfo =
410     new mozilla::LoadInfo(loadingPrincipal,
411                           nullptr, // aTriggeringPrincipal
412                           nullptr, // aLoadingNode
413                           nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
414                           nsILoadInfo::SEC_ALLOW_CHROME |
415                           nsILoadInfo::SEC_DISALLOW_SCRIPT,
416                           nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
417 
418   nsCOMPtr<nsIChannel> channel;
419   rv = protocolHandler->NewChannel2(dataURI, loadInfo, getter_AddRefs(channel));
420   NS_ENSURE_SUCCESS(rv, rv);
421 
422   // Blocking stream is OK for data URIs.
423   nsCOMPtr<nsIInputStream> stream;
424   rv = channel->Open2(getter_AddRefs(stream));
425   NS_ENSURE_SUCCESS(rv, rv);
426 
427   uint64_t available64;
428   rv = stream->Available(&available64);
429   NS_ENSURE_SUCCESS(rv, rv);
430   if (available64 == 0 || available64 > UINT32_MAX / sizeof(uint8_t))
431     return NS_ERROR_FILE_TOO_BIG;
432   uint32_t available = (uint32_t)available64;
433 
434   // Read all the decoded data.
435   uint8_t* buffer = static_cast<uint8_t*>
436                                (moz_xmalloc(sizeof(uint8_t) * available));
437   if (!buffer)
438     return NS_ERROR_OUT_OF_MEMORY;
439   uint32_t numRead;
440   rv = stream->Read(TO_CHARBUFFER(buffer), available, &numRead);
441   if (NS_FAILED(rv) || numRead != available) {
442     free(buffer);
443     return rv;
444   }
445 
446   nsAutoCString mimeType;
447   rv = channel->GetContentType(mimeType);
448   if (NS_FAILED(rv)) {
449     free(buffer);
450     return rv;
451   }
452 
453   // ReplaceFaviconData can now do the dirty work.
454   rv = ReplaceFaviconData(aFaviconURI, buffer, available, mimeType, aExpiration);
455   free(buffer);
456   NS_ENSURE_SUCCESS(rv, rv);
457 
458   return NS_OK;
459 }
460 
461 NS_IMETHODIMP
GetFaviconURLForPage(nsIURI * aPageURI,nsIFaviconDataCallback * aCallback)462 nsFaviconService::GetFaviconURLForPage(nsIURI *aPageURI,
463                                        nsIFaviconDataCallback* aCallback)
464 {
465   MOZ_ASSERT(NS_IsMainThread());
466   NS_ENSURE_ARG(aPageURI);
467   NS_ENSURE_ARG(aCallback);
468 
469   nsAutoCString pageSpec;
470   nsresult rv = aPageURI->GetSpec(pageSpec);
471   NS_ENSURE_SUCCESS(rv, rv);
472 
473   RefPtr<AsyncGetFaviconURLForPage> event =
474     new AsyncGetFaviconURLForPage(pageSpec, aCallback);
475 
476   RefPtr<Database> DB = Database::GetDatabase();
477   NS_ENSURE_STATE(DB);
478   DB->DispatchToAsyncThread(event);
479 
480   return NS_OK;
481 }
482 
483 NS_IMETHODIMP
GetFaviconDataForPage(nsIURI * aPageURI,nsIFaviconDataCallback * aCallback)484 nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
485                                         nsIFaviconDataCallback* aCallback)
486 {
487   MOZ_ASSERT(NS_IsMainThread());
488   NS_ENSURE_ARG(aPageURI);
489   NS_ENSURE_ARG(aCallback);
490 
491   nsAutoCString pageSpec;
492   nsresult rv = aPageURI->GetSpec(pageSpec);
493   NS_ENSURE_SUCCESS(rv, rv);
494 
495   RefPtr<AsyncGetFaviconDataForPage> event =
496     new AsyncGetFaviconDataForPage(pageSpec, aCallback);
497   RefPtr<Database> DB = Database::GetDatabase();
498   NS_ENSURE_STATE(DB);
499   DB->DispatchToAsyncThread(event);
500 
501   return NS_OK;
502 }
503 
504 nsresult
GetFaviconLinkForIcon(nsIURI * aFaviconURI,nsIURI ** aOutputURI)505 nsFaviconService::GetFaviconLinkForIcon(nsIURI* aFaviconURI,
506                                         nsIURI** aOutputURI)
507 {
508   NS_ENSURE_ARG(aFaviconURI);
509   NS_ENSURE_ARG_POINTER(aOutputURI);
510 
511   nsAutoCString spec;
512   if (aFaviconURI) {
513     nsresult rv = aFaviconURI->GetSpec(spec);
514     NS_ENSURE_SUCCESS(rv, rv);
515   }
516   return GetFaviconLinkForIconString(spec, aOutputURI);
517 }
518 
519 
520 NS_IMETHODIMP
AddFailedFavicon(nsIURI * aFaviconURI)521 nsFaviconService::AddFailedFavicon(nsIURI* aFaviconURI)
522 {
523   NS_ENSURE_ARG(aFaviconURI);
524 
525   nsAutoCString spec;
526   nsresult rv = aFaviconURI->GetSpec(spec);
527   NS_ENSURE_SUCCESS(rv, rv);
528 
529   mFailedFavicons.Put(spec, mFailedFaviconSerial);
530   mFailedFaviconSerial ++;
531 
532   if (mFailedFavicons.Count() > MAX_FAILED_FAVICONS) {
533     // need to expire some entries, delete the FAVICON_CACHE_REDUCE_COUNT number
534     // of items that are the oldest
535     uint32_t threshold = mFailedFaviconSerial -
536                          MAX_FAILED_FAVICONS + FAVICON_CACHE_REDUCE_COUNT;
537     for (auto iter = mFailedFavicons.Iter(); !iter.Done(); iter.Next()) {
538       if (iter.Data() < threshold) {
539         iter.Remove();
540       }
541     }
542   }
543   return NS_OK;
544 }
545 
546 
547 NS_IMETHODIMP
RemoveFailedFavicon(nsIURI * aFaviconURI)548 nsFaviconService::RemoveFailedFavicon(nsIURI* aFaviconURI)
549 {
550   NS_ENSURE_ARG(aFaviconURI);
551 
552   nsAutoCString spec;
553   nsresult rv = aFaviconURI->GetSpec(spec);
554   NS_ENSURE_SUCCESS(rv, rv);
555 
556   // we silently do nothing and succeed if the icon is not in the cache
557   mFailedFavicons.Remove(spec);
558   return NS_OK;
559 }
560 
561 
562 NS_IMETHODIMP
IsFailedFavicon(nsIURI * aFaviconURI,bool * _retval)563 nsFaviconService::IsFailedFavicon(nsIURI* aFaviconURI, bool* _retval)
564 {
565   NS_ENSURE_ARG(aFaviconURI);
566   nsAutoCString spec;
567   nsresult rv = aFaviconURI->GetSpec(spec);
568   NS_ENSURE_SUCCESS(rv, rv);
569 
570   uint32_t serial;
571   *_retval = mFailedFavicons.Get(spec, &serial);
572   return NS_OK;
573 }
574 
575 
576 // nsFaviconService::GetFaviconLinkForIconString
577 //
578 //    This computes a favicon URL with string input and using the cached
579 //    default one to minimize parsing.
580 
581 nsresult
GetFaviconLinkForIconString(const nsCString & aSpec,nsIURI ** aOutput)582 nsFaviconService::GetFaviconLinkForIconString(const nsCString& aSpec,
583                                               nsIURI** aOutput)
584 {
585   if (aSpec.IsEmpty()) {
586     // default icon for empty strings
587     if (! mDefaultIcon) {
588       nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
589                               NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
590       NS_ENSURE_SUCCESS(rv, rv);
591     }
592     return mDefaultIcon->Clone(aOutput);
593   }
594 
595   if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
596     // pass through for chrome URLs, since they can be referenced without
597     // this service
598     return NS_NewURI(aOutput, aSpec);
599   }
600 
601   nsAutoCString annoUri;
602   annoUri.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
603   annoUri += aSpec;
604   return NS_NewURI(aOutput, annoUri);
605 }
606 
607 
608 // nsFaviconService::GetFaviconSpecForIconString
609 //
610 //    This computes a favicon spec for when you don't want a URI object (as in
611 //    the tree view implementation), sparing all parsing and normalization.
612 void
GetFaviconSpecForIconString(const nsCString & aSpec,nsACString & aOutput)613 nsFaviconService::GetFaviconSpecForIconString(const nsCString& aSpec,
614                                               nsACString& aOutput)
615 {
616   if (aSpec.IsEmpty()) {
617     aOutput.AssignLiteral(FAVICON_DEFAULT_URL);
618   } else if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
619     aOutput = aSpec;
620   } else {
621     aOutput.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
622     aOutput += aSpec;
623   }
624 }
625 
626 
627 // nsFaviconService::OptimizeFaviconImage
628 //
629 // Given a blob of data (a image file already read into a buffer), optimize
630 // its size by recompressing it as a 16x16 PNG.
631 nsresult
OptimizeFaviconImage(const uint8_t * aData,uint32_t aDataLen,const nsACString & aMimeType,nsACString & aNewData,nsACString & aNewMimeType)632 nsFaviconService::OptimizeFaviconImage(const uint8_t* aData, uint32_t aDataLen,
633                                        const nsACString& aMimeType,
634                                        nsACString& aNewData,
635                                        nsACString& aNewMimeType)
636 {
637   nsresult rv;
638 
639   nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1");
640 
641   nsCOMPtr<nsIInputStream> stream;
642   rv = NS_NewByteInputStream(getter_AddRefs(stream),
643                 reinterpret_cast<const char*>(aData), aDataLen,
644                 NS_ASSIGNMENT_DEPEND);
645   NS_ENSURE_SUCCESS(rv, rv);
646 
647   // decode image
648   nsCOMPtr<imgIContainer> container;
649   rv = imgtool->DecodeImageData(stream, aMimeType, getter_AddRefs(container));
650   NS_ENSURE_SUCCESS(rv, rv);
651 
652   aNewMimeType.AssignLiteral(DEFAULT_MIME_TYPE);
653 
654   // scale and recompress
655   nsCOMPtr<nsIInputStream> iconStream;
656   rv = imgtool->EncodeScaledImage(container, aNewMimeType,
657                                   OPTIMIZED_FAVICON_DIMENSION,
658                                   OPTIMIZED_FAVICON_DIMENSION,
659                                   EmptyString(),
660                                   getter_AddRefs(iconStream));
661   NS_ENSURE_SUCCESS(rv, rv);
662 
663   // Read the stream into a new buffer.
664   rv = NS_ConsumeStream(iconStream, UINT32_MAX, aNewData);
665   NS_ENSURE_SUCCESS(rv, rv);
666 
667   return NS_OK;
668 }
669 
670 nsresult
GetFaviconDataAsync(nsIURI * aFaviconURI,mozIStorageStatementCallback * aCallback)671 nsFaviconService::GetFaviconDataAsync(nsIURI* aFaviconURI,
672                                       mozIStorageStatementCallback *aCallback)
673 {
674   NS_ASSERTION(aCallback, "Doesn't make sense to call this without a callback");
675   nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
676     "SELECT f.data, f.mime_type FROM moz_favicons f WHERE url = :icon_url"
677   );
678   NS_ENSURE_STATE(stmt);
679 
680   // Ignore the ref part of the URI before querying the database because
681   // we may have added a media fragment for rendering purposes.
682 
683   nsAutoCString faviconURI;
684   aFaviconURI->GetSpecIgnoringRef(faviconURI);
685   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), faviconURI);
686   NS_ENSURE_SUCCESS(rv, rv);
687 
688   nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
689   return stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
690 }
691 
692 ////////////////////////////////////////////////////////////////////////////////
693 //// ExpireFaviconsStatementCallbackNotifier
694 
ExpireFaviconsStatementCallbackNotifier()695 ExpireFaviconsStatementCallbackNotifier::ExpireFaviconsStatementCallbackNotifier()
696 {
697 }
698 
699 
700 NS_IMETHODIMP
HandleCompletion(uint16_t aReason)701 ExpireFaviconsStatementCallbackNotifier::HandleCompletion(uint16_t aReason)
702 {
703   // We should dispatch only if expiration has been successful.
704   if (aReason != mozIStorageStatementCallback::REASON_FINISHED)
705     return NS_OK;
706 
707   nsCOMPtr<nsIObserverService> observerService =
708     mozilla::services::GetObserverService();
709   if (observerService) {
710     (void)observerService->NotifyObservers(nullptr,
711                                            NS_PLACES_FAVICONS_EXPIRED_TOPIC_ID,
712                                            nullptr);
713   }
714 
715   return NS_OK;
716 }
717