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 19 namespace fileapi { 20 21 const char kPersistentDir[] = "/persistent"; 22 const char kTemporaryDir[] = "/temporary"; 23 const char kIsolatedDir[] = "/isolated"; 24 const char kExternalDir[] = "/external"; 25 const char kTestDir[] = "/test"; 26 27 const base::FilePath::CharType VirtualPath::kRoot[] = FILE_PATH_LITERAL("/"); 28 const 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]. 34 base::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 50 base::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 84 void 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 107 void 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 124 base::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 138 bool VirtualPath::IsAbsolute(const base::FilePath::StringType& path) { 139 return path.find(kRoot) == 0; 140 } 141 142 bool 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 150 bool 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 209 GURL 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 239 std::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 247 FileSystemType 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 263 quota::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 279 std::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 335 std::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 343 base::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 351 blink::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 380 bool 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 404 std::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 413 bool 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 441 bool 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 449 std::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 468 std::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 480 base::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