| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/profiles/profile_downloader.h" | 5 #include "chrome/browser/profiles/profile_downloader.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 #include <vector> | 8 #include <vector> |
| 9 | 9 |
| 10 #include "base/json/json_reader.h" | 10 #include "base/json/json_reader.h" |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 #include "skia/ext/image_operations.h" | 31 #include "skia/ext/image_operations.h" |
| 32 | 32 |
| 33 using content::BrowserThread; | 33 using content::BrowserThread; |
| 34 | 34 |
| 35 namespace { | 35 namespace { |
| 36 | 36 |
| 37 // Template for optional authorization header when using an OAuth access token. | 37 // Template for optional authorization header when using an OAuth access token. |
| 38 const char kAuthorizationHeader[] = | 38 const char kAuthorizationHeader[] = |
| 39 "Authorization: Bearer %s"; | 39 "Authorization: Bearer %s"; |
| 40 | 40 |
| 41 // URL requesting Picasa API for user info. | 41 // URL requesting user info. |
| 42 const char kUserEntryURL[] = | 42 const char kUserEntryURL[] = |
| 43 "http://picasaweb.google.com/data/entry/api/user/default?alt=json"; | 43 "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"; |
| 44 | 44 |
| 45 // OAuth scope for the Picasa API. | 45 // OAuth scope for the user info API. |
| 46 const char kPicasaScope[] = "http://picasaweb.google.com/data/"; | 46 const char kAPIScope[] = "https://www.googleapis.com/auth/userinfo.profile"; |
| 47 | 47 |
| 48 // Path in JSON dictionary to user's photo thumbnail URL. | 48 // Path in JSON dictionary to user's photo thumbnail URL. |
| 49 const char kPhotoThumbnailURLPath[] = "entry.gphoto$thumbnail.$t"; | 49 const char kPhotoThumbnailURLPath[] = "picture"; |
| 50 | 50 |
| 51 const char kNickNamePath[] = "entry.gphoto$nickname.$t"; | 51 const char kNickNamePath[] = "name"; |
| 52 | 52 |
| 53 // Path format for specifying thumbnail's size. | 53 // Path format for specifying thumbnail's size. |
| 54 const char kThumbnailSizeFormat[] = "s%d-c"; | 54 const char kThumbnailSizeFormat[] = "s%d-c"; |
| 55 // Default Picasa thumbnail size. | 55 // Default thumbnail size. |
| 56 const int kDefaultThumbnailSize = 64; | 56 const int kDefaultThumbnailSize = 64; |
| 57 | 57 |
| 58 // Separator of URL path components. | 58 // Separator of URL path components. |
| 59 const char kURLPathSeparator = '/'; | 59 const char kURLPathSeparator = '/'; |
| 60 | 60 |
| 61 // Photo ID of the Picasa Web Albums profile picture (base64 of 0). | 61 // Photo ID of the Picasa Web Albums profile picture (base64 of 0). |
| 62 const char kPicasaPhotoId[] = "AAAAAAAAAAA"; | 62 const char kPicasaPhotoId[] = "AAAAAAAAAAA"; |
| 63 | 63 |
| 64 // Photo version of the default PWA profile picture (base64 of 1). | 64 // Photo version of the default PWA profile picture (base64 of 1). |
| 65 const char kDefaultPicasaPhotoVersion[] = "AAAAAAAAAAE"; | 65 const char kDefaultPicasaPhotoVersion[] = "AAAAAAAAAAE"; |
| 66 | 66 |
| 67 // Photo ID of the Google+ profile picture (base64 of 2). | 67 // Photo ID of the Google+ profile picture (base64 of 2). |
| 68 const char kGooglePlusPhotoId[] = "AAAAAAAAAAI"; | 68 const char kGooglePlusPhotoId[] = "AAAAAAAAAAI"; |
| 69 | 69 |
| 70 // Photo version of the default Google+ profile picture (base64 of 0). | 70 // Photo version of the default Google+ profile picture (base64 of 0). |
| 71 const char kDefaultGooglePlusPhotoVersion[] = "AAAAAAAAAAA"; | 71 const char kDefaultGooglePlusPhotoVersion[] = "AAAAAAAAAAA"; |
| 72 | 72 |
| 73 // Number of path components in profile picture URL. | 73 // The minimum number of path components in profile picture URL. |
| 74 const size_t kProfileImageURLPathComponentsCount = 7; | 74 const size_t kProfileImageURLPathComponentsCount = 6; |
| 75 | 75 |
| 76 // Index of path component with photo ID. | 76 // Index of path component with photo ID. |
| 77 const int kPhotoIdPathComponentIndex = 2; | 77 const int kPhotoIdPathComponentIndex = 2; |
| 78 | 78 |
| 79 // Index of path component with photo version. | 79 // Index of path component with photo version. |
| 80 const int kPhotoVersionPathComponentIndex = 3; | 80 const int kPhotoVersionPathComponentIndex = 3; |
| 81 | 81 |
| 82 // Given an image URL this function builds a new URL set to |size|. |
| 83 // For example, if |size| was set to 256 and |old_url| was either: |
| 84 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/photo.jpg |
| 85 // or |
| 86 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg |
| 87 // then return value in |new_url| would be: |
| 88 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s256-c/photo.jpg |
| 89 bool GetImageURLWithSize(const GURL& old_url, int size, GURL* new_url) { |
| 90 DCHECK(new_url); |
| 91 std::vector<std::string> components; |
| 92 base::SplitString(old_url.path(), kURLPathSeparator, &components); |
| 93 if (components.size() == 0) |
| 94 return false; |
| 95 |
| 96 const std::string& old_spec = old_url.spec(); |
| 97 std::string default_size_component( |
| 98 base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize)); |
| 99 std::string new_size_component( |
| 100 base::StringPrintf(kThumbnailSizeFormat, size)); |
| 101 |
| 102 size_t pos = old_spec.find(default_size_component); |
| 103 size_t end = std::string::npos; |
| 104 if (pos != std::string::npos) { |
| 105 // The default size is already specified in the URL so it needs to be |
| 106 // replaced with the new size. |
| 107 end = pos + default_size_component.size(); |
| 108 } else { |
| 109 // The default size is not in the URL so try to insert it before the last |
| 110 // component. |
| 111 const std::string& file_name = old_url.ExtractFileName(); |
| 112 if (!file_name.empty()) { |
| 113 pos = old_spec.find(file_name); |
| 114 end = pos - 1; |
| 115 } |
| 116 } |
| 117 |
| 118 if (pos != std::string::npos) { |
| 119 std::string new_spec = old_spec.substr(0, pos) + new_size_component + |
| 120 old_spec.substr(end); |
| 121 *new_url = GURL(new_spec); |
| 122 return new_url->is_valid(); |
| 123 } |
| 124 |
| 125 // We can't set the image size, just use the default size. |
| 126 *new_url = old_url; |
| 127 return true; |
| 128 } |
| 129 |
| 82 } // namespace | 130 } // namespace |
| 83 | 131 |
| 84 bool ProfileDownloader::GetProfileNickNameAndImageURL(const std::string& data, | 132 // static |
| 85 string16* nick_name, | 133 bool ProfileDownloader::GetProfileNameAndImageURL(const std::string& data, |
| 86 std::string* url) const { | 134 string16* nick_name, |
| 135 std::string* url, |
| 136 int image_size) { |
| 87 DCHECK(nick_name); | 137 DCHECK(nick_name); |
| 88 DCHECK(url); | 138 DCHECK(url); |
| 89 *nick_name = string16(); | 139 *nick_name = string16(); |
| 90 *url = std::string(); | 140 *url = std::string(); |
| 91 | 141 |
| 92 int error_code = -1; | 142 int error_code = -1; |
| 93 std::string error_message; | 143 std::string error_message; |
| 94 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError( | 144 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError( |
| 95 data, false, &error_code, &error_message)); | 145 data, false, &error_code, &error_message)); |
| 96 if (!root_value.get()) { | 146 if (!root_value.get()) { |
| 97 LOG(ERROR) << "Error while parsing Picasa user entry response: " | 147 LOG(ERROR) << "Error while parsing user entry response: " |
| 98 << error_message; | 148 << error_message; |
| 99 return false; | 149 return false; |
| 100 } | 150 } |
| 101 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) { | 151 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) { |
| 102 LOG(ERROR) << "JSON root is not a dictionary: " | 152 LOG(ERROR) << "JSON root is not a dictionary: " |
| 103 << root_value->GetType(); | 153 << root_value->GetType(); |
| 104 return false; | 154 return false; |
| 105 } | 155 } |
| 106 base::DictionaryValue* root_dictionary = | 156 base::DictionaryValue* root_dictionary = |
| 107 static_cast<base::DictionaryValue*>(root_value.get()); | 157 static_cast<base::DictionaryValue*>(root_value.get()); |
| 108 | 158 |
| 109 if (!root_dictionary->GetString(kNickNamePath, nick_name)) { | 159 root_dictionary->GetString(kNickNamePath, nick_name); |
| 110 LOG(ERROR) << "Can't find nick name path in JSON data: " | 160 |
| 111 << data; | 161 std::string url_string; |
| 112 return false; | 162 if (root_dictionary->GetString(kPhotoThumbnailURLPath, &url_string)) { |
| 163 GURL new_url; |
| 164 if (!GetImageURLWithSize(GURL(url_string), image_size, &new_url)) { |
| 165 LOG(ERROR) << "GetImageURLWithSize failed for url: " << url_string; |
| 166 return false; |
| 167 } |
| 168 *url = new_url.spec(); |
| 113 } | 169 } |
| 114 | 170 |
| 115 std::string thumbnail_url_string; | 171 // The profile data is considered valid as long as it has a name or a picture. |
| 116 if (!root_dictionary->GetString( | 172 return !nick_name->empty() || !url->empty(); |
| 117 kPhotoThumbnailURLPath, &thumbnail_url_string)) { | |
| 118 LOG(ERROR) << "Can't find thumbnail path in JSON data: " | |
| 119 << data; | |
| 120 return false; | |
| 121 } | |
| 122 | |
| 123 // Try to change the size of thumbnail we are going to get. | |
| 124 // Typical URL looks like this: | |
| 125 // http://lh0.ggpht.com/-abcd1aBCDEf/AAAA/AAA_A/abc12/s64-c/1234567890.jpg | |
| 126 std::string default_thumbnail_size_path_component( | |
| 127 base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize)); | |
| 128 int image_size = delegate_->GetDesiredImageSideLength(); | |
| 129 std::string new_thumbnail_size_path_component( | |
| 130 base::StringPrintf(kThumbnailSizeFormat, image_size)); | |
| 131 size_t thumbnail_size_pos = | |
| 132 thumbnail_url_string.find(default_thumbnail_size_path_component); | |
| 133 if (thumbnail_size_pos != std::string::npos) { | |
| 134 size_t thumbnail_size_end = | |
| 135 thumbnail_size_pos + default_thumbnail_size_path_component.size(); | |
| 136 thumbnail_url_string = | |
| 137 thumbnail_url_string.substr(0, thumbnail_size_pos) + | |
| 138 new_thumbnail_size_path_component + | |
| 139 thumbnail_url_string.substr( | |
| 140 thumbnail_size_end, | |
| 141 thumbnail_url_string.size() - thumbnail_size_end); | |
| 142 } else { | |
| 143 LOG(WARNING) << "Hasn't found thumbnail size part in image URL: " | |
| 144 << thumbnail_url_string; | |
| 145 // Use the thumbnail URL we have. | |
| 146 } | |
| 147 | |
| 148 GURL thumbnail_url(thumbnail_url_string); | |
| 149 if (!thumbnail_url.is_valid()) { | |
| 150 LOG(ERROR) << "Thumbnail URL is not valid: " << thumbnail_url_string; | |
| 151 return false; | |
| 152 } | |
| 153 *url = thumbnail_url.spec(); | |
| 154 return true; | |
| 155 } | 173 } |
| 156 | 174 |
| 157 bool ProfileDownloader::IsDefaultProfileImageURL(const std::string& url) const { | 175 // static |
| 176 bool ProfileDownloader::IsDefaultProfileImageURL(const std::string& url) { |
| 177 if (url.empty()) |
| 178 return true; |
| 179 |
| 158 GURL image_url_object(url); | 180 GURL image_url_object(url); |
| 159 DCHECK(image_url_object.is_valid()); | 181 DCHECK(image_url_object.is_valid()); |
| 160 VLOG(1) << "URL to check for default image: " << image_url_object.spec(); | 182 VLOG(1) << "URL to check for default image: " << image_url_object.spec(); |
| 161 std::vector<std::string> path_components; | 183 std::vector<std::string> path_components; |
| 162 base::SplitString(image_url_object.path(), | 184 base::SplitString(image_url_object.path(), |
| 163 kURLPathSeparator, | 185 kURLPathSeparator, |
| 164 &path_components); | 186 &path_components); |
| 165 | 187 |
| 166 if (path_components.size() != kProfileImageURLPathComponentsCount) | 188 if (path_components.size() < kProfileImageURLPathComponentsCount) |
| 167 return false; | 189 return false; |
| 168 | 190 |
| 169 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex]; | 191 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex]; |
| 170 const std::string& photo_version = | 192 const std::string& photo_version = |
| 171 path_components[kPhotoVersionPathComponentIndex]; | 193 path_components[kPhotoVersionPathComponentIndex]; |
| 172 | 194 |
| 173 // There are at least two pairs of (ID, version) for the default photo: | 195 // There are at least two pairs of (ID, version) for the default photo: |
| 174 // the default Google+ profile photo and the default Picasa profile photo. | 196 // the default Google+ profile photo and the default Picasa profile photo. |
| 175 return ((photo_id == kPicasaPhotoId && | 197 return ((photo_id == kPicasaPhotoId && |
| 176 photo_version == kDefaultPicasaPhotoVersion) || | 198 photo_version == kDefaultPicasaPhotoVersion) || |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 236 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); | 258 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); |
| 237 } | 259 } |
| 238 user_entry_fetcher_->Start(); | 260 user_entry_fetcher_->Start(); |
| 239 } | 261 } |
| 240 | 262 |
| 241 void ProfileDownloader::StartFetchingOAuth2AccessToken() { | 263 void ProfileDownloader::StartFetchingOAuth2AccessToken() { |
| 242 TokenService* service = delegate_->GetBrowserProfile()->GetTokenService(); | 264 TokenService* service = delegate_->GetBrowserProfile()->GetTokenService(); |
| 243 DCHECK(!service->GetOAuth2LoginRefreshToken().empty()); | 265 DCHECK(!service->GetOAuth2LoginRefreshToken().empty()); |
| 244 | 266 |
| 245 std::vector<std::string> scopes; | 267 std::vector<std::string> scopes; |
| 246 scopes.push_back(kPicasaScope); | 268 scopes.push_back(kAPIScope); |
| 247 oauth2_access_token_fetcher_.reset(new OAuth2AccessTokenFetcher( | 269 oauth2_access_token_fetcher_.reset(new OAuth2AccessTokenFetcher( |
| 248 this, delegate_->GetBrowserProfile()->GetRequestContext())); | 270 this, delegate_->GetBrowserProfile()->GetRequestContext())); |
| 249 oauth2_access_token_fetcher_->Start( | 271 oauth2_access_token_fetcher_->Start( |
| 250 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), | 272 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), |
| 251 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), | 273 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), |
| 252 service->GetOAuth2LoginRefreshToken(), | 274 service->GetOAuth2LoginRefreshToken(), |
| 253 scopes); | 275 scopes); |
| 254 } | 276 } |
| 255 | 277 |
| 256 ProfileDownloader::~ProfileDownloader() {} | 278 ProfileDownloader::~ProfileDownloader() {} |
| 257 | 279 |
| 258 void ProfileDownloader::OnURLFetchComplete(const content::URLFetcher* source) { | 280 void ProfileDownloader::OnURLFetchComplete(const content::URLFetcher* source) { |
| 259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 260 std::string data; | 282 std::string data; |
| 261 source->GetResponseAsString(&data); | 283 source->GetResponseAsString(&data); |
| 262 if (source->GetResponseCode() != 200) { | 284 if (source->GetResponseCode() != 200) { |
| 263 LOG(ERROR) << "Response code is " << source->GetResponseCode(); | 285 LOG(ERROR) << "Response code is " << source->GetResponseCode(); |
| 264 LOG(ERROR) << "Url is " << source->GetURL().spec(); | 286 LOG(ERROR) << "Url is " << source->GetURL().spec(); |
| 265 LOG(ERROR) << "Data is " << data; | 287 LOG(ERROR) << "Data is " << data; |
| 266 delegate_->OnDownloadComplete(this, false); | 288 delegate_->OnDownloadComplete(this, false); |
| 267 return; | 289 return; |
| 268 } | 290 } |
| 269 | 291 |
| 270 if (source == user_entry_fetcher_.get()) { | 292 if (source == user_entry_fetcher_.get()) { |
| 271 std::string image_url; | 293 std::string image_url; |
| 272 if (!GetProfileNickNameAndImageURL(data, &profile_full_name_, &image_url)) { | 294 if (!GetProfileNameAndImageURL(data, &profile_full_name_, &image_url, |
| 295 delegate_->GetDesiredImageSideLength())) { |
| 273 delegate_->OnDownloadComplete(this, false); | 296 delegate_->OnDownloadComplete(this, false); |
| 274 return; | 297 return; |
| 275 } | 298 } |
| 276 if (IsDefaultProfileImageURL(image_url)) { | 299 if (IsDefaultProfileImageURL(image_url)) { |
| 277 VLOG(1) << "User has default profile picture"; | 300 VLOG(1) << "User has default profile picture"; |
| 278 picture_status_ = PICTURE_DEFAULT; | 301 picture_status_ = PICTURE_DEFAULT; |
| 279 delegate_->OnDownloadComplete(this, true); | 302 delegate_->OnDownloadComplete(this, true); |
| 280 return; | 303 return; |
| 281 } | 304 } |
| 282 if (!image_url.empty() && image_url == delegate_->GetCachedPictureURL()) { | 305 if (!image_url.empty() && image_url == delegate_->GetCachedPictureURL()) { |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 352 void ProfileDownloader::OnGetTokenSuccess(const std::string& access_token) { | 375 void ProfileDownloader::OnGetTokenSuccess(const std::string& access_token) { |
| 353 auth_token_ = access_token; | 376 auth_token_ = access_token; |
| 354 StartFetchingImage(); | 377 StartFetchingImage(); |
| 355 } | 378 } |
| 356 | 379 |
| 357 // Callback for OAuth2AccessTokenFetcher on failure. | 380 // Callback for OAuth2AccessTokenFetcher on failure. |
| 358 void ProfileDownloader::OnGetTokenFailure(const GoogleServiceAuthError& error) { | 381 void ProfileDownloader::OnGetTokenFailure(const GoogleServiceAuthError& error) { |
| 359 LOG(WARNING) << "ProfileDownloader: token request using refresh token failed"; | 382 LOG(WARNING) << "ProfileDownloader: token request using refresh token failed"; |
| 360 delegate_->OnDownloadComplete(this, false); | 383 delegate_->OnDownloadComplete(this, false); |
| 361 } | 384 } |
| OLD | NEW |