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
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/login/user_image_downloader.h"
6
7#include "base/json/json_reader.h"
8#include "base/logging.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/string_util.h"
11#include "base/stringprintf.h"
12#include "base/values.h"
13#include "chrome/browser/chromeos/login/authenticator.h"
14#include "chrome/browser/chromeos/login/image_downloader.h"
15#include "chrome/browser/chromeos/login/user_manager.h"
16#include "chrome/browser/profiles/profile_manager.h"
17#include "chrome/common/net/url_fetcher.h"
18#include "content/browser/browser_thread.h"
19#include "googleurl/src/gurl.h"
20
21namespace chromeos {
22
23namespace {
24
25// Contacts API URL that returns all user info.
26// TODO(avayvod): Find the way to receive less data for the user.
27const char kUserInfoURL[] =
28    "http://www.google.com/m8/feeds/contacts/default/thin?alt=json";
29
30// Template for authorization header needed for all request to Contacts API.
31const char kAuthorizationHeader[] = "Authorization: GoogleLogin auth=%s";
32
33// Schema that identifies JSON node with image url.
34const char kPhotoSchemaURL[] =
35    "http://schemas.google.com/contacts/2008/rel#photo";
36
37}  // namespace
38
39UserImageDownloader::UserImageDownloader(const std::string& username,
40                                         const std::string& auth_token)
41    : username_(username),
42      auth_token_(auth_token) {
43  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
44  if (auth_token_.empty())
45    return;
46
47  profile_fetcher_.reset(new URLFetcher(GURL(kUserInfoURL),
48                                        URLFetcher::GET,
49                                        this));
50  profile_fetcher_->set_request_context(
51      ProfileManager::GetDefaultProfile()->GetRequestContext());
52  profile_fetcher_->set_extra_request_headers(
53      base::StringPrintf(kAuthorizationHeader, auth_token_.c_str()));
54  profile_fetcher_->Start();
55}
56
57UserImageDownloader::~UserImageDownloader() {
58}
59
60void UserImageDownloader::OnURLFetchComplete(
61    const URLFetcher* source,
62    const GURL& url,
63    const net::URLRequestStatus& status,
64    int response_code,
65    const ResponseCookies& cookies,
66    const std::string& data) {
67  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
68  if (response_code != 200) {
69    LOG(ERROR) << "Response code is " << response_code;
70    LOG(ERROR) << "Url is " << url.spec();
71    LOG(ERROR) << "Data is " << data;
72    return;
73  }
74
75  if (source == profile_fetcher_.get()) {
76    GURL image_url;
77    if (!GetImageURL(data, &image_url)) {
78      LOG(ERROR) << "Didn't find image url in " << data;
79      return;
80    }
81    VLOG(1) << "Sending request to " << image_url;
82    new ImageDownloader(this, GURL(image_url), auth_token_);
83  }
84}
85
86void UserImageDownloader::OnImageDecoded(const SkBitmap& decoded_image) {
87  // Save the image to file and its path to preferences.
88  chromeos::UserManager* user_manager = chromeos::UserManager::Get();
89  if (user_manager) {
90    if (user_manager->logged_in_user().email() == username_) {
91      user_manager->SetLoggedInUserImage(decoded_image);
92    }
93    user_manager->SaveUserImage(username_, decoded_image);
94  }
95}
96
97bool UserImageDownloader::GetImageURL(const std::string& json_data,
98                                      GURL* image_url) const {
99  if (!image_url) {
100    NOTREACHED();
101    return false;
102  }
103
104  // Data is in JSON format with image url located at the following path:
105  // root > feed > entry > dictionary > link > dictionary > href.
106  scoped_ptr<Value> root(base::JSONReader::Read(json_data, true));
107  if (!root.get() || root->GetType() != Value::TYPE_DICTIONARY)
108    return false;
109
110  DictionaryValue* root_dictionary =
111      static_cast<DictionaryValue*>(root.get());
112  DictionaryValue* feed_dictionary = NULL;
113  if (!root_dictionary->GetDictionary("feed", &feed_dictionary))
114    return false;
115
116  ListValue* entry_list = NULL;
117  if (!feed_dictionary->GetList("entry", &entry_list))
118    return false;
119
120  return GetImageURLFromEntries(entry_list, image_url);
121}
122
123bool UserImageDownloader::GetImageURLFromEntries(ListValue* entry_list,
124                                                 GURL* image_url) const {
125  // The list contains info about all user's contacts including user
126  // himself. We need to find entry for the user and then get his image.
127  for (size_t i = 0; i < entry_list->GetSize(); ++i) {
128    DictionaryValue* entry_dictionary = NULL;
129    if (!entry_list->GetDictionary(i, &entry_dictionary))
130      continue;
131
132    ListValue* email_list = NULL;
133    if (!entry_dictionary->GetList("gd$email", &email_list))
134      continue;
135
136    // Match entry email address to understand that this is user's entry.
137    if (!IsUserEntry(email_list))
138      continue;
139
140    ListValue* link_list = NULL;
141    if (!entry_dictionary->GetList("link", &link_list))
142      continue;
143
144    if (GetImageURLFromLinks(link_list, image_url))
145      return true;
146  }
147
148  return false;
149}
150
151bool UserImageDownloader::IsUserEntry(ListValue* email_list) const {
152  for (size_t i = 0; i < email_list->GetSize(); ++i) {
153    DictionaryValue* email_dictionary = NULL;
154    if (!email_list->GetDictionary(i, &email_dictionary))
155      continue;
156
157    std::string email;
158    if (!email_dictionary->GetStringASCII("address", &email))
159      continue;
160
161    if (Authenticator::Canonicalize(email) == username_)
162      return true;
163  }
164  return false;
165}
166
167bool UserImageDownloader::GetImageURLFromLinks(ListValue* link_list,
168                                               GURL* image_url) const {
169  // In entry's list of links there should be one with rel pointing to photo
170  // schema.
171  for (size_t i = 0; i < link_list->GetSize(); ++i) {
172    DictionaryValue* link_dictionary = NULL;
173    if (!link_list->GetDictionary(i, &link_dictionary))
174      continue;
175
176    std::string rel;
177    if (!link_dictionary->GetStringASCII("rel", &rel))
178      continue;
179
180    if (rel != kPhotoSchemaURL)
181      continue;
182
183    std::string url;
184    if (!link_dictionary->GetStringASCII("href", &url))
185      continue;
186
187    *image_url = GURL(url);
188    return true;
189  }
190  return false;
191}
192
193}  // namespace chromeos
194