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 |