// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webkit/common/fileapi/file_system_util.h"
#include <algorithm>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "net/base/escape.h"
#include "net/base/net_errors.h"
#include "url/gurl.h"
#include "webkit/common/database/database_identifier.h"
namespace fileapi {
const char kPersistentDir[] = "/persistent";
const char kTemporaryDir[] = "/temporary";
const char kIsolatedDir[] = "/isolated";
const char kExternalDir[] = "/external";
const char kTestDir[] = "/test";
const base::FilePath::CharType VirtualPath::kRoot[] = FILE_PATH_LITERAL("/");
const base::FilePath::CharType VirtualPath::kSeparator = FILE_PATH_LITERAL('/');
// TODO(ericu): Consider removing support for '\', even on Windows, if possible.
// There's a lot of test code that will need reworking, and we may have trouble
// with base::FilePath elsewhere [e.g. DirName and other methods may also need
// replacement].
base::FilePath VirtualPath::BaseName(const base::FilePath& virtual_path) {
base::FilePath::StringType path = virtual_path.value();
// Keep everything after the final separator, but if the pathname is only
// one character and it's a separator, leave it alone.
while (path.size() > 1 && base::FilePath::IsSeparator(path[path.size() - 1]))
path.resize(path.size() - 1);
base::FilePath::StringType::size_type last_separator =
path.find_last_of(base::FilePath::kSeparators);
if (last_separator != base::FilePath::StringType::npos &&
last_separator < path.size() - 1)
path.erase(0, last_separator + 1);
return base::FilePath(path);
}
base::FilePath VirtualPath::DirName(const base::FilePath& virtual_path) {
typedef base::FilePath::StringType StringType;
StringType path = virtual_path.value();
// The logic below is taken from that of base::FilePath::DirName, except
// that this version never cares about '//' or drive-letters even on win32.
// Strip trailing separators.
while (path.size() > 1 && base::FilePath::IsSeparator(path[path.size() - 1]))
path.resize(path.size() - 1);
StringType::size_type last_separator =
path.find_last_of(base::FilePath::kSeparators);
if (last_separator == StringType::npos) {
// path_ is in the current directory.
return base::FilePath(base::FilePath::kCurrentDirectory);
}
if (last_separator == 0) {
// path_ is in the root directory.
return base::FilePath(path.substr(0, 1));
}
// path_ is somewhere else, trim the basename.
path.resize(last_separator);
// Strip trailing separators.
while (path.size() > 1 && base::FilePath::IsSeparator(path[path.size() - 1]))
path.resize(path.size() - 1);
if (path.empty())
return base::FilePath(base::FilePath::kCurrentDirectory);
return base::FilePath(path);
}
void VirtualPath::GetComponents(
const base::FilePath& path,
std::vector<base::FilePath::StringType>* components) {
typedef base::FilePath::StringType StringType;
DCHECK(components);
if (!components)
return;
components->clear();
if (path.value().empty())
return;
StringType::size_type begin = 0, end = 0;
while (begin < path.value().length() && end != StringType::npos) {
end = path.value().find_first_of(base::FilePath::kSeparators, begin);
StringType component = path.value().substr(
begin, end == StringType::npos ? StringType::npos : end - begin);
if (!component.empty() && component != base::FilePath::kCurrentDirectory)
components->push_back(component);
begin = end + 1;
}
}
void VirtualPath::GetComponentsUTF8Unsafe(
const base::FilePath& path,
std::vector<std::string>* components) {
DCHECK(components);
if (!components)
return;
components->clear();
std::vector<base::FilePath::StringType> stringtype_components;
VirtualPath::GetComponents(path, &stringtype_components);
std::vector<base::FilePath::StringType>::const_iterator it;
for (it = stringtype_components.begin(); it != stringtype_components.end();
++it) {
components->push_back(base::FilePath(*it).AsUTF8Unsafe());
}
}
base::FilePath::StringType VirtualPath::GetNormalizedFilePath(
const base::FilePath& path) {
base::FilePath::StringType normalized_path = path.value();
const size_t num_separators = base::FilePath::StringType(
base::FilePath::kSeparators).length();
for (size_t i = 0; i < num_separators; ++i) {
std::replace(normalized_path.begin(), normalized_path.end(),
base::FilePath::kSeparators[i], kSeparator);
}
return (IsAbsolute(normalized_path)) ?
normalized_path : base::FilePath::StringType(kRoot) + normalized_path;
}
bool VirtualPath::IsAbsolute(const base::FilePath::StringType& path) {
return path.find(kRoot) == 0;
}
bool VirtualPath::IsRootPath(const base::FilePath& path) {
std::vector<base::FilePath::StringType> components;
VirtualPath::GetComponents(path, &components);
return (path.empty() || components.empty() ||
(components.size() == 1 &&
components[0] == VirtualPath::kRoot));
}
bool ParseFileSystemSchemeURL(const GURL& url,
GURL* origin_url,
FileSystemType* type,
base::FilePath* virtual_path) {
GURL origin;
FileSystemType file_system_type = kFileSystemTypeUnknown;
if (!url.is_valid() || !url.SchemeIsFileSystem())
return false;
const struct {
FileSystemType type;
const char* dir;
} kValidTypes[] = {
{ kFileSystemTypePersistent, kPersistentDir },
{ kFileSystemTypeTemporary, kTemporaryDir },
{ kFileSystemTypeIsolated, kIsolatedDir },
{ kFileSystemTypeExternal, kExternalDir },
{ kFileSystemTypeTest, kTestDir },
};
// A path of the inner_url contains only mount type part (e.g. "/temporary").
DCHECK(url.inner_url());
std::string inner_path = url.inner_url()->path();
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidTypes); ++i) {
if (inner_path == kValidTypes[i].dir) {
file_system_type = kValidTypes[i].type;
break;
}
}
if (file_system_type == kFileSystemTypeUnknown)
return false;
std::string path = net::UnescapeURLComponent(url.path(),
net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS |
net::UnescapeRule::CONTROL_CHARS);
// Ensure the path is relative.
while (!path.empty() && path[0] == '/')
path.erase(0, 1);
base::FilePath converted_path = base::FilePath::FromUTF8Unsafe(path);
// All parent references should have been resolved in the renderer.
if (converted_path.ReferencesParent())
return false;
if (origin_url)
*origin_url = url.GetOrigin();
if (type)
*type = file_system_type;
if (virtual_path)
*virtual_path = converted_path.NormalizePathSeparators().
StripTrailingSeparators();
return true;
}
GURL GetFileSystemRootURI(const GURL& origin_url, FileSystemType type) {
// origin_url is based on a security origin, so http://foo.com or file:///
// instead of the corresponding filesystem URL.
DCHECK(!origin_url.SchemeIsFileSystem());
std::string url = "filesystem:" + origin_url.GetWithEmptyPath().spec();
switch (type) {
case kFileSystemTypeTemporary:
url += (kTemporaryDir + 1); // We don't want the leading slash.
return GURL(url + "/");
case kFileSystemTypePersistent:
url += (kPersistentDir + 1); // We don't want the leading slash.
return GURL(url + "/");
case kFileSystemTypeExternal:
url += (kExternalDir + 1); // We don't want the leading slash.
return GURL(url + "/");
case kFileSystemTypeIsolated:
url += (kIsolatedDir + 1); // We don't want the leading slash.
return GURL(url + "/");
case kFileSystemTypeTest:
url += (kTestDir + 1); // We don't want the leading slash.
return GURL(url + "/");
// Internal types are always pointed via isolated or external URLs.
default:
NOTREACHED();
}
NOTREACHED();
return GURL();
}
std::string GetFileSystemName(const GURL& origin_url, FileSystemType type) {
std::string origin_identifier =
webkit_database::GetIdentifierFromOrigin(origin_url);
std::string type_string = GetFileSystemTypeString(type);
DCHECK(!type_string.empty());
return origin_identifier + ":" + type_string;
}
FileSystemType QuotaStorageTypeToFileSystemType(
quota::StorageType storage_type) {
switch (storage_type) {
case quota::kStorageTypeTemporary:
return kFileSystemTypeTemporary;
case quota::kStorageTypePersistent:
return kFileSystemTypePersistent;
case quota::kStorageTypeSyncable:
return kFileSystemTypeSyncable;
case quota::kStorageTypeQuotaNotManaged:
case quota::kStorageTypeUnknown:
return kFileSystemTypeUnknown;
}
return kFileSystemTypeUnknown;
}
quota::StorageType FileSystemTypeToQuotaStorageType(FileSystemType type) {
switch (type) {
case kFileSystemTypeTemporary:
return quota::kStorageTypeTemporary;
case kFileSystemTypePersistent:
return quota::kStorageTypePersistent;
case kFileSystemTypeSyncable:
case kFileSystemTypeSyncableForInternalSync:
return quota::kStorageTypeSyncable;
case kFileSystemTypePluginPrivate:
return quota::kStorageTypeQuotaNotManaged;
default:
return quota::kStorageTypeUnknown;
}
}
std::string GetFileSystemTypeString(FileSystemType type) {
switch (type) {
case kFileSystemTypeTemporary:
return "Temporary";
case kFileSystemTypePersistent:
return "Persistent";
case kFileSystemTypeIsolated:
return "Isolated";
case kFileSystemTypeExternal:
return "External";
case kFileSystemTypeTest:
return "Test";
case kFileSystemTypeNativeLocal:
return "NativeLocal";
case kFileSystemTypeRestrictedNativeLocal:
return "RestrictedNativeLocal";
case kFileSystemTypeDragged:
return "Dragged";
case kFileSystemTypeNativeMedia:
return "NativeMedia";
case kFileSystemTypeDeviceMedia:
return "DeviceMedia";
case kFileSystemTypePicasa:
return "Picasa";
case kFileSystemTypeItunes:
return "Itunes";
case kFileSystemTypeIphoto:
return "Iphoto";
case kFileSystemTypeDrive:
return "Drive";
case kFileSystemTypeSyncable:
case kFileSystemTypeSyncableForInternalSync:
return "Syncable";
case kFileSystemTypeNativeForPlatformApp:
return "NativeForPlatformApp";
case kFileSystemTypeForTransientFile:
return "TransientFile";
case kFileSystemTypePluginPrivate:
return "PluginPrivate";
case kFileSystemTypeCloudDevice:
return "CloudDevice";
case kFileSystemTypeProvided:
return "Provided";
case kFileSystemTypeDeviceMediaAsFileStorage:
return "DeviceMediaStorage";
case kFileSystemInternalTypeEnumStart:
case kFileSystemInternalTypeEnumEnd:
NOTREACHED();
// Fall through.
case kFileSystemTypeUnknown:
return "Unknown";
}
NOTREACHED();
return std::string();
}
std::string FilePathToString(const base::FilePath& file_path) {
#if defined(OS_WIN)
return base::UTF16ToUTF8(file_path.value());
#elif defined(OS_POSIX)
return file_path.value();
#endif
}
base::FilePath StringToFilePath(const std::string& file_path_string) {
#if defined(OS_WIN)
return base::FilePath(base::UTF8ToUTF16(file_path_string));
#elif defined(OS_POSIX)
return base::FilePath(file_path_string);
#endif
}
blink::WebFileError FileErrorToWebFileError(
base::File::Error error_code) {
switch (error_code) {
case base::File::FILE_ERROR_NOT_FOUND:
return blink::WebFileErrorNotFound;
case base::File::FILE_ERROR_INVALID_OPERATION:
case base::File::FILE_ERROR_EXISTS:
case base::File::FILE_ERROR_NOT_EMPTY:
return blink::WebFileErrorInvalidModification;
case base::File::FILE_ERROR_NOT_A_DIRECTORY:
case base::File::FILE_ERROR_NOT_A_FILE:
return blink::WebFileErrorTypeMismatch;
case base::File::FILE_ERROR_ACCESS_DENIED:
return blink::WebFileErrorNoModificationAllowed;
case base::File::FILE_ERROR_FAILED:
return blink::WebFileErrorInvalidState;
case base::File::FILE_ERROR_ABORT:
return blink::WebFileErrorAbort;
case base::File::FILE_ERROR_SECURITY:
return blink::WebFileErrorSecurity;
case base::File::FILE_ERROR_NO_SPACE:
return blink::WebFileErrorQuotaExceeded;
case base::File::FILE_ERROR_INVALID_URL:
return blink::WebFileErrorEncoding;
default:
return blink::WebFileErrorInvalidModification;
}
}
bool GetFileSystemPublicType(
const std::string type_string,
blink::WebFileSystemType* type) {
DCHECK(type);
if (type_string == "Temporary") {
*type = blink::WebFileSystemTypeTemporary;
return true;
}
if (type_string == "Persistent") {
*type = blink::WebFileSystemTypePersistent;
return true;
}
if (type_string == "Isolated") {
*type = blink::WebFileSystemTypeIsolated;
return true;
}
if (type_string == "External") {
*type = blink::WebFileSystemTypeExternal;
return true;
}
NOTREACHED();
return false;
}
std::string GetIsolatedFileSystemName(const GURL& origin_url,
const std::string& filesystem_id) {
std::string name(fileapi::GetFileSystemName(
origin_url, fileapi::kFileSystemTypeIsolated));
name.append("_");
name.append(filesystem_id);
return name;
}
bool CrackIsolatedFileSystemName(const std::string& filesystem_name,
std::string* filesystem_id) {
DCHECK(filesystem_id);
// |filesystem_name| is of the form {origin}:isolated_{filesystem_id}.
std::string start_token(":");
start_token = start_token.append(
GetFileSystemTypeString(kFileSystemTypeIsolated)).append("_");
// WebKit uses different case in its constant for isolated file system
// names, so we do a case insensitive compare by converting both strings
// to uppercase.
// TODO(benwells): Remove this when WebKit uses the same constant.
start_token = StringToUpperASCII(start_token);
std::string filesystem_name_upper = StringToUpperASCII(filesystem_name);
size_t pos = filesystem_name_upper.find(start_token);
if (pos == std::string::npos)
return false;
if (pos == 0)
return false;
*filesystem_id = filesystem_name.substr(pos + start_token.length(),
std::string::npos);
if (filesystem_id->empty())
return false;
return true;
}
bool ValidateIsolatedFileSystemId(const std::string& filesystem_id) {
const size_t kExpectedFileSystemIdSize = 32;
if (filesystem_id.size() != kExpectedFileSystemIdSize)
return false;
const std::string kExpectedChars("ABCDEF0123456789");
return base::ContainsOnlyChars(filesystem_id, kExpectedChars);
}
std::string GetIsolatedFileSystemRootURIString(
const GURL& origin_url,
const std::string& filesystem_id,
const std::string& optional_root_name) {
std::string root = GetFileSystemRootURI(origin_url,
kFileSystemTypeIsolated).spec();
if (base::FilePath::FromUTF8Unsafe(filesystem_id).ReferencesParent())
return std::string();
root.append(net::EscapePath(filesystem_id));
root.append("/");
if (!optional_root_name.empty()) {
if (base::FilePath::FromUTF8Unsafe(optional_root_name).ReferencesParent())
return std::string();
root.append(net::EscapePath(optional_root_name));
root.append("/");
}
return root;
}
std::string GetExternalFileSystemRootURIString(
const GURL& origin_url,
const std::string& mount_name) {
std::string root = GetFileSystemRootURI(origin_url,
kFileSystemTypeExternal).spec();
if (base::FilePath::FromUTF8Unsafe(mount_name).ReferencesParent())
return std::string();
root.append(net::EscapePath(mount_name));
root.append("/");
return root;
}
base::File::Error NetErrorToFileError(int error) {
switch (error) {
case net::OK:
return base::File::FILE_OK;
case net::ERR_ADDRESS_IN_USE:
return base::File::FILE_ERROR_IN_USE;
case net::ERR_FILE_EXISTS:
return base::File::FILE_ERROR_EXISTS;
case net::ERR_FILE_NOT_FOUND:
return base::File::FILE_ERROR_NOT_FOUND;
case net::ERR_ACCESS_DENIED:
return base::File::FILE_ERROR_ACCESS_DENIED;
case net::ERR_TOO_MANY_SOCKET_STREAMS:
return base::File::FILE_ERROR_TOO_MANY_OPENED;
case net::ERR_OUT_OF_MEMORY:
return base::File::FILE_ERROR_NO_MEMORY;
case net::ERR_FILE_NO_SPACE:
return base::File::FILE_ERROR_NO_SPACE;
case net::ERR_INVALID_ARGUMENT:
case net::ERR_INVALID_HANDLE:
return base::File::FILE_ERROR_INVALID_OPERATION;
case net::ERR_ABORTED:
case net::ERR_CONNECTION_ABORTED:
return base::File::FILE_ERROR_ABORT;
case net::ERR_ADDRESS_INVALID:
case net::ERR_INVALID_URL:
return base::File::FILE_ERROR_INVALID_URL;
default:
return base::File::FILE_ERROR_FAILED;
}
}
} // namespace fileapi