michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* Mac OS X-specific local file uri parsing */ michael@0: #include "nsURLHelper.h" michael@0: #include "nsEscape.h" michael@0: #include "nsIFile.h" michael@0: #include "nsTArray.h" michael@0: #include "nsReadableUtils.h" michael@0: #include michael@0: michael@0: static nsTArray *gVolumeList = nullptr; michael@0: michael@0: static bool pathBeginsWithVolName(const nsACString& path, nsACString& firstPathComponent) michael@0: { michael@0: // Return whether the 1st path component in path (escaped) is equal to the name michael@0: // of a mounted volume. Return the 1st path component (unescaped) in any case. michael@0: // This needs to be done as quickly as possible, so we cache a list of volume names. michael@0: // XXX Register an event handler to detect drives being mounted/unmounted? michael@0: michael@0: if (!gVolumeList) { michael@0: gVolumeList = new nsTArray; michael@0: if (!gVolumeList) { michael@0: return false; // out of memory michael@0: } michael@0: } michael@0: michael@0: // Cache a list of volume names michael@0: if (!gVolumeList->Length()) { michael@0: OSErr err; michael@0: ItemCount volumeIndex = 1; michael@0: michael@0: do { michael@0: HFSUniStr255 volName; michael@0: FSRef rootDirectory; michael@0: err = ::FSGetVolumeInfo(0, volumeIndex, nullptr, kFSVolInfoNone, nullptr, michael@0: &volName, &rootDirectory); michael@0: if (err == noErr) { michael@0: NS_ConvertUTF16toUTF8 volNameStr(Substring((char16_t *)volName.unicode, michael@0: (char16_t *)volName.unicode + volName.length)); michael@0: gVolumeList->AppendElement(volNameStr); michael@0: volumeIndex++; michael@0: } michael@0: } while (err == noErr); michael@0: } michael@0: michael@0: // Extract the first component of the path michael@0: nsACString::const_iterator start; michael@0: path.BeginReading(start); michael@0: start.advance(1); // path begins with '/' michael@0: nsACString::const_iterator directory_end; michael@0: path.EndReading(directory_end); michael@0: nsACString::const_iterator component_end(start); michael@0: FindCharInReadable('/', component_end, directory_end); michael@0: michael@0: nsAutoCString flatComponent((Substring(start, component_end))); michael@0: NS_UnescapeURL(flatComponent); michael@0: int32_t foundIndex = gVolumeList->IndexOf(flatComponent); michael@0: firstPathComponent = flatComponent; michael@0: return (foundIndex != -1); michael@0: } michael@0: michael@0: void michael@0: net_ShutdownURLHelperOSX() michael@0: { michael@0: delete gVolumeList; michael@0: gVolumeList = nullptr; michael@0: } michael@0: michael@0: static nsresult convertHFSPathtoPOSIX(const nsACString& hfsPath, nsACString& posixPath) michael@0: { michael@0: // Use CFURL to do the conversion. We don't want to do this by simply michael@0: // using SwapSlashColon - we need the charset mapped from MacRoman michael@0: // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject to change) michael@0: // prepended if the path is not on the boot drive. michael@0: michael@0: CFStringRef pathStrRef = CFStringCreateWithCString(nullptr, michael@0: PromiseFlatCString(hfsPath).get(), michael@0: kCFStringEncodingMacRoman); michael@0: if (!pathStrRef) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr, michael@0: pathStrRef, kCFURLHFSPathStyle, true); michael@0: if (urlRef) { michael@0: UInt8 pathBuf[PATH_MAX]; michael@0: if (CFURLGetFileSystemRepresentation(urlRef, true, pathBuf, sizeof(pathBuf))) { michael@0: posixPath = (char *)pathBuf; michael@0: rv = NS_OK; michael@0: } michael@0: } michael@0: CFRelease(pathStrRef); michael@0: if (urlRef) michael@0: CFRelease(urlRef); michael@0: return rv; michael@0: } michael@0: michael@0: static void SwapSlashColon(char *s) michael@0: { michael@0: while (*s) { michael@0: if (*s == '/') michael@0: *s = ':'; michael@0: else if (*s == ':') michael@0: *s = '/'; michael@0: s++; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result) michael@0: { michael@0: // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp michael@0: michael@0: nsresult rv; michael@0: nsAutoCString ePath; michael@0: michael@0: // construct URL spec from native file path michael@0: rv = aFile->GetNativePath(ePath); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsAutoCString escPath; michael@0: NS_NAMED_LITERAL_CSTRING(prefix, "file://"); michael@0: michael@0: // Escape the path with the directory mask michael@0: if (NS_EscapeURL(ePath.get(), ePath.Length(), esc_Directory+esc_Forced, escPath)) michael@0: escPath.Insert(prefix, 0); michael@0: else michael@0: escPath.Assign(prefix + ePath); michael@0: michael@0: // esc_Directory does not escape the semicolons, so if a filename michael@0: // contains semicolons we need to manually escape them. michael@0: // This replacement should be removed in bug #473280 michael@0: escPath.ReplaceSubstring(";", "%3b"); michael@0: michael@0: result = escPath; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result) michael@0: { michael@0: // NOTE: See also the implementation in nsURLHelperUnix.cpp michael@0: // This matches it except for the HFS path handling. michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr localFile; michael@0: rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localFile)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsAutoCString directory, fileBaseName, fileExtension, path; michael@0: bool bHFSPath = false; michael@0: michael@0: rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!directory.IsEmpty()) { michael@0: NS_EscapeURL(directory, esc_Directory|esc_AlwaysCopy, path); michael@0: michael@0: // The canonical form of file URLs on OSX use POSIX paths: michael@0: // file:///path-name. michael@0: // But, we still encounter file URLs that use HFS paths: michael@0: // file:///volume-name/path-name michael@0: // Determine that here and normalize HFS paths to POSIX. michael@0: nsAutoCString possibleVolName; michael@0: if (pathBeginsWithVolName(directory, possibleVolName)) { michael@0: // Though we know it begins with a volume name, it could still michael@0: // be a valid POSIX path if the boot drive is named "Mac HD" michael@0: // and there is a directory "Mac HD" at its root. If such a michael@0: // directory doesn't exist, we'll assume this is an HFS path. michael@0: FSRef testRef; michael@0: possibleVolName.Insert("/", 0); michael@0: if (::FSPathMakeRef((UInt8*)possibleVolName.get(), &testRef, nullptr) != noErr) michael@0: bHFSPath = true; michael@0: } michael@0: michael@0: if (bHFSPath) { michael@0: // "%2F"s need to become slashes, while all other slashes need to michael@0: // become colons. If we start out by changing "%2F"s to colons, we michael@0: // can reply on SwapSlashColon() to do what we need michael@0: path.ReplaceSubstring("%2F", ":"); michael@0: path.Cut(0, 1); // directory begins with '/' michael@0: SwapSlashColon((char *)path.get()); michael@0: // At this point, path is an HFS path made using the same michael@0: // algorithm as nsURLHelperMac. We'll convert to POSIX below. michael@0: } michael@0: } michael@0: if (!fileBaseName.IsEmpty()) michael@0: NS_EscapeURL(fileBaseName, esc_FileBaseName|esc_AlwaysCopy, path); michael@0: if (!fileExtension.IsEmpty()) { michael@0: path += '.'; michael@0: NS_EscapeURL(fileExtension, esc_FileExtension|esc_AlwaysCopy, path); michael@0: } michael@0: michael@0: NS_UnescapeURL(path); michael@0: if (path.Length() != strlen(path.get())) michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: michael@0: if (bHFSPath) michael@0: convertHFSPathtoPOSIX(path, path); michael@0: michael@0: // assuming path is encoded in the native charset michael@0: rv = localFile->InitWithNativePath(path); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: NS_ADDREF(*result = localFile); michael@0: return NS_OK; michael@0: }