1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* Mac OS X-specific local file uri parsing */
8 #include "nsURLHelper.h"
12 #include "nsReadableUtils.h"
13 #include <Carbon/Carbon.h>
15 static nsTArray
<nsCString
>* gVolumeList
= nullptr;
17 static bool pathBeginsWithVolName(const nsACString
& path
,
18 nsACString
& firstPathComponent
) {
19 // Return whether the 1st path component in path (escaped) is equal to the
20 // name of a mounted volume. Return the 1st path component (unescaped) in any
21 // case. This needs to be done as quickly as possible, so we cache a list of
23 // XXX Register an event handler to detect drives being mounted/unmounted?
26 gVolumeList
= new nsTArray
<nsCString
>;
28 return false; // out of memory
32 // Cache a list of volume names
33 if (!gVolumeList
->Length()) {
35 ItemCount volumeIndex
= 1;
40 err
= ::FSGetVolumeInfo(0, volumeIndex
, nullptr, kFSVolInfoNone
, nullptr,
41 &volName
, &rootDirectory
);
43 NS_ConvertUTF16toUTF8
volNameStr(
44 Substring((char16_t
*)volName
.unicode
,
45 (char16_t
*)volName
.unicode
+ volName
.length
));
46 gVolumeList
->AppendElement(volNameStr
);
49 } while (err
== noErr
);
52 // Extract the first component of the path
53 nsACString::const_iterator start
;
54 path
.BeginReading(start
);
55 start
.advance(1); // path begins with '/'
56 nsACString::const_iterator directory_end
;
57 path
.EndReading(directory_end
);
58 nsACString::const_iterator
component_end(start
);
59 FindCharInReadable('/', component_end
, directory_end
);
61 nsAutoCString
flatComponent((Substring(start
, component_end
)));
62 NS_UnescapeURL(flatComponent
);
63 int32_t foundIndex
= gVolumeList
->IndexOf(flatComponent
);
64 firstPathComponent
= flatComponent
;
65 return (foundIndex
!= -1);
68 void net_ShutdownURLHelperOSX() {
70 gVolumeList
= nullptr;
73 static nsresult
convertHFSPathtoPOSIX(const nsACString
& hfsPath
,
74 nsACString
& posixPath
) {
75 // Use CFURL to do the conversion. We don't want to do this by simply
76 // using SwapSlashColon - we need the charset mapped from MacRoman
77 // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject
78 // to change) prepended if the path is not on the boot drive.
80 CFStringRef pathStrRef
= CFStringCreateWithCString(
81 nullptr, PromiseFlatCString(hfsPath
).get(), kCFStringEncodingMacRoman
);
82 if (!pathStrRef
) return NS_ERROR_FAILURE
;
84 nsresult rv
= NS_ERROR_FAILURE
;
85 CFURLRef urlRef
= CFURLCreateWithFileSystemPath(nullptr, pathStrRef
,
86 kCFURLHFSPathStyle
, true);
88 UInt8 pathBuf
[PATH_MAX
];
89 if (CFURLGetFileSystemRepresentation(urlRef
, true, pathBuf
,
91 posixPath
= (char*)pathBuf
;
95 CFRelease(pathStrRef
);
96 if (urlRef
) CFRelease(urlRef
);
100 static void SwapSlashColon(char* s
) {
110 nsresult
net_GetURLSpecFromActualFile(nsIFile
* aFile
, nsACString
& result
) {
111 // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp
116 // construct URL spec from native file path
117 rv
= aFile
->GetNativePath(ePath
);
118 if (NS_FAILED(rv
)) return rv
;
120 nsAutoCString escPath
;
121 constexpr auto prefix
= "file://"_ns
;
123 // Escape the path with the directory mask
124 if (NS_EscapeURL(ePath
.get(), ePath
.Length(), esc_Directory
+ esc_Forced
,
126 escPath
.Insert(prefix
, 0);
128 escPath
.Assign(prefix
+ ePath
);
130 // esc_Directory does not escape the semicolons, so if a filename
131 // contains semicolons we need to manually escape them.
132 // This replacement should be removed in bug #473280
133 escPath
.ReplaceSubstring(";", "%3b");
139 nsresult
net_GetFileFromURLSpec(const nsACString
& aURL
, nsIFile
** result
) {
140 // NOTE: See also the implementation in nsURLHelperUnix.cpp
141 // This matches it except for the HFS path handling.
145 nsCOMPtr
<nsIFile
> localFile
;
146 rv
= NS_NewNativeLocalFile(""_ns
, true, getter_AddRefs(localFile
));
147 if (NS_FAILED(rv
)) return rv
;
149 nsAutoCString directory
, fileBaseName
, fileExtension
, path
;
150 bool bHFSPath
= false;
152 rv
= net_ParseFileURL(aURL
, directory
, fileBaseName
, fileExtension
);
153 if (NS_FAILED(rv
)) return rv
;
155 if (!directory
.IsEmpty()) {
156 NS_EscapeURL(directory
, esc_Directory
| esc_AlwaysCopy
, path
);
158 // The canonical form of file URLs on OSX use POSIX paths:
159 // file:///path-name.
160 // But, we still encounter file URLs that use HFS paths:
161 // file:///volume-name/path-name
162 // Determine that here and normalize HFS paths to POSIX.
163 nsAutoCString possibleVolName
;
164 if (pathBeginsWithVolName(directory
, possibleVolName
)) {
165 // Though we know it begins with a volume name, it could still
166 // be a valid POSIX path if the boot drive is named "Mac HD"
167 // and there is a directory "Mac HD" at its root. If such a
168 // directory doesn't exist, we'll assume this is an HFS path.
170 possibleVolName
.InsertLiteral("/", 0);
171 if (::FSPathMakeRef((UInt8
*)possibleVolName
.get(), &testRef
, nullptr) !=
177 // "%2F"s need to become slashes, while all other slashes need to
178 // become colons. If we start out by changing "%2F"s to colons, we
179 // can reply on SwapSlashColon() to do what we need
180 path
.ReplaceSubstring("%2F", ":");
181 path
.Cut(0, 1); // directory begins with '/'
182 SwapSlashColon((char*)path
.get());
183 // At this point, path is an HFS path made using the same
184 // algorithm as nsURLHelperMac. We'll convert to POSIX below.
187 if (!fileBaseName
.IsEmpty())
188 NS_EscapeURL(fileBaseName
, esc_FileBaseName
| esc_AlwaysCopy
, path
);
189 if (!fileExtension
.IsEmpty()) {
191 NS_EscapeURL(fileExtension
, esc_FileExtension
| esc_AlwaysCopy
, path
);
194 NS_UnescapeURL(path
);
195 if (path
.Length() != strlen(path
.get())) return NS_ERROR_FILE_INVALID_PATH
;
197 if (bHFSPath
) convertHFSPathtoPOSIX(path
, path
);
199 // assuming path is encoded in the native charset
200 rv
= localFile
->InitWithNativePath(path
);
201 if (NS_FAILED(rv
)) return rv
;
203 localFile
.forget(result
);