Bug 1839170 - Refactor Snap pulling, Add Firefox Snap Core22 and GNOME 42 SDK symbols...
[gecko.git] / dom / system / PathUtils.cpp
blobd43171c4e93f12b7d9454a5ffd2f74913bb33083
1 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
7 #include "PathUtils.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/DataMutex.h"
11 #include "mozilla/ErrorNames.h"
12 #include "mozilla/ErrorResult.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/MozPromise.h"
15 #include "mozilla/RefPtr.h"
16 #include "mozilla/Result.h"
17 #include "mozilla/ResultExtensions.h"
18 #include "mozilla/Span.h"
19 #include "mozilla/dom/DOMParser.h"
20 #include "mozilla/dom/PathUtilsBinding.h"
21 #include "mozilla/dom/Promise.h"
22 #include "nsAppDirectoryServiceDefs.h"
23 #include "nsCOMPtr.h"
24 #include "nsDirectoryServiceDefs.h"
25 #include "nsDirectoryServiceUtils.h"
26 #include "nsIFile.h"
27 #include "nsIGlobalObject.h"
28 #include "nsLocalFile.h"
29 #include "nsNetUtil.h"
30 #include "nsString.h"
31 #include "xpcpublic.h"
33 namespace mozilla::dom {
35 static constexpr auto ERROR_EMPTY_PATH =
36 "PathUtils does not support empty paths"_ns;
37 static constexpr auto ERROR_INITIALIZE_PATH = "Could not initialize path"_ns;
38 static constexpr auto ERROR_GET_PARENT = "Could not get parent path"_ns;
39 static constexpr auto ERROR_JOIN = "Could not append to path"_ns;
41 static constexpr auto COLON = ": "_ns;
43 static void ThrowError(ErrorResult& aErr, const nsresult aResult,
44 const nsCString& aMessage) {
45 nsAutoCStringN<32> errName;
46 GetErrorName(aResult, errName);
48 nsAutoCStringN<256> formattedMsg;
49 formattedMsg.Append(aMessage);
50 formattedMsg.Append(COLON);
51 formattedMsg.Append(errName);
53 switch (aResult) {
54 case NS_ERROR_FILE_UNRECOGNIZED_PATH:
55 aErr.ThrowOperationError(formattedMsg);
56 break;
58 case NS_ERROR_FILE_ACCESS_DENIED:
59 aErr.ThrowInvalidAccessError(formattedMsg);
60 break;
62 case NS_ERROR_FAILURE:
63 default:
64 aErr.ThrowUnknownError(formattedMsg);
65 break;
69 static bool DoWindowsPathCheck() {
70 #ifdef XP_WIN
71 # ifdef DEBUG
72 return true;
73 # else // DEBUG
74 return xpc::IsInAutomation();
75 # endif // DEBUG
76 #else // XP_WIN
77 return false;
78 #endif // XP_WIN
81 /* static */
82 nsresult PathUtils::InitFileWithPath(nsIFile* aFile, const nsAString& aPath) {
83 if (DoWindowsPathCheck()) {
84 MOZ_RELEASE_ASSERT(!aPath.Contains(u'/'),
85 "Windows paths cannot include forward slashes");
88 MOZ_ASSERT(aFile);
89 return aFile->InitWithPath(aPath);
92 StaticDataMutex<Maybe<PathUtils::DirectoryCache>> PathUtils::sDirCache{
93 "sDirCache"};
95 /**
96 * Return the leaf name, including leading path separators in the case of
97 * Windows UNC drive paths.
99 * @param aFile The file whose leaf name is to be returned.
100 * @param aResult The string to hold the resulting leaf name.
101 * @param aParent The pre-computed parent of |aFile|. If not provided, it will
102 * be computed.
104 static nsresult GetLeafNamePreservingRoot(nsIFile* aFile, nsString& aResult,
105 nsIFile* aParent = nullptr) {
106 MOZ_ASSERT(aFile);
108 nsCOMPtr<nsIFile> parent = aParent;
109 if (!parent) {
110 MOZ_TRY(aFile->GetParent(getter_AddRefs(parent)));
113 if (parent) {
114 return aFile->GetLeafName(aResult);
117 // We have reached the root path. On Windows, the leafname for a UNC path
118 // will not have the leading backslashes, so we need to use the entire path
119 // here:
121 // * for a UNIX root path (/) this will be /;
122 // * for a Windows drive path (e.g., C:), this will be the drive path (C:);
123 // and
124 // * for a Windows UNC server path (e.g., \\\\server), this will be the full
125 // server path (\\\\server).
126 return aFile->GetPath(aResult);
129 void PathUtils::Filename(const GlobalObject&, const nsAString& aPath,
130 nsString& aResult, ErrorResult& aErr) {
131 if (aPath.IsEmpty()) {
132 aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
133 return;
136 nsCOMPtr<nsIFile> path = new nsLocalFile();
137 if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
138 ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
139 return;
142 if (nsresult rv = GetLeafNamePreservingRoot(path, aResult); NS_FAILED(rv)) {
143 ThrowError(aErr, rv, "Could not get leaf name of path"_ns);
144 return;
148 void PathUtils::Parent(const GlobalObject&, const nsAString& aPath,
149 const int32_t aDepth, nsString& aResult,
150 ErrorResult& aErr) {
151 if (aPath.IsEmpty()) {
152 aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
153 return;
156 nsCOMPtr<nsIFile> path = new nsLocalFile();
157 if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
158 ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
159 return;
162 if (aDepth <= 0) {
163 aErr.ThrowNotSupportedError("A depth of at least 1 is required");
164 return;
167 nsCOMPtr<nsIFile> parent;
168 for (int32_t i = 0; path && i < aDepth; i++) {
169 if (nsresult rv = path->GetParent(getter_AddRefs(parent)); NS_FAILED(rv)) {
170 ThrowError(aErr, rv, ERROR_GET_PARENT);
171 return;
173 path = parent;
176 if (parent) {
177 MOZ_ALWAYS_SUCCEEDS(parent->GetPath(aResult));
178 } else {
179 aResult = VoidString();
183 void PathUtils::Join(const GlobalObject&, const Sequence<nsString>& aComponents,
184 nsString& aResult, ErrorResult& aErr) {
185 nsCOMPtr<nsIFile> path = Join(Span(aComponents), aErr);
186 if (aErr.Failed()) {
187 return;
190 MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
193 already_AddRefed<nsIFile> PathUtils::Join(
194 const Span<const nsString>& aComponents, ErrorResult& aErr) {
195 if (aComponents.IsEmpty() || aComponents[0].IsEmpty()) {
196 aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
197 return nullptr;
200 nsCOMPtr<nsIFile> path = new nsLocalFile();
201 if (nsresult rv = InitFileWithPath(path, aComponents[0]); NS_FAILED(rv)) {
202 ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
203 return nullptr;
206 const auto components = Span<const nsString>(aComponents).Subspan(1);
207 for (const auto& component : components) {
208 if (nsresult rv = path->Append(component); NS_FAILED(rv)) {
209 ThrowError(aErr, rv, ERROR_JOIN);
210 return nullptr;
214 return path.forget();
217 void PathUtils::JoinRelative(const GlobalObject&, const nsAString& aBasePath,
218 const nsAString& aRelativePath, nsString& aResult,
219 ErrorResult& aErr) {
220 if (aRelativePath.IsEmpty()) {
221 aResult = aBasePath;
222 return;
225 nsCOMPtr<nsIFile> path = new nsLocalFile();
226 if (nsresult rv = InitFileWithPath(path, aBasePath); NS_FAILED(rv)) {
227 ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
228 return;
231 if (nsresult rv = path->AppendRelativePath(aRelativePath); NS_FAILED(rv)) {
232 ThrowError(aErr, rv, ERROR_JOIN);
233 return;
236 MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
239 void PathUtils::ToExtendedWindowsPath(const GlobalObject&,
240 const nsAString& aPath, nsString& aResult,
241 ErrorResult& aErr) {
242 #ifndef XP_WIN
243 aErr.ThrowNotAllowedError("Operation is windows specific"_ns);
244 return;
245 #else
246 if (aPath.IsEmpty()) {
247 aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
248 return;
251 const nsAString& str1 = Substring(aPath, 1, 1);
252 const nsAString& str2 = Substring(aPath, 2, aPath.Length() - 2);
254 bool isUNC = aPath.Length() >= 2 &&
255 (aPath.First() == '\\' || aPath.First() == '/') &&
256 (str1.EqualsLiteral("\\") || str1.EqualsLiteral("/"));
258 constexpr auto pathPrefix = u"\\\\?\\"_ns;
259 const nsAString& uncPath = pathPrefix + u"UNC\\"_ns + str2;
260 const nsAString& normalPath = pathPrefix + aPath;
262 nsCOMPtr<nsIFile> path = new nsLocalFile();
263 if (nsresult rv = InitFileWithPath(path, isUNC ? uncPath : normalPath);
264 NS_FAILED(rv)) {
265 ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
266 return;
269 MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
270 #endif
273 void PathUtils::Normalize(const GlobalObject&, const nsAString& aPath,
274 nsString& aResult, ErrorResult& aErr) {
275 if (aPath.IsEmpty()) {
276 aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
277 return;
280 nsCOMPtr<nsIFile> path = new nsLocalFile();
281 if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
282 ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
283 return;
286 if (nsresult rv = path->Normalize(); NS_FAILED(rv)) {
287 ThrowError(aErr, rv, "Could not normalize path"_ns);
288 return;
291 MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
294 void PathUtils::Split(const GlobalObject&, const nsAString& aPath,
295 nsTArray<nsString>& aResult, ErrorResult& aErr) {
296 if (aPath.IsEmpty()) {
297 aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
298 return;
301 nsCOMPtr<nsIFile> path = new nsLocalFile();
302 if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
303 ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
304 return;
307 while (path) {
308 auto* component = aResult.EmplaceBack(fallible);
309 if (!component) {
310 aErr.Throw(NS_ERROR_OUT_OF_MEMORY);
311 return;
314 nsCOMPtr<nsIFile> parent;
315 if (nsresult rv = path->GetParent(getter_AddRefs(parent)); NS_FAILED(rv)) {
316 ThrowError(aErr, rv, ERROR_GET_PARENT);
317 return;
320 // GetLeafPreservingRoot cannot fail if we pass it a parent path.
321 MOZ_ALWAYS_SUCCEEDS(GetLeafNamePreservingRoot(path, *component, parent));
323 path = parent;
326 aResult.Reverse();
329 void PathUtils::SplitRelative(const GlobalObject& aGlobal,
330 const nsAString& aPath,
331 const SplitRelativeOptions& aOptions,
332 nsTArray<nsString>& aResult, ErrorResult& aErr) {
333 if (aPath.IsEmpty()) {
334 aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
335 return;
338 if (DoWindowsPathCheck()) {
339 MOZ_RELEASE_ASSERT(!aPath.Contains(u'/'),
340 "Windows paths cannot include forward slashes");
343 if (IsAbsolute(aGlobal, aPath)) {
344 aErr.ThrowNotAllowedError(
345 "PathUtils.splitRelative requires a relative path"_ns);
346 return;
349 #ifdef XP_WIN
350 constexpr auto SEPARATOR = u'\\';
351 #else
352 constexpr auto SEPARATOR = u'/';
353 #endif
355 constexpr auto PARENT = u".."_ns;
356 constexpr auto CURRENT = u"."_ns;
358 for (const nsAString& pathComponent :
359 nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>{aPath,
360 SEPARATOR}
361 .ToRange()) {
362 if (!aOptions.mAllowEmpty && pathComponent.IsEmpty()) {
363 aErr.ThrowNotAllowedError(
364 "PathUtils.splitRelative: Empty directory components (\"\") not "
365 "allowed by options");
366 return;
369 if (!aOptions.mAllowParentDir && pathComponent == PARENT) {
370 aErr.ThrowNotAllowedError(
371 "PathUtils.splitRelative: Parent directory components (\"..\") not "
372 "allowed by options");
373 return;
376 if (!aOptions.mAllowCurrentDir && pathComponent == CURRENT) {
377 aErr.ThrowNotAllowedError(
378 "PathUtils.splitRelative: Current directory components (\".\") not "
379 "allowed by options");
380 return;
383 aResult.AppendElement(pathComponent);
387 void PathUtils::ToFileURI(const GlobalObject&, const nsAString& aPath,
388 nsCString& aResult, ErrorResult& aErr) {
389 if (aPath.IsEmpty()) {
390 aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
391 return;
394 nsCOMPtr<nsIFile> path = new nsLocalFile();
395 if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
396 ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
397 return;
400 nsCOMPtr<nsIURI> uri;
401 if (nsresult rv = NS_NewFileURI(getter_AddRefs(uri), path); NS_FAILED(rv)) {
402 ThrowError(aErr, rv, "Could not initialize File URI"_ns);
403 return;
406 if (nsresult rv = uri->GetSpec(aResult); NS_FAILED(rv)) {
407 ThrowError(aErr, rv, "Could not retrieve URI spec"_ns);
408 return;
412 bool PathUtils::IsAbsolute(const GlobalObject&, const nsAString& aPath) {
413 nsCOMPtr<nsIFile> path = new nsLocalFile();
414 nsresult rv = InitFileWithPath(path, aPath);
415 return NS_SUCCEEDED(rv);
418 void PathUtils::GetProfileDirSync(const GlobalObject&, nsString& aResult,
419 ErrorResult& aErr) {
420 MOZ_ASSERT(NS_IsMainThread());
422 auto guard = sDirCache.Lock();
423 DirectoryCache::Ensure(guard.ref())
424 .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::Profile);
426 void PathUtils::GetLocalProfileDirSync(const GlobalObject&, nsString& aResult,
427 ErrorResult& aErr) {
428 MOZ_ASSERT(NS_IsMainThread());
430 auto guard = sDirCache.Lock();
431 DirectoryCache::Ensure(guard.ref())
432 .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::LocalProfile);
434 void PathUtils::GetTempDirSync(const GlobalObject&, nsString& aResult,
435 ErrorResult& aErr) {
436 MOZ_ASSERT(NS_IsMainThread());
438 auto guard = sDirCache.Lock();
439 DirectoryCache::Ensure(guard.ref())
440 .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::Temp);
443 void PathUtils::GetXulLibraryPathSync(const GlobalObject&, nsString& aResult,
444 ErrorResult& aErr) {
445 MOZ_ASSERT(NS_IsMainThread());
447 auto guard = sDirCache.Lock();
448 DirectoryCache::Ensure(guard.ref())
449 .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::XulLibrary);
452 already_AddRefed<Promise> PathUtils::GetProfileDirAsync(
453 const GlobalObject& aGlobal, ErrorResult& aErr) {
454 MOZ_ASSERT(!NS_IsMainThread());
456 auto guard = sDirCache.Lock();
457 return DirectoryCache::Ensure(guard.ref())
458 .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::Profile);
461 already_AddRefed<Promise> PathUtils::GetLocalProfileDirAsync(
462 const GlobalObject& aGlobal, ErrorResult& aErr) {
463 MOZ_ASSERT(!NS_IsMainThread());
465 auto guard = sDirCache.Lock();
466 return DirectoryCache::Ensure(guard.ref())
467 .GetDirectoryAsync(aGlobal, aErr,
468 DirectoryCache::Directory::LocalProfile);
471 already_AddRefed<Promise> PathUtils::GetTempDirAsync(
472 const GlobalObject& aGlobal, ErrorResult& aErr) {
473 MOZ_ASSERT(!NS_IsMainThread());
475 auto guard = sDirCache.Lock();
476 return DirectoryCache::Ensure(guard.ref())
477 .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::Temp);
480 already_AddRefed<Promise> PathUtils::GetXulLibraryPathAsync(
481 const GlobalObject& aGlobal, ErrorResult& aErr) {
482 MOZ_ASSERT(!NS_IsMainThread());
484 auto guard = sDirCache.Lock();
485 return DirectoryCache::Ensure(guard.ref())
486 .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::XulLibrary);
489 PathUtils::DirectoryCache::DirectoryCache() {
490 for (auto& dir : mDirectories) {
491 dir.SetIsVoid(true);
495 PathUtils::DirectoryCache& PathUtils::DirectoryCache::Ensure(
496 Maybe<PathUtils::DirectoryCache>& aCache) {
497 if (aCache.isNothing()) {
498 aCache.emplace();
500 auto clearAtShutdown = []() {
501 RunOnShutdown([]() {
502 auto cache = PathUtils::sDirCache.Lock();
503 cache->reset();
507 if (NS_IsMainThread()) {
508 clearAtShutdown();
509 } else {
510 NS_DispatchToMainThread(
511 NS_NewRunnableFunction(__func__, std::move(clearAtShutdown)));
515 return aCache.ref();
518 void PathUtils::DirectoryCache::GetDirectorySync(
519 nsString& aResult, ErrorResult& aErr, const Directory aRequestedDir) {
520 MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
522 if (nsresult rv = PopulateDirectoriesImpl(aRequestedDir); NS_FAILED(rv)) {
523 nsAutoCStringN<32> errorName;
524 GetErrorName(rv, errorName);
526 nsAutoCStringN<256> msg;
527 msg.Append("Could not retrieve directory "_ns);
528 msg.Append(kDirectoryNames[aRequestedDir]);
529 msg.Append(COLON);
530 msg.Append(errorName);
532 aErr.ThrowUnknownError(msg);
533 return;
536 aResult = mDirectories[aRequestedDir];
539 already_AddRefed<Promise> PathUtils::DirectoryCache::GetDirectoryAsync(
540 const GlobalObject& aGlobal, ErrorResult& aErr,
541 const Directory aRequestedDir) {
542 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
543 RefPtr<Promise> promise = Promise::Create(global, aErr);
544 if (aErr.Failed()) {
545 return nullptr;
548 if (RefPtr<PopulateDirectoriesPromise> p =
549 PopulateDirectories(aRequestedDir)) {
550 p->Then(
551 GetCurrentSerialEventTarget(), __func__,
552 [promise, aRequestedDir](const Ok&) {
553 auto cache = PathUtils::sDirCache.Lock();
554 cache.ref()->ResolveWithDirectory(promise, aRequestedDir);
556 [promise](const nsresult& aRv) { promise->MaybeReject(aRv); });
557 } else {
558 ResolveWithDirectory(promise, aRequestedDir);
561 return promise.forget();
564 void PathUtils::DirectoryCache::ResolveWithDirectory(
565 Promise* aPromise, const Directory aRequestedDir) {
566 MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
567 MOZ_RELEASE_ASSERT(!mDirectories[aRequestedDir].IsVoid());
568 aPromise->MaybeResolve(mDirectories[aRequestedDir]);
571 already_AddRefed<PathUtils::DirectoryCache::PopulateDirectoriesPromise>
572 PathUtils::DirectoryCache::PopulateDirectories(
573 const PathUtils::DirectoryCache::Directory aRequestedDir) {
574 MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
576 // If we have already resolved the requested directory, we can return
577 // immediately.
578 // Otherwise, if we have already fired off a request to populate the entry,
579 // so we can return the corresponding promise immediately. caller will queue
580 // a Thenable onto that promise to resolve/reject the request.
581 if (!mDirectories[aRequestedDir].IsVoid()) {
582 return nullptr;
584 if (!mPromises[aRequestedDir].IsEmpty()) {
585 return mPromises[aRequestedDir].Ensure(__func__);
588 RefPtr<PopulateDirectoriesPromise> promise =
589 mPromises[aRequestedDir].Ensure(__func__);
591 if (NS_IsMainThread()) {
592 nsresult rv = PopulateDirectoriesImpl(aRequestedDir);
593 ResolvePopulateDirectoriesPromise(rv, aRequestedDir);
594 } else {
595 nsCOMPtr<nsIRunnable> runnable =
596 NS_NewRunnableFunction(__func__, [aRequestedDir]() {
597 auto cache = PathUtils::sDirCache.Lock();
598 nsresult rv = cache.ref()->PopulateDirectoriesImpl(aRequestedDir);
599 cache.ref()->ResolvePopulateDirectoriesPromise(rv, aRequestedDir);
601 NS_DispatchToMainThread(runnable.forget());
604 return promise.forget();
607 void PathUtils::DirectoryCache::ResolvePopulateDirectoriesPromise(
608 nsresult aRv, const PathUtils::DirectoryCache::Directory aRequestedDir) {
609 MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
611 if (NS_SUCCEEDED(aRv)) {
612 mPromises[aRequestedDir].Resolve(Ok{}, __func__);
613 } else {
614 mPromises[aRequestedDir].Reject(aRv, __func__);
618 nsresult PathUtils::DirectoryCache::PopulateDirectoriesImpl(
619 const PathUtils::DirectoryCache::Directory aRequestedDir) {
620 MOZ_RELEASE_ASSERT(NS_IsMainThread());
621 MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
623 if (!mDirectories[aRequestedDir].IsVoid()) {
624 // In between when this promise was dispatched to the main thread and now,
625 // the directory cache has had this entry populated (via the
626 // on-main-thread sync method).
627 return NS_OK;
630 nsCOMPtr<nsIFile> path;
632 MOZ_TRY(NS_GetSpecialDirectory(kDirectoryNames[aRequestedDir],
633 getter_AddRefs(path)));
634 MOZ_TRY(path->GetPath(mDirectories[aRequestedDir]));
636 return NS_OK;
639 } // namespace mozilla::dom