Skip to content

Commit 83d7041

Browse files
committed
Make stripRawPathPrefix and removeTrailingBackslash use temporary buffer.
- Move stripRawPathPrefix and removeTrailingBackslash to use a mutable temporary buffer to call PathCchStripPrefix and PathCchRemoveBackslash - Could not use buffer.withMemoryRebound and getCstring() as on Windows this seem to produce corrupt data. - Add more tests for unParsed '\\?\' and device '\\.\' paths - Remove the PATHCCH_CANONICALIZE_SLASHES flag as it is not needed.
1 parent e267bc3 commit 83d7041

File tree

2 files changed

+225
-37
lines changed

2 files changed

+225
-37
lines changed

Sources/TSCBasic/Path.swift

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -458,36 +458,6 @@ private struct WindowsPath: Path, Sendable {
458458
return !path.withCString(encodedAs: UTF16.self, PathIsRelativeW)
459459
}
460460

461-
/// When this function returns successfully, the same path string will have had the prefix removed,
462-
/// if the prefix was present. If no prefix was present, the string will be unchanged.
463-
static func stripPrefix(_ path: String) -> String {
464-
return path.withCString(encodedAs: UTF16.self) { cStringPtr in
465-
let mutableCStringPtr = UnsafeMutablePointer(mutating: cStringPtr)
466-
let result = PathCchStripPrefix(mutableCStringPtr, path.utf16.count + 1)
467-
if result == S_OK {
468-
return String(decodingCString: mutableCStringPtr, as: UTF16.self)
469-
}
470-
return path
471-
}
472-
}
473-
474-
/// Remove a trailing backslash from a path if the following conditions
475-
/// are true:
476-
/// * Path is not a root path
477-
/// * Pash has a trailing backslash
478-
/// If conditions are not met then the string is returned unchanged.
479-
static func removeTrailingBackslash(_ path: String) -> String {
480-
return path.withCString(encodedAs: UTF16.self) { cStringPtr in
481-
let mutableCStringPtr = UnsafeMutablePointer(mutating: cStringPtr)
482-
let result = PathCchRemoveBackslash(mutableCStringPtr, path.utf16.count + 1)
483-
484-
if result == S_OK {
485-
return String(decodingCString: mutableCStringPtr, as: UTF16.self)
486-
}
487-
return path
488-
}
489-
}
490-
491461
/// Create a canonicalized path representation for Windows.
492462
/// Returns a potentially `\\?\`-prefixed version of the path,
493463
/// to ensure long paths greater than MAX_PATH (260) characters are handled correctly.
@@ -517,7 +487,7 @@ private struct WindowsPath: Path, Sendable {
517487
// 2. Canonicalize the path.
518488
// This will add the \\?\ prefix if needed based on the path's length.
519489
var pwszCanonicalPath: LPWSTR?
520-
let flags: ULONG = numericCast(PATHCCH_ALLOW_LONG_PATHS.rawValue) | numericCast(PATHCCH_CANONICALIZE_SLASHES.rawValue)
490+
let flags: ULONG = numericCast(PATHCCH_ALLOW_LONG_PATHS.rawValue)
521491
let result = PathAllocCanonicalize(pwszFullPath.baseAddress, flags, &pwszCanonicalPath)
522492
if let pwszCanonicalPath {
523493
defer { LocalFree(pwszCanonicalPath) }
@@ -581,15 +551,20 @@ private struct WindowsPath: Path, Sendable {
581551
let normalized: UnsafePointer<Int8> = string.fileSystemRepresentation
582552
defer { normalized.deallocate() }
583553
// Remove prefix from the components, allowing for comparison across normalized paths.
584-
return Self.stripPrefix(String(cString: normalized)).components(separatedBy: #"\"#).filter { !$0.isEmpty }
554+
var prefixStrippedPath = PathCchStripPrefix(String(cString: normalized))
555+
// The '\\.\'' prefix is not removed by PathCchStripPrefix do this manually.
556+
if prefixStrippedPath.starts(with: #"\\.\"#) {
557+
prefixStrippedPath = String(prefixStrippedPath.dropFirst(4))
558+
}
559+
return prefixStrippedPath.components(separatedBy: #"\"#).filter { !$0.isEmpty }
585560
}
586561

587562
var parentDirectory: Self {
588563
return self == .root ? self : Self(string: dirname)
589564
}
590565

591566
init(string: String) {
592-
let noPrefixPath = Self.stripPrefix(string)
567+
let noPrefixPath = PathCchStripPrefix(string)
593568
let prefix = string.replacingOccurrences(of: noPrefixPath, with: "") // Just the prefix or empty
594569

595570
// Perform drive designator normalization i.e. 'c:\' to 'C:\' on string.
@@ -616,7 +591,7 @@ private struct WindowsPath: Path, Sendable {
616591
}
617592
do {
618593
let canonicalizedPath = try Self.canonicalPathRepresentation(realpath)
619-
let normalizedPath = Self.removeTrailingBackslash(canonicalizedPath) // AbsolutePath states paths have no trailing separator.
594+
let normalizedPath = PathCchRemoveBackslash(canonicalizedPath) // AbsolutePath states paths have no trailing separator.
620595
self.init(string: normalizedPath)
621596
} catch {
622597
throw PathValidationError.invalidAbsolutePath("\(path): \(error)")
@@ -703,6 +678,40 @@ fileprivate func WIN32_FROM_HRESULT(_ hr: HRESULT) -> DWORD {
703678
return DWORD(hr)
704679
}
705680

681+
/// Removes the "\\?\" prefix, if present, from a file path. When this function returns successfully,
682+
/// the same path string will have the prefix removed,if the prefix was present.
683+
/// If no prefix was present,the string will be unchanged.
684+
func PathCchStripPrefix(_ path: String) -> String {
685+
return path.withCString(encodedAs: UTF16.self) { cStringPtr in
686+
withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: path.utf16.count + 1) { buffer in
687+
buffer.initialize(from: UnsafeBufferPointer(start: cStringPtr, count: path.utf16.count + 1))
688+
let result = PathCchStripPrefix(buffer.baseAddress!, buffer.count)
689+
if result == S_OK {
690+
return String(decodingCString: buffer.baseAddress!, as: UTF16.self)
691+
}
692+
return path
693+
}
694+
}
695+
}
696+
697+
/// Remove a trailing backslash from a path if the following conditions
698+
/// are true:
699+
/// * Path is not a root path
700+
/// * Pash has a trailing backslash
701+
/// If conditions are not met then the string is returned unchanged.
702+
func PathCchRemoveBackslash(_ path: String) -> String {
703+
return path.withCString(encodedAs: UTF16.self) { cStringPtr in
704+
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: path.utf16.count + 1) { buffer in
705+
buffer.initialize(from: UnsafeBufferPointer(start: cStringPtr, count: path.utf16.count + 1))
706+
let result = PathCchRemoveBackslash(buffer.baseAddress!, path.utf16.count + 1)
707+
if result == S_OK {
708+
return String(decodingCString: buffer.baseAddress!, as: UTF16.self)
709+
}
710+
return path
711+
}
712+
return path
713+
}
714+
}
706715
#else
707716
private struct UNIXPath: Path, Sendable {
708717
let string: String

0 commit comments

Comments
 (0)