123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/search_provider_logos/google_logo_api.h"
- #include <stdint.h>
- #include <algorithm>
- #include <memory>
- #include "base/base64.h"
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/check.h"
- #include "base/command_line.h"
- #include "base/feature_list.h"
- #include "base/json/json_reader.h"
- #include "base/logging.h"
- #include "base/memory/ref_counted.h"
- #include "base/memory/ref_counted_memory.h"
- #include "base/strings/string_piece.h"
- #include "base/strings/string_util.h"
- #include "base/values.h"
- #include "components/google/core/common/google_util.h"
- #include "components/search_provider_logos/switches.h"
- #include "url/third_party/mozilla/url_parse.h"
- #include "url/url_constants.h"
- namespace search_provider_logos {
- namespace {
- const int kDefaultIframeWidthPx = 500;
- const int kDefaultIframeHeightPx = 200;
- } // namespace
- GURL GetGoogleDoodleURL(const GURL& google_base_url) {
- const base::CommandLine* command_line =
- base::CommandLine::ForCurrentProcess();
- if (command_line->HasSwitch(switches::kGoogleDoodleUrl)) {
- return GURL(command_line->GetSwitchValueASCII(switches::kGoogleDoodleUrl));
- }
- GURL::Replacements replacements;
- replacements.SetPathStr("async/ddljson");
- // Make sure we use https rather than http (except for .cn).
- if (google_base_url.SchemeIs(url::kHttpScheme) &&
- !base::EndsWith(google_base_url.host_piece(), ".cn",
- base::CompareCase::INSENSITIVE_ASCII)) {
- replacements.SetSchemeStr(url::kHttpsScheme);
- }
- return google_base_url.ReplaceComponents(replacements);
- }
- GURL AppendFingerprintParamToDoodleURL(const GURL& logo_url,
- const std::string& fingerprint) {
- if (fingerprint.empty()) {
- return logo_url;
- }
- return google_util::AppendToAsyncQueryParam(logo_url, "es_dfp", fingerprint);
- }
- GURL AppendPreliminaryParamsToDoodleURL(bool gray_background,
- bool for_webui_ntp,
- const GURL& logo_url) {
- auto url = google_util::AppendToAsyncQueryParam(logo_url, "ntp",
- for_webui_ntp ? "2" : "1");
- if (gray_background) {
- url = google_util::AppendToAsyncQueryParam(url, "graybg", "1");
- }
- return url;
- }
- namespace {
- const char kResponsePreamble[] = ")]}'";
- GURL ParseUrl(const base::Value& parent_dict,
- const std::string& key,
- const GURL& base_url) {
- DCHECK(parent_dict.is_dict());
- const std::string* url_str = parent_dict.FindStringKey(key);
- if (!url_str || url_str->empty()) {
- return GURL();
- }
- GURL result = base_url.Resolve(*url_str);
- // If the base URL is https:// (which should almost always be the case, see
- // above), then we require all other URLs to be https:// too.
- if (base_url.SchemeIs(url::kHttpsScheme) &&
- !result.SchemeIs(url::kHttpsScheme)) {
- return GURL();
- }
- return result;
- }
- // On success returns a pair of <mime_type, data>.
- // On error returns a pair of <string, nullptr>.
- // mime_type should be ignored if data is nullptr.
- std::pair<std::string, scoped_refptr<base::RefCountedString>>
- ParseEncodedImageData(const std::string& encoded_image_data) {
- std::pair<std::string, scoped_refptr<base::RefCountedString>> result;
- GURL encoded_image_uri(encoded_image_data);
- if (!encoded_image_uri.is_valid() ||
- !encoded_image_uri.SchemeIs(url::kDataScheme)) {
- return result;
- }
- std::string content = encoded_image_uri.GetContent();
- // The content should look like this: "image/png;base64,aaa..." (where
- // "aaa..." is the base64-encoded image data).
- size_t mime_type_end = content.find_first_of(';');
- if (mime_type_end == std::string::npos)
- return result;
- std::string mime_type = content.substr(0, mime_type_end);
- size_t base64_begin = mime_type_end + 1;
- size_t base64_end = content.find_first_of(',', base64_begin);
- if (base64_end == std::string::npos)
- return result;
- auto base64 = base::MakeStringPiece(content.begin() + base64_begin,
- content.begin() + base64_end);
- if (base64 != "base64")
- return result;
- size_t data_begin = base64_end + 1;
- auto data =
- base::MakeStringPiece(content.begin() + data_begin, content.end());
- std::string decoded_data;
- if (!base::Base64Decode(data, &decoded_data))
- return result;
- result.first = mime_type;
- result.second = base::RefCountedString::TakeString(&decoded_data);
- return result;
- }
- } // namespace
- std::unique_ptr<EncodedLogo> ParseDoodleLogoResponse(
- const GURL& base_url,
- std::unique_ptr<std::string> response,
- base::Time response_time,
- bool* parsing_failed) {
- // The response may start with )]}'. Ignore this.
- base::StringPiece response_sp(*response);
- if (base::StartsWith(response_sp, kResponsePreamble))
- response_sp.remove_prefix(strlen(kResponsePreamble));
- // Default parsing failure to be true.
- *parsing_failed = true;
- auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(response_sp);
- if (!parsed_json.has_value()) {
- LOG(WARNING) << parsed_json.error().message << " at "
- << parsed_json.error().line << ":"
- << parsed_json.error().column;
- return nullptr;
- }
- if (!parsed_json->is_dict())
- return nullptr;
- const base::Value* ddljson = parsed_json->FindDictKey("ddljson");
- if (!ddljson)
- return nullptr;
- // If there is no logo today, the "ddljson" dictionary will be empty.
- if (ddljson->DictEmpty()) {
- *parsing_failed = false;
- return nullptr;
- }
- auto logo = std::make_unique<EncodedLogo>();
- const std::string* doodle_type = ddljson->FindStringKey("doodle_type");
- logo->metadata.type = LogoType::SIMPLE;
- if (doodle_type) {
- if (*doodle_type == "ANIMATED") {
- logo->metadata.type = LogoType::ANIMATED;
- } else if (*doodle_type == "INTERACTIVE") {
- logo->metadata.type = LogoType::INTERACTIVE;
- } else if (*doodle_type == "VIDEO") {
- logo->metadata.type = LogoType::INTERACTIVE;
- }
- }
- const bool is_simple = (logo->metadata.type == LogoType::SIMPLE);
- const bool is_animated = (logo->metadata.type == LogoType::ANIMATED);
- bool is_interactive = (logo->metadata.type == LogoType::INTERACTIVE);
- // Check if the main image is animated.
- if (is_animated) {
- // If animated, get the URL for the animated image.
- const base::Value* image = ddljson->FindDictKey("large_image");
- if (!image)
- return nullptr;
- logo->metadata.animated_url = ParseUrl(*image, "url", base_url);
- if (!logo->metadata.animated_url.is_valid())
- return nullptr;
- const base::Value* dark_image = ddljson->FindDictKey("dark_large_image");
- if (dark_image)
- logo->metadata.dark_animated_url = ParseUrl(*dark_image, "url", base_url);
- }
- if (is_simple || is_animated) {
- const base::Value* image = ddljson->FindDictKey("large_image");
- if (image) {
- if (absl::optional<int> width_px = image->FindIntKey("width"))
- logo->metadata.width_px = *width_px;
- if (absl::optional<int> height_px = image->FindIntKey("height"))
- logo->metadata.height_px = *height_px;
- }
- const base::Value* dark_image = ddljson->FindDictKey("dark_large_image");
- if (dark_image) {
- if (const std::string* background_color =
- dark_image->FindStringKey("background_color")) {
- logo->metadata.dark_background_color = *background_color;
- }
- if (absl::optional<int> width_px = dark_image->FindIntKey("width"))
- logo->metadata.dark_width_px = *width_px;
- if (absl::optional<int> height_px = dark_image->FindIntKey("height"))
- logo->metadata.dark_height_px = *height_px;
- }
- }
- const bool is_eligible_for_share_button =
- (logo->metadata.type == LogoType::ANIMATED ||
- logo->metadata.type == LogoType::SIMPLE);
- if (is_eligible_for_share_button) {
- const base::Value* share_button = ddljson->FindDictKey("share_button");
- const std::string* short_link_ptr = ddljson->FindStringKey("short_link");
- // The short link in the doodle proto is an incomplete URL with the format
- // //g.co/*, //doodle.gle/* or //google.com?doodle=*.
- // Complete the URL if possible.
- if (share_button && short_link_ptr && short_link_ptr->find("//") == 0) {
- std::string short_link_str = *short_link_ptr;
- short_link_str.insert(0, "https:");
- logo->metadata.short_link = GURL(std::move(short_link_str));
- if (logo->metadata.short_link.is_valid()) {
- if (absl::optional<int> offset_x = share_button->FindIntKey("offset_x"))
- logo->metadata.share_button_x = *offset_x;
- if (absl::optional<int> offset_y = share_button->FindIntKey("offset_y"))
- logo->metadata.share_button_y = *offset_y;
- if (absl::optional<double> opacity =
- share_button->FindDoubleKey("opacity")) {
- logo->metadata.share_button_opacity = *opacity;
- }
- if (const std::string* icon = share_button->FindStringKey("icon_image"))
- logo->metadata.share_button_icon = *icon;
- if (const std::string* bg_color =
- share_button->FindStringKey("background_color")) {
- logo->metadata.share_button_bg = *bg_color;
- }
- }
- }
- const base::Value* dark_share_button =
- ddljson->FindDictKey("dark_share_button");
- if (dark_share_button) {
- if (logo->metadata.short_link.is_valid()) {
- if (absl::optional<int> offset_x =
- dark_share_button->FindIntKey("offset_x")) {
- logo->metadata.dark_share_button_x = *offset_x;
- }
- if (absl::optional<int> offset_y =
- dark_share_button->FindIntKey("offset_y")) {
- logo->metadata.dark_share_button_y = *offset_y;
- }
- if (absl::optional<double> opacity =
- dark_share_button->FindDoubleKey("opacity")) {
- logo->metadata.dark_share_button_opacity = *opacity;
- }
- if (const std::string* icon =
- dark_share_button->FindStringKey("icon_image")) {
- logo->metadata.dark_share_button_icon = *icon;
- }
- if (const std::string* bg_color =
- dark_share_button->FindStringKey("background_color")) {
- logo->metadata.dark_share_button_bg = *bg_color;
- }
- }
- }
- }
- logo->metadata.full_page_url =
- ParseUrl(*ddljson, "fullpage_interactive_url", base_url);
- // Data is optional, since we may be revalidating a cached logo.
- // If there is a CTA image, get that; otherwise use the regular image.
- const std::string* encoded_image_data =
- ddljson->FindStringKey("cta_data_uri");
- if (!encoded_image_data)
- encoded_image_data = ddljson->FindStringKey("data_uri");
- if (encoded_image_data) {
- auto [mime_type, data] = ParseEncodedImageData(*encoded_image_data);
- if (!data)
- return nullptr;
- logo->metadata.mime_type = mime_type;
- logo->encoded_image = data;
- }
- const std::string* dark_encoded_image_data =
- ddljson->FindStringKey("dark_cta_data_uri");
- if (!dark_encoded_image_data)
- dark_encoded_image_data = ddljson->FindStringKey("dark_data_uri");
- if (dark_encoded_image_data) {
- auto [mime_type, data] = ParseEncodedImageData(*dark_encoded_image_data);
- if (data)
- logo->metadata.dark_mime_type = mime_type;
- logo->dark_encoded_image = data;
- }
- logo->metadata.on_click_url = ParseUrl(*ddljson, "target_url", base_url);
- if (const std::string* alt_text = ddljson->FindStringKey("alt_text"))
- logo->metadata.alt_text = *alt_text;
- logo->metadata.cta_log_url = ParseUrl(*ddljson, "cta_log_url", base_url);
- logo->metadata.dark_cta_log_url =
- ParseUrl(*ddljson, "dark_cta_log_url", base_url);
- logo->metadata.log_url = ParseUrl(*ddljson, "log_url", base_url);
- logo->metadata.dark_log_url = ParseUrl(*ddljson, "dark_log_url", base_url);
- if (const std::string* fingerprint = ddljson->FindStringKey("fingerprint"))
- logo->metadata.fingerprint = *fingerprint;
- if (is_interactive) {
- const std::string* behavior =
- ddljson->FindStringKey("launch_interactive_behavior");
- if (behavior && (*behavior == "NEW_WINDOW")) {
- logo->metadata.type = LogoType::SIMPLE;
- logo->metadata.on_click_url = logo->metadata.full_page_url;
- is_interactive = false;
- }
- }
- logo->metadata.iframe_width_px = 0;
- logo->metadata.iframe_height_px = 0;
- if (is_interactive) {
- logo->metadata.iframe_width_px =
- ddljson->FindIntKey("iframe_width_px").value_or(kDefaultIframeWidthPx);
- logo->metadata.iframe_height_px = ddljson->FindIntKey("iframe_height_px")
- .value_or(kDefaultIframeHeightPx);
- }
- base::TimeDelta time_to_live;
- // The JSON doesn't guarantee the number to fit into an int.
- if (absl::optional<double> ttl_ms =
- ddljson->FindDoubleKey("time_to_live_ms")) {
- time_to_live = base::Milliseconds(*ttl_ms);
- logo->metadata.can_show_after_expiration = false;
- } else {
- time_to_live = base::Milliseconds(kMaxTimeToLiveMS);
- logo->metadata.can_show_after_expiration = true;
- }
- logo->metadata.expiration_time = response_time + time_to_live;
- *parsing_failed = false;
- return logo;
- }
- } // namespace search_provider_logos
|