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