file_system_util.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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/chromeos/drive/file_system_util.h"
6
7#include <string>
8
9#include "base/basictypes.h"
10#include "base/bind.h"
11#include "base/bind_helpers.h"
12#include "base/file_util.h"
13#include "base/files/file_enumerator.h"
14#include "base/files/file_path.h"
15#include "base/files/scoped_platform_file_closer.h"
16#include "base/i18n/icu_string_conversions.h"
17#include "base/json/json_file_value_serializer.h"
18#include "base/logging.h"
19#include "base/md5.h"
20#include "base/memory/scoped_ptr.h"
21#include "base/message_loop/message_loop_proxy.h"
22#include "base/strings/string_number_conversions.h"
23#include "base/strings/string_util.h"
24#include "base/strings/stringprintf.h"
25#include "base/threading/sequenced_worker_pool.h"
26#include "chrome/browser/browser_process.h"
27#include "chrome/browser/chromeos/drive/drive.pb.h"
28#include "chrome/browser/chromeos/drive/drive_integration_service.h"
29#include "chrome/browser/chromeos/drive/file_system_interface.h"
30#include "chrome/browser/chromeos/drive/file_write_helper.h"
31#include "chrome/browser/chromeos/drive/job_list.h"
32#include "chrome/browser/google_apis/gdata_wapi_parser.h"
33#include "chrome/browser/profiles/profile.h"
34#include "chrome/browser/profiles/profile_manager.h"
35#include "chrome/common/chrome_constants.h"
36#include "chrome/common/chrome_paths_internal.h"
37#include "chrome/common/url_constants.h"
38#include "chromeos/chromeos_constants.h"
39#include "content/public/browser/browser_thread.h"
40#include "net/base/escape.h"
41#include "webkit/browser/fileapi/file_system_url.h"
42
43using content::BrowserThread;
44
45namespace drive {
46namespace util {
47
48namespace {
49
50const char kDriveMountPointPath[] = "/special/drive";
51
52const base::FilePath::CharType kDriveMyDriveMountPointPath[] =
53    FILE_PATH_LITERAL("/special/drive/root");
54
55const base::FilePath::CharType kDriveMyDriveRootPath[] =
56    FILE_PATH_LITERAL("drive/root");
57
58const base::FilePath::CharType kFileCacheVersionDir[] =
59    FILE_PATH_LITERAL("v1");
60
61const char kSlash[] = "/";
62const char kEscapedSlash[] = "\xE2\x88\x95";
63
64const base::FilePath& GetDriveMyDriveMountPointPath() {
65  CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_mydrive_mount_path,
66      (kDriveMyDriveMountPointPath));
67  return drive_mydrive_mount_path;
68}
69
70FileSystemInterface* GetFileSystem(Profile* profile) {
71  DriveIntegrationService* integration_service =
72      DriveIntegrationServiceFactory::GetForProfile(profile);
73  return integration_service ? integration_service->file_system() : NULL;
74}
75
76FileWriteHelper* GetFileWriteHelper(Profile* profile) {
77  DriveIntegrationService* integration_service =
78      DriveIntegrationServiceFactory::GetForProfile(profile);
79  return integration_service ? integration_service->file_write_helper() : NULL;
80}
81
82std::string ReadStringFromGDocFile(const base::FilePath& file_path,
83                                   const std::string& key) {
84  const int64 kMaxGDocSize = 4096;
85  int64 file_size = 0;
86  if (!file_util::GetFileSize(file_path, &file_size) ||
87      file_size > kMaxGDocSize) {
88    DLOG(INFO) << "File too large to be a GDoc file " << file_path.value();
89    return std::string();
90  }
91
92  JSONFileValueSerializer reader(file_path);
93  std::string error_message;
94  scoped_ptr<base::Value> root_value(reader.Deserialize(NULL, &error_message));
95  if (!root_value) {
96    DLOG(INFO) << "Failed to parse " << file_path.value() << " as JSON."
97               << " error = " << error_message;
98    return std::string();
99  }
100
101  base::DictionaryValue* dictionary_value = NULL;
102  std::string result;
103  if (!root_value->GetAsDictionary(&dictionary_value) ||
104      !dictionary_value->GetString(key, &result)) {
105    DLOG(INFO) << "No value for the given key is stored in "
106               << file_path.value() << ". key = " << key;
107    return std::string();
108  }
109
110  return result;
111}
112
113// Moves all files under |directory_from| to |directory_to|.
114void MoveAllFilesFromDirectory(const base::FilePath& directory_from,
115                               const base::FilePath& directory_to) {
116  base::FileEnumerator enumerator(directory_from, false,  // not recursive
117                                  base::FileEnumerator::FILES);
118  for (base::FilePath file_from = enumerator.Next(); !file_from.empty();
119       file_from = enumerator.Next()) {
120    const base::FilePath file_to = directory_to.Append(file_from.BaseName());
121    if (!base::PathExists(file_to))  // Do not overwrite existing files.
122      base::Move(file_from, file_to);
123  }
124}
125
126}  // namespace
127
128const base::FilePath& GetDriveGrandRootPath() {
129  CR_DEFINE_STATIC_LOCAL(base::FilePath, grand_root_path,
130      (util::kDriveGrandRootDirName));
131  return grand_root_path;
132}
133
134const base::FilePath& GetDriveMyDriveRootPath() {
135  CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_root_path,
136      (util::kDriveMyDriveRootPath));
137  return drive_root_path;
138}
139
140const base::FilePath& GetDriveMountPointPath() {
141  CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_mount_path,
142      (base::FilePath::FromUTF8Unsafe(kDriveMountPointPath)));
143  return drive_mount_path;
144}
145
146FileSystemInterface* GetFileSystemByProfileId(void* profile_id) {
147  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
148
149  // |profile_id| needs to be checked with ProfileManager::IsValidProfile
150  // before using it.
151  Profile* profile = reinterpret_cast<Profile*>(profile_id);
152  if (!g_browser_process->profile_manager()->IsValidProfile(profile))
153    return NULL;
154  return GetFileSystem(profile);
155}
156
157bool IsSpecialResourceId(const std::string& resource_id) {
158  return resource_id == kDriveGrandRootSpecialResourceId ||
159      resource_id == kDriveOtherDirSpecialResourceId;
160}
161
162ResourceEntry CreateMyDriveRootEntry(const std::string& root_resource_id) {
163  ResourceEntry mydrive_root;
164  mydrive_root.mutable_file_info()->set_is_directory(true);
165  mydrive_root.set_resource_id(root_resource_id);
166  mydrive_root.set_parent_resource_id(util::kDriveGrandRootSpecialResourceId);
167  mydrive_root.set_title(util::kDriveMyDriveRootDirName);
168  return mydrive_root;
169}
170
171ResourceEntry CreateOtherDirEntry() {
172  ResourceEntry other_dir;
173  other_dir.mutable_file_info()->set_is_directory(true);
174  other_dir.set_resource_id(util::kDriveOtherDirSpecialResourceId);
175  other_dir.set_parent_resource_id(util::kDriveGrandRootSpecialResourceId);
176  other_dir.set_title(util::kDriveOtherDirName);
177  return other_dir;
178}
179
180const std::string& GetDriveMountPointPathAsString() {
181  CR_DEFINE_STATIC_LOCAL(std::string, drive_mount_path_string,
182      (kDriveMountPointPath));
183  return drive_mount_path_string;
184}
185
186GURL FilePathToDriveURL(const base::FilePath& path) {
187  std::string url(base::StringPrintf("%s:%s",
188                                     chrome::kDriveScheme,
189                                     path.AsUTF8Unsafe().c_str()));
190  return GURL(url);
191}
192
193base::FilePath DriveURLToFilePath(const GURL& url) {
194  if (!url.is_valid() || url.scheme() != chrome::kDriveScheme)
195    return base::FilePath();
196  std::string path_string = net::UnescapeURLComponent(
197      url.path(), net::UnescapeRule::NORMAL);
198  return base::FilePath::FromUTF8Unsafe(path_string);
199}
200
201void MaybeSetDriveURL(Profile* profile, const base::FilePath& path, GURL* url) {
202  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203
204  if (!IsUnderDriveMountPoint(path))
205    return;
206
207  FileSystemInterface* file_system = GetFileSystem(profile);
208  if (!file_system)
209    return;
210
211  *url = FilePathToDriveURL(util::ExtractDrivePath(path));
212}
213
214bool IsUnderDriveMountPoint(const base::FilePath& path) {
215  return GetDriveMountPointPath() == path ||
216         GetDriveMountPointPath().IsParent(path);
217}
218
219bool NeedsNamespaceMigration(const base::FilePath& path) {
220  // Before migration, "My Drive" which was represented as "drive.
221  // The user might use some path pointing a directory in "My Drive".
222  // e.g. "drive/downloads_dir"
223  // We changed the path for the "My Drive" to "drive/root", hence the user pref
224  // pointing to the old path needs update to the new path.
225  // e.g. "drive/root/downloads_dir"
226  // If |path| already points to some directory in "drive/root", there's no need
227  // to update it.
228  return IsUnderDriveMountPoint(path) &&
229         !(GetDriveMyDriveMountPointPath() == path ||
230           GetDriveMyDriveMountPointPath().IsParent(path));
231}
232
233base::FilePath ConvertToMyDriveNamespace(const base::FilePath& path) {
234  DCHECK(NeedsNamespaceMigration(path));
235
236  // Need to migrate "/special/drive(.*)" to "/special/drive/root(.*)".
237  // Append the relative path from "/special/drive".
238  base::FilePath new_path(GetDriveMyDriveMountPointPath());
239  GetDriveMountPointPath().AppendRelativePath(path, &new_path);
240  DVLOG(1) << "Migrate download.default_directory setting from "
241      << path.AsUTF8Unsafe() << " to " << new_path.AsUTF8Unsafe();
242  DCHECK(!NeedsNamespaceMigration(new_path));
243  return new_path;
244}
245
246base::FilePath ExtractDrivePath(const base::FilePath& path) {
247  if (!IsUnderDriveMountPoint(path))
248    return base::FilePath();
249
250  base::FilePath drive_path = GetDriveGrandRootPath();
251  GetDriveMountPointPath().AppendRelativePath(path, &drive_path);
252  return drive_path;
253}
254
255base::FilePath ExtractDrivePathFromFileSystemUrl(
256    const fileapi::FileSystemURL& url) {
257  if (!url.is_valid() || url.type() != fileapi::kFileSystemTypeDrive)
258    return base::FilePath();
259  return ExtractDrivePath(url.path());
260}
261
262base::FilePath GetCacheRootPath(Profile* profile) {
263  base::FilePath cache_base_path;
264  chrome::GetUserCacheDirectory(profile->GetPath(), &cache_base_path);
265  base::FilePath cache_root_path =
266      cache_base_path.Append(chromeos::kDriveCacheDirname);
267  return cache_root_path.Append(kFileCacheVersionDir);
268}
269
270std::string EscapeCacheFileName(const std::string& filename) {
271  // This is based on net/base/escape.cc: net::(anonymous namespace)::Escape
272  std::string escaped;
273  for (size_t i = 0; i < filename.size(); ++i) {
274    char c = filename[i];
275    if (c == '%' || c == '.' || c == '/') {
276      base::StringAppendF(&escaped, "%%%02X", c);
277    } else {
278      escaped.push_back(c);
279    }
280  }
281  return escaped;
282}
283
284std::string UnescapeCacheFileName(const std::string& filename) {
285  std::string unescaped;
286  for (size_t i = 0; i < filename.size(); ++i) {
287    char c = filename[i];
288    if (c == '%' && i + 2 < filename.length()) {
289      c = (HexDigitToInt(filename[i + 1]) << 4) +
290           HexDigitToInt(filename[i + 2]);
291      i += 2;
292    }
293    unescaped.push_back(c);
294  }
295  return unescaped;
296}
297
298std::string NormalizeFileName(const std::string& input) {
299  DCHECK(IsStringUTF8(input));
300
301  std::string output;
302  if (!base::ConvertToUtf8AndNormalize(input, base::kCodepageUTF8, &output))
303    output = input;
304  ReplaceChars(output, kSlash, std::string(kEscapedSlash), &output);
305  return output;
306}
307
308void MigrateCacheFilesFromOldDirectories(
309    const base::FilePath& cache_root_directory) {
310  const base::FilePath persistent_directory =
311      cache_root_directory.AppendASCII("persistent");
312  const base::FilePath tmp_directory =
313      cache_root_directory.AppendASCII("tmp");
314  if (!base::PathExists(persistent_directory))
315    return;
316
317  const base::FilePath cache_file_directory =
318      cache_root_directory.Append(kCacheFileDirectory);
319
320  // Move all files inside "persistent" to "files".
321  MoveAllFilesFromDirectory(persistent_directory, cache_file_directory);
322  base::DeleteFile(persistent_directory,  true /* recursive */);
323
324  // Move all files inside "tmp" to "files".
325  MoveAllFilesFromDirectory(tmp_directory, cache_file_directory);
326}
327
328void PrepareWritableFileAndRun(Profile* profile,
329                               const base::FilePath& path,
330                               const OpenFileCallback& callback) {
331  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
332  DCHECK(!callback.is_null());
333  if (IsUnderDriveMountPoint(path)) {
334    FileWriteHelper* file_write_helper = GetFileWriteHelper(profile);
335    if (!file_write_helper)
336      return;
337    base::FilePath remote_path(ExtractDrivePath(path));
338    file_write_helper->PrepareWritableFileAndRun(remote_path, callback);
339  } else {
340    content::BrowserThread::GetBlockingPool()->PostTask(
341        FROM_HERE, base::Bind(callback, FILE_ERROR_OK, path));
342  }
343}
344
345void EnsureDirectoryExists(Profile* profile,
346                           const base::FilePath& directory,
347                           const FileOperationCallback& callback) {
348  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
349  DCHECK(!callback.is_null());
350  if (IsUnderDriveMountPoint(directory)) {
351    FileSystemInterface* file_system = GetFileSystem(profile);
352    DCHECK(file_system);
353    file_system->CreateDirectory(
354        ExtractDrivePath(directory),
355        true /* is_exclusive */,
356        true /* is_recursive */,
357        callback);
358  } else {
359    base::MessageLoopProxy::current()->PostTask(
360        FROM_HERE, base::Bind(callback, FILE_ERROR_OK));
361  }
362}
363
364void EmptyFileOperationCallback(FileError error) {
365}
366
367bool CreateGDocFile(const base::FilePath& file_path,
368                    const GURL& url,
369                    const std::string& resource_id) {
370  std::string content = base::StringPrintf(
371      "{\"url\": \"%s\", \"resource_id\": \"%s\"}",
372      url.spec().c_str(), resource_id.c_str());
373  return file_util::WriteFile(file_path, content.data(), content.size()) ==
374      static_cast<int>(content.size());
375}
376
377bool HasGDocFileExtension(const base::FilePath& file_path) {
378  return google_apis::ResourceEntry::ClassifyEntryKindByFileExtension(
379      file_path) &
380      google_apis::ResourceEntry::KIND_OF_HOSTED_DOCUMENT;
381}
382
383GURL ReadUrlFromGDocFile(const base::FilePath& file_path) {
384  return GURL(ReadStringFromGDocFile(file_path, "url"));
385}
386
387std::string ReadResourceIdFromGDocFile(const base::FilePath& file_path) {
388  return ReadStringFromGDocFile(file_path, "resource_id");
389}
390
391std::string GetMd5Digest(const base::FilePath& file_path) {
392  const int kBufferSize = 512 * 1024;  // 512kB.
393
394  base::PlatformFile file = base::CreatePlatformFile(
395      file_path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
396      NULL, NULL);
397  if (file == base::kInvalidPlatformFileValue)
398    return std::string();
399  base::ScopedPlatformFileCloser file_closer(&file);
400
401  base::MD5Context context;
402  base::MD5Init(&context);
403
404  scoped_ptr<char[]> buffer(new char[kBufferSize]);
405  while (true) {
406    int result = base::ReadPlatformFileCurPosNoBestEffort(
407        file, buffer.get(), kBufferSize);
408
409    if (result < 0) {
410      // Found an error.
411      return std::string();
412    }
413
414    if (result == 0) {
415      // End of file.
416      break;
417    }
418
419    base::MD5Update(&context, base::StringPiece(buffer.get(), result));
420  }
421
422  base::MD5Digest digest;
423  base::MD5Final(&digest, &context);
424  return MD5DigestToBase16(digest);
425}
426
427}  // namespace util
428}  // namespace drive
429