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;
}