1 // Copyright (c) 2011 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 5 #include "chrome/browser/chromeos/login/user_image_downloader.h" 6 7 #include "base/json/json_reader.h" 8 #include "base/logging.h" 9 #include "base/memory/scoped_ptr.h" 10 #include "base/string_util.h" 11 #include "base/stringprintf.h" 12 #include "base/values.h" 13 #include "chrome/browser/chromeos/login/authenticator.h" 14 #include "chrome/browser/chromeos/login/image_downloader.h" 15 #include "chrome/browser/chromeos/login/user_manager.h" 16 #include "chrome/browser/profiles/profile_manager.h" 17 #include "chrome/common/net/url_fetcher.h" 18 #include "content/browser/browser_thread.h" 19 #include "googleurl/src/gurl.h" 20 21 namespace chromeos { 22 23 namespace { 24 25 // Contacts API URL that returns all user info. 26 // TODO(avayvod): Find the way to receive less data for the user. 27 const char kUserInfoURL[] = 28 "http://www.google.com/m8/feeds/contacts/default/thin?alt=json"; 29 30 // Template for authorization header needed for all request to Contacts API. 31 const char kAuthorizationHeader[] = "Authorization: GoogleLogin auth=%s"; 32 33 // Schema that identifies JSON node with image url. 34 const char kPhotoSchemaURL[] = 35 "http://schemas.google.com/contacts/2008/rel#photo"; 36 37 } // namespace 38 39 UserImageDownloader::UserImageDownloader(const std::string& username, 40 const std::string& auth_token) 41 : username_(username), 42 auth_token_(auth_token) { 43 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 44 if (auth_token_.empty()) 45 return; 46 47 profile_fetcher_.reset(new URLFetcher(GURL(kUserInfoURL), 48 URLFetcher::GET, 49 this)); 50 profile_fetcher_->set_request_context( 51 ProfileManager::GetDefaultProfile()->GetRequestContext()); 52 profile_fetcher_->set_extra_request_headers( 53 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); 54 profile_fetcher_->Start(); 55 } 56 57 UserImageDownloader::~UserImageDownloader() { 58 } 59 60 void UserImageDownloader::OnURLFetchComplete( 61 const URLFetcher* source, 62 const GURL& url, 63 const net::URLRequestStatus& status, 64 int response_code, 65 const ResponseCookies& cookies, 66 const std::string& data) { 67 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 68 if (response_code != 200) { 69 LOG(ERROR) << "Response code is " << response_code; 70 LOG(ERROR) << "Url is " << url.spec(); 71 LOG(ERROR) << "Data is " << data; 72 return; 73 } 74 75 if (source == profile_fetcher_.get()) { 76 GURL image_url; 77 if (!GetImageURL(data, &image_url)) { 78 LOG(ERROR) << "Didn't find image url in " << data; 79 return; 80 } 81 VLOG(1) << "Sending request to " << image_url; 82 new ImageDownloader(this, GURL(image_url), auth_token_); 83 } 84 } 85 86 void UserImageDownloader::OnImageDecoded(const SkBitmap& decoded_image) { 87 // Save the image to file and its path to preferences. 88 chromeos::UserManager* user_manager = chromeos::UserManager::Get(); 89 if (user_manager) { 90 if (user_manager->logged_in_user().email() == username_) { 91 user_manager->SetLoggedInUserImage(decoded_image); 92 } 93 user_manager->SaveUserImage(username_, decoded_image); 94 } 95 } 96 97 bool UserImageDownloader::GetImageURL(const std::string& json_data, 98 GURL* image_url) const { 99 if (!image_url) { 100 NOTREACHED(); 101 return false; 102 } 103 104 // Data is in JSON format with image url located at the following path: 105 // root > feed > entry > dictionary > link > dictionary > href. 106 scoped_ptr<Value> root(base::JSONReader::Read(json_data, true)); 107 if (!root.get() || root->GetType() != Value::TYPE_DICTIONARY) 108 return false; 109 110 DictionaryValue* root_dictionary = 111 static_cast<DictionaryValue*>(root.get()); 112 DictionaryValue* feed_dictionary = NULL; 113 if (!root_dictionary->GetDictionary("feed", &feed_dictionary)) 114 return false; 115 116 ListValue* entry_list = NULL; 117 if (!feed_dictionary->GetList("entry", &entry_list)) 118 return false; 119 120 return GetImageURLFromEntries(entry_list, image_url); 121 } 122 123 bool UserImageDownloader::GetImageURLFromEntries(ListValue* entry_list, 124 GURL* image_url) const { 125 // The list contains info about all user's contacts including user 126 // himself. We need to find entry for the user and then get his image. 127 for (size_t i = 0; i < entry_list->GetSize(); ++i) { 128 DictionaryValue* entry_dictionary = NULL; 129 if (!entry_list->GetDictionary(i, &entry_dictionary)) 130 continue; 131 132 ListValue* email_list = NULL; 133 if (!entry_dictionary->GetList("gd$email", &email_list)) 134 continue; 135 136 // Match entry email address to understand that this is user's entry. 137 if (!IsUserEntry(email_list)) 138 continue; 139 140 ListValue* link_list = NULL; 141 if (!entry_dictionary->GetList("link", &link_list)) 142 continue; 143 144 if (GetImageURLFromLinks(link_list, image_url)) 145 return true; 146 } 147 148 return false; 149 } 150 151 bool UserImageDownloader::IsUserEntry(ListValue* email_list) const { 152 for (size_t i = 0; i < email_list->GetSize(); ++i) { 153 DictionaryValue* email_dictionary = NULL; 154 if (!email_list->GetDictionary(i, &email_dictionary)) 155 continue; 156 157 std::string email; 158 if (!email_dictionary->GetStringASCII("address", &email)) 159 continue; 160 161 if (Authenticator::Canonicalize(email) == username_) 162 return true; 163 } 164 return false; 165 } 166 167 bool UserImageDownloader::GetImageURLFromLinks(ListValue* link_list, 168 GURL* image_url) const { 169 // In entry's list of links there should be one with rel pointing to photo 170 // schema. 171 for (size_t i = 0; i < link_list->GetSize(); ++i) { 172 DictionaryValue* link_dictionary = NULL; 173 if (!link_list->GetDictionary(i, &link_dictionary)) 174 continue; 175 176 std::string rel; 177 if (!link_dictionary->GetStringASCII("rel", &rel)) 178 continue; 179 180 if (rel != kPhotoSchemaURL) 181 continue; 182 183 std::string url; 184 if (!link_dictionary->GetStringASCII("href", &url)) 185 continue; 186 187 *image_url = GURL(url); 188 return true; 189 } 190 return false; 191 } 192 193 } // namespace chromeos 194