Skip to content

Commit ef651ce

Browse files
authored
Properly write non-ASCII file names on Windows for file creation (#1060)
1 parent 94adc6a commit ef651ce

File tree

2 files changed

+27
-9
lines changed

2 files changed

+27
-9
lines changed

Sources/FoundationEssentials/Data/Data+Writing.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,22 @@ import WASILibc
3737

3838
// MARK: - Helpers
3939

40+
#if os(Windows)
41+
private func openFileDescriptorProtected(path: UnsafePointer<UInt16>, flags: Int32, options: Data.WritingOptions) -> Int32 {
42+
var fd: CInt = 0
43+
_ = _wsopen_s(&fd, path, flags, _SH_DENYNO, _S_IREAD | _S_IWRITE)
44+
return fd
45+
}
46+
#else
4047
private func openFileDescriptorProtected(path: UnsafePointer<CChar>, flags: Int32, options: Data.WritingOptions) -> Int32 {
4148
#if FOUNDATION_FRAMEWORK
4249
// Use file protection on this platform
4350
return _NSOpenFileDescriptor_Protected(path, Int(flags), options, 0o666)
44-
#elseif os(Windows)
45-
var fd: CInt = 0
46-
_ = _sopen_s(&fd, path, flags, _SH_DENYNO, _S_IREAD | _S_IWRITE)
47-
return fd
4851
#else
4952
return open(path, flags, 0o666)
5053
#endif
5154
}
55+
#endif
5256

5357
private func writeToFileDescriptorWithProgress(_ fd: Int32, buffer: UnsafeRawBufferPointer, reportProgress: Bool) throws -> Int {
5458
// Fetch this once
@@ -159,18 +163,18 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL,
159163
// The warning diligently tells us we shouldn't be using mktemp() because blindly opening the returned path opens us up to a TOCTOU race. However, in this case, we're being careful by doing O_CREAT|O_EXCL and repeating, just like the implementation of mkstemp.
160164
// Furthermore, we can't compatibly switch to mkstemp() until we have the ability to set fchmod correctly, which requires the ability to query the current umask, which we don't have. (22033100)
161165
#if os(Windows)
162-
guard _mktemp_s(templateFileSystemRep, template.count + 1) == 0 else {
166+
guard _mktemp_s(templateFileSystemRep, strlen(templateFileSystemRep) + 1) == 0 else {
163167
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false, variant: variant)
164168
}
165-
let flags: CInt = _O_BINARY | _O_CREAT | _O_EXCL | _O_RDWR
169+
let fd = String(cString: templateFileSystemRep).withCString(encodedAs: UTF16.self) {
170+
openFileDescriptorProtected(path: $0, flags: _O_BINARY | _O_CREAT | _O_EXCL | _O_RDWR, options: options)
171+
}
166172
#else
167173
guard mktemp(templateFileSystemRep) != nil else {
168174
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false, variant: variant)
169175
}
170-
let flags: CInt = O_CREAT | O_EXCL | O_RDWR
176+
let fd = openFileDescriptorProtected(path: templateFileSystemRep, flags: O_CREAT | O_EXCL | O_RDWR, options: options)
171177
#endif
172-
173-
let fd = openFileDescriptorProtected(path: templateFileSystemRep, flags: flags, options: options)
174178
if fd >= 0 {
175179
// Got a good fd
176180
return (fd, String(cString: templateFileSystemRep))

Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,4 +1037,18 @@ final class FileManagerTests : XCTestCase {
10371037
XCTAssertEqual(FileManager.default.homeDirectory(forUser: UUID().uuidString), fallbackPath)
10381038
#endif
10391039
}
1040+
1041+
func testWindowsDirectoryCreationCrash() throws {
1042+
try FileManagerPlayground {
1043+
Directory("a\u{301}") {
1044+
1045+
}
1046+
}.test {
1047+
XCTAssertTrue($0.fileExists(atPath: "a\u{301}"))
1048+
let data = randomData()
1049+
XCTAssertTrue($0.createFile(atPath: "a\u{301}/test", contents: data))
1050+
XCTAssertTrue($0.fileExists(atPath: "a\u{301}/test"))
1051+
XCTAssertEqual($0.contents(atPath: "a\u{301}/test"), data)
1052+
}
1053+
}
10401054
}

0 commit comments

Comments
 (0)