Fix build break
[chromium-blink-merge.git] / chrome / browser / profiles / profile_downloader.cc
blob6464ecb3f7a9956f9d212a75a03805013ea51198
1 // Copyright (c) 2013 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.
5 #include "chrome/browser/profiles/profile_downloader.h"
7 #include <string>
8 #include <vector>
10 #include "base/json/json_reader.h"
11 #include "base/logging.h"
12 #include "base/message_loop.h"
13 #include "base/string_util.h"
14 #include "base/stringprintf.h"
15 #include "base/strings/string_split.h"
16 #include "base/values.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_downloader_delegate.h"
19 #include "chrome/browser/signin/token_service.h"
20 #include "chrome/browser/signin/token_service_factory.h"
21 #include "chrome/common/chrome_notification_types.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/notification_details.h"
24 #include "content/public/browser/notification_observer.h"
25 #include "content/public/browser/notification_registrar.h"
26 #include "content/public/browser/notification_source.h"
27 #include "content/public/browser/notification_types.h"
28 #include "google_apis/gaia/gaia_constants.h"
29 #include "google_apis/gaia/gaia_urls.h"
30 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
31 #include "googleurl/src/gurl.h"
32 #include "net/base/load_flags.h"
33 #include "net/url_request/url_fetcher.h"
34 #include "net/url_request/url_request_status.h"
35 #include "skia/ext/image_operations.h"
37 using content::BrowserThread;
39 namespace {
41 // Template for optional authorization header when using an OAuth access token.
42 const char kAuthorizationHeader[] =
43 "Authorization: Bearer %s";
45 // URL requesting user info.
46 const char kUserEntryURL[] =
47 "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
49 // OAuth scope for the user info API.
50 const char kAPIScope[] = "https://www.googleapis.com/auth/userinfo.profile";
52 // Path in JSON dictionary to user's photo thumbnail URL.
53 const char kPhotoThumbnailURLPath[] = "picture";
55 const char kNickNamePath[] = "name";
57 // Path format for specifying thumbnail's size.
58 const char kThumbnailSizeFormat[] = "s%d-c";
59 // Default thumbnail size.
60 const int kDefaultThumbnailSize = 64;
62 // Separator of URL path components.
63 const char kURLPathSeparator = '/';
65 // Photo ID of the Picasa Web Albums profile picture (base64 of 0).
66 const char kPicasaPhotoId[] = "AAAAAAAAAAA";
68 // Photo version of the default PWA profile picture (base64 of 1).
69 const char kDefaultPicasaPhotoVersion[] = "AAAAAAAAAAE";
71 // The minimum number of path components in profile picture URL.
72 const size_t kProfileImageURLPathComponentsCount = 6;
74 // Index of path component with photo ID.
75 const int kPhotoIdPathComponentIndex = 2;
77 // Index of path component with photo version.
78 const int kPhotoVersionPathComponentIndex = 3;
80 // Given an image URL this function builds a new URL set to |size|.
81 // For example, if |size| was set to 256 and |old_url| was either:
82 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/photo.jpg
83 // or
84 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg
85 // then return value in |new_url| would be:
86 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s256-c/photo.jpg
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;
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));
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;
116 if (pos != std::string::npos) {
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();
123 // We can't set the image size, just use the default size.
124 *new_url = old_url;
125 return true;
128 } // namespace
130 // static
131 bool ProfileDownloader::GetProfileNameAndImageURL(const std::string& data,
132 string16* nick_name,
133 std::string* url,
134 int image_size) {
135 DCHECK(nick_name);
136 DCHECK(url);
137 *nick_name = string16();
138 *url = std::string();
140 int error_code = -1;
141 std::string error_message;
142 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError(
143 data, base::JSON_PARSE_RFC, &error_code, &error_message));
144 if (!root_value.get()) {
145 LOG(ERROR) << "Error while parsing user entry response: "
146 << error_message;
147 return false;
149 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) {
150 LOG(ERROR) << "JSON root is not a dictionary: "
151 << root_value->GetType();
152 return false;
154 base::DictionaryValue* root_dictionary =
155 static_cast<base::DictionaryValue*>(root_value.get());
157 root_dictionary->GetString(kNickNamePath, nick_name);
159 std::string url_string;
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;
166 *url = new_url.spec();
169 // The profile data is considered valid as long as it has a name or a picture.
170 return !nick_name->empty() || !url->empty();
173 // static
174 bool ProfileDownloader::IsDefaultProfileImageURL(const std::string& url) {
175 if (url.empty())
176 return true;
178 GURL image_url_object(url);
179 DCHECK(image_url_object.is_valid());
180 VLOG(1) << "URL to check for default image: " << image_url_object.spec();
181 std::vector<std::string> path_components;
182 base::SplitString(image_url_object.path(),
183 kURLPathSeparator,
184 &path_components);
186 if (path_components.size() < kProfileImageURLPathComponentsCount)
187 return false;
189 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex];
190 const std::string& photo_version =
191 path_components[kPhotoVersionPathComponentIndex];
193 // Check that the ID and version match the default Picasa profile photo.
194 return photo_id == kPicasaPhotoId &&
195 photo_version == kDefaultPicasaPhotoVersion;
198 ProfileDownloader::ProfileDownloader(ProfileDownloaderDelegate* delegate)
199 : delegate_(delegate),
200 picture_status_(PICTURE_FAILED) {
201 DCHECK(delegate_);
204 void ProfileDownloader::Start() {
205 VLOG(1) << "Starting profile downloader...";
206 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
208 TokenService* service =
209 TokenServiceFactory::GetForProfile(delegate_->GetBrowserProfile());
210 if (!service) {
211 // This can happen in some test paths.
212 LOG(WARNING) << "User has no token service";
213 delegate_->OnProfileDownloadFailure(
214 this, ProfileDownloaderDelegate::TOKEN_ERROR);
215 return;
218 if (service->HasOAuthLoginToken()) {
219 StartFetchingOAuth2AccessToken();
220 } else {
221 registrar_.Add(this,
222 chrome::NOTIFICATION_TOKEN_AVAILABLE,
223 content::Source<TokenService>(service));
224 registrar_.Add(this,
225 chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
226 content::Source<TokenService>(service));
230 string16 ProfileDownloader::GetProfileFullName() const {
231 return profile_full_name_;
234 SkBitmap ProfileDownloader::GetProfilePicture() const {
235 return profile_picture_;
238 ProfileDownloader::PictureStatus ProfileDownloader::GetProfilePictureStatus()
239 const {
240 return picture_status_;
243 std::string ProfileDownloader::GetProfilePictureURL() const {
244 return picture_url_;
247 void ProfileDownloader::StartFetchingImage() {
248 VLOG(1) << "Fetching user entry with token: " << auth_token_;
249 user_entry_fetcher_.reset(net::URLFetcher::Create(
250 GURL(kUserEntryURL), net::URLFetcher::GET, this));
251 user_entry_fetcher_->SetRequestContext(
252 delegate_->GetBrowserProfile()->GetRequestContext());
253 user_entry_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
254 net::LOAD_DO_NOT_SAVE_COOKIES);
255 if (!auth_token_.empty()) {
256 user_entry_fetcher_->SetExtraRequestHeaders(
257 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str()));
259 user_entry_fetcher_->Start();
262 void ProfileDownloader::StartFetchingOAuth2AccessToken() {
263 TokenService* service =
264 TokenServiceFactory::GetForProfile(delegate_->GetBrowserProfile());
265 DCHECK(!service->GetOAuth2LoginRefreshToken().empty());
267 std::vector<std::string> scopes;
268 scopes.push_back(kAPIScope);
269 oauth2_access_token_fetcher_.reset(new OAuth2AccessTokenFetcher(
270 this, delegate_->GetBrowserProfile()->GetRequestContext()));
271 oauth2_access_token_fetcher_->Start(
272 GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
273 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
274 service->GetOAuth2LoginRefreshToken(),
275 scopes);
278 ProfileDownloader::~ProfileDownloader() {}
280 void ProfileDownloader::OnURLFetchComplete(const net::URLFetcher* source) {
281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
282 std::string data;
283 source->GetResponseAsString(&data);
284 bool network_error =
285 source->GetStatus().status() != net::URLRequestStatus::SUCCESS;
286 if (network_error || source->GetResponseCode() != 200) {
287 LOG(WARNING) << "Fetching profile data failed";
288 DVLOG(1) << " Status: " << source->GetStatus().status();
289 DVLOG(1) << " Error: " << source->GetStatus().error();
290 DVLOG(1) << " Response code: " << source->GetResponseCode();
291 DVLOG(1) << " Url: " << source->GetURL().spec();
292 delegate_->OnProfileDownloadFailure(this, network_error ?
293 ProfileDownloaderDelegate::NETWORK_ERROR :
294 ProfileDownloaderDelegate::SERVICE_ERROR);
295 return;
298 if (source == user_entry_fetcher_.get()) {
299 std::string image_url;
300 if (!GetProfileNameAndImageURL(data, &profile_full_name_, &image_url,
301 delegate_->GetDesiredImageSideLength())) {
302 delegate_->OnProfileDownloadFailure(
303 this, ProfileDownloaderDelegate::SERVICE_ERROR);
304 return;
306 if (!delegate_->NeedsProfilePicture()) {
307 VLOG(1) << "Skipping profile picture download";
308 delegate_->OnProfileDownloadSuccess(this);
309 return;
311 if (IsDefaultProfileImageURL(image_url)) {
312 VLOG(1) << "User has default profile picture";
313 picture_status_ = PICTURE_DEFAULT;
314 delegate_->OnProfileDownloadSuccess(this);
315 return;
317 if (!image_url.empty() && image_url == delegate_->GetCachedPictureURL()) {
318 VLOG(1) << "Picture URL matches cached picture URL";
319 picture_status_ = PICTURE_CACHED;
320 delegate_->OnProfileDownloadSuccess(this);
321 return;
323 VLOG(1) << "Fetching profile image from " << image_url;
324 picture_url_ = image_url;
325 profile_image_fetcher_.reset(net::URLFetcher::Create(
326 GURL(image_url), net::URLFetcher::GET, this));
327 profile_image_fetcher_->SetRequestContext(
328 delegate_->GetBrowserProfile()->GetRequestContext());
329 profile_image_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
330 net::LOAD_DO_NOT_SAVE_COOKIES);
331 if (!auth_token_.empty()) {
332 profile_image_fetcher_->SetExtraRequestHeaders(
333 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str()));
335 profile_image_fetcher_->Start();
336 } else if (source == profile_image_fetcher_.get()) {
337 VLOG(1) << "Decoding the image...";
338 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder(
339 this, data, ImageDecoder::DEFAULT_CODEC);
340 scoped_refptr<base::MessageLoopProxy> task_runner =
341 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
342 image_decoder->Start(task_runner);
346 void ProfileDownloader::OnImageDecoded(const ImageDecoder* decoder,
347 const SkBitmap& decoded_image) {
348 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
349 int image_size = delegate_->GetDesiredImageSideLength();
350 profile_picture_ = skia::ImageOperations::Resize(
351 decoded_image,
352 skia::ImageOperations::RESIZE_BEST,
353 image_size,
354 image_size);
355 picture_status_ = PICTURE_SUCCESS;
356 delegate_->OnProfileDownloadSuccess(this);
359 void ProfileDownloader::OnDecodeImageFailed(const ImageDecoder* decoder) {
360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
361 delegate_->OnProfileDownloadFailure(
362 this, ProfileDownloaderDelegate::IMAGE_DECODE_FAILED);
365 void ProfileDownloader::Observe(
366 int type,
367 const content::NotificationSource& source,
368 const content::NotificationDetails& details) {
369 DCHECK(type == chrome::NOTIFICATION_TOKEN_AVAILABLE ||
370 type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED);
372 TokenService::TokenAvailableDetails* token_details =
373 content::Details<TokenService::TokenAvailableDetails>(details).ptr();
375 if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) {
376 if (token_details->service() ==
377 GaiaConstants::kGaiaOAuth2LoginRefreshToken) {
378 registrar_.RemoveAll();
379 StartFetchingOAuth2AccessToken();
381 } else {
382 if (token_details->service() ==
383 GaiaConstants::kGaiaOAuth2LoginRefreshToken) {
384 LOG(WARNING) << "ProfileDownloader: token request failed";
385 delegate_->OnProfileDownloadFailure(
386 this, ProfileDownloaderDelegate::TOKEN_ERROR);
391 // Callback for OAuth2AccessTokenFetcher on success. |access_token| is the token
392 // used to start fetching user data.
393 void ProfileDownloader::OnGetTokenSuccess(const std::string& access_token,
394 const base::Time& expiration_time) {
395 auth_token_ = access_token;
396 StartFetchingImage();
399 // Callback for OAuth2AccessTokenFetcher on failure.
400 void ProfileDownloader::OnGetTokenFailure(const GoogleServiceAuthError& error) {
401 LOG(WARNING) << "ProfileDownloader: token request using refresh token failed";
402 delegate_->OnProfileDownloadFailure(
403 this, ProfileDownloaderDelegate::TOKEN_ERROR);