1// Copyright (c) 2012 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/ui/webui/screenshot_source.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/file_util.h"
10#include "base/files/file_path.h"
11#include "base/i18n/time_formatting.h"
12#include "base/memory/ref_counted_memory.h"
13#include "base/message_loop/message_loop.h"
14#include "base/path_service.h"
15#include "base/prefs/pref_service.h"
16#include "base/strings/string16.h"
17#include "base/strings/string_util.h"
18#include "base/strings/stringprintf.h"
19#include "chrome/browser/browser_process.h"
20#include "chrome/browser/download/download_prefs.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/profiles/profile_manager.h"
23#include "chrome/common/chrome_paths.h"
24#include "chrome/common/pref_names.h"
25#include "chrome/common/url_constants.h"
26#include "url/url_canon.h"
27#include "url/url_util.h"
28
29#if defined(USE_ASH)
30#include "ash/shell.h"
31#include "ash/shell_delegate.h"
32#endif
33
34#if defined(OS_CHROMEOS)
35#include "chrome/browser/chromeos/drive/drive_integration_service.h"
36#include "chrome/browser/chromeos/drive/file_system_interface.h"
37#include "chrome/browser/chromeos/drive/file_system_util.h"
38#include "chromeos/login/login_state.h"
39#include "content/public/browser/browser_thread.h"
40#endif
41
42// static
43const char ScreenshotSource::kScreenshotUrlRoot[] = "chrome://screenshots/";
44// static
45const char ScreenshotSource::kScreenshotCurrent[] = "current";
46// static
47const char ScreenshotSource::kScreenshotSaved[] = "saved/";
48#if defined(OS_CHROMEOS)
49// static
50const char ScreenshotSource::kScreenshotPrefix[] = "Screenshot ";
51// static
52const char ScreenshotSource::kScreenshotSuffix[] = ".png";
53#endif
54
55bool ShouldUse24HourClock() {
56#if defined(OS_CHROMEOS)
57  Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
58  if (profile) {
59    return profile->GetPrefs()->GetBoolean(prefs::kUse24HourClock);
60  }
61#endif
62  return base::GetHourClockType() == base::k24HourClock;
63}
64
65ScreenshotSource::ScreenshotSource(
66    std::vector<unsigned char>* current_screenshot,
67    Profile* profile)
68    : profile_(profile) {
69  // Setup the last screenshot taken.
70  if (current_screenshot)
71    current_screenshot_.reset(new ScreenshotData(*current_screenshot));
72  else
73    current_screenshot_.reset(new ScreenshotData());
74}
75
76ScreenshotSource::~ScreenshotSource() {}
77
78// static
79std::string ScreenshotSource::GetScreenshotBaseFilename() {
80  base::Time::Exploded now;
81  base::Time::Now().LocalExplode(&now);
82
83  // We don't use base/i18n/time_formatting.h here because it doesn't
84  // support our format.  Don't use ICU either to avoid i18n file names
85  // for non-English locales.
86  // TODO(mukai): integrate this logic somewhere time_formatting.h
87  std::string file_name = base::StringPrintf(
88      "Screenshot %d-%02d-%02d at ", now.year, now.month, now.day_of_month);
89
90  if (ShouldUse24HourClock()) {
91    file_name.append(base::StringPrintf(
92        "%02d.%02d.%02d", now.hour, now.minute, now.second));
93  } else {
94    int hour = now.hour;
95    if (hour > 12) {
96      hour -= 12;
97    } else if (hour == 0) {
98      hour = 12;
99    }
100    file_name.append(base::StringPrintf(
101        "%d.%02d.%02d ", hour, now.minute, now.second));
102    file_name.append((now.hour >= 12) ? "PM" : "AM");
103  }
104
105  return file_name;
106}
107
108#if defined(USE_ASH)
109
110// static
111bool ScreenshotSource::AreScreenshotsDisabled() {
112  return g_browser_process->local_state()->GetBoolean(
113      prefs::kDisableScreenshots);
114}
115
116// static
117bool ScreenshotSource::GetScreenshotDirectory(base::FilePath* directory) {
118  if (ScreenshotSource::AreScreenshotsDisabled())
119    return false;
120
121  bool is_logged_in = true;
122
123#if defined(OS_CHROMEOS)
124  is_logged_in = chromeos::LoginState::Get()->IsUserLoggedIn();
125#endif
126
127  if (is_logged_in) {
128    DownloadPrefs* download_prefs = DownloadPrefs::FromBrowserContext(
129        ash::Shell::GetInstance()->delegate()->GetCurrentBrowserContext());
130    *directory = download_prefs->DownloadPath();
131  } else  {
132    if (!file_util::GetTempDir(directory)) {
133      LOG(ERROR) << "Failed to find temporary directory.";
134      return false;
135    }
136  }
137  return true;
138}
139
140#endif
141
142std::string ScreenshotSource::GetSource() const {
143  return chrome::kChromeUIScreenshotPath;
144}
145
146void ScreenshotSource::StartDataRequest(
147  const std::string& path,
148  int render_process_id,
149  int render_view_id,
150  const content::URLDataSource::GotDataCallback& callback) {
151  SendScreenshot(path, callback);
152}
153
154std::string ScreenshotSource::GetMimeType(const std::string&) const {
155  // We need to explicitly return a mime type, otherwise if the user tries to
156  // drag the image they get no extension.
157  return "image/png";
158}
159
160ScreenshotDataPtr ScreenshotSource::GetCachedScreenshot(
161    const std::string& screenshot_path) {
162  std::map<std::string, ScreenshotDataPtr>::iterator pos;
163  std::string path = screenshot_path.substr(
164      0, screenshot_path.find_first_of("?"));
165  if ((pos = cached_screenshots_.find(path)) != cached_screenshots_.end()) {
166    return pos->second;
167  } else {
168    return ScreenshotDataPtr(new ScreenshotData);
169  }
170}
171
172void ScreenshotSource::SendScreenshot(
173    const std::string& screenshot_path,
174    const content::URLDataSource::GotDataCallback& callback) {
175  // Strip the query param value - we only use it as a hack to ensure our
176  // image gets reloaded instead of being pulled from the browser cache
177  std::string path = screenshot_path.substr(
178      0, screenshot_path.find_first_of("?"));
179  if (path == ScreenshotSource::kScreenshotCurrent) {
180    CacheAndSendScreenshot(path, callback, current_screenshot_);
181#if defined(OS_CHROMEOS)
182  } else if (path.compare(0, strlen(ScreenshotSource::kScreenshotSaved),
183             ScreenshotSource::kScreenshotSaved) == 0) {
184    using content::BrowserThread;
185
186    std::string filename =
187        path.substr(strlen(ScreenshotSource::kScreenshotSaved));
188
189    url_canon::RawCanonOutputT<char16> decoded;
190    url_util::DecodeURLEscapeSequences(
191        filename.data(), filename.size(), &decoded);
192    // Screenshot filenames don't use non-ascii characters.
193    std::string decoded_filename = UTF16ToASCII(string16(
194        decoded.data(), decoded.length()));
195
196    base::FilePath download_path;
197    GetScreenshotDirectory(&download_path);
198    if (drive::util::IsUnderDriveMountPoint(download_path)) {
199      drive::FileSystemInterface* file_system =
200          drive::DriveIntegrationServiceFactory::GetForProfile(
201              profile_)->file_system();
202      file_system->GetFileByPath(
203          drive::util::ExtractDrivePath(download_path).Append(decoded_filename),
204          base::Bind(&ScreenshotSource::GetSavedScreenshotCallback,
205                     base::Unretained(this), screenshot_path, callback));
206    } else {
207      BrowserThread::PostTask(
208          BrowserThread::FILE, FROM_HERE,
209          base::Bind(&ScreenshotSource::SendSavedScreenshot,
210                     base::Unretained(this),
211                     screenshot_path,
212                     callback, download_path.Append(decoded_filename)));
213    }
214#endif
215  } else {
216    CacheAndSendScreenshot(
217        path, callback, ScreenshotDataPtr(new ScreenshotData()));
218  }
219}
220
221#if defined(OS_CHROMEOS)
222void ScreenshotSource::SendSavedScreenshot(
223    const std::string& screenshot_path,
224    const content::URLDataSource::GotDataCallback& callback,
225    const base::FilePath& file) {
226  ScreenshotDataPtr read_bytes(new ScreenshotData);
227  int64 file_size = 0;
228
229  if (!file_util::GetFileSize(file, &file_size)) {
230    CacheAndSendScreenshot(screenshot_path, callback, read_bytes);
231    return;
232  }
233
234  read_bytes->resize(file_size);
235  if (!file_util::ReadFile(file, reinterpret_cast<char*>(&read_bytes->front()),
236                           static_cast<int>(file_size)))
237    read_bytes->clear();
238
239  CacheAndSendScreenshot(screenshot_path, callback, read_bytes);
240}
241
242void ScreenshotSource::GetSavedScreenshotCallback(
243    const std::string& screenshot_path,
244    const content::URLDataSource::GotDataCallback& callback,
245    drive::FileError error,
246    const base::FilePath& file,
247    scoped_ptr<drive::ResourceEntry> entry) {
248  if (error != drive::FILE_ERROR_OK) {
249    ScreenshotDataPtr read_bytes(new ScreenshotData);
250    CacheAndSendScreenshot(screenshot_path, callback, read_bytes);
251    return;
252  }
253
254  content::BrowserThread::PostTask(
255      content::BrowserThread::FILE, FROM_HERE,
256      base::Bind(&ScreenshotSource::SendSavedScreenshot,
257                 base::Unretained(this), screenshot_path, callback, file));
258}
259#endif
260
261void ScreenshotSource::CacheAndSendScreenshot(
262    const std::string& screenshot_path,
263    const content::URLDataSource::GotDataCallback& callback,
264    ScreenshotDataPtr bytes) {
265  // Strip the query from the screenshot path.
266  std::string path = screenshot_path.substr(
267      0, screenshot_path.find_first_of("?"));
268  cached_screenshots_[path] = bytes;
269  callback.Run(new base::RefCountedBytes(*bytes));
270}
271