Skip to content

Commit f14ca3d

Browse files
authored
Merge pull request #1343 from apple/QuoteWindowsPathsProperly
Use system-appropriate method when quoting path arguments on command-lines
2 parents 3f15981 + 30cab54 commit f14ca3d

File tree

2 files changed

+43
-39
lines changed

2 files changed

+43
-39
lines changed

Sources/SwiftDriver/Execution/ArgsResolver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public final class ArgsResolver {
116116
private func unsafeResolve(path: VirtualPath, quotePaths: Bool) throws -> String {
117117
// If there was a path mapping, use it.
118118
if let actualPath = pathMapping[path] {
119-
return quotePaths ? "'\(actualPath)'" : actualPath
119+
return quotePaths ? quoteArgument(actualPath) : actualPath
120120
}
121121

122122
// Return the path from the temporary directory if this is a temporary file.
@@ -146,7 +146,7 @@ public final class ArgsResolver {
146146
// Otherwise, return the path.
147147
let result = path.name
148148
pathMapping[path] = result
149-
return quotePaths ? "'\(result)'" : result
149+
return quotePaths ? quoteArgument(result) : result
150150
}
151151

152152
private func createFileList(path: VirtualPath, contents: [VirtualPath]) throws {

Sources/SwiftDriver/Utilities/System.swift

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,45 @@ import Darwin
1616
import Glibc
1717
#endif
1818

19+
func argumentNeedsQuoting(_ argument: String) -> Bool {
20+
if argument.isEmpty { return false }
21+
let chars: Set<Character> = Set("\t \"&'()*<>\\`^|\n")
22+
return argument.firstIndex(where: { chars.contains($0) }) != argument.endIndex
23+
}
24+
25+
func quoteArgument(_ argument: String) -> String {
26+
#if os(Windows)
27+
var unquoted: Substring = argument[...]
28+
var quoted: String = "\""
29+
while !unquoted.isEmpty {
30+
guard let firstNonBS = unquoted.firstIndex(where: { $0 != "\\" }) else {
31+
// The rest of the string is backslashes. Escape all of them and exit.
32+
(0 ..< (2 * unquoted.count)).forEach { _ in quoted += "\\" }
33+
break
34+
}
35+
36+
let bsCount = unquoted.distance(from: unquoted.startIndex, to: firstNonBS)
37+
if unquoted[firstNonBS] == "\"" {
38+
// This is an embedded quote. Escape all preceding backslashes, then
39+
// add one additional backslash to escape the quote.
40+
(0 ..< (2 * bsCount + 1)).forEach { _ in quoted += "\\" }
41+
quoted += "\""
42+
} else {
43+
// This is just a normal character. Don't escape any of the preceding
44+
// backslashes, just append them as they are and then append the
45+
// character.
46+
(0 ..< bsCount).forEach { _ in quoted += "\\" }
47+
quoted += "\(unquoted[firstNonBS])"
48+
}
49+
50+
unquoted = unquoted.dropFirst(bsCount + 1)
51+
}
52+
return quoted + "\""
53+
#else
54+
return "'" + argument + "'"
55+
#endif
56+
}
57+
1958
#if canImport(Darwin) || os(Linux) || os(Android) || os(OpenBSD)
2059
// Adapted from llvm::sys::commandLineFitsWithinSystemLimits.
2160
func commandLineFitsWithinSystemLimits(path: String, args: [String]) -> Bool {
@@ -49,45 +88,10 @@ func commandLineFitsWithinSystemLimits(path: String, args: [String]) -> Bool {
4988
#elseif os(Windows)
5089
func commandLineFitsWithinSystemLimits(path: String, args: [String]) -> Bool {
5190
func flattenWindowsCommandLine(_ arguments: [String]) -> String {
52-
func argNeedsQuoting(_ argument: String) -> Bool {
53-
if argument.isEmpty { return false }
54-
let chars: Set<Character> = Set("\t \"&'()*<>\\`^|\n")
55-
return argument.firstIndex(where: { chars.contains($0) }) != argument.endIndex
56-
}
57-
58-
func quote(_ argument: String) -> String {
59-
var unquoted: Substring = argument[...]
60-
var quoted: String = "\""
61-
while !unquoted.isEmpty {
62-
guard let firstNonBS = unquoted.firstIndex(where: { $0 != "\\" }) else {
63-
// The rest of the string is backslashes. Escape all of them and exit.
64-
(0 ..< (2 * unquoted.count)).forEach { _ in quoted += "\\" }
65-
break
66-
}
67-
68-
let bsCount = unquoted.distance(from: unquoted.startIndex, to: firstNonBS)
69-
if unquoted[firstNonBS] == "\"" {
70-
// This is an embedded quote. Escape all preceding backslashes, then
71-
// add one additional backslash to escape the quote.
72-
(0 ..< (2 * bsCount + 1)).forEach { _ in quoted += "\\" }
73-
quoted += "\""
74-
} else {
75-
// This is just a normal character. Don't escape any of the preceding
76-
// backslashes, just append them as they are and then append the
77-
// character.
78-
(0 ..< bsCount).forEach { _ in quoted += "\\" }
79-
quoted += "\(unquoted[firstNonBS])"
80-
}
81-
82-
unquoted = unquoted.dropFirst(bsCount + 1)
83-
}
84-
return quoted + "\""
85-
}
86-
8791
var quoted: String = ""
8892
for arg in arguments {
89-
if argNeedsQuoting(arg) {
90-
quoted += quote(arg)
93+
if argumentNeedsQuoting(arg) {
94+
quoted += quoteArgument(arg)
9195
} else {
9296
quoted += arg
9397
}

0 commit comments

Comments
 (0)