google_logo_api.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/search_provider_logos/google_logo_api.h"
  5. #include <stdint.h>
  6. #include <algorithm>
  7. #include <memory>
  8. #include "base/base64.h"
  9. #include "base/bind.h"
  10. #include "base/callback.h"
  11. #include "base/check.h"
  12. #include "base/command_line.h"
  13. #include "base/feature_list.h"
  14. #include "base/json/json_reader.h"
  15. #include "base/logging.h"
  16. #include "base/memory/ref_counted.h"
  17. #include "base/memory/ref_counted_memory.h"
  18. #include "base/strings/string_piece.h"
  19. #include "base/strings/string_util.h"
  20. #include "base/values.h"
  21. #include "components/google/core/common/google_util.h"
  22. #include "components/search_provider_logos/switches.h"
  23. #include "url/third_party/mozilla/url_parse.h"
  24. #include "url/url_constants.h"
  25. namespace search_provider_logos {
  26. namespace {
  27. const int kDefaultIframeWidthPx = 500;
  28. const int kDefaultIframeHeightPx = 200;
  29. } // namespace
  30. GURL GetGoogleDoodleURL(const GURL& google_base_url) {
  31. const base::CommandLine* command_line =
  32. base::CommandLine::ForCurrentProcess();
  33. if (command_line->HasSwitch(switches::kGoogleDoodleUrl)) {
  34. return GURL(command_line->GetSwitchValueASCII(switches::kGoogleDoodleUrl));
  35. }
  36. GURL::Replacements replacements;
  37. replacements.SetPathStr("async/ddljson");
  38. // Make sure we use https rather than http (except for .cn).
  39. if (google_base_url.SchemeIs(url::kHttpScheme) &&
  40. !base::EndsWith(google_base_url.host_piece(), ".cn",
  41. base::CompareCase::INSENSITIVE_ASCII)) {
  42. replacements.SetSchemeStr(url::kHttpsScheme);
  43. }
  44. return google_base_url.ReplaceComponents(replacements);
  45. }
  46. GURL AppendFingerprintParamToDoodleURL(const GURL& logo_url,
  47. const std::string& fingerprint) {
  48. if (fingerprint.empty()) {
  49. return logo_url;
  50. }
  51. return google_util::AppendToAsyncQueryParam(logo_url, "es_dfp", fingerprint);
  52. }
  53. GURL AppendPreliminaryParamsToDoodleURL(bool gray_background,
  54. bool for_webui_ntp,
  55. const GURL& logo_url) {
  56. auto url = google_util::AppendToAsyncQueryParam(logo_url, "ntp",
  57. for_webui_ntp ? "2" : "1");
  58. if (gray_background) {
  59. url = google_util::AppendToAsyncQueryParam(url, "graybg", "1");
  60. }
  61. return url;
  62. }
  63. namespace {
  64. const char kResponsePreamble[] = ")]}'";
  65. GURL ParseUrl(const base::Value& parent_dict,
  66. const std::string& key,
  67. const GURL& base_url) {
  68. DCHECK(parent_dict.is_dict());
  69. const std::string* url_str = parent_dict.FindStringKey(key);
  70. if (!url_str || url_str->empty()) {
  71. return GURL();
  72. }
  73. GURL result = base_url.Resolve(*url_str);
  74. // If the base URL is https:// (which should almost always be the case, see
  75. // above), then we require all other URLs to be https:// too.
  76. if (base_url.SchemeIs(url::kHttpsScheme) &&
  77. !result.SchemeIs(url::kHttpsScheme)) {
  78. return GURL();
  79. }
  80. return result;
  81. }
  82. // On success returns a pair of <mime_type, data>.
  83. // On error returns a pair of <string, nullptr>.
  84. // mime_type should be ignored if data is nullptr.
  85. std::pair<std::string, scoped_refptr<base::RefCountedString>>
  86. ParseEncodedImageData(const std::string& encoded_image_data) {
  87. std::pair<std::string, scoped_refptr<base::RefCountedString>> result;
  88. GURL encoded_image_uri(encoded_image_data);
  89. if (!encoded_image_uri.is_valid() ||
  90. !encoded_image_uri.SchemeIs(url::kDataScheme)) {
  91. return result;
  92. }
  93. std::string content = encoded_image_uri.GetContent();
  94. // The content should look like this: "image/png;base64,aaa..." (where
  95. // "aaa..." is the base64-encoded image data).
  96. size_t mime_type_end = content.find_first_of(';');
  97. if (mime_type_end == std::string::npos)
  98. return result;
  99. std::string mime_type = content.substr(0, mime_type_end);
  100. size_t base64_begin = mime_type_end + 1;
  101. size_t base64_end = content.find_first_of(',', base64_begin);
  102. if (base64_end == std::string::npos)
  103. return result;
  104. auto base64 = base::MakeStringPiece(content.begin() + base64_begin,
  105. content.begin() + base64_end);
  106. if (base64 != "base64")
  107. return result;
  108. size_t data_begin = base64_end + 1;
  109. auto data =
  110. base::MakeStringPiece(content.begin() + data_begin, content.end());
  111. std::string decoded_data;
  112. if (!base::Base64Decode(data, &decoded_data))
  113. return result;
  114. result.first = mime_type;
  115. result.second = base::RefCountedString::TakeString(&decoded_data);
  116. return result;
  117. }
  118. } // namespace
  119. std::unique_ptr<EncodedLogo> ParseDoodleLogoResponse(
  120. const GURL& base_url,
  121. std::unique_ptr<std::string> response,
  122. base::Time response_time,
  123. bool* parsing_failed) {
  124. // The response may start with )]}'. Ignore this.
  125. base::StringPiece response_sp(*response);
  126. if (base::StartsWith(response_sp, kResponsePreamble))
  127. response_sp.remove_prefix(strlen(kResponsePreamble));
  128. // Default parsing failure to be true.
  129. *parsing_failed = true;
  130. auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(response_sp);
  131. if (!parsed_json.has_value()) {
  132. LOG(WARNING) << parsed_json.error().message << " at "
  133. << parsed_json.error().line << ":"
  134. << parsed_json.error().column;
  135. return nullptr;
  136. }
  137. if (!parsed_json->is_dict())
  138. return nullptr;
  139. const base::Value* ddljson = parsed_json->FindDictKey("ddljson");
  140. if (!ddljson)
  141. return nullptr;
  142. // If there is no logo today, the "ddljson" dictionary will be empty.
  143. if (ddljson->DictEmpty()) {
  144. *parsing_failed = false;
  145. return nullptr;
  146. }
  147. auto logo = std::make_unique<EncodedLogo>();
  148. const std::string* doodle_type = ddljson->FindStringKey("doodle_type");
  149. logo->metadata.type = LogoType::SIMPLE;
  150. if (doodle_type) {
  151. if (*doodle_type == "ANIMATED") {
  152. logo->metadata.type = LogoType::ANIMATED;
  153. } else if (*doodle_type == "INTERACTIVE") {
  154. logo->metadata.type = LogoType::INTERACTIVE;
  155. } else if (*doodle_type == "VIDEO") {
  156. logo->metadata.type = LogoType::INTERACTIVE;
  157. }
  158. }
  159. const bool is_simple = (logo->metadata.type == LogoType::SIMPLE);
  160. const bool is_animated = (logo->metadata.type == LogoType::ANIMATED);
  161. bool is_interactive = (logo->metadata.type == LogoType::INTERACTIVE);
  162. // Check if the main image is animated.
  163. if (is_animated) {
  164. // If animated, get the URL for the animated image.
  165. const base::Value* image = ddljson->FindDictKey("large_image");
  166. if (!image)
  167. return nullptr;
  168. logo->metadata.animated_url = ParseUrl(*image, "url", base_url);
  169. if (!logo->metadata.animated_url.is_valid())
  170. return nullptr;
  171. const base::Value* dark_image = ddljson->FindDictKey("dark_large_image");
  172. if (dark_image)
  173. logo->metadata.dark_animated_url = ParseUrl(*dark_image, "url", base_url);
  174. }
  175. if (is_simple || is_animated) {
  176. const base::Value* image = ddljson->FindDictKey("large_image");
  177. if (image) {
  178. if (absl::optional<int> width_px = image->FindIntKey("width"))
  179. logo->metadata.width_px = *width_px;
  180. if (absl::optional<int> height_px = image->FindIntKey("height"))
  181. logo->metadata.height_px = *height_px;
  182. }
  183. const base::Value* dark_image = ddljson->FindDictKey("dark_large_image");
  184. if (dark_image) {
  185. if (const std::string* background_color =
  186. dark_image->FindStringKey("background_color")) {
  187. logo->metadata.dark_background_color = *background_color;
  188. }
  189. if (absl::optional<int> width_px = dark_image->FindIntKey("width"))
  190. logo->metadata.dark_width_px = *width_px;
  191. if (absl::optional<int> height_px = dark_image->FindIntKey("height"))
  192. logo->metadata.dark_height_px = *height_px;
  193. }
  194. }
  195. const bool is_eligible_for_share_button =
  196. (logo->metadata.type == LogoType::ANIMATED ||
  197. logo->metadata.type == LogoType::SIMPLE);
  198. if (is_eligible_for_share_button) {
  199. const base::Value* share_button = ddljson->FindDictKey("share_button");
  200. const std::string* short_link_ptr = ddljson->FindStringKey("short_link");
  201. // The short link in the doodle proto is an incomplete URL with the format
  202. // //g.co/*, //doodle.gle/* or //google.com?doodle=*.
  203. // Complete the URL if possible.
  204. if (share_button && short_link_ptr && short_link_ptr->find("//") == 0) {
  205. std::string short_link_str = *short_link_ptr;
  206. short_link_str.insert(0, "https:");
  207. logo->metadata.short_link = GURL(std::move(short_link_str));
  208. if (logo->metadata.short_link.is_valid()) {
  209. if (absl::optional<int> offset_x = share_button->FindIntKey("offset_x"))
  210. logo->metadata.share_button_x = *offset_x;
  211. if (absl::optional<int> offset_y = share_button->FindIntKey("offset_y"))
  212. logo->metadata.share_button_y = *offset_y;
  213. if (absl::optional<double> opacity =
  214. share_button->FindDoubleKey("opacity")) {
  215. logo->metadata.share_button_opacity = *opacity;
  216. }
  217. if (const std::string* icon = share_button->FindStringKey("icon_image"))
  218. logo->metadata.share_button_icon = *icon;
  219. if (const std::string* bg_color =
  220. share_button->FindStringKey("background_color")) {
  221. logo->metadata.share_button_bg = *bg_color;
  222. }
  223. }
  224. }
  225. const base::Value* dark_share_button =
  226. ddljson->FindDictKey("dark_share_button");
  227. if (dark_share_button) {
  228. if (logo->metadata.short_link.is_valid()) {
  229. if (absl::optional<int> offset_x =
  230. dark_share_button->FindIntKey("offset_x")) {
  231. logo->metadata.dark_share_button_x = *offset_x;
  232. }
  233. if (absl::optional<int> offset_y =
  234. dark_share_button->FindIntKey("offset_y")) {
  235. logo->metadata.dark_share_button_y = *offset_y;
  236. }
  237. if (absl::optional<double> opacity =
  238. dark_share_button->FindDoubleKey("opacity")) {
  239. logo->metadata.dark_share_button_opacity = *opacity;
  240. }
  241. if (const std::string* icon =
  242. dark_share_button->FindStringKey("icon_image")) {
  243. logo->metadata.dark_share_button_icon = *icon;
  244. }
  245. if (const std::string* bg_color =
  246. dark_share_button->FindStringKey("background_color")) {
  247. logo->metadata.dark_share_button_bg = *bg_color;
  248. }
  249. }
  250. }
  251. }
  252. logo->metadata.full_page_url =
  253. ParseUrl(*ddljson, "fullpage_interactive_url", base_url);
  254. // Data is optional, since we may be revalidating a cached logo.
  255. // If there is a CTA image, get that; otherwise use the regular image.
  256. const std::string* encoded_image_data =
  257. ddljson->FindStringKey("cta_data_uri");
  258. if (!encoded_image_data)
  259. encoded_image_data = ddljson->FindStringKey("data_uri");
  260. if (encoded_image_data) {
  261. auto [mime_type, data] = ParseEncodedImageData(*encoded_image_data);
  262. if (!data)
  263. return nullptr;
  264. logo->metadata.mime_type = mime_type;
  265. logo->encoded_image = data;
  266. }
  267. const std::string* dark_encoded_image_data =
  268. ddljson->FindStringKey("dark_cta_data_uri");
  269. if (!dark_encoded_image_data)
  270. dark_encoded_image_data = ddljson->FindStringKey("dark_data_uri");
  271. if (dark_encoded_image_data) {
  272. auto [mime_type, data] = ParseEncodedImageData(*dark_encoded_image_data);
  273. if (data)
  274. logo->metadata.dark_mime_type = mime_type;
  275. logo->dark_encoded_image = data;
  276. }
  277. logo->metadata.on_click_url = ParseUrl(*ddljson, "target_url", base_url);
  278. if (const std::string* alt_text = ddljson->FindStringKey("alt_text"))
  279. logo->metadata.alt_text = *alt_text;
  280. logo->metadata.cta_log_url = ParseUrl(*ddljson, "cta_log_url", base_url);
  281. logo->metadata.dark_cta_log_url =
  282. ParseUrl(*ddljson, "dark_cta_log_url", base_url);
  283. logo->metadata.log_url = ParseUrl(*ddljson, "log_url", base_url);
  284. logo->metadata.dark_log_url = ParseUrl(*ddljson, "dark_log_url", base_url);
  285. if (const std::string* fingerprint = ddljson->FindStringKey("fingerprint"))
  286. logo->metadata.fingerprint = *fingerprint;
  287. if (is_interactive) {
  288. const std::string* behavior =
  289. ddljson->FindStringKey("launch_interactive_behavior");
  290. if (behavior && (*behavior == "NEW_WINDOW")) {
  291. logo->metadata.type = LogoType::SIMPLE;
  292. logo->metadata.on_click_url = logo->metadata.full_page_url;
  293. is_interactive = false;
  294. }
  295. }
  296. logo->metadata.iframe_width_px = 0;
  297. logo->metadata.iframe_height_px = 0;
  298. if (is_interactive) {
  299. logo->metadata.iframe_width_px =
  300. ddljson->FindIntKey("iframe_width_px").value_or(kDefaultIframeWidthPx);
  301. logo->metadata.iframe_height_px = ddljson->FindIntKey("iframe_height_px")
  302. .value_or(kDefaultIframeHeightPx);
  303. }
  304. base::TimeDelta time_to_live;
  305. // The JSON doesn't guarantee the number to fit into an int.
  306. if (absl::optional<double> ttl_ms =
  307. ddljson->FindDoubleKey("time_to_live_ms")) {
  308. time_to_live = base::Milliseconds(*ttl_ms);
  309. logo->metadata.can_show_after_expiration = false;
  310. } else {
  311. time_to_live = base::Milliseconds(kMaxTimeToLiveMS);
  312. logo->metadata.can_show_after_expiration = true;
  313. }
  314. logo->metadata.expiration_time = response_time + time_to_live;
  315. *parsing_failed = false;
  316. return logo;
  317. }
  318. } // namespace search_provider_logos