Line data Source code
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 "NullPrincipal.h"
35 : #include "imgICache.h"
36 :
37 : #define MAX_FAILED_FAVICONS 256
38 : #define FAVICON_CACHE_REDUCE_COUNT 64
39 :
40 : #define UNASSOCIATED_FAVICONS_LENGTH 32
41 :
42 : // When replaceFaviconData is called, we store the icons in an in-memory cache
43 : // instead of in storage. Icons in the cache are expired according to this
44 : // interval.
45 : #define UNASSOCIATED_ICON_EXPIRY_INTERVAL 60000
46 :
47 : using namespace mozilla;
48 : using namespace mozilla::places;
49 :
50 : /**
51 : * Used to notify a topic to system observers on async execute completion.
52 : * Will throw on error.
53 : */
54 0 : class ExpireFaviconsStatementCallbackNotifier : public AsyncStatementCallback
55 : {
56 : public:
57 : ExpireFaviconsStatementCallbackNotifier();
58 : NS_IMETHOD HandleCompletion(uint16_t aReason);
59 : };
60 :
61 : namespace {
62 :
63 : /**
64 : * Extracts and filters native sizes from the given container, based on the
65 : * list of sizes we are supposed to retain.
66 : * All calculation is done considering square sizes and the largest side.
67 : * In case of multiple frames of the same size, only the first one is retained.
68 : */
69 : nsresult
70 0 : GetFramesInfoForContainer(imgIContainer* aContainer,
71 : nsTArray<FrameData>& aFramesInfo) {
72 : // Don't extract frames from animated images.
73 : bool animated;
74 0 : nsresult rv = aContainer->GetAnimated(&animated);
75 0 : if (NS_FAILED(rv) || !animated) {
76 0 : nsTArray<nsIntSize> nativeSizes;
77 0 : rv = aContainer->GetNativeSizes(nativeSizes);
78 0 : if (NS_SUCCEEDED(rv) && nativeSizes.Length() > 1) {
79 0 : for (uint32_t i = 0; i < nativeSizes.Length(); ++i) {
80 0 : nsIntSize nativeSize = nativeSizes[i];
81 : // Only retain square frames.
82 0 : if (nativeSize.width != nativeSize.height) {
83 0 : continue;
84 : }
85 : // Check if it's one of the sizes we care about.
86 0 : auto end = std::end(sFaviconSizes);
87 0 : uint16_t* matchingSize = std::find(std::begin(sFaviconSizes), end,
88 0 : nativeSize.width);
89 0 : if (matchingSize != end) {
90 : // We must avoid duped sizes, an image could contain multiple frames of
91 : // the same size, but we can only store one. We could use an hashtable,
92 : // but considered the average low number of frames, we'll just do a
93 : // linear search.
94 0 : bool dupe = false;
95 0 : for (const auto& frameInfo : aFramesInfo) {
96 0 : if (frameInfo.width == *matchingSize) {
97 0 : dupe = true;
98 0 : break;
99 : }
100 : }
101 0 : if (!dupe) {
102 0 : aFramesInfo.AppendElement(FrameData(i, *matchingSize));
103 : }
104 : }
105 : }
106 : }
107 : }
108 :
109 0 : if (aFramesInfo.Length() == 0) {
110 : // Always have at least the default size.
111 : int32_t width;
112 0 : rv = aContainer->GetWidth(&width);
113 0 : NS_ENSURE_SUCCESS(rv, rv);
114 : int32_t height;
115 0 : rv = aContainer->GetHeight(&height);
116 0 : NS_ENSURE_SUCCESS(rv, rv);
117 : // For non-square images, pick the largest side.
118 0 : aFramesInfo.AppendElement(FrameData(0, std::max(width, height)));
119 : }
120 0 : return NS_OK;
121 : }
122 :
123 : } // namespace
124 :
125 2 : PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
126 :
127 3 : NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID)
128 76 : NS_IMPL_ISUPPORTS_CI(
129 : nsFaviconService
130 : , nsIFaviconService
131 : , mozIAsyncFavicons
132 : , nsITimerCallback
133 : )
134 :
135 1 : nsFaviconService::nsFaviconService()
136 : : mFailedFaviconSerial(0)
137 : , mFailedFavicons(MAX_FAILED_FAVICONS / 2)
138 1 : , mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH)
139 : {
140 1 : NS_ASSERTION(!gFaviconService,
141 : "Attempting to create two instances of the service!");
142 1 : gFaviconService = this;
143 1 : }
144 :
145 :
146 0 : nsFaviconService::~nsFaviconService()
147 : {
148 0 : NS_ASSERTION(gFaviconService == this,
149 : "Deleting a non-singleton instance of the service");
150 0 : if (gFaviconService == this)
151 0 : gFaviconService = nullptr;
152 0 : }
153 :
154 : Atomic<int64_t> nsFaviconService::sLastInsertedIconId(0);
155 :
156 : void // static
157 0 : nsFaviconService::StoreLastInsertedId(const nsACString& aTable,
158 : const int64_t aLastInsertedId) {
159 0 : MOZ_ASSERT(aTable.EqualsLiteral("moz_icons"));
160 0 : sLastInsertedIconId = aLastInsertedId;
161 0 : }
162 :
163 : nsresult
164 1 : nsFaviconService::Init()
165 : {
166 1 : mDB = Database::GetDatabase();
167 1 : NS_ENSURE_STATE(mDB);
168 :
169 1 : mExpireUnassociatedIconsTimer = do_CreateInstance("@mozilla.org/timer;1");
170 1 : NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
171 :
172 : // Check if there are still icon payloads to be converted.
173 : bool shouldConvertPayloads =
174 1 : Preferences::GetBool(PREF_CONVERT_PAYLOADS, false);
175 1 : if (shouldConvertPayloads) {
176 0 : ConvertUnsupportedPayloads(mDB->MainConn());
177 : }
178 :
179 1 : return NS_OK;
180 : }
181 :
182 : NS_IMETHODIMP
183 0 : nsFaviconService::ExpireAllFavicons()
184 : {
185 0 : NS_ENSURE_STATE(mDB);
186 :
187 0 : nsCOMPtr<mozIStorageAsyncStatement> removePagesStmt = mDB->GetAsyncStatement(
188 : "DELETE FROM moz_pages_w_icons"
189 0 : );
190 0 : NS_ENSURE_STATE(removePagesStmt);
191 0 : nsCOMPtr<mozIStorageAsyncStatement> removeIconsStmt = mDB->GetAsyncStatement(
192 : "DELETE FROM moz_icons"
193 0 : );
194 0 : NS_ENSURE_STATE(removeIconsStmt);
195 0 : nsCOMPtr<mozIStorageAsyncStatement> unlinkIconsStmt = mDB->GetAsyncStatement(
196 : "DELETE FROM moz_icons_to_pages"
197 0 : );
198 0 : NS_ENSURE_STATE(unlinkIconsStmt);
199 :
200 : mozIStorageBaseStatement* stmts[] = {
201 0 : removePagesStmt.get()
202 0 : , removeIconsStmt.get()
203 0 : , unlinkIconsStmt.get()
204 0 : };
205 0 : nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
206 0 : if (!conn) {
207 0 : return NS_ERROR_UNEXPECTED;
208 : }
209 0 : nsCOMPtr<mozIStoragePendingStatement> ps;
210 : RefPtr<ExpireFaviconsStatementCallbackNotifier> callback =
211 0 : new ExpireFaviconsStatementCallbackNotifier();
212 0 : return conn->ExecuteAsync(stmts, ArrayLength(stmts),
213 0 : callback, getter_AddRefs(ps));
214 : }
215 :
216 : ////////////////////////////////////////////////////////////////////////////////
217 : //// nsITimerCallback
218 :
219 : NS_IMETHODIMP
220 0 : nsFaviconService::Notify(nsITimer* timer)
221 : {
222 0 : if (timer != mExpireUnassociatedIconsTimer.get()) {
223 0 : return NS_ERROR_INVALID_ARG;
224 : }
225 :
226 0 : PRTime now = PR_Now();
227 0 : for (auto iter = mUnassociatedIcons.Iter(); !iter.Done(); iter.Next()) {
228 0 : UnassociatedIconHashKey* iconKey = iter.Get();
229 0 : if (now - iconKey->created >= UNASSOCIATED_ICON_EXPIRY_INTERVAL) {
230 0 : iter.Remove();
231 : }
232 : }
233 :
234 : // Re-init the expiry timer if the cache isn't empty.
235 0 : if (mUnassociatedIcons.Count() > 0) {
236 0 : mExpireUnassociatedIconsTimer->InitWithCallback(
237 0 : this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
238 : }
239 :
240 0 : return NS_OK;
241 : }
242 :
243 : ////////////////////////////////////////////////////////////////////////////////
244 : //// nsIFaviconService
245 :
246 : NS_IMETHODIMP
247 0 : nsFaviconService::GetDefaultFavicon(nsIURI** _retval)
248 : {
249 0 : NS_ENSURE_ARG_POINTER(_retval);
250 :
251 : // not found, use default
252 0 : if (!mDefaultIcon) {
253 0 : nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
254 0 : NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
255 0 : NS_ENSURE_SUCCESS(rv, rv);
256 : }
257 0 : return mDefaultIcon->Clone(_retval);
258 : }
259 :
260 : NS_IMETHODIMP
261 0 : nsFaviconService::GetDefaultFaviconMimeType(nsACString& _retval)
262 : {
263 0 : _retval = NS_LITERAL_CSTRING(FAVICON_DEFAULT_MIMETYPE);
264 0 : return NS_OK;
265 : }
266 :
267 : void
268 0 : nsFaviconService::SendFaviconNotifications(nsIURI* aPageURI,
269 : nsIURI* aFaviconURI,
270 : const nsACString& aGUID)
271 : {
272 0 : nsAutoCString faviconSpec;
273 0 : nsNavHistory* history = nsNavHistory::GetHistoryService();
274 0 : if (history && NS_SUCCEEDED(aFaviconURI->GetSpec(faviconSpec))) {
275 : // Invalide page-icon image cache, since the icon is about to change.
276 0 : nsCString spec;
277 0 : nsresult rv = aPageURI->GetSpec(spec);
278 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
279 0 : if (NS_SUCCEEDED(rv)) {
280 0 : nsCString pageIconSpec("page-icon:");
281 0 : pageIconSpec.Append(spec);
282 0 : nsCOMPtr<nsIURI> pageIconURI;
283 0 : rv = NS_NewURI(getter_AddRefs(pageIconURI), pageIconSpec);
284 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
285 0 : if (NS_SUCCEEDED(rv)) {
286 0 : nsCOMPtr<imgICache> imgCache;
287 0 : rv = GetImgTools()->GetImgCacheForDocument(nullptr, getter_AddRefs(imgCache));
288 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
289 0 : if (NS_SUCCEEDED(rv)) {
290 0 : Unused << imgCache->RemoveEntry(pageIconURI, nullptr);
291 : }
292 : }
293 : }
294 :
295 : history->SendPageChangedNotification(aPageURI,
296 : nsINavHistoryObserver::ATTRIBUTE_FAVICON,
297 0 : NS_ConvertUTF8toUTF16(faviconSpec),
298 0 : aGUID);
299 : }
300 0 : }
301 :
302 : NS_IMETHODIMP
303 1 : nsFaviconService::SetAndFetchFaviconForPage(nsIURI* aPageURI,
304 : nsIURI* aFaviconURI,
305 : bool aForceReload,
306 : uint32_t aFaviconLoadType,
307 : nsIFaviconDataCallback* aCallback,
308 : nsIPrincipal* aLoadingPrincipal,
309 : mozIPlacesPendingOperation **_canceler)
310 : {
311 1 : MOZ_ASSERT(NS_IsMainThread());
312 1 : NS_ENSURE_ARG(aPageURI);
313 1 : NS_ENSURE_ARG(aFaviconURI);
314 1 : NS_ENSURE_ARG_POINTER(_canceler);
315 :
316 : // If a favicon is in the failed cache, only load it during a forced reload.
317 : bool previouslyFailed;
318 1 : nsresult rv = IsFailedFavicon(aFaviconURI, &previouslyFailed);
319 1 : NS_ENSURE_SUCCESS(rv, rv);
320 1 : if (previouslyFailed) {
321 0 : if (aForceReload) {
322 0 : RemoveFailedFavicon(aFaviconURI);
323 : }
324 : else {
325 0 : return NS_OK;
326 : }
327 : }
328 :
329 2 : nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
330 1 : MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
331 1 : if (!loadingPrincipal) {
332 : // Let's default to the nullPrincipal if no loadingPrincipal is provided.
333 : const char16_t* params[] = {
334 : u"nsFaviconService::setAndFetchFaviconForPage()",
335 : u"nsFaviconService::setAndFetchFaviconForPage(..., [optional aLoadingPrincipal])"
336 0 : };
337 0 : nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
338 0 : NS_LITERAL_CSTRING("Security by Default"),
339 : nullptr, // aDocument
340 : nsContentUtils::eNECKO_PROPERTIES,
341 : "APIDeprecationWarning",
342 0 : params, ArrayLength(params));
343 0 : loadingPrincipal = NullPrincipal::Create();
344 : }
345 1 : NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
346 :
347 1 : bool loadPrivate = aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE;
348 :
349 : // Build page data.
350 2 : PageData page;
351 1 : rv = aPageURI->GetSpec(page.spec);
352 1 : NS_ENSURE_SUCCESS(rv, rv);
353 : // URIs can arguably lack a host.
354 1 : Unused << aPageURI->GetHost(page.host);
355 1 : if (StringBeginsWith(page.host, NS_LITERAL_CSTRING("www."))) {
356 0 : page.host.Cut(0, 4);
357 : }
358 : bool canAddToHistory;
359 1 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
360 1 : NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
361 1 : rv = navHistory->CanAddURI(aPageURI, &canAddToHistory);
362 1 : NS_ENSURE_SUCCESS(rv, rv);
363 1 : page.canAddToHistory = !!canAddToHistory && !loadPrivate;
364 :
365 : // Build icon data.
366 2 : IconData icon;
367 : // If we have an in-memory icon payload, it overwrites the actual request.
368 1 : UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(aFaviconURI);
369 1 : if (iconKey) {
370 0 : icon = iconKey->iconData;
371 0 : mUnassociatedIcons.RemoveEntry(iconKey);
372 : } else {
373 1 : icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
374 1 : rv = aFaviconURI->GetSpec(icon.spec);
375 1 : NS_ENSURE_SUCCESS(rv, rv);
376 : // URIs can arguably lack a host.
377 1 : Unused << aFaviconURI->GetHost(icon.host);
378 1 : if (StringBeginsWith(icon.host, NS_LITERAL_CSTRING("www."))) {
379 0 : icon.host.Cut(0, 4);
380 : }
381 2 : nsAutoCString path;
382 1 : rv = aFaviconURI->GetPath(path);
383 1 : if (NS_SUCCEEDED(rv) && path.EqualsLiteral("/favicon.ico")) {
384 1 : icon.rootIcon = 1;
385 : }
386 : }
387 :
388 : // If the page url points to an image, the icon's url will be the same.
389 : // TODO (Bug 403651): store a resample of the image. For now avoid that
390 : // for database size and UX concerns.
391 : // Don't store favicons for error pages too.
392 2 : if (icon.spec.Equals(page.spec) ||
393 1 : icon.spec.Equals(FAVICON_ERRORPAGE_URL)) {
394 0 : return NS_OK;
395 : }
396 :
397 : RefPtr<AsyncFetchAndSetIconForPage> event =
398 : new AsyncFetchAndSetIconForPage(icon, page, loadPrivate,
399 2 : aCallback, aLoadingPrincipal);
400 :
401 : // Get the target thread and start the work.
402 : // DB will be updated and observers notified when data has finished loading.
403 2 : RefPtr<Database> DB = Database::GetDatabase();
404 1 : NS_ENSURE_STATE(DB);
405 1 : DB->DispatchToAsyncThread(event);
406 :
407 : // Return this event to the caller to allow aborting an eventual fetch.
408 1 : event.forget(_canceler);
409 :
410 1 : return NS_OK;
411 : }
412 :
413 : NS_IMETHODIMP
414 0 : nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
415 : const uint8_t* aData,
416 : uint32_t aDataLen,
417 : const nsACString& aMimeType,
418 : PRTime aExpiration)
419 : {
420 0 : MOZ_ASSERT(NS_IsMainThread());
421 0 : NS_ENSURE_ARG(aFaviconURI);
422 0 : NS_ENSURE_ARG(aData);
423 0 : NS_ENSURE_ARG(aDataLen > 0);
424 0 : NS_ENSURE_ARG(aMimeType.Length() > 0);
425 0 : NS_ENSURE_ARG(imgLoader::SupportImageWithMimeType(PromiseFlatCString(aMimeType).get(),
426 : AcceptedMimeTypes::IMAGES_AND_DOCUMENTS));
427 :
428 0 : if (aExpiration == 0) {
429 0 : aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
430 : }
431 :
432 0 : UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(aFaviconURI);
433 0 : if (!iconKey) {
434 0 : return NS_ERROR_OUT_OF_MEMORY;
435 : }
436 :
437 0 : iconKey->created = PR_Now();
438 :
439 : // If the cache contains unassociated icons, an expiry timer should already exist, otherwise
440 : // there may be a timer left hanging around, so make sure we fire a new one.
441 0 : int32_t unassociatedCount = mUnassociatedIcons.Count();
442 0 : if (unassociatedCount == 1) {
443 0 : mExpireUnassociatedIconsTimer->Cancel();
444 0 : mExpireUnassociatedIconsTimer->InitWithCallback(
445 0 : this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
446 : }
447 :
448 0 : IconData* iconData = &(iconKey->iconData);
449 0 : iconData->expiration = aExpiration;
450 0 : iconData->status = ICON_STATUS_CACHED;
451 0 : iconData->fetchMode = FETCH_NEVER;
452 0 : nsresult rv = aFaviconURI->GetSpec(iconData->spec);
453 0 : NS_ENSURE_SUCCESS(rv, rv);
454 0 : nsAutoCString path;
455 0 : rv = aFaviconURI->GetPath(path);
456 0 : if (NS_SUCCEEDED(rv) && path.EqualsLiteral("/favicon.ico")) {
457 0 : iconData->rootIcon = 1;
458 : }
459 : // URIs can arguably lack a host.
460 0 : Unused << aFaviconURI->GetHost(iconData->host);
461 0 : if (StringBeginsWith(iconData->host, NS_LITERAL_CSTRING("www."))) {
462 0 : iconData->host.Cut(0, 4);
463 : }
464 :
465 0 : IconPayload payload;
466 0 : payload.mimeType = aMimeType;
467 0 : payload.data.Assign(TO_CHARBUFFER(aData), aDataLen);
468 0 : if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
469 0 : payload.width = UINT16_MAX;
470 : }
471 : // There may already be a previous payload, so ensure to only have one.
472 0 : iconData->payloads.Clear();
473 0 : iconData->payloads.AppendElement(payload);
474 :
475 0 : rv = OptimizeIconSizes(*iconData);
476 0 : NS_ENSURE_SUCCESS(rv, rv);
477 :
478 : // If there's not valid payload, don't store the icon into to the database.
479 0 : if ((*iconData).payloads.Length() == 0) {
480 : // We cannot optimize this favicon size and we are over the maximum size
481 : // allowed, so we will not save data to the db to avoid bloating it.
482 0 : mUnassociatedIcons.RemoveEntry(aFaviconURI);
483 0 : return NS_ERROR_FAILURE;
484 : }
485 :
486 : // If the database contains an icon at the given url, we will update the
487 : // database immediately so that the associated pages are kept in sync.
488 : // Otherwise, do nothing and let the icon be picked up from the memory hash.
489 0 : RefPtr<AsyncReplaceFaviconData> event = new AsyncReplaceFaviconData(*iconData);
490 0 : RefPtr<Database> DB = Database::GetDatabase();
491 0 : NS_ENSURE_STATE(DB);
492 0 : DB->DispatchToAsyncThread(event);
493 :
494 0 : return NS_OK;
495 : }
496 :
497 : NS_IMETHODIMP
498 0 : nsFaviconService::ReplaceFaviconDataFromDataURL(nsIURI* aFaviconURI,
499 : const nsAString& aDataURL,
500 : PRTime aExpiration,
501 : nsIPrincipal* aLoadingPrincipal)
502 : {
503 0 : NS_ENSURE_ARG(aFaviconURI);
504 0 : NS_ENSURE_TRUE(aDataURL.Length() > 0, NS_ERROR_INVALID_ARG);
505 0 : if (aExpiration == 0) {
506 0 : aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
507 : }
508 :
509 0 : nsCOMPtr<nsIURI> dataURI;
510 0 : nsresult rv = NS_NewURI(getter_AddRefs(dataURI), aDataURL);
511 0 : NS_ENSURE_SUCCESS(rv, rv);
512 :
513 : // Use the data: protocol handler to convert the data.
514 0 : nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
515 0 : NS_ENSURE_SUCCESS(rv, rv);
516 0 : nsCOMPtr<nsIProtocolHandler> protocolHandler;
517 0 : rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler));
518 0 : NS_ENSURE_SUCCESS(rv, rv);
519 :
520 0 : nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
521 0 : MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
522 0 : if (!loadingPrincipal) {
523 : // Let's default to the nullPrincipal if no loadingPrincipal is provided.
524 : const char16_t* params[] = {
525 : u"nsFaviconService::ReplaceFaviconDataFromDataURL()",
526 : u"nsFaviconService::ReplaceFaviconDataFromDataURL(..., [optional aLoadingPrincipal])"
527 0 : };
528 0 : nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
529 0 : NS_LITERAL_CSTRING("Security by Default"),
530 : nullptr, // aDocument
531 : nsContentUtils::eNECKO_PROPERTIES,
532 : "APIDeprecationWarning",
533 0 : params, ArrayLength(params));
534 :
535 0 : loadingPrincipal = NullPrincipal::Create();
536 : }
537 0 : NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
538 :
539 : nsCOMPtr<nsILoadInfo> loadInfo =
540 : new mozilla::LoadInfo(loadingPrincipal,
541 : nullptr, // aTriggeringPrincipal
542 : nullptr, // aLoadingNode
543 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
544 : nsILoadInfo::SEC_ALLOW_CHROME |
545 : nsILoadInfo::SEC_DISALLOW_SCRIPT,
546 0 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
547 :
548 0 : nsCOMPtr<nsIChannel> channel;
549 0 : rv = protocolHandler->NewChannel2(dataURI, loadInfo, getter_AddRefs(channel));
550 0 : NS_ENSURE_SUCCESS(rv, rv);
551 :
552 : // Blocking stream is OK for data URIs.
553 0 : nsCOMPtr<nsIInputStream> stream;
554 0 : rv = channel->Open2(getter_AddRefs(stream));
555 0 : NS_ENSURE_SUCCESS(rv, rv);
556 :
557 : uint64_t available64;
558 0 : rv = stream->Available(&available64);
559 0 : NS_ENSURE_SUCCESS(rv, rv);
560 0 : if (available64 == 0 || available64 > UINT32_MAX / sizeof(uint8_t))
561 0 : return NS_ERROR_FILE_TOO_BIG;
562 0 : uint32_t available = (uint32_t)available64;
563 :
564 : // Read all the decoded data.
565 : uint8_t* buffer = static_cast<uint8_t*>
566 0 : (moz_xmalloc(sizeof(uint8_t) * available));
567 0 : if (!buffer)
568 0 : return NS_ERROR_OUT_OF_MEMORY;
569 : uint32_t numRead;
570 0 : rv = stream->Read(TO_CHARBUFFER(buffer), available, &numRead);
571 0 : if (NS_FAILED(rv) || numRead != available) {
572 0 : free(buffer);
573 0 : return rv;
574 : }
575 :
576 0 : nsAutoCString mimeType;
577 0 : rv = channel->GetContentType(mimeType);
578 0 : if (NS_FAILED(rv)) {
579 0 : free(buffer);
580 0 : return rv;
581 : }
582 :
583 : // ReplaceFaviconData can now do the dirty work.
584 0 : rv = ReplaceFaviconData(aFaviconURI, buffer, available, mimeType, aExpiration);
585 0 : free(buffer);
586 0 : NS_ENSURE_SUCCESS(rv, rv);
587 :
588 0 : return NS_OK;
589 : }
590 :
591 : NS_IMETHODIMP
592 0 : nsFaviconService::GetFaviconURLForPage(nsIURI *aPageURI,
593 : nsIFaviconDataCallback* aCallback,
594 : uint16_t aPreferredWidth)
595 : {
596 0 : MOZ_ASSERT(NS_IsMainThread());
597 0 : NS_ENSURE_ARG(aPageURI);
598 0 : NS_ENSURE_ARG(aCallback);
599 :
600 0 : nsAutoCString pageSpec;
601 0 : nsresult rv = aPageURI->GetSpec(pageSpec);
602 0 : NS_ENSURE_SUCCESS(rv, rv);
603 0 : nsAutoCString pageHost;
604 : // It's expected that some domains may not have a host.
605 0 : Unused << aPageURI->GetHost(pageHost);
606 :
607 : RefPtr<AsyncGetFaviconURLForPage> event =
608 0 : new AsyncGetFaviconURLForPage(pageSpec, pageHost, aPreferredWidth, aCallback);
609 :
610 0 : RefPtr<Database> DB = Database::GetDatabase();
611 0 : NS_ENSURE_STATE(DB);
612 0 : DB->DispatchToAsyncThread(event);
613 :
614 0 : return NS_OK;
615 : }
616 :
617 : NS_IMETHODIMP
618 0 : nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
619 : nsIFaviconDataCallback* aCallback,
620 : uint16_t aPreferredWidth)
621 : {
622 0 : MOZ_ASSERT(NS_IsMainThread());
623 0 : NS_ENSURE_ARG(aPageURI);
624 0 : NS_ENSURE_ARG(aCallback);
625 :
626 0 : nsAutoCString pageSpec;
627 0 : nsresult rv = aPageURI->GetSpec(pageSpec);
628 0 : NS_ENSURE_SUCCESS(rv, rv);
629 0 : nsAutoCString pageHost;
630 : // It's expected that some domains may not have a host.
631 0 : Unused << aPageURI->GetHost(pageHost);
632 :
633 : RefPtr<AsyncGetFaviconDataForPage> event =
634 0 : new AsyncGetFaviconDataForPage(pageSpec, pageHost, aPreferredWidth, aCallback);
635 0 : RefPtr<Database> DB = Database::GetDatabase();
636 0 : NS_ENSURE_STATE(DB);
637 0 : DB->DispatchToAsyncThread(event);
638 :
639 0 : return NS_OK;
640 : }
641 :
642 : NS_IMETHODIMP
643 0 : nsFaviconService::CopyFavicons(nsIURI* aFromPageURI,
644 : nsIURI* aToPageURI,
645 : uint32_t aFaviconLoadType,
646 : nsIFaviconDataCallback* aCallback)
647 : {
648 0 : MOZ_ASSERT(NS_IsMainThread());
649 0 : NS_ENSURE_ARG(aFromPageURI);
650 0 : NS_ENSURE_ARG(aToPageURI);
651 0 : NS_ENSURE_TRUE(aFaviconLoadType >= nsIFaviconService::FAVICON_LOAD_PRIVATE &&
652 : aFaviconLoadType <= nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
653 : NS_ERROR_INVALID_ARG);
654 :
655 0 : PageData fromPage;
656 0 : nsresult rv = aFromPageURI->GetSpec(fromPage.spec);
657 0 : NS_ENSURE_SUCCESS(rv, rv);
658 0 : PageData toPage;
659 0 : rv = aToPageURI->GetSpec(toPage.spec);
660 0 : NS_ENSURE_SUCCESS(rv, rv);
661 :
662 : bool canAddToHistory;
663 0 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
664 0 : NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
665 0 : rv = navHistory->CanAddURI(aToPageURI, &canAddToHistory);
666 0 : NS_ENSURE_SUCCESS(rv, rv);
667 0 : toPage.canAddToHistory = !!canAddToHistory &&
668 : aFaviconLoadType != nsIFaviconService::FAVICON_LOAD_PRIVATE;
669 :
670 0 : RefPtr<AsyncCopyFavicons> event = new AsyncCopyFavicons(fromPage, toPage, aCallback);
671 :
672 : // Get the target thread and start the work.
673 : // DB will be updated and observers notified when done.
674 0 : RefPtr<Database> DB = Database::GetDatabase();
675 0 : NS_ENSURE_STATE(DB);
676 0 : DB->DispatchToAsyncThread(event);
677 :
678 0 : return NS_OK;
679 : }
680 :
681 : nsresult
682 0 : nsFaviconService::GetFaviconLinkForIcon(nsIURI* aFaviconURI,
683 : nsIURI** aOutputURI)
684 : {
685 0 : NS_ENSURE_ARG(aFaviconURI);
686 0 : NS_ENSURE_ARG_POINTER(aOutputURI);
687 :
688 0 : nsAutoCString spec;
689 0 : if (aFaviconURI) {
690 0 : nsresult rv = aFaviconURI->GetSpec(spec);
691 0 : NS_ENSURE_SUCCESS(rv, rv);
692 : }
693 0 : return GetFaviconLinkForIconString(spec, aOutputURI);
694 : }
695 :
696 :
697 : NS_IMETHODIMP
698 1 : nsFaviconService::AddFailedFavicon(nsIURI* aFaviconURI)
699 : {
700 1 : NS_ENSURE_ARG(aFaviconURI);
701 :
702 2 : nsAutoCString spec;
703 1 : nsresult rv = aFaviconURI->GetSpec(spec);
704 1 : NS_ENSURE_SUCCESS(rv, rv);
705 :
706 1 : mFailedFavicons.Put(spec, mFailedFaviconSerial);
707 1 : mFailedFaviconSerial ++;
708 :
709 1 : if (mFailedFavicons.Count() > MAX_FAILED_FAVICONS) {
710 : // need to expire some entries, delete the FAVICON_CACHE_REDUCE_COUNT number
711 : // of items that are the oldest
712 0 : uint32_t threshold = mFailedFaviconSerial -
713 0 : MAX_FAILED_FAVICONS + FAVICON_CACHE_REDUCE_COUNT;
714 0 : for (auto iter = mFailedFavicons.Iter(); !iter.Done(); iter.Next()) {
715 0 : if (iter.Data() < threshold) {
716 0 : iter.Remove();
717 : }
718 : }
719 : }
720 1 : return NS_OK;
721 : }
722 :
723 :
724 : NS_IMETHODIMP
725 0 : nsFaviconService::RemoveFailedFavicon(nsIURI* aFaviconURI)
726 : {
727 0 : NS_ENSURE_ARG(aFaviconURI);
728 :
729 0 : nsAutoCString spec;
730 0 : nsresult rv = aFaviconURI->GetSpec(spec);
731 0 : NS_ENSURE_SUCCESS(rv, rv);
732 :
733 : // we silently do nothing and succeed if the icon is not in the cache
734 0 : mFailedFavicons.Remove(spec);
735 0 : return NS_OK;
736 : }
737 :
738 :
739 : NS_IMETHODIMP
740 2 : nsFaviconService::IsFailedFavicon(nsIURI* aFaviconURI, bool* _retval)
741 : {
742 2 : NS_ENSURE_ARG(aFaviconURI);
743 4 : nsAutoCString spec;
744 2 : nsresult rv = aFaviconURI->GetSpec(spec);
745 2 : NS_ENSURE_SUCCESS(rv, rv);
746 :
747 : uint32_t serial;
748 2 : *_retval = mFailedFavicons.Get(spec, &serial);
749 2 : return NS_OK;
750 : }
751 :
752 :
753 : // nsFaviconService::GetFaviconLinkForIconString
754 : //
755 : // This computes a favicon URL with string input and using the cached
756 : // default one to minimize parsing.
757 :
758 : nsresult
759 0 : nsFaviconService::GetFaviconLinkForIconString(const nsCString& aSpec,
760 : nsIURI** aOutput)
761 : {
762 0 : if (aSpec.IsEmpty()) {
763 0 : return GetDefaultFavicon(aOutput);
764 : }
765 :
766 0 : if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
767 : // pass through for chrome URLs, since they can be referenced without
768 : // this service
769 0 : return NS_NewURI(aOutput, aSpec);
770 : }
771 :
772 0 : nsAutoCString annoUri;
773 0 : annoUri.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
774 0 : annoUri += aSpec;
775 0 : return NS_NewURI(aOutput, annoUri);
776 : }
777 :
778 : /**
779 : * Checks the icon and evaluates if it needs to be optimized.
780 : *
781 : * @param aIcon
782 : * The icon to be evaluated.
783 : */
784 : nsresult
785 0 : nsFaviconService::OptimizeIconSizes(IconData& aIcon)
786 : {
787 : // TODO (bug 1346139): move optimization to the async thread.
788 0 : MOZ_ASSERT(NS_IsMainThread());
789 : // There should only be a single payload at this point, it may have to be
790 : // split though, if it's an ico file.
791 0 : MOZ_ASSERT(aIcon.payloads.Length() == 1);
792 :
793 : // Even if the page provides a large image for the favicon (eg, a highres
794 : // image or a multiresolution .ico file), don't try to store more data than
795 : // needed.
796 0 : IconPayload payload = aIcon.payloads[0];
797 0 : if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
798 : // Nothing to optimize, but check the payload size.
799 0 : if (payload.data.Length() >= nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
800 0 : aIcon.payloads.Clear();
801 : }
802 0 : return NS_OK;
803 : }
804 :
805 : // Make space for the optimized payloads.
806 0 : aIcon.payloads.Clear();
807 :
808 0 : nsCOMPtr<nsIInputStream> stream;
809 0 : nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
810 : payload.data.get(),
811 0 : payload.data.Length(),
812 0 : NS_ASSIGNMENT_DEPEND);
813 0 : NS_ENSURE_SUCCESS(rv, rv);
814 :
815 : // decode image
816 0 : nsCOMPtr<imgIContainer> container;
817 0 : rv = GetImgTools()->DecodeImageData(stream, payload.mimeType,
818 0 : getter_AddRefs(container));
819 0 : NS_ENSURE_SUCCESS(rv, rv);
820 :
821 : // For ICO files, we must evaluate each of the frames we care about.
822 0 : nsTArray<FrameData> framesInfo;
823 0 : rv = GetFramesInfoForContainer(container, framesInfo);
824 0 : NS_ENSURE_SUCCESS(rv, rv);
825 :
826 0 : for (const auto& frameInfo : framesInfo) {
827 0 : IconPayload newPayload;
828 0 : newPayload.mimeType = NS_LITERAL_CSTRING(PNG_MIME_TYPE);
829 0 : newPayload.width = frameInfo.width;
830 0 : for (uint16_t size : sFaviconSizes) {
831 : // The icon could be smaller than 16, that is our minimum.
832 : // Icons smaller than 16px are kept as-is.
833 0 : if (frameInfo.width >= 16) {
834 0 : if (size > frameInfo.width) {
835 0 : continue;
836 : }
837 0 : newPayload.width = size;
838 : }
839 :
840 : // If the original payload is png and the size is the same, rescale the
841 : // image only if it's larger than the maximum allowed.
842 0 : if (newPayload.mimeType.Equals(payload.mimeType) &&
843 0 : newPayload.width == frameInfo.width &&
844 0 : payload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
845 0 : newPayload.data = payload.data;
846 : } else {
847 : // Otherwise, scale and recompress.
848 : // Since EncodeScaledImage uses SYNC_DECODE, it will pick the best frame.
849 0 : nsCOMPtr<nsIInputStream> iconStream;
850 0 : rv = GetImgTools()->EncodeScaledImage(container,
851 : newPayload.mimeType,
852 0 : newPayload.width,
853 0 : newPayload.width,
854 0 : EmptyString(),
855 0 : getter_AddRefs(iconStream));
856 0 : NS_ENSURE_SUCCESS(rv, rv);
857 : // Read the stream into the new buffer.
858 0 : rv = NS_ConsumeStream(iconStream, UINT32_MAX, newPayload.data);
859 0 : NS_ENSURE_SUCCESS(rv, rv);
860 : }
861 :
862 : // If the icon size is good, we are done, otherwise try the next size.
863 0 : if (newPayload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
864 0 : break;
865 : }
866 : }
867 :
868 0 : MOZ_ASSERT(newPayload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE);
869 0 : if (newPayload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
870 0 : aIcon.payloads.AppendElement(newPayload);
871 : }
872 : }
873 :
874 0 : return NS_OK;
875 : }
876 :
877 : nsresult
878 0 : nsFaviconService::GetFaviconDataAsync(const nsCString& aFaviconURI,
879 : mozIStorageStatementCallback *aCallback)
880 : {
881 0 : MOZ_ASSERT(aCallback, "Doesn't make sense to call this without a callback");
882 :
883 0 : nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
884 : "/*Do not warn (bug no: not worth adding an index */ "
885 : "SELECT data, width FROM moz_icons "
886 : "WHERE fixed_icon_url_hash = hash(fixup_url(:url)) AND icon_url = :url "
887 : "ORDER BY width DESC"
888 0 : );
889 0 : NS_ENSURE_STATE(stmt);
890 :
891 0 : nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aFaviconURI);
892 0 : NS_ENSURE_SUCCESS(rv, rv);
893 :
894 0 : nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
895 0 : return stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
896 : }
897 :
898 : void // static
899 0 : nsFaviconService::ConvertUnsupportedPayloads(mozIStorageConnection* aDBConn)
900 : {
901 0 : MOZ_ASSERT(NS_IsMainThread());
902 : // Ensure imgTools are initialized, so that the image decoders can be used
903 : // off the main thread.
904 0 : nsCOMPtr<imgITools> imgTools = do_CreateInstance("@mozilla.org/image/tools;1");
905 :
906 0 : Preferences::SetBool(PREF_CONVERT_PAYLOADS, true);
907 0 : MOZ_ASSERT(aDBConn);
908 0 : if (aDBConn) {
909 : RefPtr<FetchAndConvertUnsupportedPayloads> event =
910 0 : new FetchAndConvertUnsupportedPayloads(aDBConn);
911 0 : nsCOMPtr<nsIEventTarget> target = do_GetInterface(aDBConn);
912 0 : MOZ_ASSERT(target);
913 0 : if (target) {
914 0 : (void)target->Dispatch(event, NS_DISPATCH_NORMAL);
915 : }
916 : }
917 0 : }
918 :
919 : NS_IMETHODIMP
920 0 : nsFaviconService::PreferredSizeFromURI(nsIURI* aURI, uint16_t* _size)
921 : {
922 0 : *_size = UINT16_MAX;
923 0 : nsAutoCString ref;
924 : // Check for a ref first.
925 0 : if (NS_FAILED(aURI->GetRef(ref)) || ref.Length() == 0)
926 0 : return NS_OK;
927 :
928 : // Look for a "size=" fragment.
929 0 : int32_t start = ref.RFind("size=");
930 0 : if (start >= 0 && ref.Length() > static_cast<uint32_t>(start) + 5) {
931 0 : nsDependentCSubstring size;
932 : // This is safe regardless, since Rebind checks start is not over Length().
933 0 : size.Rebind(ref, start + 5);
934 : // Check if the string contains any non-digit.
935 0 : auto begin = size.BeginReading(), end = size.EndReading();
936 0 : for (auto ch = begin; ch < end; ++ch) {
937 0 : if (*ch < '0' || *ch > '9') {
938 : // Not a digit.
939 0 : return NS_OK;
940 : }
941 : }
942 : // Convert the string to an integer value.
943 : nsresult rv;
944 0 : uint16_t val = PromiseFlatCString(size).ToInteger(&rv);
945 0 : if (NS_SUCCEEDED(rv)) {
946 0 : *_size = val;
947 : }
948 : }
949 0 : return NS_OK;
950 : }
951 :
952 : ////////////////////////////////////////////////////////////////////////////////
953 : //// ExpireFaviconsStatementCallbackNotifier
954 :
955 0 : ExpireFaviconsStatementCallbackNotifier::ExpireFaviconsStatementCallbackNotifier()
956 : {
957 0 : }
958 :
959 :
960 : NS_IMETHODIMP
961 0 : ExpireFaviconsStatementCallbackNotifier::HandleCompletion(uint16_t aReason)
962 : {
963 : // We should dispatch only if expiration has been successful.
964 0 : if (aReason != mozIStorageStatementCallback::REASON_FINISHED)
965 0 : return NS_OK;
966 :
967 : nsCOMPtr<nsIObserverService> observerService =
968 0 : mozilla::services::GetObserverService();
969 0 : if (observerService) {
970 0 : (void)observerService->NotifyObservers(nullptr,
971 : NS_PLACES_FAVICONS_EXPIRED_TOPIC_ID,
972 0 : nullptr);
973 : }
974 :
975 0 : return NS_OK;
976 : }
|