Skip to content

URLSession.download crashes when using custom URLProtocols #5465

Description

@wade-tregaskis

Summary

A forced unwrap of the location parameter in the internal completion handler causes a crash, because it is nil when driven by a custom URLProtocol because there's no way for custom protocols to provide the file location that URLSession is demanding.

Environment

  • OS: Linux (Debian GNU/Linux rodete)
  • Swift version: 6.2.4

Root Cause

855:                 let completionHandler: URLSession._TaskRegistry.DownloadTaskCompletion = { location, response, error in
856:                     if let error = error {
857:                         continuation.resume(throwing: error)
858:                     } else {
859:                         continuation.resume(returning: (location!, response!))  // <-- BAD
860:                     }
861:                 }

When using a custom URLProtocol, there's no way to specify location, because it comes from an internal property:

let temporaryFileURL = urlProtocol.properties[URLProtocol._PropertyKey.temporaryFileURL] as! URL

…which is private within Foundation:

extension URLProtocol {
    enum _PropertyKey: String, Sendable {
        case responseData
        case temporaryFileURL
    }
}

Fix

Make the temporaryFileURL property key public, or remove the implicit requirement that it be used by URLProtocol subclasses.

In the interim, it would be nice if FoundationNetworking didn't crash - it could remove those forced unwraps. e.g.:

--- a/Sources/FoundationNetworking/URLSession/URLSession.swift
+++ b/Sources/FoundationNetworking/URLSession/URLSession.swift
@@ -855,3 +855,10 @@
                 let completionHandler: URLSession._TaskRegistry.DownloadTaskCompletion = { location, response, error in
                     if let error = error {
                         continuation.resume(throwing: error)
                     } else {
-                        continuation.resume(returning: (location!, response!))
+                        guard let location = location, let response = response else {
+                            let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: [NSLocalizedDescriptionKey: "Internal error: download task completed but temporary file location was missing."]))
+                            continuation.resume(throwing: urlError)
+                            return
+                        }
+                        continuation.resume(returning: (location, response))
                     }

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions