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. |
Ivan Korotkov
2011/12/04 15:08:52
URL requesting G+ API for user info
sail
2011/12/05 19:26:12
It turns out that using the G+ API won't work. It
| |
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"; |
Ivan Korotkov
2011/12/04 15:08:52
We probably don't need this one since we're not us
sail
2011/12/05 19:26:12
Since we're using the G+ API it seems safer to lea
| |
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: | |
84 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/photo.jpg | |
85 // then return value in |new_url| would be: | |
86 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s256-c/photo.jpg | |
Munjal (Google)
2011/12/05 09:09:24
It would be nice to mention the other case also wh
sail
2011/12/05 19:26:12
Done.
| |
87 bool GetImageURLWithSize(const GURL& old_url, int size, GURL* new_url) { | |
88 DCHECK(new_url); | |
89 std::vector<std::string> components; | |
90 base::SplitString(old_url.path(), kURLPathSeparator, &components); | |
91 if (components.size() == 0) | |
92 return false; | |
93 | |
94 const std::string& old_spec = old_url.spec(); | |
95 std::string default_size_component( | |
96 base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize)); | |
97 std::string new_size_component( | |
98 base::StringPrintf(kThumbnailSizeFormat, size)); | |
99 | |
100 size_t pos = old_spec.find(default_size_component); | |
101 size_t end = std::string::npos; | |
102 if (pos != std::string::npos) { | |
103 // The default size is already specified in the URL so it needs to be | |
104 // replaced with the new size. | |
105 end = pos + default_size_component.size(); | |
106 } else { | |
107 // The default size is not in the URL so try to insert it before the last | |
108 // component. | |
109 const std::string& file_name = old_url.ExtractFileName(); | |
110 if (!file_name.empty()) { | |
111 pos = old_spec.find(file_name); | |
112 end = pos - 1; | |
113 } | |
114 } | |
115 | |
116 if (pos != std::string::npos) { | |
Munjal (Google)
2011/12/05 09:09:24
NIt: this can be a DCHECK or a CHECk instead of if
sail
2011/12/05 19:26:12
pos can be std::string::npos if the URL doesn't ha
| |
117 std::string new_spec = old_spec.substr(0, pos) + new_size_component + | |
118 old_spec.substr(end); | |
119 *new_url = GURL(new_spec); | |
120 return new_url->is_valid(); | |
121 } | |
122 | |
123 // We can't set the image size, just use the default size. | |
124 *new_url = old_url; | |
125 return true; | |
126 } | |
127 | |
82 } // namespace | 128 } // namespace |
83 | 129 |
84 bool ProfileDownloader::GetProfileNickNameAndImageURL(const std::string& data, | 130 // static |
85 string16* nick_name, | 131 bool ProfileDownloader::GetProfileNameAndImageURL(const std::string& data, |
86 std::string* url) const { | 132 string16* nick_name, |
133 std::string* url, | |
134 int image_size) { | |
87 DCHECK(nick_name); | 135 DCHECK(nick_name); |
88 DCHECK(url); | 136 DCHECK(url); |
89 *nick_name = string16(); | 137 *nick_name = string16(); |
90 *url = std::string(); | 138 *url = std::string(); |
91 | 139 |
92 int error_code = -1; | 140 int error_code = -1; |
93 std::string error_message; | 141 std::string error_message; |
94 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError( | 142 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError( |
95 data, false, &error_code, &error_message)); | 143 data, false, &error_code, &error_message)); |
96 if (!root_value.get()) { | 144 if (!root_value.get()) { |
97 LOG(ERROR) << "Error while parsing Picasa user entry response: " | 145 LOG(ERROR) << "Error while parsing user entry response: " |
98 << error_message; | 146 << error_message; |
99 return false; | 147 return false; |
100 } | 148 } |
101 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) { | 149 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) { |
102 LOG(ERROR) << "JSON root is not a dictionary: " | 150 LOG(ERROR) << "JSON root is not a dictionary: " |
103 << root_value->GetType(); | 151 << root_value->GetType(); |
104 return false; | 152 return false; |
105 } | 153 } |
106 base::DictionaryValue* root_dictionary = | 154 base::DictionaryValue* root_dictionary = |
107 static_cast<base::DictionaryValue*>(root_value.get()); | 155 static_cast<base::DictionaryValue*>(root_value.get()); |
108 | 156 |
109 if (!root_dictionary->GetString(kNickNamePath, nick_name)) { | 157 root_dictionary->GetString(kNickNamePath, nick_name); |
110 LOG(ERROR) << "Can't find nick name path in JSON data: " | 158 |
111 << data; | 159 std::string url_string; |
112 return false; | 160 if (root_dictionary->GetString(kPhotoThumbnailURLPath, &url_string)) { |
161 GURL new_url; | |
162 if (!GetImageURLWithSize(GURL(url_string), image_size, &new_url)) { | |
163 LOG(ERROR) << "GetImageURLWithSize failed for url: " << url_string; | |
164 return false; | |
165 } | |
166 *url = new_url.spec(); | |
113 } | 167 } |
114 | 168 |
115 std::string thumbnail_url_string; | 169 // The profile data is considered valid as long as it has a name or a picture. |
116 if (!root_dictionary->GetString( | 170 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 } | 171 } |
156 | 172 |
157 bool ProfileDownloader::IsDefaultProfileImageURL(const std::string& url) const { | 173 bool ProfileDownloader::IsDefaultProfileImageURL(const std::string& url) const { |
174 if (url.empty()) | |
175 return true; | |
176 | |
158 GURL image_url_object(url); | 177 GURL image_url_object(url); |
159 DCHECK(image_url_object.is_valid()); | 178 DCHECK(image_url_object.is_valid()); |
160 VLOG(1) << "URL to check for default image: " << image_url_object.spec(); | 179 VLOG(1) << "URL to check for default image: " << image_url_object.spec(); |
161 std::vector<std::string> path_components; | 180 std::vector<std::string> path_components; |
162 base::SplitString(image_url_object.path(), | 181 base::SplitString(image_url_object.path(), |
163 kURLPathSeparator, | 182 kURLPathSeparator, |
164 &path_components); | 183 &path_components); |
165 | 184 |
166 if (path_components.size() != kProfileImageURLPathComponentsCount) | 185 if (path_components.size() < kProfileImageURLPathComponentsCount) |
167 return false; | 186 return false; |
168 | 187 |
169 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex]; | 188 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex]; |
170 const std::string& photo_version = | 189 const std::string& photo_version = |
171 path_components[kPhotoVersionPathComponentIndex]; | 190 path_components[kPhotoVersionPathComponentIndex]; |
172 | 191 |
173 // There are at least two pairs of (ID, version) for the default photo: | 192 // 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. | 193 // the default Google+ profile photo and the default Picasa profile photo. |
175 return ((photo_id == kPicasaPhotoId && | 194 return ((photo_id == kPicasaPhotoId && |
176 photo_version == kDefaultPicasaPhotoVersion) || | 195 photo_version == kDefaultPicasaPhotoVersion) || |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
236 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); | 255 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); |
237 } | 256 } |
238 user_entry_fetcher_->Start(); | 257 user_entry_fetcher_->Start(); |
239 } | 258 } |
240 | 259 |
241 void ProfileDownloader::StartFetchingOAuth2AccessToken() { | 260 void ProfileDownloader::StartFetchingOAuth2AccessToken() { |
242 TokenService* service = delegate_->GetBrowserProfile()->GetTokenService(); | 261 TokenService* service = delegate_->GetBrowserProfile()->GetTokenService(); |
243 DCHECK(!service->GetOAuth2LoginRefreshToken().empty()); | 262 DCHECK(!service->GetOAuth2LoginRefreshToken().empty()); |
244 | 263 |
245 std::vector<std::string> scopes; | 264 std::vector<std::string> scopes; |
246 scopes.push_back(kPicasaScope); | 265 scopes.push_back(kAPIScope); |
247 oauth2_access_token_fetcher_.reset(new OAuth2AccessTokenFetcher( | 266 oauth2_access_token_fetcher_.reset(new OAuth2AccessTokenFetcher( |
248 this, delegate_->GetBrowserProfile()->GetRequestContext())); | 267 this, delegate_->GetBrowserProfile()->GetRequestContext())); |
249 oauth2_access_token_fetcher_->Start( | 268 oauth2_access_token_fetcher_->Start( |
250 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), | 269 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), |
251 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), | 270 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), |
252 service->GetOAuth2LoginRefreshToken(), | 271 service->GetOAuth2LoginRefreshToken(), |
253 scopes); | 272 scopes); |
254 } | 273 } |
255 | 274 |
256 ProfileDownloader::~ProfileDownloader() {} | 275 ProfileDownloader::~ProfileDownloader() {} |
257 | 276 |
258 void ProfileDownloader::OnURLFetchComplete(const content::URLFetcher* source) { | 277 void ProfileDownloader::OnURLFetchComplete(const content::URLFetcher* source) { |
259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 278 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
260 std::string data; | 279 std::string data; |
261 source->GetResponseAsString(&data); | 280 source->GetResponseAsString(&data); |
262 if (source->GetResponseCode() != 200) { | 281 if (source->GetResponseCode() != 200) { |
263 LOG(ERROR) << "Response code is " << source->GetResponseCode(); | 282 LOG(ERROR) << "Response code is " << source->GetResponseCode(); |
264 LOG(ERROR) << "Url is " << source->GetURL().spec(); | 283 LOG(ERROR) << "Url is " << source->GetURL().spec(); |
265 LOG(ERROR) << "Data is " << data; | 284 LOG(ERROR) << "Data is " << data; |
266 delegate_->OnDownloadComplete(this, false); | 285 delegate_->OnDownloadComplete(this, false); |
267 return; | 286 return; |
268 } | 287 } |
269 | 288 |
270 if (source == user_entry_fetcher_.get()) { | 289 if (source == user_entry_fetcher_.get()) { |
271 std::string image_url; | 290 std::string image_url; |
272 if (!GetProfileNickNameAndImageURL(data, &profile_full_name_, &image_url)) { | 291 if (!GetProfileNameAndImageURL(data, &profile_full_name_, &image_url, |
292 delegate_->GetDesiredImageSideLength())) { | |
273 delegate_->OnDownloadComplete(this, false); | 293 delegate_->OnDownloadComplete(this, false); |
274 return; | 294 return; |
275 } | 295 } |
276 if (IsDefaultProfileImageURL(image_url)) { | 296 if (IsDefaultProfileImageURL(image_url)) { |
277 VLOG(1) << "User has default profile picture"; | 297 VLOG(1) << "User has default profile picture"; |
278 picture_status_ = PICTURE_DEFAULT; | 298 picture_status_ = PICTURE_DEFAULT; |
279 delegate_->OnDownloadComplete(this, true); | 299 delegate_->OnDownloadComplete(this, true); |
280 return; | 300 return; |
281 } | 301 } |
282 if (!image_url.empty() && image_url == delegate_->GetCachedPictureURL()) { | 302 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) { | 372 void ProfileDownloader::OnGetTokenSuccess(const std::string& access_token) { |
353 auth_token_ = access_token; | 373 auth_token_ = access_token; |
354 StartFetchingImage(); | 374 StartFetchingImage(); |
355 } | 375 } |
356 | 376 |
357 // Callback for OAuth2AccessTokenFetcher on failure. | 377 // Callback for OAuth2AccessTokenFetcher on failure. |
358 void ProfileDownloader::OnGetTokenFailure(const GoogleServiceAuthError& error) { | 378 void ProfileDownloader::OnGetTokenFailure(const GoogleServiceAuthError& error) { |
359 LOG(WARNING) << "ProfileDownloader: token request using refresh token failed"; | 379 LOG(WARNING) << "ProfileDownloader: token request using refresh token failed"; |
360 delegate_->OnDownloadComplete(this, false); | 380 delegate_->OnDownloadComplete(this, false); |
361 } | 381 } |
OLD | NEW |