Skip to content

Commit e2220be

Browse files
committed
Add Support for '\' in Windows Paths
While URLs get converted to forward slashes on Windows, strings are handled as is and thus need to be able to handle \ separators. Since many Windows apis also support /, we'll handle that as well, but default to \ if required.
1 parent f9b18eb commit e2220be

File tree

2 files changed

+55
-49
lines changed

2 files changed

+55
-49
lines changed

Foundation/NSPathUtilities.swift

+34-28
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99

1010
import CoreFoundation
1111

12+
#if os(Windows)
13+
let defaultPathSep = "\\"
14+
let validPathSeps: [Character] = ["\\", "/"]
15+
#else
16+
let defaultPathSep = "/"
17+
let validPathSeps: [Character] = ["/"]
18+
#endif
19+
1220
public func NSTemporaryDirectory() -> String {
1321
#if os(Windows)
1422
let cchLength: DWORD = GetTempPathW(0, nil)
@@ -51,8 +59,8 @@ public func NSTemporaryDirectory() -> String {
5159
}
5260
#endif
5361
if let tmpdir = ProcessInfo.processInfo.environment["TMPDIR"] {
54-
if !tmpdir.hasSuffix("/") {
55-
return tmpdir + "/"
62+
if !validPathSeps.contains(where: { tmpdir.hasSuffix(String($0)) }) {
63+
return tmpdir + defaultPathSep
5664
} else {
5765
return tmpdir
5866
}
@@ -70,15 +78,15 @@ public func NSTemporaryDirectory() -> String {
7078
extension String {
7179

7280
internal var _startOfLastPathComponent : String.Index {
73-
precondition(!hasSuffix("/") && length > 1)
81+
precondition(!validPathSeps.contains(where: { hasSuffix(String($0)) }) && length > 1)
7482

7583
let startPos = startIndex
7684
var curPos = endIndex
7785

7886
// Find the beginning of the component
7987
while curPos > startPos {
8088
let prevPos = index(before: curPos)
81-
if self[prevPos] == "/" {
89+
if validPathSeps.contains(self[prevPos]) {
8290
break
8391
}
8492
curPos = prevPos
@@ -88,7 +96,7 @@ extension String {
8896
}
8997

9098
internal var _startOfPathExtension : String.Index? {
91-
precondition(!hasSuffix("/"))
99+
precondition(!validPathSeps.contains(where: { hasSuffix(String($0)) }))
92100

93101
var currentPosition = endIndex
94102
let startOfLastPathComponent = _startOfLastPathComponent
@@ -97,7 +105,7 @@ extension String {
97105
while currentPosition > startOfLastPathComponent {
98106
let previousPosition = index(before: currentPosition)
99107
let character = self[previousPosition]
100-
if character == "/" {
108+
if validPathSeps.contains(character) {
101109
return nil
102110
} else if character == "." {
103111
if startOfLastPathComponent == previousPosition {
@@ -132,10 +140,10 @@ extension String {
132140
if isEmpty {
133141
return str
134142
}
135-
if hasSuffix("/") {
143+
if validPathSeps.contains(where: { hasSuffix(String($0)) }) {
136144
return self + str
137145
}
138-
return self + "/" + str
146+
return self + defaultPathSep + str
139147
}
140148

141149
internal func _stringByFixingSlashes(compress : Bool = true, stripTrailing: Bool = true) -> String {
@@ -146,13 +154,13 @@ extension String {
146154
var curPos = startPos
147155

148156
while curPos < endPos {
149-
if result[curPos] == "/" {
157+
if validPathSeps.contains(result[curPos]) {
150158
var afterLastSlashPos = curPos
151-
while afterLastSlashPos < endPos && result[afterLastSlashPos] == "/" {
159+
while afterLastSlashPos < endPos && validPathSeps.contains(result[afterLastSlashPos]) {
152160
afterLastSlashPos = result.index(after: afterLastSlashPos)
153161
}
154162
if afterLastSlashPos != result.index(after: curPos) {
155-
result.replaceSubrange(curPos ..< afterLastSlashPos, with: ["/"])
163+
result.replaceSubrange(curPos ..< afterLastSlashPos, with: [Character(defaultPathSep)])
156164
endPos = result.endIndex
157165
}
158166
curPos = afterLastSlashPos
@@ -161,7 +169,7 @@ extension String {
161169
}
162170
}
163171
}
164-
if stripTrailing && result.length > 1 && result.hasSuffix("/") {
172+
if stripTrailing && result.length > 1 && validPathSeps.contains(where: {result.hasSuffix(String($0))}) {
165173
result.remove(at: result.index(before: result.endIndex))
166174
}
167175
return result
@@ -227,7 +235,7 @@ extension NSString {
227235

228236
public var deletingLastPathComponent : String {
229237
let fixedSelf = _stringByFixingSlashes()
230-
if fixedSelf == "/" || fixedSelf == "" {
238+
if validPathSeps.contains(where: { String($0) == fixedSelf}) || fixedSelf == "" {
231239
return fixedSelf
232240
}
233241

@@ -239,7 +247,7 @@ extension NSString {
239247

240248
// absolute path, single component
241249
case fixedSelf.index(after: fixedSelf.startIndex):
242-
return "/"
250+
return defaultPathSep
243251

244252
// all common cases
245253
case let startOfLast:
@@ -248,7 +256,7 @@ extension NSString {
248256
}
249257

250258
internal func _stringByFixingSlashes(compress : Bool = true, stripTrailing: Bool = true) -> String {
251-
if _swiftObject == "/" {
259+
if validPathSeps.contains(where: { String($0) == _swiftObject }) {
252260
return _swiftObject
253261
}
254262

@@ -259,13 +267,13 @@ extension NSString {
259267
var curPos = startPos
260268

261269
while curPos < endPos {
262-
if result[curPos] == "/" {
270+
if validPathSeps.contains(result[curPos]) {
263271
var afterLastSlashPos = curPos
264-
while afterLastSlashPos < endPos && result[afterLastSlashPos] == "/" {
272+
while afterLastSlashPos < endPos && validPathSeps.contains(result[afterLastSlashPos]) {
265273
afterLastSlashPos = result.index(after: afterLastSlashPos)
266274
}
267275
if afterLastSlashPos != result.index(after: curPos) {
268-
result.replaceSubrange(curPos ..< afterLastSlashPos, with: ["/"])
276+
result.replaceSubrange(curPos ..< afterLastSlashPos, with: [Character(defaultPathSep)])
269277
endPos = result.endIndex
270278
}
271279
curPos = afterLastSlashPos
@@ -274,7 +282,7 @@ extension NSString {
274282
}
275283
}
276284
}
277-
if stripTrailing && result.hasSuffix("/") {
285+
if stripTrailing && validPathSeps.contains(where: { result.hasSuffix(String($0)) }) {
278286
result.remove(at: result.index(before: result.endIndex))
279287
}
280288
return result
@@ -314,7 +322,7 @@ extension NSString {
314322
}
315323

316324
public func appendingPathExtension(_ str: String) -> String? {
317-
if str.hasPrefix("/") || self == "" || self == "/" {
325+
if validPathSeps.contains(where: { str.hasPrefix(String($0)) }) || self == "" || validPathSeps.contains(where: { String($0)._nsObject == self }) {
318326
print("Cannot append extension \(str) to path \(self)")
319327
return nil
320328
}
@@ -327,7 +335,7 @@ extension NSString {
327335
return _swiftObject
328336
}
329337

330-
let endOfUserName = _swiftObject.firstIndex(of: "/") ?? _swiftObject.endIndex
338+
let endOfUserName = _swiftObject.firstIndex(where : { validPathSeps.contains($0) }) ?? _swiftObject.endIndex
331339
let startOfUserName = _swiftObject.index(after: _swiftObject.startIndex)
332340
let userName = String(_swiftObject[startOfUserName..<endOfUserName])
333341
let optUserName: String? = userName.isEmpty ? nil : userName
@@ -359,12 +367,10 @@ extension NSString {
359367
}
360368

361369
// TODO: pathComponents keeps final path separator if any. Check that logic.
362-
if components.last == "/" && components.count > 1 {
370+
if validPathSeps.contains(where: { String($0) == components.last }) && components.count > 1 {
363371
components.removeLast()
364372
}
365373

366-
let isAbsolutePath = components.first == "/"
367-
368374
var resolvedPath = components.removeFirst()
369375
for component in components {
370376
switch component {
@@ -426,7 +432,7 @@ extension NSString {
426432
let commonPath = searchAllFilesInDirectory ? path : _ensureLastPathSeparator(deletingLastPathComponent)
427433

428434
if searchAllFilesInDirectory {
429-
outputName = "/"
435+
outputName = defaultPathSep
430436
} else {
431437
if let lcp = _longestCommonPrefix(matches, caseSensitive: flag) {
432438
outputName = (commonPath + lcp)
@@ -439,7 +445,7 @@ extension NSString {
439445
}
440446

441447
internal func _stringIsPathToDirectory(_ path: String) -> Bool {
442-
if !path.hasSuffix("/") {
448+
if !validPathSeps.contains(where: { path.hasSuffix(String($0)) }) {
443449
return false
444450
}
445451

@@ -538,11 +544,11 @@ extension NSString {
538544
}
539545

540546
internal func _ensureLastPathSeparator(_ path: String) -> String {
541-
if path.hasSuffix("/") || path.isEmpty {
547+
if validPathSeps.contains(where: { path.hasSuffix(String($0)) }) || path.isEmpty {
542548
return path
543549
}
544550

545-
return path + "/"
551+
return path + defaultPathSep
546552
}
547553

548554
public var fileSystemRepresentation: UnsafePointer<Int8> {

Foundation/NSURL.swift

+21-21
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,27 @@ internal func _pathComponents(_ path: String?) -> [String]? {
4747
let characterView = p
4848
var curPos = characterView.startIndex
4949
let endPos = characterView.endIndex
50-
if characterView[curPos] == "/" {
51-
result.append("/")
50+
if validPathSeps.contains(characterView[curPos]) {
51+
result.append(defaultPathSep)
5252
}
5353

5454
while curPos < endPos {
55-
while curPos < endPos && characterView[curPos] == "/" {
55+
while curPos < endPos && validPathSeps.contains(characterView[curPos]) {
5656
curPos = characterView.index(after: curPos)
5757
}
5858
if curPos == endPos {
5959
break
6060
}
6161
var curEnd = curPos
62-
while curEnd < endPos && characterView[curEnd] != "/" {
62+
while curEnd < endPos && !validPathSeps.contains(characterView[curEnd]) {
6363
curEnd = characterView.index(after: curEnd)
6464
}
6565
result.append(String(characterView[curPos ..< curEnd]))
6666
curPos = curEnd
6767
}
6868
}
69-
if p.length > 1 && p.hasSuffix("/") {
70-
result.append("/")
69+
if p.length > 1 && validPathSeps.contains(where: { p.hasSuffix(String($0)) }) {
70+
result.append(defaultPathSep)
7171
}
7272
return result
7373
}
@@ -337,7 +337,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
337337
let thePath = _standardizedPath(path)
338338

339339
var isDir: ObjCBool = false
340-
if thePath.hasSuffix("/") {
340+
if validPathSeps.contains(where: { thePath.hasSuffix(String($0)) }) {
341341
isDir = true
342342
} else {
343343
let absolutePath: String
@@ -367,7 +367,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
367367
}
368368

369369
var isDir: ObjCBool = false
370-
if thePath.hasSuffix("/") {
370+
if validPathSeps.contains(where: { thePath.hasSuffix(String($0)) }) {
371371
isDir = true
372372
} else {
373373
if !FileManager.default.fileExists(atPath: path, isDirectory: &isDir) {
@@ -881,7 +881,7 @@ extension NSURL {
881881
*/
882882
open class func fileURL(withPathComponents components: [String]) -> URL? {
883883
let path = NSString.path(withComponents: components)
884-
if components.last == "/" {
884+
if validPathSeps.contains(where: { String($0) == components.last }) {
885885
return URL(fileURLWithPath: path, isDirectory: true)
886886
} else {
887887
return URL(fileURLWithPath: path)
@@ -893,7 +893,7 @@ extension NSURL {
893893
return nil
894894
}
895895

896-
if p == "/" {
896+
if validPathSeps.contains(where: { String($0) == p }) {
897897
return p
898898
}
899899

@@ -904,13 +904,13 @@ extension NSURL {
904904
var curPos = startPos
905905

906906
while curPos < endPos {
907-
if result[curPos] == "/" {
907+
if validPathSeps.contains(result[curPos]) {
908908
var afterLastSlashPos = curPos
909-
while afterLastSlashPos < endPos && result[afterLastSlashPos] == "/" {
909+
while afterLastSlashPos < endPos && validPathSeps.contains(result[afterLastSlashPos]) {
910910
afterLastSlashPos = result.index(after: afterLastSlashPos)
911911
}
912912
if afterLastSlashPos != result.index(after: curPos) {
913-
result.replaceSubrange(curPos ..< afterLastSlashPos, with: ["/"])
913+
result.replaceSubrange(curPos ..< afterLastSlashPos, with: [Character(defaultPathSep)])
914914
endPos = result.endIndex
915915
}
916916
curPos = afterLastSlashPos
@@ -919,7 +919,7 @@ extension NSURL {
919919
}
920920
}
921921
}
922-
if stripTrailing && result.hasSuffix("/") {
922+
if stripTrailing && validPathSeps.contains(where: { result.hasSuffix(String($0)) }) {
923923
result.remove(at: result.index(before: result.endIndex))
924924
}
925925
return result
@@ -957,7 +957,7 @@ extension NSURL {
957957

958958
open func appendingPathComponent(_ pathComponent: String) -> URL? {
959959
var result : URL? = appendingPathComponent(pathComponent, isDirectory: false)
960-
if !pathComponent.hasSuffix("/") && isFileURL {
960+
if !validPathSeps.contains(where: { pathComponent.hasSuffix(String($0)) }) && isFileURL {
961961
if let urlWithoutDirectory = result {
962962
var isDir: ObjCBool = false
963963
if FileManager.default.fileExists(atPath: urlWithoutDirectory.path, isDirectory: &isDir) && isDir.boolValue {
@@ -1046,8 +1046,8 @@ extension NSURL {
10461046
resolvedPath = resolvedPath._tryToRemovePathPrefix("/private") ?? resolvedPath
10471047
}
10481048

1049-
if isExistingDirectory.boolValue && !resolvedPath.hasSuffix("/") {
1050-
resolvedPath += "/"
1049+
if isExistingDirectory.boolValue && !validPathSeps.contains(where: { resolvedPath.hasSuffix(String($0)) }) {
1050+
resolvedPath += defaultPathSep
10511051
}
10521052

10531053
return URL(fileURLWithPath: resolvedPath)
@@ -1056,15 +1056,15 @@ extension NSURL {
10561056
fileprivate func _pathByRemovingDots(_ comps: [String]) -> String {
10571057
var components = comps
10581058

1059-
if(components.last == "/") {
1059+
if validPathSeps.contains(where: { String($0) == components.last }) {
10601060
components.removeLast()
10611061
}
10621062

10631063
guard !components.isEmpty else {
10641064
return self.path!
10651065
}
10661066

1067-
let isAbsolutePath = components.first == "/"
1067+
let isAbsolutePath = components.first?.isAbsolutePath ?? false
10681068
var result : String = components.removeFirst()
10691069

10701070
for component in components {
@@ -1078,8 +1078,8 @@ extension NSURL {
10781078
}
10791079
}
10801080

1081-
if(self.path!.hasSuffix("/")) {
1082-
result += "/"
1081+
if validPathSeps.contains(where: { self.path!.hasSuffix(String($0)) }) {
1082+
result += defaultPathSep
10831083
}
10841084

10851085
return result

0 commit comments

Comments
 (0)