Coverage Report

Created: 2018-09-25 14:53

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