favicon_source.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/webui/favicon_source.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/bind.h"
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/bind_helpers.h"
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/favicon/favicon_service_factory.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/history/top_sites.h"
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/profiles/profile.h"
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/webui/web_ui_util.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/url_constants.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/locale_settings.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/ui_resources.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/layout.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)FaviconSource::FaviconSource(Profile* profile, IconType type)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : DataSource(type == FAVICON ? chrome::kChromeUIFaviconHost :
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     chrome::kChromeUITouchIconHost,
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 MessageLoop::current()) {
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Init(profile, type);
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)FaviconSource::FaviconSource(Profile* profile,
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                             IconType type,
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                             const std::string& source_name)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : DataSource(source_name, MessageLoop::current()) {
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Init(profile, type);
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)FaviconSource::~FaviconSource() {
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FaviconSource::Init(Profile* profile, IconType type) {
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  profile_ = profile->GetOriginalProfile();
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  icon_types_ = type == FAVICON ? history::FAVICON :
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      history::TOUCH_PRECOMPOSED_ICON | history::TOUCH_ICON |
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      history::FAVICON;
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FaviconSource::StartDataRequest(const std::string& path,
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     bool is_incognito,
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     int request_id) {
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  FaviconService* favicon_service =
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!favicon_service || path.empty()) {
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SendDefaultResponse(IconRequest(request_id, 16, ui::SCALE_FACTOR_NONE));
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int size_in_dip = gfx::kFaviconSize;
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ui::ScaleFactor scale_factor = ui::SCALE_FACTOR_NONE;
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  FaviconService::Handle handle;
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (path.size() > 8 &&
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      (path.substr(0, 8) == "iconurl/" || path.substr(0, 8) == "iconurl@")) {
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    size_t prefix_length = 8;
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Optional scale factor appended to iconurl, which may be @1x or @2x.
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (path.at(7) == '@') {
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        size_t slash = path.find("/");
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        std::string scale_str = path.substr(8, slash - 8);
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        web_ui_util::ParseScaleFactor(scale_str, &scale_factor);
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        prefix_length = slash + 1;
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // TODO(michaelbai): Change GetRawFavicon to support combination of
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // IconType.
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    handle = favicon_service->GetRawFavicon(
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        GURL(path.substr(prefix_length)),
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        history::FAVICON,
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        size_in_dip,
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        scale_factor,
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        &cancelable_consumer_,
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        base::Bind(&FaviconSource::OnFaviconDataAvailable,
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   base::Unretained(this)));
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GURL url;
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (path.size() > 5 && path.substr(0, 5) == "size/") {
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      size_t slash = path.find("/", 5);
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      size_t scale_delimiter = path.find("@", 5);
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      std::string size = path.substr(5, slash - 5);
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      size_in_dip = atoi(size.c_str());
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      DCHECK(size_in_dip == 64 || size_in_dip == 32 || size_in_dip == 16) <<
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          "only 64x64, 32x32 and 16x16 icons are supported";
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Optional scale factor.
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (scale_delimiter != std::string::npos && scale_delimiter < slash) {
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        DCHECK(size_in_dip == 16);
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        std::string scale_str = path.substr(scale_delimiter + 1,
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                            slash - scale_delimiter - 1);
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        web_ui_util::ParseScaleFactor(scale_str, &scale_factor);
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      url = GURL(path.substr(slash + 1));
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // URL requests prefixed with "origin/" are converted to a form with an
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // empty path and a valid scheme. (e.g., example.com -->
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // http://example.com/ or http://example.com/a --> http://example.com/)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (path.size() > 7 && path.substr(0, 7) == "origin/") {
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        std::string originalUrl = path.substr(7);
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // If the original URL does not specify a scheme (e.g., example.com
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // instead of http://example.com), add "http://" as a default.
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (!GURL(originalUrl).has_scheme())
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          originalUrl = "http://" + originalUrl;
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // Strip the path beyond the top-level domain.
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        url = GURL(originalUrl).GetOrigin();
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      } else {
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        url = GURL(path);
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Intercept requests for prepopulated pages.
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (size_t i = 0; i < arraysize(history::kPrepopulatedPages); i++) {
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (url.spec() ==
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          l10n_util::GetStringUTF8(history::kPrepopulatedPages[i].url_id)) {
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        SendResponse(request_id,
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            ResourceBundle::GetSharedInstance().LoadDataResourceBytesForScale(
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                history::kPrepopulatedPages[i].favicon_id,
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                scale_factor));
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return;
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    handle = favicon_service->GetRawFaviconForURL(
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        FaviconService::FaviconForURLParams(
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            profile_,
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            url,
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            icon_types_,
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            size_in_dip,
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            &cancelable_consumer_),
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        scale_factor,
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        base::Bind(&FaviconSource::OnFaviconDataAvailable,
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   base::Unretained(this)));
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Attach the ChromeURLDataManager request ID to the history request.
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cancelable_consumer_.SetClientData(favicon_service,
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     handle,
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     IconRequest(request_id,
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                 size_in_dip,
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                 scale_factor));
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)std::string FaviconSource::GetMimeType(const std::string&) const {
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // We need to explicitly return a mime type, otherwise if the user tries to
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // drag the image they get no extension.
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return "image/png";
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool FaviconSource::ShouldReplaceExistingSource() const {
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Leave the existing DataSource in place, otherwise we'll drop any pending
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // requests on the floor.
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FaviconSource::OnFaviconDataAvailable(
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    FaviconService::Handle request_handle,
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const history::FaviconBitmapResult& bitmap_result) {
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  FaviconService* favicon_service =
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const IconRequest& request =
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cancelable_consumer_.GetClientData(favicon_service,
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         request_handle);
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (bitmap_result.is_valid()) {
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Forward the data along to the networking system.
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SendResponse(request.request_id, bitmap_result.bitmap_data);
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SendDefaultResponse(request);
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FaviconSource::SendDefaultResponse(const IconRequest& icon_request) {
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int favicon_index;
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int resource_id;
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  switch (icon_request.size_in_dip) {
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case 64:
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      favicon_index = SIZE_64;
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      resource_id = IDR_DEFAULT_FAVICON_64;
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case 32:
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      favicon_index = SIZE_32;
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      resource_id = IDR_DEFAULT_FAVICON_32;
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    default:
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      favicon_index = SIZE_16;
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      resource_id = IDR_DEFAULT_FAVICON;
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::RefCountedMemory* default_favicon = default_favicons_[favicon_index];
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!default_favicon) {
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ui::ScaleFactor scale_factor = icon_request.scale_factor;
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    default_favicon = ResourceBundle::GetSharedInstance()
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        .LoadDataResourceBytesForScale(resource_id, scale_factor);
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    default_favicons_[favicon_index] = default_favicon;
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SendResponse(icon_request.request_id, default_favicon);
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
203