Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 5 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include "mozilla/dom/Directory.h" |
michael@0 | 8 | |
michael@0 | 9 | #include "CreateDirectoryTask.h" |
michael@0 | 10 | #include "CreateFileTask.h" |
michael@0 | 11 | #include "FileSystemPermissionRequest.h" |
michael@0 | 12 | #include "GetFileOrDirectoryTask.h" |
michael@0 | 13 | #include "RemoveTask.h" |
michael@0 | 14 | |
michael@0 | 15 | #include "nsCharSeparatedTokenizer.h" |
michael@0 | 16 | #include "nsString.h" |
michael@0 | 17 | #include "mozilla/dom/DirectoryBinding.h" |
michael@0 | 18 | #include "mozilla/dom/FileSystemBase.h" |
michael@0 | 19 | #include "mozilla/dom/FileSystemUtils.h" |
michael@0 | 20 | #include "mozilla/dom/UnionTypes.h" |
michael@0 | 21 | |
michael@0 | 22 | // Resolve the name collision of Microsoft's API name with macros defined in |
michael@0 | 23 | // Windows header files. Undefine the macro of CreateDirectory to avoid |
michael@0 | 24 | // Directory#CreateDirectory being replaced by Directory#CreateDirectoryW. |
michael@0 | 25 | #ifdef CreateDirectory |
michael@0 | 26 | #undef CreateDirectory |
michael@0 | 27 | #endif |
michael@0 | 28 | // Undefine the macro of CreateFile to avoid Directory#CreateFile being replaced |
michael@0 | 29 | // by Directory#CreateFileW. |
michael@0 | 30 | #ifdef CreateFile |
michael@0 | 31 | #undef CreateFile |
michael@0 | 32 | #endif |
michael@0 | 33 | |
michael@0 | 34 | namespace mozilla { |
michael@0 | 35 | namespace dom { |
michael@0 | 36 | |
michael@0 | 37 | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(Directory) |
michael@0 | 38 | NS_IMPL_CYCLE_COLLECTING_ADDREF(Directory) |
michael@0 | 39 | NS_IMPL_CYCLE_COLLECTING_RELEASE(Directory) |
michael@0 | 40 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Directory) |
michael@0 | 41 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
michael@0 | 42 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
michael@0 | 43 | NS_INTERFACE_MAP_END |
michael@0 | 44 | |
michael@0 | 45 | // static |
michael@0 | 46 | already_AddRefed<Promise> |
michael@0 | 47 | Directory::GetRoot(FileSystemBase* aFileSystem) |
michael@0 | 48 | { |
michael@0 | 49 | nsRefPtr<GetFileOrDirectoryTask> task = new GetFileOrDirectoryTask( |
michael@0 | 50 | aFileSystem, EmptyString(), true); |
michael@0 | 51 | FileSystemPermissionRequest::RequestForTask(task); |
michael@0 | 52 | return task->GetPromise(); |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | Directory::Directory(FileSystemBase* aFileSystem, |
michael@0 | 56 | const nsAString& aPath) |
michael@0 | 57 | : mFileSystem(aFileSystem) |
michael@0 | 58 | , mPath(aPath) |
michael@0 | 59 | { |
michael@0 | 60 | MOZ_ASSERT(aFileSystem, "aFileSystem should not be null."); |
michael@0 | 61 | // Remove the trailing "/". |
michael@0 | 62 | mPath.Trim(FILESYSTEM_DOM_PATH_SEPARATOR, false, true); |
michael@0 | 63 | |
michael@0 | 64 | SetIsDOMBinding(); |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | Directory::~Directory() |
michael@0 | 68 | { |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | nsPIDOMWindow* |
michael@0 | 72 | Directory::GetParentObject() const |
michael@0 | 73 | { |
michael@0 | 74 | return mFileSystem->GetWindow(); |
michael@0 | 75 | } |
michael@0 | 76 | |
michael@0 | 77 | JSObject* |
michael@0 | 78 | Directory::WrapObject(JSContext* aCx) |
michael@0 | 79 | { |
michael@0 | 80 | return DirectoryBinding::Wrap(aCx, this); |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | void |
michael@0 | 84 | Directory::GetName(nsString& aRetval) const |
michael@0 | 85 | { |
michael@0 | 86 | aRetval.Truncate(); |
michael@0 | 87 | |
michael@0 | 88 | if (mPath.IsEmpty()) { |
michael@0 | 89 | aRetval = mFileSystem->GetRootName(); |
michael@0 | 90 | return; |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | aRetval = Substring(mPath, |
michael@0 | 94 | mPath.RFindChar(FileSystemUtils::kSeparatorChar) + 1); |
michael@0 | 95 | } |
michael@0 | 96 | |
michael@0 | 97 | already_AddRefed<Promise> |
michael@0 | 98 | Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions) |
michael@0 | 99 | { |
michael@0 | 100 | nsresult error = NS_OK; |
michael@0 | 101 | nsString realPath; |
michael@0 | 102 | nsRefPtr<nsIDOMBlob> blobData; |
michael@0 | 103 | InfallibleTArray<uint8_t> arrayData; |
michael@0 | 104 | bool replace = (aOptions.mIfExists == CreateIfExistsMode::Replace); |
michael@0 | 105 | |
michael@0 | 106 | // Get the file content. |
michael@0 | 107 | if (aOptions.mData.WasPassed()) { |
michael@0 | 108 | auto& data = aOptions.mData.Value(); |
michael@0 | 109 | if (data.IsString()) { |
michael@0 | 110 | NS_ConvertUTF16toUTF8 str(data.GetAsString()); |
michael@0 | 111 | arrayData.AppendElements(reinterpret_cast<const uint8_t *>(str.get()), |
michael@0 | 112 | str.Length()); |
michael@0 | 113 | } else if (data.IsArrayBuffer()) { |
michael@0 | 114 | ArrayBuffer& buffer = data.GetAsArrayBuffer(); |
michael@0 | 115 | buffer.ComputeLengthAndData(); |
michael@0 | 116 | arrayData.AppendElements(buffer.Data(), buffer.Length()); |
michael@0 | 117 | } else if (data.IsArrayBufferView()){ |
michael@0 | 118 | ArrayBufferView& view = data.GetAsArrayBufferView(); |
michael@0 | 119 | view.ComputeLengthAndData(); |
michael@0 | 120 | arrayData.AppendElements(view.Data(), view.Length()); |
michael@0 | 121 | } else { |
michael@0 | 122 | blobData = data.GetAsBlob(); |
michael@0 | 123 | } |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | if (!DOMPathToRealPath(aPath, realPath)) { |
michael@0 | 127 | error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | nsRefPtr<CreateFileTask> task = new CreateFileTask(mFileSystem, realPath, |
michael@0 | 131 | blobData, arrayData, replace); |
michael@0 | 132 | task->SetError(error); |
michael@0 | 133 | FileSystemPermissionRequest::RequestForTask(task); |
michael@0 | 134 | return task->GetPromise(); |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | already_AddRefed<Promise> |
michael@0 | 138 | Directory::CreateDirectory(const nsAString& aPath) |
michael@0 | 139 | { |
michael@0 | 140 | nsresult error = NS_OK; |
michael@0 | 141 | nsString realPath; |
michael@0 | 142 | if (!DOMPathToRealPath(aPath, realPath)) { |
michael@0 | 143 | error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; |
michael@0 | 144 | } |
michael@0 | 145 | nsRefPtr<CreateDirectoryTask> task = new CreateDirectoryTask( |
michael@0 | 146 | mFileSystem, realPath); |
michael@0 | 147 | task->SetError(error); |
michael@0 | 148 | FileSystemPermissionRequest::RequestForTask(task); |
michael@0 | 149 | return task->GetPromise(); |
michael@0 | 150 | } |
michael@0 | 151 | |
michael@0 | 152 | already_AddRefed<Promise> |
michael@0 | 153 | Directory::Get(const nsAString& aPath) |
michael@0 | 154 | { |
michael@0 | 155 | nsresult error = NS_OK; |
michael@0 | 156 | nsString realPath; |
michael@0 | 157 | if (!DOMPathToRealPath(aPath, realPath)) { |
michael@0 | 158 | error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; |
michael@0 | 159 | } |
michael@0 | 160 | nsRefPtr<GetFileOrDirectoryTask> task = new GetFileOrDirectoryTask( |
michael@0 | 161 | mFileSystem, realPath, false); |
michael@0 | 162 | task->SetError(error); |
michael@0 | 163 | FileSystemPermissionRequest::RequestForTask(task); |
michael@0 | 164 | return task->GetPromise(); |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | already_AddRefed<Promise> |
michael@0 | 168 | Directory::Remove(const StringOrFileOrDirectory& aPath) |
michael@0 | 169 | { |
michael@0 | 170 | return RemoveInternal(aPath, false); |
michael@0 | 171 | } |
michael@0 | 172 | |
michael@0 | 173 | already_AddRefed<Promise> |
michael@0 | 174 | Directory::RemoveDeep(const StringOrFileOrDirectory& aPath) |
michael@0 | 175 | { |
michael@0 | 176 | return RemoveInternal(aPath, true); |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | already_AddRefed<Promise> |
michael@0 | 180 | Directory::RemoveInternal(const StringOrFileOrDirectory& aPath, bool aRecursive) |
michael@0 | 181 | { |
michael@0 | 182 | nsresult error = NS_OK; |
michael@0 | 183 | nsString realPath; |
michael@0 | 184 | nsCOMPtr<nsIDOMFile> file; |
michael@0 | 185 | |
michael@0 | 186 | // Check and get the target path. |
michael@0 | 187 | |
michael@0 | 188 | if (aPath.IsFile()) { |
michael@0 | 189 | file = aPath.GetAsFile(); |
michael@0 | 190 | goto parameters_check_done; |
michael@0 | 191 | } |
michael@0 | 192 | |
michael@0 | 193 | if (aPath.IsString()) { |
michael@0 | 194 | if (!DOMPathToRealPath(aPath.GetAsString(), realPath)) { |
michael@0 | 195 | error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; |
michael@0 | 196 | } |
michael@0 | 197 | goto parameters_check_done; |
michael@0 | 198 | } |
michael@0 | 199 | |
michael@0 | 200 | if (!mFileSystem->IsSafeDirectory(&aPath.GetAsDirectory())) { |
michael@0 | 201 | error = NS_ERROR_DOM_SECURITY_ERR; |
michael@0 | 202 | goto parameters_check_done; |
michael@0 | 203 | } |
michael@0 | 204 | |
michael@0 | 205 | realPath = aPath.GetAsDirectory().mPath; |
michael@0 | 206 | // The target must be a descendant of this directory. |
michael@0 | 207 | if (!FileSystemUtils::IsDescendantPath(mPath, realPath)) { |
michael@0 | 208 | error = NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR; |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | parameters_check_done: |
michael@0 | 212 | |
michael@0 | 213 | nsRefPtr<RemoveTask> task = new RemoveTask(mFileSystem, mPath, file, realPath, |
michael@0 | 214 | aRecursive); |
michael@0 | 215 | task->SetError(error); |
michael@0 | 216 | FileSystemPermissionRequest::RequestForTask(task); |
michael@0 | 217 | return task->GetPromise(); |
michael@0 | 218 | } |
michael@0 | 219 | |
michael@0 | 220 | FileSystemBase* |
michael@0 | 221 | Directory::GetFileSystem() const |
michael@0 | 222 | { |
michael@0 | 223 | return mFileSystem.get(); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | bool |
michael@0 | 227 | Directory::DOMPathToRealPath(const nsAString& aPath, nsAString& aRealPath) const |
michael@0 | 228 | { |
michael@0 | 229 | aRealPath.Truncate(); |
michael@0 | 230 | |
michael@0 | 231 | nsString relativePath; |
michael@0 | 232 | relativePath = aPath; |
michael@0 | 233 | |
michael@0 | 234 | // Trim white spaces. |
michael@0 | 235 | static const char kWhitespace[] = "\b\t\r\n "; |
michael@0 | 236 | relativePath.Trim(kWhitespace); |
michael@0 | 237 | |
michael@0 | 238 | if (!IsValidRelativePath(relativePath)) { |
michael@0 | 239 | return false; |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | aRealPath = mPath + NS_LITERAL_STRING(FILESYSTEM_DOM_PATH_SEPARATOR) + |
michael@0 | 243 | relativePath; |
michael@0 | 244 | |
michael@0 | 245 | return true; |
michael@0 | 246 | } |
michael@0 | 247 | |
michael@0 | 248 | // static |
michael@0 | 249 | bool |
michael@0 | 250 | Directory::IsValidRelativePath(const nsString& aPath) |
michael@0 | 251 | { |
michael@0 | 252 | // We don't allow empty relative path to access the root. |
michael@0 | 253 | if (aPath.IsEmpty()) { |
michael@0 | 254 | return false; |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | // Leading and trailing "/" are not allowed. |
michael@0 | 258 | if (aPath.First() == FileSystemUtils::kSeparatorChar || |
michael@0 | 259 | aPath.Last() == FileSystemUtils::kSeparatorChar) { |
michael@0 | 260 | return false; |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | NS_NAMED_LITERAL_STRING(kCurrentDir, "."); |
michael@0 | 264 | NS_NAMED_LITERAL_STRING(kParentDir, ".."); |
michael@0 | 265 | |
michael@0 | 266 | // Split path and check each path component. |
michael@0 | 267 | nsCharSeparatedTokenizer tokenizer(aPath, FileSystemUtils::kSeparatorChar); |
michael@0 | 268 | while (tokenizer.hasMoreTokens()) { |
michael@0 | 269 | nsDependentSubstring pathComponent = tokenizer.nextToken(); |
michael@0 | 270 | // The path containing empty components, such as "foo//bar", is invalid. |
michael@0 | 271 | // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar", |
michael@0 | 272 | // to walk up the directory. |
michael@0 | 273 | if (pathComponent.IsEmpty() || |
michael@0 | 274 | pathComponent.Equals(kCurrentDir) || |
michael@0 | 275 | pathComponent.Equals(kParentDir)) { |
michael@0 | 276 | return false; |
michael@0 | 277 | } |
michael@0 | 278 | } |
michael@0 | 279 | |
michael@0 | 280 | return true; |
michael@0 | 281 | } |
michael@0 | 282 | |
michael@0 | 283 | } // namespace dom |
michael@0 | 284 | } // namespace mozilla |