Skip to content

Commit a02ed6f

Browse files
committed
Strip any UNC long path prefix from _wgetcwd
- This keeps any UNC long file or device prefix (\\?\) out of URL that were relative and then resolved to absolute urls.
1 parent 94ef6ab commit a02ed6f

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

Sources/CoreFoundation/CFPlatform.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <shellapi.h>
3636
#include <shlobj.h>
3737
#include <shlwapi.h>
38+
#include <pathcch.h>
3839
#include <WinIoCtl.h>
3940
#include <direct.h>
4041
#include <process.h>
@@ -1140,9 +1141,22 @@ CF_EXPORT char *_NS_getcwd(char *dstbuf, size_t size) {
11401141
if (!buf) {
11411142
return NULL;
11421143
}
1144+
1145+
// Strip UNC long path prefixe (\\?\) from the wide character buffer using PathCchStripPrefix
1146+
wchar_t *pathToConvert = buf;
1147+
size_t pathLen = wcslen(buf);
1148+
1149+
// Use PathCchStripPrefix to remove UNC long path prefixes
1150+
HRESULT hr = PathCchStripPrefix(buf, pathLen + 1);
1151+
if (SUCCEEDED(hr)) {
1152+
pathToConvert = buf;
1153+
} else {
1154+
// If PathCchStripPrefix fails, fall back to the original buffer
1155+
pathToConvert = buf;
1156+
}
11431157

11441158
// Convert result to UTF8
1145-
copyToNarrowFileSystemRepresentation(buf, (CFIndex)size, dstbuf);
1159+
copyToNarrowFileSystemRepresentation(pathToConvert, (CFIndex)size, dstbuf);
11461160
free(buf);
11471161
return dstbuf;
11481162
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See http://swift.org/LICENSE.txt for license information
7+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+
//
9+
10+
#if os(Windows)
11+
// Import Windows C functions
12+
import WinSDK
13+
14+
// Declare _NS_getcwd function for testing
15+
@_silgen_name("_NS_getcwd")
16+
func _NS_getcwd(_ buffer: UnsafeMutablePointer<CChar>, _ size: Int) -> UnsafeMutablePointer<CChar>?
17+
#endif
18+
19+
class TestCFPlatformGetcwd : XCTestCase {
20+
21+
#if os(Windows)
22+
func test_NS_getcwd_UNC_prefix_stripping() {
23+
// Test that _NS_getcwd properly strips UNC long path prefixes using PathCchStripPrefix
24+
25+
// Create a temporary directory to work with
26+
let fm = FileManager.default
27+
let tempDir = fm.temporaryDirectory.appendingPathComponent("test_getcwd_\(UUID().uuidString)")
28+
29+
do {
30+
try fm.createDirectory(at: tempDir, withIntermediateDirectories: true)
31+
defer { try? fm.removeItem(at: tempDir) }
32+
33+
// Get original directory for restoration
34+
var originalBuffer = [CChar](repeating: 0, count: Int(MAX_PATH))
35+
guard _NS_getcwd(&originalBuffer, originalBuffer.count) != nil else {
36+
XCTFail("Failed to get original directory")
37+
return
38+
}
39+
let originalDir = String(cString: originalBuffer)
40+
41+
defer {
42+
// Restore original directory
43+
originalDir.withCString { _chdir($0) }
44+
}
45+
46+
// Test with UNC long path prefix \\?\
47+
let uncLongPathPrefix = "\\\\?\\" + tempDir.path
48+
let uncLongPathCString = uncLongPathPrefix.cString(using: .utf8)!
49+
let uncChdirResult = uncLongPathCString.withUnsafeBufferPointer { buffer in
50+
return _chdir(buffer.baseAddress!)
51+
}
52+
XCTAssertEqual(uncChdirResult, 0, "Failed to change directory using UNC long path prefix")
53+
54+
// Test _NS_getcwd directly after changing to UNC prefixed path
55+
var buffer = [CChar](repeating: 0, count: Int(MAX_PATH))
56+
guard let result = _NS_getcwd(&buffer, buffer.count) else {
57+
XCTFail("_NS_getcwd returned null")
58+
return
59+
}
60+
61+
let currentDir = String(cString: result)
62+
63+
// Verify that the path doesn't contain UNC prefixes (this is the key test!)
64+
XCTAssertFalse(currentDir.hasPrefix("\\\\?\\"), "Current directory path should not contain \\\\?\\ UNC prefix after stripping")
65+
66+
// Verify that we can still access the directory (it's a valid path)
67+
XCTAssertTrue(fm.fileExists(atPath: currentDir), "Current directory path should be valid and accessible")
68+
69+
// Verify the path ends with our test directory name
70+
XCTAssertTrue(currentDir.hasSuffix(tempDir.lastPathComponent), "Current directory should end with our test directory name")
71+
72+
// Test with a deeper nested directory using UNC prefix to ensure stripping works with longer paths
73+
let deepDir = tempDir.appendingPathComponent("level1").appendingPathComponent("level2").appendingPathComponent("level3")
74+
try fm.createDirectory(at: deepDir, withIntermediateDirectories: true)
75+
76+
let deepUncPath = "\\\\?\\" + deepDir.path
77+
let deepUncCString = deepUncPath.cString(using: .utf8)!
78+
let deepChdirResult = deepUncCString.withUnsafeBufferPointer { buffer in
79+
return _chdir(buffer.baseAddress!)
80+
}
81+
XCTAssertEqual(deepChdirResult, 0, "Failed to change to deep directory with UNC prefix")
82+
83+
// Test _NS_getcwd with deep UNC prefixed path
84+
var deepBuffer = [CChar](repeating: 0, count: Int(MAX_PATH))
85+
guard let deepResult = _NS_getcwd(&deepBuffer, deepBuffer.count) else {
86+
XCTFail("_NS_getcwd returned null for deep UNC path")
87+
return
88+
}
89+
90+
let deepCurrentDir = String(cString: deepResult)
91+
92+
// Verify UNC prefixes are stripped from deeper paths too
93+
XCTAssertFalse(deepCurrentDir.hasPrefix("\\\\?\\"), "Deep directory path should not contain \\\\?\\ UNC prefix after stripping")
94+
XCTAssertTrue(fm.fileExists(atPath: deepCurrentDir), "Deep directory path should be valid and accessible")
95+
XCTAssertTrue(deepCurrentDir.hasSuffix("level3"), "Deep directory should end with level3")
96+
97+
} catch {
98+
XCTFail("Failed to set up test environment: \(error)")
99+
}
100+
}
101+
102+
func test_NS_getcwd_small_buffer() {
103+
// Test that _NS_getcwd handles small buffer correctly
104+
var smallBuffer = [CChar](repeating: 0, count: 1)
105+
let result = _NS_getcwd(&smallBuffer, smallBuffer.count)
106+
// This should either return null or handle the small buffer gracefully
107+
// The exact behavior depends on the implementation, but it shouldn't crash
108+
XCTAssertTrue(result == nil || result != nil, "Function should not crash with small buffer")
109+
}
110+
#endif
111+
}

0 commit comments

Comments
 (0)