Skip to content

Commit b7b9c64

Browse files
authored
Merge branch 'swiftlang:main' into proposal/progress-reporter
2 parents 8d6c552 + 80588e9 commit b7b9c64

File tree

16 files changed

+178
-101
lines changed

16 files changed

+178
-101
lines changed

.github/workflows/pull_request.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Pull request
2+
3+
on:
4+
pull_request:
5+
types: [opened, reopened, synchronize]
6+
7+
jobs:
8+
tests:
9+
name: Test
10+
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
11+
with:
12+
linux_swift_versions: '["nightly-main"]'
13+
windows_swift_versions: '["nightly-main"]'
14+
15+
soundness:
16+
name: Soundness
17+
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
18+
with:
19+
license_header_check_project_name: "Swift.org"
20+
license_header_check_enabled: false
21+
docs_check_enabled: false
22+
format_check_enabled: false
23+
unacceptable_language_check_enabled: false
24+
api_breakage_check_enabled: false

Sources/FoundationEssentials/Data/Data+Writing.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL,
171171
guard _mktemp_s(templateFileSystemRep, strlen(templateFileSystemRep) + 1) == 0 else {
172172
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false, variant: variant)
173173
}
174-
let fd = String(cString: templateFileSystemRep).withCString(encodedAs: UTF16.self) {
174+
let fd = try String(cString: templateFileSystemRep).withNTPathRepresentation {
175175
openFileDescriptorProtected(path: $0, flags: _O_BINARY | _O_CREAT | _O_EXCL | _O_RDWR, options: options)
176176
}
177177
#else

Sources/FoundationEssentials/FileManager/FileManager+Directories.swift

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -257,36 +257,6 @@ extension _FileManagerImpl {
257257
try fileManager.createDirectory(atPath: path, withIntermediateDirectories: createIntermediates, attributes: attributes)
258258
}
259259

260-
#if os(Windows)
261-
/// If `path` is absolute, this is the same as `path.withNTPathRepresentation`.
262-
/// If `path` is relative, this creates an absolute path of `path` relative to `currentDirectoryPath` and runs
263-
/// `body` with that path.
264-
private func withAbsoluteNTPathRepresentation<Result>(
265-
of path: String,
266-
_ body: (UnsafePointer<WCHAR>) throws -> Result
267-
) throws -> Result {
268-
try path.withNTPathRepresentation { pwszPath in
269-
if !PathIsRelativeW(pwszPath) {
270-
// We already have an absolute path. Nothing to do
271-
return try body(pwszPath)
272-
}
273-
guard let currentDirectoryPath else {
274-
preconditionFailure("We should always have a current directory on Windows")
275-
}
276-
277-
// We have a relateive path. Make it absolute.
278-
let absoluteUrl = URL(
279-
filePath: path,
280-
directoryHint: .isDirectory,
281-
relativeTo: URL(filePath: currentDirectoryPath, directoryHint: .isDirectory)
282-
)
283-
return try absoluteUrl.path.withNTPathRepresentation { pwszPath in
284-
return try body(pwszPath)
285-
}
286-
}
287-
}
288-
#endif
289-
290260
func createDirectory(
291261
atPath path: String,
292262
withIntermediateDirectories createIntermediates: Bool,
@@ -301,7 +271,7 @@ extension _FileManagerImpl {
301271
if createIntermediates {
302272
// `SHCreateDirectoryExW` requires an absolute path while `CreateDirectoryW` works based on the current working
303273
// directory.
304-
try withAbsoluteNTPathRepresentation(of: path) { pwszPath in
274+
try path.withNTPathRepresentation { pwszPath in
305275
let errorCode = SHCreateDirectoryExW(nil, pwszPath, &saAttributes)
306276
guard let errorCode = DWORD(exactly: errorCode) else {
307277
// `SHCreateDirectoryExW` returns `Int` but all error codes are defined in terms of `DWORD`, aka

Sources/FoundationEssentials/FileManager/FileManager+SymbolicLinks.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ extension _FileManagerImpl {
6464

6565
try path.withNTPathRepresentation { lpSymlinkFileName in
6666
try destPath.withFileSystemRepresentation {
67-
try String(cString: $0!).withCString(encodedAs: UTF16.self) { lpTargetFileName in
67+
try String(cString: $0!).withNTPathRepresentation(relative: true) { lpTargetFileName in
6868
if CreateSymbolicLinkW(lpSymlinkFileName, lpTargetFileName, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE | (bIsDirectory ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0)) == 0 {
6969
throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: false)
7070
}

Sources/FoundationEssentials/FileManager/FileOperations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ enum _FileOperations {
829829
try src.withNTPathRepresentation { pwszSource in
830830
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
831831
guard GetFileAttributesExW(pwszSource, GetFileExInfoStandard, &faAttributes) else {
832-
throw CocoaError.errorWithFilePath(.fileReadNoSuchFile, src, variant: bCopyFile ? "Copy" : "Link", source: src, destination: dst)
832+
throw CocoaError.errorWithFilePath(src, win32: GetLastError(), reading: true, variant: bCopyFile ? "Copy" : "Link", source: src, destination: dst)
833833
}
834834

835835
guard delegate.shouldPerformOnItemAtPath(src, to: dst) else { return }

Sources/FoundationEssentials/String/String+Internals.swift

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ import Darwin
2323
import WinSDK
2424

2525
extension String {
26-
package func withNTPathRepresentation<Result>(_ body: (UnsafePointer<WCHAR>) throws -> Result) throws -> Result {
26+
/// Invokes `body` with a resolved and potentially `\\?\`-prefixed version of the pointee,
27+
/// to ensure long paths greater than MAX_PATH (260) characters are handled correctly.
28+
///
29+
/// - parameter relative: Returns the original path without transforming through GetFullPathNameW + PathCchCanonicalizeEx, if the path is relative.
30+
/// - seealso: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
31+
package func withNTPathRepresentation<Result>(relative: Bool = false, _ body: (UnsafePointer<WCHAR>) throws -> Result) throws -> Result {
2732
guard !isEmpty else {
2833
throw CocoaError.errorWithFilePath(.fileReadInvalidFileName, "")
2934
}
@@ -35,15 +40,42 @@ extension String {
3540
// leading slash indicates a rooted path on the drive for the current
3641
// working directory.
3742
return try Substring(self.utf8.dropFirst(bLeadingSlash ? 1 : 0)).withCString(encodedAs: UTF16.self) { pwszPath in
43+
if relative && PathIsRelativeW(pwszPath) {
44+
return try body(pwszPath)
45+
}
46+
3847
// 1. Normalize the path first.
48+
// Contrary to the documentation, this works on long paths independently
49+
// of the registry or process setting to enable long paths (but it will also
50+
// not add the \\?\ prefix required by other functions under these conditions).
3951
let dwLength: DWORD = GetFullPathNameW(pwszPath, 0, nil, nil)
40-
return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) {
41-
guard GetFullPathNameW(pwszPath, DWORD($0.count), $0.baseAddress, nil) > 0 else {
52+
return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) { pwszFullPath in
53+
guard (1..<dwLength).contains(GetFullPathNameW(pwszPath, DWORD(pwszFullPath.count), pwszFullPath.baseAddress, nil)) else {
4254
throw CocoaError.errorWithFilePath(self, win32: GetLastError(), reading: true)
4355
}
4456

45-
// 2. Perform the operation on the normalized path.
46-
return try body($0.baseAddress!)
57+
// 1.5 Leave \\.\ prefixed paths alone since device paths are already an exact representation and PathCchCanonicalizeEx will mangle these.
58+
if let base = pwszFullPath.baseAddress,
59+
base[0] == UInt16(UInt8._backslash),
60+
base[1] == UInt16(UInt8._backslash),
61+
base[2] == UInt16(UInt8._period),
62+
base[3] == UInt16(UInt8._backslash) {
63+
return try body(base)
64+
}
65+
66+
// 2. Canonicalize the path.
67+
// This will add the \\?\ prefix if needed based on the path's length.
68+
var pwszCanonicalPath: LPWSTR?
69+
let flags: ULONG = PATHCCH_ALLOW_LONG_PATHS
70+
let result = PathAllocCanonicalize(pwszFullPath.baseAddress, flags, &pwszCanonicalPath)
71+
if let pwszCanonicalPath {
72+
defer { LocalFree(pwszCanonicalPath) }
73+
if result == S_OK {
74+
// 3. Perform the operation on the normalized path.
75+
return try body(pwszCanonicalPath)
76+
}
77+
}
78+
throw CocoaError.errorWithFilePath(self, win32: WIN32_FROM_HRESULT(result), reading: true)
4779
}
4880
}
4981
}

Sources/FoundationEssentials/URL/URL.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -690,18 +690,6 @@ public struct URL: Equatable, Sendable, Hashable {
690690
///
691691
/// Returns `nil` if a `URL` cannot be formed with the string (for example, if the string contains characters that are illegal in a URL, or is an empty string).
692692
public init?(string: __shared String, relativeTo url: __shared URL?) {
693-
#if os(Linux)
694-
// Workaround for a Linux-only crash where swift-corelibs-foundation's
695-
// NSURL.baseURL.getter returns a value of 0x1 when bridging to URL.
696-
// Crash doesn't occur when swift-corelibs-foundation is rebuilt with
697-
// the new swift-foundation URL code, so this is temporary to get
698-
// swift-foundation CI to pass.
699-
if unsafeBitCast(url, to: (UnsafeRawPointer, UnsafeRawPointer).self) == (UnsafeRawPointer(bitPattern: 0x1), UnsafeRawPointer(bitPattern: 0x0)) {
700-
guard let inner = URL._type.init(string: string, relativeTo: nil) else { return nil }
701-
_url = inner.convertingFileReference()
702-
return
703-
}
704-
#endif
705693
guard let inner = URL._type.init(string: string, relativeTo: url) else { return nil }
706694
_url = inner.convertingFileReference()
707695
}

Sources/FoundationEssentials/URL/URLParser.swift

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -435,41 +435,6 @@ internal struct RFC3986Parser {
435435
return validate(string: password, component: .password, percentEncodingAllowed: percentEncodingAllowed)
436436
}
437437

438-
private static func isIPvFuture(_ innerHost: some StringProtocol) -> Bool {
439-
// precondition: IP-literal == "[" innerHost [ "%25" zoneID ] "]"
440-
var iter = innerHost.utf8.makeIterator()
441-
guard iter.next() == UInt8(ascii: "v") else { return false }
442-
guard let second = iter.next(), second.isValidHexDigit else { return false }
443-
while let next = iter.next() {
444-
if next.isValidHexDigit { continue }
445-
if next == ._dot { return true }
446-
return false
447-
}
448-
return false
449-
}
450-
451-
/// Only checks that the characters are allowed in an IPv6 address.
452-
/// Does not validate the format of the IPv6 address.
453-
private static func validateIPv6Address(_ address: some StringProtocol) -> Bool {
454-
let isValid = address.utf8.withContiguousStorageIfAvailable {
455-
$0.allSatisfy { $0.isValidHexDigit || $0 == UInt8(ascii: ":") || $0 == UInt8(ascii: ".") }
456-
}
457-
if let isValid {
458-
return isValid
459-
}
460-
#if FOUNDATION_FRAMEWORK
461-
if let fastCharacters = address._ns._fastCharacterContents() {
462-
let charsBuffer = UnsafeBufferPointer(start: fastCharacters, count: address._ns.length)
463-
return charsBuffer.allSatisfy {
464-
guard $0 < 128 else { return false }
465-
let v = UInt8($0)
466-
return v.isValidHexDigit || v == UInt8(ascii: ":") || v == UInt8(ascii: ".")
467-
}
468-
}
469-
#endif
470-
return address.utf8.allSatisfy { $0.isValidHexDigit || $0 == UInt8(ascii: ":") || $0 == UInt8(ascii: ".") }
471-
}
472-
473438
/// Validates an IP-literal host string that has leading and trailing brackets.
474439
/// If the host string contains a zone ID delimiter "%", this must be percent encoded to "%25" to be valid.
475440
/// The zone ID may contain any `reg_name` characters, including percent-encoding.
@@ -483,11 +448,7 @@ internal struct RFC3986Parser {
483448

484449
guard let percentIndex = utf8.firstIndex(of: UInt8(ascii: "%")) else {
485450
// There is no zoneID, so the whole innerHost must be the IP-literal address.
486-
if isIPvFuture(innerHost) {
487-
return validate(string: innerHost, component: .hostIPvFuture, percentEncodingAllowed: false)
488-
} else {
489-
return validateIPv6Address(innerHost)
490-
}
451+
return validate(string: innerHost, component: .hostIPvFuture, percentEncodingAllowed: false)
491452
}
492453

493454
// The first "%" in an IP-literal must be the zone ID delimiter.
@@ -503,11 +464,7 @@ internal struct RFC3986Parser {
503464
return false
504465
}
505466

506-
if isIPvFuture(innerHost) {
507-
return validate(string: innerHost[..<percentIndex], component: .hostIPvFuture, percentEncodingAllowed: false) && validate(string: innerHost[innerHost.index(after: twoAfterIndex)...], component: .hostZoneID)
508-
} else {
509-
return validateIPv6Address(innerHost[..<percentIndex]) && validate(string: innerHost[innerHost.index(after: twoAfterIndex)...], component: .hostZoneID)
510-
}
467+
return validate(string: innerHost[..<percentIndex], component: .hostIPvFuture, percentEncodingAllowed: false) && validate(string: innerHost[innerHost.index(after: twoAfterIndex)...], component: .hostZoneID)
511468
}
512469

513470
private static func validate(host: some StringProtocol, knownIPLiteral: Bool = false) -> Bool {

Sources/FoundationEssentials/URL/URLTemplate.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ extension URL {
130130
}
131131

132132
// MARK: - Parse
133-
133+
#if FOUNDATION_FRAMEWORK
134134
extension URL.Template {
135135
/// Creates a new template from its text form.
136136
///
@@ -164,6 +164,7 @@ extension URL.Template {
164164
}
165165
}
166166
}
167+
#endif
167168

168169
// MARK: -
169170

Sources/FoundationEssentials/URL/URLTemplate_Expression.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#if FOUNDATION_FRAMEWORK
1314
internal import RegexBuilder
15+
#endif
16+
1417
#if canImport(CollectionsInternal)
1518
internal import CollectionsInternal
1619
#elseif canImport(OrderedCollections)
@@ -79,6 +82,7 @@ extension URL.Template.Expression.Element: CustomStringConvertible {
7982
}
8083
}
8184

85+
#if FOUNDATION_FRAMEWORK
8286
extension URL.Template.Expression {
8387
init(_ input: String) throws {
8488
var remainder = input[...]
@@ -202,6 +206,7 @@ extension URL.Template {
202206
}
203207
}
204208
}
209+
#endif
205210

206211
// .------------------------------------------------------------------.
207212
// | NUL + . / ; ? & # |

0 commit comments

Comments
 (0)