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 "webkit/common/fileapi/file_system_util.h"
6
7#include <algorithm>
8
9#include "base/files/file_path.h"
10#include "base/logging.h"
11#include "base/strings/string_util.h"
12#include "base/strings/sys_string_conversions.h"
13#include "base/strings/utf_string_conversions.h"
14#include "net/base/escape.h"
15#include "net/base/net_errors.h"
16#include "url/gurl.h"
17#include "webkit/common/database/database_identifier.h"
18
19namespace fileapi {
20
21const char kPersistentDir[] = "/persistent";
22const char kTemporaryDir[] = "/temporary";
23const char kIsolatedDir[] = "/isolated";
24const char kExternalDir[] = "/external";
25const char kTestDir[] = "/test";
26
27const base::FilePath::CharType VirtualPath::kRoot[] = FILE_PATH_LITERAL("/");
28const base::FilePath::CharType VirtualPath::kSeparator = FILE_PATH_LITERAL('/');
29
30// TODO(ericu): Consider removing support for '\', even on Windows, if possible.
31// There's a lot of test code that will need reworking, and we may have trouble
32// with base::FilePath elsewhere [e.g. DirName and other methods may also need
33// replacement].
34base::FilePath VirtualPath::BaseName(const base::FilePath& virtual_path) {
35  base::FilePath::StringType path = virtual_path.value();
36
37  // Keep everything after the final separator, but if the pathname is only
38  // one character and it's a separator, leave it alone.
39  while (path.size() > 1 && base::FilePath::IsSeparator(path[path.size() - 1]))
40    path.resize(path.size() - 1);
41  base::FilePath::StringType::size_type last_separator =
42      path.find_last_of(base::FilePath::kSeparators);
43  if (last_separator != base::FilePath::StringType::npos &&
44      last_separator < path.size() - 1)
45    path.erase(0, last_separator + 1);
46
47  return base::FilePath(path);
48}
49
50base::FilePath VirtualPath::DirName(const base::FilePath& virtual_path) {
51  typedef base::FilePath::StringType StringType;
52  StringType  path = virtual_path.value();
53
54  // The logic below is taken from that of base::FilePath::DirName, except
55  // that this version never cares about '//' or drive-letters even on win32.
56
57  // Strip trailing separators.
58  while (path.size() > 1 && base::FilePath::IsSeparator(path[path.size() - 1]))
59    path.resize(path.size() - 1);
60
61  StringType::size_type last_separator =
62      path.find_last_of(base::FilePath::kSeparators);
63  if (last_separator == StringType::npos) {
64    // path_ is in the current directory.
65    return base::FilePath(base::FilePath::kCurrentDirectory);
66  }
67  if (last_separator == 0) {
68    // path_ is in the root directory.
69    return base::FilePath(path.substr(0, 1));
70  }
71  // path_ is somewhere else, trim the basename.
72  path.resize(last_separator);
73
74  // Strip trailing separators.
75  while (path.size() > 1 && base::FilePath::IsSeparator(path[path.size() - 1]))
76    path.resize(path.size() - 1);
77
78  if (path.empty())
79    return base::FilePath(base::FilePath::kCurrentDirectory);
80
81  return base::FilePath(path);
82}
83
84void VirtualPath::GetComponents(
85    const base::FilePath& path,
86    std::vector<base::FilePath::StringType>* components) {
87  typedef base::FilePath::StringType StringType;
88
89  DCHECK(components);
90  if (!components)
91    return;
92  components->clear();
93  if (path.value().empty())
94    return;
95
96  StringType::size_type begin = 0, end = 0;
97  while (begin < path.value().length() && end != StringType::npos) {
98    end = path.value().find_first_of(base::FilePath::kSeparators, begin);
99    StringType component = path.value().substr(
100        begin, end == StringType::npos ? StringType::npos : end - begin);
101    if (!component.empty() && component != base::FilePath::kCurrentDirectory)
102      components->push_back(component);
103    begin = end + 1;
104  }
105}
106
107void VirtualPath::GetComponentsUTF8Unsafe(
108    const base::FilePath& path,
109    std::vector<std::string>* components) {
110  DCHECK(components);
111  if (!components)
112    return;
113  components->clear();
114
115  std::vector<base::FilePath::StringType> stringtype_components;
116  VirtualPath::GetComponents(path, &stringtype_components);
117  std::vector<base::FilePath::StringType>::const_iterator it;
118  for (it = stringtype_components.begin(); it != stringtype_components.end();
119       ++it) {
120    components->push_back(base::FilePath(*it).AsUTF8Unsafe());
121  }
122}
123
124base::FilePath::StringType VirtualPath::GetNormalizedFilePath(
125    const base::FilePath& path) {
126  base::FilePath::StringType normalized_path = path.value();
127  const size_t num_separators = base::FilePath::StringType(
128      base::FilePath::kSeparators).length();
129  for (size_t i = 0; i < num_separators; ++i) {
130    std::replace(normalized_path.begin(), normalized_path.end(),
131                 base::FilePath::kSeparators[i], kSeparator);
132  }
133
134  return (IsAbsolute(normalized_path)) ?
135      normalized_path : base::FilePath::StringType(kRoot) + normalized_path;
136}
137
138bool VirtualPath::IsAbsolute(const base::FilePath::StringType& path) {
139  return path.find(kRoot) == 0;
140}
141
142bool VirtualPath::IsRootPath(const base::FilePath& path) {
143  std::vector<base::FilePath::StringType> components;
144  VirtualPath::GetComponents(path, &components);
145  return (path.empty() || components.empty() ||
146          (components.size() == 1 &&
147           components[0] == VirtualPath::kRoot));
148}
149
150bool ParseFileSystemSchemeURL(const GURL& url,
151                              GURL* origin_url,
152                              FileSystemType* type,
153                              base::FilePath* virtual_path) {
154  GURL origin;
155  FileSystemType file_system_type = kFileSystemTypeUnknown;
156
157  if (!url.is_valid() || !url.SchemeIsFileSystem())
158    return false;
159
160  const struct {
161    FileSystemType type;
162    const char* dir;
163  } kValidTypes[] = {
164    { kFileSystemTypePersistent, kPersistentDir },
165    { kFileSystemTypeTemporary, kTemporaryDir },
166    { kFileSystemTypeIsolated, kIsolatedDir },
167    { kFileSystemTypeExternal, kExternalDir },
168    { kFileSystemTypeTest, kTestDir },
169  };
170
171  // A path of the inner_url contains only mount type part (e.g. "/temporary").
172  DCHECK(url.inner_url());
173  std::string inner_path = url.inner_url()->path();
174  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidTypes); ++i) {
175    if (inner_path == kValidTypes[i].dir) {
176      file_system_type = kValidTypes[i].type;
177      break;
178    }
179  }
180
181  if (file_system_type == kFileSystemTypeUnknown)
182    return false;
183
184  std::string path = net::UnescapeURLComponent(url.path(),
185      net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS |
186      net::UnescapeRule::CONTROL_CHARS);
187
188  // Ensure the path is relative.
189  while (!path.empty() && path[0] == '/')
190    path.erase(0, 1);
191
192  base::FilePath converted_path = base::FilePath::FromUTF8Unsafe(path);
193
194  // All parent references should have been resolved in the renderer.
195  if (converted_path.ReferencesParent())
196    return false;
197
198  if (origin_url)
199    *origin_url = url.GetOrigin();
200  if (type)
201    *type = file_system_type;
202  if (virtual_path)
203    *virtual_path = converted_path.NormalizePathSeparators().
204        StripTrailingSeparators();
205
206  return true;
207}
208
209GURL GetFileSystemRootURI(const GURL& origin_url, FileSystemType type) {
210  // origin_url is based on a security origin, so http://foo.com or file:///
211  // instead of the corresponding filesystem URL.
212  DCHECK(!origin_url.SchemeIsFileSystem());
213
214  std::string url = "filesystem:" + origin_url.GetWithEmptyPath().spec();
215  switch (type) {
216    case kFileSystemTypeTemporary:
217      url += (kTemporaryDir + 1);  // We don't want the leading slash.
218      return GURL(url + "/");
219    case kFileSystemTypePersistent:
220      url += (kPersistentDir + 1);  // We don't want the leading slash.
221      return GURL(url + "/");
222    case kFileSystemTypeExternal:
223      url += (kExternalDir + 1);  // We don't want the leading slash.
224      return GURL(url + "/");
225    case kFileSystemTypeIsolated:
226      url += (kIsolatedDir + 1);  // We don't want the leading slash.
227      return GURL(url + "/");
228    case kFileSystemTypeTest:
229      url += (kTestDir + 1);  // We don't want the leading slash.
230      return GURL(url + "/");
231      // Internal types are always pointed via isolated or external URLs.
232    default:
233      NOTREACHED();
234  }
235  NOTREACHED();
236  return GURL();
237}
238
239std::string GetFileSystemName(const GURL& origin_url, FileSystemType type) {
240  std::string origin_identifier =
241      webkit_database::GetIdentifierFromOrigin(origin_url);
242  std::string type_string = GetFileSystemTypeString(type);
243  DCHECK(!type_string.empty());
244  return origin_identifier + ":" + type_string;
245}
246
247FileSystemType QuotaStorageTypeToFileSystemType(
248    quota::StorageType storage_type) {
249  switch (storage_type) {
250    case quota::kStorageTypeTemporary:
251      return kFileSystemTypeTemporary;
252    case quota::kStorageTypePersistent:
253      return kFileSystemTypePersistent;
254    case quota::kStorageTypeSyncable:
255      return kFileSystemTypeSyncable;
256    case quota::kStorageTypeQuotaNotManaged:
257    case quota::kStorageTypeUnknown:
258      return kFileSystemTypeUnknown;
259  }
260  return kFileSystemTypeUnknown;
261}
262
263quota::StorageType FileSystemTypeToQuotaStorageType(FileSystemType type) {
264  switch (type) {
265    case kFileSystemTypeTemporary:
266      return quota::kStorageTypeTemporary;
267    case kFileSystemTypePersistent:
268      return quota::kStorageTypePersistent;
269    case kFileSystemTypeSyncable:
270    case kFileSystemTypeSyncableForInternalSync:
271      return quota::kStorageTypeSyncable;
272    case kFileSystemTypePluginPrivate:
273      return quota::kStorageTypeQuotaNotManaged;
274    default:
275      return quota::kStorageTypeUnknown;
276  }
277}
278
279std::string GetFileSystemTypeString(FileSystemType type) {
280  switch (type) {
281    case kFileSystemTypeTemporary:
282      return "Temporary";
283    case kFileSystemTypePersistent:
284      return "Persistent";
285    case kFileSystemTypeIsolated:
286      return "Isolated";
287    case kFileSystemTypeExternal:
288      return "External";
289    case kFileSystemTypeTest:
290      return "Test";
291    case kFileSystemTypeNativeLocal:
292      return "NativeLocal";
293    case kFileSystemTypeRestrictedNativeLocal:
294      return "RestrictedNativeLocal";
295    case kFileSystemTypeDragged:
296      return "Dragged";
297    case kFileSystemTypeNativeMedia:
298      return "NativeMedia";
299    case kFileSystemTypeDeviceMedia:
300      return "DeviceMedia";
301    case kFileSystemTypePicasa:
302      return "Picasa";
303    case kFileSystemTypeItunes:
304      return "Itunes";
305    case kFileSystemTypeIphoto:
306      return "Iphoto";
307    case kFileSystemTypeDrive:
308      return "Drive";
309    case kFileSystemTypeSyncable:
310    case kFileSystemTypeSyncableForInternalSync:
311      return "Syncable";
312    case kFileSystemTypeNativeForPlatformApp:
313      return "NativeForPlatformApp";
314    case kFileSystemTypeForTransientFile:
315      return "TransientFile";
316    case kFileSystemTypePluginPrivate:
317      return "PluginPrivate";
318    case kFileSystemTypeCloudDevice:
319      return "CloudDevice";
320    case kFileSystemTypeProvided:
321      return "Provided";
322    case kFileSystemTypeDeviceMediaAsFileStorage:
323      return "DeviceMediaStorage";
324    case kFileSystemInternalTypeEnumStart:
325    case kFileSystemInternalTypeEnumEnd:
326      NOTREACHED();
327      // Fall through.
328    case kFileSystemTypeUnknown:
329      return "Unknown";
330  }
331  NOTREACHED();
332  return std::string();
333}
334
335std::string FilePathToString(const base::FilePath& file_path) {
336#if defined(OS_WIN)
337  return base::UTF16ToUTF8(file_path.value());
338#elif defined(OS_POSIX)
339  return file_path.value();
340#endif
341}
342
343base::FilePath StringToFilePath(const std::string& file_path_string) {
344#if defined(OS_WIN)
345  return base::FilePath(base::UTF8ToUTF16(file_path_string));
346#elif defined(OS_POSIX)
347  return base::FilePath(file_path_string);
348#endif
349}
350
351blink::WebFileError FileErrorToWebFileError(
352    base::File::Error error_code) {
353  switch (error_code) {
354    case base::File::FILE_ERROR_NOT_FOUND:
355      return blink::WebFileErrorNotFound;
356    case base::File::FILE_ERROR_INVALID_OPERATION:
357    case base::File::FILE_ERROR_EXISTS:
358    case base::File::FILE_ERROR_NOT_EMPTY:
359      return blink::WebFileErrorInvalidModification;
360    case base::File::FILE_ERROR_NOT_A_DIRECTORY:
361    case base::File::FILE_ERROR_NOT_A_FILE:
362      return blink::WebFileErrorTypeMismatch;
363    case base::File::FILE_ERROR_ACCESS_DENIED:
364      return blink::WebFileErrorNoModificationAllowed;
365    case base::File::FILE_ERROR_FAILED:
366      return blink::WebFileErrorInvalidState;
367    case base::File::FILE_ERROR_ABORT:
368      return blink::WebFileErrorAbort;
369    case base::File::FILE_ERROR_SECURITY:
370      return blink::WebFileErrorSecurity;
371    case base::File::FILE_ERROR_NO_SPACE:
372      return blink::WebFileErrorQuotaExceeded;
373    case base::File::FILE_ERROR_INVALID_URL:
374      return blink::WebFileErrorEncoding;
375    default:
376      return blink::WebFileErrorInvalidModification;
377  }
378}
379
380bool GetFileSystemPublicType(
381    const std::string type_string,
382    blink::WebFileSystemType* type) {
383  DCHECK(type);
384  if (type_string == "Temporary") {
385    *type = blink::WebFileSystemTypeTemporary;
386    return true;
387  }
388  if (type_string == "Persistent") {
389    *type = blink::WebFileSystemTypePersistent;
390    return true;
391  }
392  if (type_string == "Isolated") {
393    *type = blink::WebFileSystemTypeIsolated;
394    return true;
395  }
396  if (type_string == "External") {
397    *type = blink::WebFileSystemTypeExternal;
398    return true;
399  }
400  NOTREACHED();
401  return false;
402}
403
404std::string GetIsolatedFileSystemName(const GURL& origin_url,
405                                      const std::string& filesystem_id) {
406  std::string name(fileapi::GetFileSystemName(
407      origin_url, fileapi::kFileSystemTypeIsolated));
408  name.append("_");
409  name.append(filesystem_id);
410  return name;
411}
412
413bool CrackIsolatedFileSystemName(const std::string& filesystem_name,
414                                 std::string* filesystem_id) {
415  DCHECK(filesystem_id);
416
417  // |filesystem_name| is of the form {origin}:isolated_{filesystem_id}.
418  std::string start_token(":");
419  start_token = start_token.append(
420      GetFileSystemTypeString(kFileSystemTypeIsolated)).append("_");
421  // WebKit uses different case in its constant for isolated file system
422  // names, so we do a case insensitive compare by converting both strings
423  // to uppercase.
424  // TODO(benwells): Remove this when WebKit uses the same constant.
425  start_token = StringToUpperASCII(start_token);
426  std::string filesystem_name_upper = StringToUpperASCII(filesystem_name);
427  size_t pos = filesystem_name_upper.find(start_token);
428  if (pos == std::string::npos)
429    return false;
430  if (pos == 0)
431    return false;
432
433  *filesystem_id = filesystem_name.substr(pos + start_token.length(),
434                                          std::string::npos);
435  if (filesystem_id->empty())
436    return false;
437
438  return true;
439}
440
441bool ValidateIsolatedFileSystemId(const std::string& filesystem_id) {
442  const size_t kExpectedFileSystemIdSize = 32;
443  if (filesystem_id.size() != kExpectedFileSystemIdSize)
444    return false;
445  const std::string kExpectedChars("ABCDEF0123456789");
446  return base::ContainsOnlyChars(filesystem_id, kExpectedChars);
447}
448
449std::string GetIsolatedFileSystemRootURIString(
450    const GURL& origin_url,
451    const std::string& filesystem_id,
452    const std::string& optional_root_name) {
453  std::string root = GetFileSystemRootURI(origin_url,
454                                          kFileSystemTypeIsolated).spec();
455  if (base::FilePath::FromUTF8Unsafe(filesystem_id).ReferencesParent())
456    return std::string();
457  root.append(net::EscapePath(filesystem_id));
458  root.append("/");
459  if (!optional_root_name.empty()) {
460    if (base::FilePath::FromUTF8Unsafe(optional_root_name).ReferencesParent())
461      return std::string();
462    root.append(net::EscapePath(optional_root_name));
463    root.append("/");
464  }
465  return root;
466}
467
468std::string GetExternalFileSystemRootURIString(
469    const GURL& origin_url,
470    const std::string& mount_name) {
471  std::string root = GetFileSystemRootURI(origin_url,
472                                          kFileSystemTypeExternal).spec();
473  if (base::FilePath::FromUTF8Unsafe(mount_name).ReferencesParent())
474    return std::string();
475  root.append(net::EscapePath(mount_name));
476  root.append("/");
477  return root;
478}
479
480base::File::Error NetErrorToFileError(int error) {
481  switch (error) {
482    case net::OK:
483      return base::File::FILE_OK;
484    case net::ERR_ADDRESS_IN_USE:
485      return base::File::FILE_ERROR_IN_USE;
486    case net::ERR_FILE_EXISTS:
487      return base::File::FILE_ERROR_EXISTS;
488    case net::ERR_FILE_NOT_FOUND:
489      return base::File::FILE_ERROR_NOT_FOUND;
490    case net::ERR_ACCESS_DENIED:
491      return base::File::FILE_ERROR_ACCESS_DENIED;
492    case net::ERR_TOO_MANY_SOCKET_STREAMS:
493      return base::File::FILE_ERROR_TOO_MANY_OPENED;
494    case net::ERR_OUT_OF_MEMORY:
495      return base::File::FILE_ERROR_NO_MEMORY;
496    case net::ERR_FILE_NO_SPACE:
497      return base::File::FILE_ERROR_NO_SPACE;
498    case net::ERR_INVALID_ARGUMENT:
499    case net::ERR_INVALID_HANDLE:
500      return base::File::FILE_ERROR_INVALID_OPERATION;
501    case net::ERR_ABORTED:
502    case net::ERR_CONNECTION_ABORTED:
503      return base::File::FILE_ERROR_ABORT;
504    case net::ERR_ADDRESS_INVALID:
505    case net::ERR_INVALID_URL:
506      return base::File::FILE_ERROR_INVALID_URL;
507    default:
508      return base::File::FILE_ERROR_FAILED;
509  }
510}
511
512}  // namespace fileapi
513