Source code

Revision control

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Mac OS X-specific local file uri parsing */
#include "nsURLHelper.h"
#include "nsEscape.h"
#include "nsIFile.h"
#include "nsTArray.h"
#include "nsReadableUtils.h"
#include <Carbon/Carbon.h>

static nsTArray<nsCString> *gVolumeList = nullptr;

static bool pathBeginsWithVolName(const nsACString &path,
                                  nsACString &firstPathComponent) {
  // Return whether the 1st path component in path (escaped) is equal to the
  // name of a mounted volume. Return the 1st path component (unescaped) in any
  // case. This needs to be done as quickly as possible, so we cache a list of
  // volume names.
  // XXX Register an event handler to detect drives being mounted/unmounted?

  if (!gVolumeList) {
    gVolumeList = new nsTArray<nsCString>;
    if (!gVolumeList) {
      return false;  // out of memory
    }
  }

  // Cache a list of volume names
  if (!gVolumeList->Length()) {
    OSErr err;
    ItemCount volumeIndex = 1;

    do {
      HFSUniStr255 volName;
      FSRef rootDirectory;
      err = ::FSGetVolumeInfo(0, volumeIndex, nullptr, kFSVolInfoNone, nullptr,
                              &volName, &rootDirectory);
      if (err == noErr) {
        NS_ConvertUTF16toUTF8 volNameStr(
            Substring((char16_t *)volName.unicode,
                      (char16_t *)volName.unicode + volName.length));
        gVolumeList->AppendElement(volNameStr);
        volumeIndex++;
      }
    } while (err == noErr);
  }

  // Extract the first component of the path
  nsACString::const_iterator start;
  path.BeginReading(start);
  start.advance(1);  // path begins with '/'
  nsACString::const_iterator directory_end;
  path.EndReading(directory_end);
  nsACString::const_iterator component_end(start);
  FindCharInReadable('/', component_end, directory_end);

  nsAutoCString flatComponent((Substring(start, component_end)));
  NS_UnescapeURL(flatComponent);
  int32_t foundIndex = gVolumeList->IndexOf(flatComponent);
  firstPathComponent = flatComponent;
  return (foundIndex != -1);
}

void net_ShutdownURLHelperOSX() {
  delete gVolumeList;
  gVolumeList = nullptr;
}

static nsresult convertHFSPathtoPOSIX(const nsACString &hfsPath,
                                      nsACString &posixPath) {
  // Use CFURL to do the conversion. We don't want to do this by simply
  // using SwapSlashColon - we need the charset mapped from MacRoman
  // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject
  // to change) prepended if the path is not on the boot drive.

  CFStringRef pathStrRef = CFStringCreateWithCString(
      nullptr, PromiseFlatCString(hfsPath).get(), kCFStringEncodingMacRoman);
  if (!pathStrRef) return NS_ERROR_FAILURE;

  nsresult rv = NS_ERROR_FAILURE;
  CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr, pathStrRef,
                                                  kCFURLHFSPathStyle, true);
  if (urlRef) {
    UInt8 pathBuf[PATH_MAX];
    if (CFURLGetFileSystemRepresentation(urlRef, true, pathBuf,
                                         sizeof(pathBuf))) {
      posixPath = (char *)pathBuf;
      rv = NS_OK;
    }
  }
  CFRelease(pathStrRef);
  if (urlRef) CFRelease(urlRef);
  return rv;
}

static void SwapSlashColon(char *s) {
  while (*s) {
    if (*s == '/')
      *s = ':';
    else if (*s == ':')
      *s = '/';
    s++;
  }
}

nsresult net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result) {
  // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp

  nsresult rv;
  nsAutoCString ePath;

  // construct URL spec from native file path
  rv = aFile->GetNativePath(ePath);
  if (NS_FAILED(rv)) return rv;

  nsAutoCString escPath;
  NS_NAMED_LITERAL_CSTRING(prefix, "file://");

  // Escape the path with the directory mask
  if (NS_EscapeURL(ePath.get(), ePath.Length(), esc_Directory + esc_Forced,
                   escPath))
    escPath.Insert(prefix, 0);
  else
    escPath.Assign(prefix + ePath);

  // esc_Directory does not escape the semicolons, so if a filename
  // contains semicolons we need to manually escape them.
  // This replacement should be removed in bug #473280
  escPath.ReplaceSubstring(";", "%3b");

  result = escPath;
  return NS_OK;
}

nsresult net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result) {
  // NOTE: See also the implementation in nsURLHelperUnix.cpp
  // This matches it except for the HFS path handling.

  nsresult rv;

  nsCOMPtr<nsIFile> localFile;
  rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localFile));
  if (NS_FAILED(rv)) return rv;

  nsAutoCString directory, fileBaseName, fileExtension, path;
  bool bHFSPath = false;

  rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
  if (NS_FAILED(rv)) return rv;

  if (!directory.IsEmpty()) {
    NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path);

    // The canonical form of file URLs on OSX use POSIX paths:
    //   file:///path-name.
    // But, we still encounter file URLs that use HFS paths:
    //   file:///volume-name/path-name
    // Determine that here and normalize HFS paths to POSIX.
    nsAutoCString possibleVolName;
    if (pathBeginsWithVolName(directory, possibleVolName)) {
      // Though we know it begins with a volume name, it could still
      // be a valid POSIX path if the boot drive is named "Mac HD"
      // and there is a directory "Mac HD" at its root. If such a
      // directory doesn't exist, we'll assume this is an HFS path.
      FSRef testRef;
      possibleVolName.InsertLiteral("/", 0);
      if (::FSPathMakeRef((UInt8 *)possibleVolName.get(), &testRef, nullptr) !=
          noErr)
        bHFSPath = true;
    }

    if (bHFSPath) {
      // "%2F"s need to become slashes, while all other slashes need to
      // become colons. If we start out by changing "%2F"s to colons, we
      // can reply on SwapSlashColon() to do what we need
      path.ReplaceSubstring("%2F", ":");
      path.Cut(0, 1);  // directory begins with '/'
      SwapSlashColon((char *)path.get());
      // At this point, path is an HFS path made using the same
      // algorithm as nsURLHelperMac. We'll convert to POSIX below.
    }
  }
  if (!fileBaseName.IsEmpty())
    NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path);
  if (!fileExtension.IsEmpty()) {
    path += '.';
    NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path);
  }

  NS_UnescapeURL(path);
  if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH;

  if (bHFSPath) convertHFSPathtoPOSIX(path, path);

  // assuming path is encoded in the native charset
  rv = localFile->InitWithNativePath(path);
  if (NS_FAILED(rv)) return rv;

  localFile.forget(result);
  return NS_OK;
}