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"
10 #include "base/json/json_reader.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/values.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/profiles/profile_downloader_delegate.h"
20 #include "chrome/browser/profiles/profile_manager.h"
21 #include "chrome/browser/signin/account_fetcher_service_factory.h"
22 #include "chrome/browser/signin/account_tracker_service_factory.h"
23 #include "chrome/browser/signin/chrome_signin_client_factory.h"
24 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
25 #include "chrome/browser/signin/signin_manager_factory.h"
26 #include "components/data_use_measurement/core/data_use_user_data.h"
27 #include "components/signin/core/browser/account_fetcher_service.h"
28 #include "components/signin/core/browser/profile_oauth2_token_service.h"
29 #include "components/signin/core/browser/signin_client.h"
30 #include "components/signin/core/browser/signin_manager.h"
31 #include "components/signin/core/common/profile_management_switches.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "google_apis/gaia/gaia_constants.h"
34 #include "net/base/load_flags.h"
35 #include "net/url_request/url_fetcher.h"
36 #include "net/url_request/url_request_status.h"
37 #include "skia/ext/image_operations.h"
40 using content::BrowserThread
;
44 // Template for optional authorization header when using an OAuth access token.
45 const char kAuthorizationHeader
[] =
46 "Authorization: Bearer %s";
48 // Path format for specifying thumbnail's size.
49 const char kThumbnailSizeFormat
[] = "s%d-c";
50 // Default thumbnail size.
51 const int kDefaultThumbnailSize
= 64;
53 // Separator of URL path components.
54 const char kURLPathSeparator
= '/';
56 // Photo ID of the Picasa Web Albums profile picture (base64 of 0).
57 const char kPicasaPhotoId
[] = "AAAAAAAAAAA";
59 // Photo version of the default PWA profile picture (base64 of 1).
60 const char kDefaultPicasaPhotoVersion
[] = "AAAAAAAAAAE";
62 // The minimum number of path components in profile picture URL.
63 const size_t kProfileImageURLPathComponentsCount
= 6;
65 // Index of path component with photo ID.
66 const int kPhotoIdPathComponentIndex
= 2;
68 // Index of path component with photo version.
69 const int kPhotoVersionPathComponentIndex
= 3;
71 // Given an image URL this function builds a new URL set to |size|.
72 // For example, if |size| was set to 256 and |old_url| was either:
73 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/photo.jpg
75 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg
76 // then return value in |new_url| would be:
77 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s256-c/photo.jpg
78 bool GetImageURLWithSize(const GURL
& old_url
, int size
, GURL
* new_url
) {
80 std::vector
<std::string
> components
= base::SplitString(
81 old_url
.path(), std::string(1, kURLPathSeparator
),
82 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
83 if (components
.size() == 0)
86 const std::string
& old_spec
= old_url
.spec();
87 std::string
default_size_component(
88 base::StringPrintf(kThumbnailSizeFormat
, kDefaultThumbnailSize
));
89 std::string
new_size_component(
90 base::StringPrintf(kThumbnailSizeFormat
, size
));
92 size_t pos
= old_spec
.find(default_size_component
);
93 size_t end
= std::string::npos
;
94 if (pos
!= std::string::npos
) {
95 // The default size is already specified in the URL so it needs to be
96 // replaced with the new size.
97 end
= pos
+ default_size_component
.size();
99 // The default size is not in the URL so try to insert it before the last
101 const std::string
& file_name
= old_url
.ExtractFileName();
102 if (!file_name
.empty()) {
103 pos
= old_spec
.find(file_name
);
108 if (pos
!= std::string::npos
) {
109 std::string new_spec
= old_spec
.substr(0, pos
) + new_size_component
+
110 old_spec
.substr(end
);
111 *new_url
= GURL(new_spec
);
112 return new_url
->is_valid();
115 // We can't set the image size, just use the default size.
123 bool ProfileDownloader::IsDefaultProfileImageURL(const std::string
& url
) {
127 GURL
image_url_object(url
);
128 DCHECK(image_url_object
.is_valid());
129 VLOG(1) << "URL to check for default image: " << image_url_object
.spec();
130 std::vector
<std::string
> path_components
= base::SplitString(
131 image_url_object
.path(), std::string(1, kURLPathSeparator
),
132 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
134 if (path_components
.size() < kProfileImageURLPathComponentsCount
)
137 const std::string
& photo_id
= path_components
[kPhotoIdPathComponentIndex
];
138 const std::string
& photo_version
=
139 path_components
[kPhotoVersionPathComponentIndex
];
141 // Check that the ID and version match the default Picasa profile photo.
142 return photo_id
== kPicasaPhotoId
&&
143 photo_version
== kDefaultPicasaPhotoVersion
;
146 ProfileDownloader::ProfileDownloader(ProfileDownloaderDelegate
* delegate
)
147 : OAuth2TokenService::Consumer("profile_downloader"),
149 picture_status_(PICTURE_FAILED
),
150 account_tracker_service_(
151 AccountTrackerServiceFactory::GetForProfile(
152 delegate_
->GetBrowserProfile())),
153 waiting_for_account_info_(false) {
155 account_tracker_service_
->AddObserver(this);
158 void ProfileDownloader::Start() {
159 StartForAccount(std::string());
162 void ProfileDownloader::StartForAccount(const std::string
& account_id
) {
163 VLOG(1) << "Starting profile downloader...";
164 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
166 ProfileOAuth2TokenService
* service
=
167 ProfileOAuth2TokenServiceFactory::GetForProfile(
168 delegate_
->GetBrowserProfile());
170 // This can happen in some test paths.
171 LOG(WARNING
) << "User has no token service";
172 delegate_
->OnProfileDownloadFailure(
173 this, ProfileDownloaderDelegate::TOKEN_ERROR
);
177 SigninManagerBase
* signin_manager
=
178 SigninManagerFactory::GetForProfile(delegate_
->GetBrowserProfile());
181 signin_manager
->GetAuthenticatedAccountId() : account_id
;
182 if (service
->RefreshTokenIsAvailable(account_id_
))
183 StartFetchingOAuth2AccessToken();
185 service
->AddObserver(this);
188 base::string16
ProfileDownloader::GetProfileHostedDomain() const {
189 return base::UTF8ToUTF16(account_info_
.hosted_domain
);
192 base::string16
ProfileDownloader::GetProfileFullName() const {
193 return base::UTF8ToUTF16(account_info_
.full_name
);
196 base::string16
ProfileDownloader::GetProfileGivenName() const {
197 return base::UTF8ToUTF16(account_info_
.given_name
);
200 std::string
ProfileDownloader::GetProfileLocale() const {
201 return account_info_
.locale
;
204 SkBitmap
ProfileDownloader::GetProfilePicture() const {
205 return profile_picture_
;
208 ProfileDownloader::PictureStatus
ProfileDownloader::GetProfilePictureStatus()
210 return picture_status_
;
213 std::string
ProfileDownloader::GetProfilePictureURL() const {
215 if (GetImageURLWithSize(GURL(account_info_
.picture_url
),
216 delegate_
->GetDesiredImageSideLength(),
220 return account_info_
.picture_url
;
223 void ProfileDownloader::StartFetchingImage() {
224 VLOG(1) << "Fetching user entry with token: " << auth_token_
;
225 account_info_
= account_tracker_service_
->GetAccountInfo(account_id_
);
227 if (delegate_
->IsPreSignin()) {
228 AccountFetcherServiceFactory::GetForProfile(delegate_
->GetBrowserProfile())
229 ->FetchUserInfoBeforeSignin(account_id_
);
232 if (account_info_
.IsValid()) {
233 // FetchImageData might call the delegate's OnProfileDownloadSuccess
234 // synchronously, causing |this| to be deleted so there should not be more
238 waiting_for_account_info_
= true;
242 void ProfileDownloader::StartFetchingOAuth2AccessToken() {
243 Profile
* profile
= delegate_
->GetBrowserProfile();
244 OAuth2TokenService::ScopeSet scopes
;
245 scopes
.insert(GaiaConstants::kGoogleUserInfoProfile
);
246 // Increase scope to get hd attribute to determine if lock should be enabled.
247 if (switches::IsNewProfileManagement())
248 scopes
.insert(GaiaConstants::kGoogleUserInfoEmail
);
249 ProfileOAuth2TokenService
* token_service
=
250 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
);
251 oauth2_access_token_request_
= token_service
->StartRequest(
252 account_id_
, scopes
, this);
255 ProfileDownloader::~ProfileDownloader() {
256 // Ensures PO2TS observation is cleared when ProfileDownloader is destructed
257 // before refresh token is available.
258 ProfileOAuth2TokenService
* service
=
259 ProfileOAuth2TokenServiceFactory::GetForProfile(
260 delegate_
->GetBrowserProfile());
262 service
->RemoveObserver(this);
264 account_tracker_service_
->RemoveObserver(this);
267 void ProfileDownloader::FetchImageData() {
268 DCHECK(account_info_
.IsValid());
269 std::string image_url_with_size
= GetProfilePictureURL();
271 if (!delegate_
->NeedsProfilePicture()) {
272 VLOG(1) << "Skipping profile picture download";
273 delegate_
->OnProfileDownloadSuccess(this);
276 if (IsDefaultProfileImageURL(image_url_with_size
)) {
277 VLOG(1) << "User has default profile picture";
278 picture_status_
= PICTURE_DEFAULT
;
279 delegate_
->OnProfileDownloadSuccess(this);
282 if (!image_url_with_size
.empty() &&
283 image_url_with_size
== delegate_
->GetCachedPictureURL()) {
284 VLOG(1) << "Picture URL matches cached picture URL";
285 picture_status_
= PICTURE_CACHED
;
286 delegate_
->OnProfileDownloadSuccess(this);
290 VLOG(1) << "Fetching profile image from " << image_url_with_size
;
291 profile_image_fetcher_
= net::URLFetcher::Create(
292 GURL(image_url_with_size
), net::URLFetcher::GET
, this);
293 data_use_measurement::DataUseUserData::AttachToFetcher(
294 profile_image_fetcher_
.get(),
295 data_use_measurement::DataUseUserData::PROFILE_DOWNLOADER
);
296 profile_image_fetcher_
->SetRequestContext(
297 delegate_
->GetBrowserProfile()->GetRequestContext());
298 profile_image_fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
299 net::LOAD_DO_NOT_SAVE_COOKIES
);
301 if (!auth_token_
.empty()) {
302 profile_image_fetcher_
->SetExtraRequestHeaders(
303 base::StringPrintf(kAuthorizationHeader
, auth_token_
.c_str()));
306 profile_image_fetcher_
->Start();
309 void ProfileDownloader::OnURLFetchComplete(const net::URLFetcher
* source
) {
310 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
312 source
->GetResponseAsString(&data
);
314 source
->GetStatus().status() != net::URLRequestStatus::SUCCESS
;
315 if (network_error
|| source
->GetResponseCode() != 200) {
316 LOG(WARNING
) << "Fetching profile data failed";
317 DVLOG(1) << " Status: " << source
->GetStatus().status();
318 DVLOG(1) << " Error: " << source
->GetStatus().error();
319 DVLOG(1) << " Response code: " << source
->GetResponseCode();
320 DVLOG(1) << " Url: " << source
->GetURL().spec();
321 profile_image_fetcher_
.reset();
322 delegate_
->OnProfileDownloadFailure(this, network_error
?
323 ProfileDownloaderDelegate::NETWORK_ERROR
:
324 ProfileDownloaderDelegate::SERVICE_ERROR
);
326 profile_image_fetcher_
.reset();
327 VLOG(1) << "Decoding the image...";
328 ImageDecoder::Start(this, data
);
332 void ProfileDownloader::OnImageDecoded(const SkBitmap
& decoded_image
) {
333 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
334 int image_size
= delegate_
->GetDesiredImageSideLength();
335 profile_picture_
= skia::ImageOperations::Resize(
337 skia::ImageOperations::RESIZE_BEST
,
340 picture_status_
= PICTURE_SUCCESS
;
341 delegate_
->OnProfileDownloadSuccess(this);
344 void ProfileDownloader::OnDecodeImageFailed() {
345 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
346 delegate_
->OnProfileDownloadFailure(
347 this, ProfileDownloaderDelegate::IMAGE_DECODE_FAILED
);
350 void ProfileDownloader::OnRefreshTokenAvailable(const std::string
& account_id
) {
351 ProfileOAuth2TokenService
* service
=
352 ProfileOAuth2TokenServiceFactory::GetForProfile(
353 delegate_
->GetBrowserProfile());
354 if (account_id
!= account_id_
)
357 service
->RemoveObserver(this);
358 StartFetchingOAuth2AccessToken();
361 // Callback for OAuth2TokenService::Request on success. |access_token| is the
362 // token used to start fetching user data.
363 void ProfileDownloader::OnGetTokenSuccess(
364 const OAuth2TokenService::Request
* request
,
365 const std::string
& access_token
,
366 const base::Time
& expiration_time
) {
367 DCHECK_EQ(request
, oauth2_access_token_request_
.get());
368 oauth2_access_token_request_
.reset();
369 auth_token_
= access_token
;
370 StartFetchingImage();
373 // Callback for OAuth2TokenService::Request on failure.
374 void ProfileDownloader::OnGetTokenFailure(
375 const OAuth2TokenService::Request
* request
,
376 const GoogleServiceAuthError
& error
) {
377 DCHECK_EQ(request
, oauth2_access_token_request_
.get());
378 oauth2_access_token_request_
.reset();
379 LOG(WARNING
) << "ProfileDownloader: token request using refresh token failed:"
381 delegate_
->OnProfileDownloadFailure(
382 this, ProfileDownloaderDelegate::TOKEN_ERROR
);
385 void ProfileDownloader::OnAccountUpdated(const AccountInfo
& info
) {
386 if (info
.account_id
== account_id_
&& info
.IsValid()) {
387 account_info_
= info
;
389 // If the StartFetchingImage was called before we had valid info, the
390 // downloader has been waiting so we need to fetch the image data now.
391 if (waiting_for_account_info_
) {
392 waiting_for_account_info_
= false;
393 // FetchImageData might call the delegate's OnProfileDownloadSuccess
394 // synchronously, causing |this| to be deleted so there should not be more