// 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 #include #include "base/base64.h" #include "base/json/json_reader.h" #include "base/memory/ref_counted_memory.h" #include "base/strings/string_util.h" #include "base/values.h" namespace search_provider_logos { namespace { const char kResponsePreamble[] = ")]}'"; } GURL GoogleAppendQueryparamsToLogoURL(const GURL& logo_url, const std::string& fingerprint, bool wants_cta) { // Note: we can't just use net::AppendQueryParameter() because it escapes // ":" to "%3A", but the server requires the colon not to be escaped. // See: http://crbug.com/413845 // TODO(newt): Switch to using net::AppendQueryParameter once it no longer // escapes ":" if (!fingerprint.empty() || wants_cta) { std::string query(logo_url.query()); if (!query.empty()) query += "&"; query += "async="; if (!fingerprint.empty()) { query += "es_dfp:" + fingerprint; if (wants_cta) query += ","; } if (wants_cta) { query += "cta:1"; } GURL::Replacements replacements; replacements.SetQueryStr(query); return logo_url.ReplaceComponents(replacements); } return logo_url; } scoped_ptr GoogleParseLogoResponse( const scoped_ptr& response, base::Time response_time, bool* parsing_failed) { // Google doodles are sent as JSON with a prefix. Example: // )]}' {"update":{"logo":{ // "data": "/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/...", // "mime_type": "image/png", // "fingerprint": "db063e32", // "target": "http://www.google.com.au/search?q=Wilbur+Christiansen", // "url": "http://www.google.com/logos/doodle.png", // "alt": "Wilbur Christiansen's Birthday" // "time_to_live": 1389304799 // }}} // The response may start with )]}'. Ignore this. base::StringPiece response_sp(*response); if (response_sp.starts_with(kResponsePreamble)) response_sp.remove_prefix(strlen(kResponsePreamble)); // Default parsing failure to be true. *parsing_failed = true; scoped_ptr value = base::JSONReader::Read(response_sp); if (!value.get()) return scoped_ptr(); // The important data lives inside several nested dictionaries: // {"update": {"logo": { "mime_type": ..., etc } } } const base::DictionaryValue* outer_dict; if (!value->GetAsDictionary(&outer_dict)) return scoped_ptr(); const base::DictionaryValue* update_dict; if (!outer_dict->GetDictionary("update", &update_dict)) return scoped_ptr(); // If there is no logo today, the "update" dictionary will be empty. if (update_dict->empty()) { *parsing_failed = false; return scoped_ptr(); } const base::DictionaryValue* logo_dict; if (!update_dict->GetDictionary("logo", &logo_dict)) return scoped_ptr(); scoped_ptr logo(new EncodedLogo()); std::string encoded_image_base64; if (logo_dict->GetString("data", &encoded_image_base64)) { // Data is optional, since we may be revalidating a cached logo. base::RefCountedString* encoded_image_string = new base::RefCountedString(); if (!base::Base64Decode(encoded_image_base64, &encoded_image_string->data())) return scoped_ptr(); logo->encoded_image = encoded_image_string; if (!logo_dict->GetString("mime_type", &logo->metadata.mime_type)) return scoped_ptr(); } // Don't check return values since these fields are optional. logo_dict->GetString("target", &logo->metadata.on_click_url); logo_dict->GetString("fingerprint", &logo->metadata.fingerprint); logo_dict->GetString("alt", &logo->metadata.alt_text); // Existance of url indicates |data| is a call to action image for an // animated doodle. |url| points to that animated doodle. logo_dict->GetString("url", &logo->metadata.animated_url); base::TimeDelta time_to_live; int time_to_live_ms; if (logo_dict->GetInteger("time_to_live", &time_to_live_ms)) { time_to_live = base::TimeDelta::FromMilliseconds( std::min(static_cast(time_to_live_ms), kMaxTimeToLiveMS)); logo->metadata.can_show_after_expiration = false; } else { time_to_live = base::TimeDelta::FromMilliseconds(kMaxTimeToLiveMS); logo->metadata.can_show_after_expiration = true; } logo->metadata.expiration_time = response_time + time_to_live; // If this point is reached, parsing has succeeded. *parsing_failed = false; return logo; } } // namespace search_provider_logos