Skip to content

Commit 88dcf1b

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 88dcf1b

File tree

2 files changed

+131
-1
lines changed

2 files changed

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

0 commit comments

Comments
 (0)