diff --git a/Package.swift b/Package.swift index 4a9dc5dc71..7ba345e7c3 100644 --- a/Package.swift +++ b/Package.swift @@ -58,7 +58,9 @@ if let environmentPath = Context.environment["CURL_INCLUDE_PATH"] { var curlLinkFlags: [LinkerSetting] = [ .linkedLibrary("libcurl.lib", .when(platforms: [.windows])), - .linkedLibrary("zlibstatic.lib", .when(platforms: [.windows])) + .linkedLibrary("zlibstatic.lib", .when(platforms: [.windows])), + .linkedLibrary("brotlicommon.lib", .when(platforms: [.windows])), + .linkedLibrary("brotlidec.lib", .when(platforms: [.windows])) ] if let environmentPath = Context.environment["CURL_LIBRARY_PATH"] { curlLinkFlags.append(.unsafeFlags([ @@ -70,6 +72,11 @@ if let environmentPath = Context.environment["ZLIB_LIBRARY_PATH"] { "-L\(environmentPath)" ])) } +if let environmentPath = Context.environment["BROTLI_LIBRARY_PATH"] { + curlLinkFlags.append(.unsafeFlags([ + "-L\(environmentPath)" + ])) +} var libxmlLinkFlags: [LinkerSetting] = [ .linkedLibrary("libxml2s.lib", .when(platforms: [.windows])) diff --git a/Tests/Foundation/HTTPServer.swift b/Tests/Foundation/HTTPServer.swift index a6457f285f..7b42864d50 100644 --- a/Tests/Foundation/HTTPServer.swift +++ b/Tests/Foundation/HTTPServer.swift @@ -481,6 +481,18 @@ class _HTTPServer: CustomStringConvertible { "\r\n").data(using: .utf8)! try tcpSocket.writeRawData(responseData) } + + func respondWithAcceptEncoding(request: _HTTPRequest) throws { + var responseData: Data + if let acceptEncoding = request.getHeader(for: "Accept-Encoding") { + let content = acceptEncoding.data(using: .utf8)! + responseData = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Length: \(content.count)\r\n\r\n".data(using: .utf8)! + responseData.append(content) + } else { + responseData = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Length: 0\r\n\r\n".data(using: .utf8)! + } + try tcpSocket.writeRawData(responseData) + } } struct _HTTPRequest: CustomStringConvertible { @@ -690,6 +702,8 @@ public class TestURLSessionServer: CustomStringConvertible { try httpServer.respondWithUnauthorizedHeader() } else if req.uri.hasPrefix("/web-socket") { try handleWebSocketRequest(req) + } else if req.uri.hasPrefix("/accept-encoding") { + try httpServer.respondWithAcceptEncoding(request: req) } else { let response = try getResponse(request: req) try httpServer.respond(with: response) @@ -852,6 +866,16 @@ public class TestURLSessionServer: CustomStringConvertible { "Content-Encoding: gzip"].joined(separator: _HTTPUtils.CRLF), bodyData: helloWorld) } + + if uri == "/brotli-response" { + // This is "Hello World!" brotli encoded. + let helloWorld = Data([0x8B, 0x05, 0x80, 0x48, 0x65, 0x6C, 0x6C, 0x6F, + 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x03]) + return _HTTPResponse(response: .OK, + headers: ["Content-Length: \(helloWorld.count)", + "Content-Encoding: br"].joined(separator: _HTTPUtils.CRLF), + bodyData: helloWorld) + } if uri == "/echo-query" { let body = request.parameters.map { "\($0.key)=\($0.value)" }.joined(separator: "&") diff --git a/Tests/Foundation/TestURLSession.swift b/Tests/Foundation/TestURLSession.swift index df12d9a085..9bbfb922cd 100644 --- a/Tests/Foundation/TestURLSession.swift +++ b/Tests/Foundation/TestURLSession.swift @@ -31,6 +31,18 @@ final class TestURLSession: LoopbackServerTest, @unchecked Sendable { } } + func test_dataTaskWithAcceptEncoding() async { + let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/accept-encoding" + let url = URL(string: urlString)! + let d = DataTask(with: expectation(description: "GET \(urlString): with a delegate")) + d.run(with: url) + waitForExpectations(timeout: 12) + if !d.error { + let supportedEncodings = d.capital.split(separator: ",").map { $0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines ) } + XCTAssert(supportedEncodings.contains("br"), "test_dataTaskWithURLRequest returned an unexpected result") + } + } + func test_dataTaskWithURLCompletionHandler() async { //shared session await dataTaskWithURLCompletionHandler(with: URLSession.shared) @@ -256,6 +268,17 @@ final class TestURLSession: LoopbackServerTest, @unchecked Sendable { } } + func test_brotliDataTask() async { + let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/brotli-response" + let url = URL(string: urlString)! + let d = DataTask(with: expectation(description: "GET \(urlString): brotli response")) + d.run(with: url) + waitForExpectations(timeout: 12) + if !d.error { + XCTAssertEqual(d.capital, "Hello World!") + } + } + func test_downloadTaskWithURL() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/country.txt" let url = URL(string: urlString)! diff --git a/cmake/modules/WindowsSwiftPMDependencies.cmake b/cmake/modules/WindowsSwiftPMDependencies.cmake index 4346a734b8..555187218f 100644 --- a/cmake/modules/WindowsSwiftPMDependencies.cmake +++ b/cmake/modules/WindowsSwiftPMDependencies.cmake @@ -37,6 +37,18 @@ function(_foundation_setup_windows_swiftpm_dependencies_target) EXCLUDE_FROM_ALL YES ) + ExternalProject_Add(brotli + GIT_REPOSITORY https://github.com/google/brotli + GIT_TAG v1.1.0 + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DEST_DIR}/brotli + -DCMAKE_C_COMPILER=cl + -DBUILD_SHARED_LIBS=NO + -DCMAKE_POSITION_INDEPENDENT_CODE=YES + -DCMAKE_BUILD_TYPE=Release + EXCLUDE_FROM_ALL YES + ) + ExternalProject_Add(libxml GIT_REPOSITORY https://github.com/gnome/libxml2.git GIT_TAG v2.11.5 @@ -63,6 +75,15 @@ function(_foundation_setup_windows_swiftpm_dependencies_target) # Add a custom target for zlib's install step that curl can depend on ExternalProject_Add_StepTargets(zlib install) + set(BROTLI_ROOT "${DEST_DIR}/brotli") + set(BROTLI_LIBRARY_DIR "${BROTLI_ROOT}/lib") + set(BROTLI_INCLUDE_DIR "${BROTLI_ROOT}/include") + set(BROTLICOMMON_LIBRARY_PATH "${BROTLI_LIBRARY_DIR}/brotlicommon.lib") + set(BROTLIDEC_LIBRARY_PATH "${BROTLI_LIBRARY_DIR}/brotlidec.lib") + + # Add a custom target for brotli's install step that curl can depend on + ExternalProject_Add_StepTargets(brotli install) + ExternalProject_Add(curl GIT_REPOSITORY https://github.com/curl/curl.git GIT_TAG curl-8_9_1 @@ -75,7 +96,7 @@ function(_foundation_setup_windows_swiftpm_dependencies_target) -DCURL_CA_BUNDLE=none -DCURL_CA_FALLBACK=NO -DCURL_CA_PATH=none - -DCURL_BROTLI=NO + -DCURL_BROTLI=YES -DCURL_DISABLE_ALTSVC=NO -DCURL_DISABLE_AWS=YES -DCURL_DISABLE_BASIC_AUTH=NO @@ -150,7 +171,10 @@ function(_foundation_setup_windows_swiftpm_dependencies_target) -DZLIB_ROOT=${ZLIB_ROOT} -DZLIB_LIBRARY=${ZLIB_LIBRARY_PATH} -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} - DEPENDS zlib-install + -DBROTLIDEC_LIBRARY=${BROTLIDEC_LIBRARY_PATH} + -DBROTLICOMMON_LIBRARY=${BROTLICOMMON_LIBRARY_PATH} + -DBROTLI_INCLUDE_DIR=${BROTLI_INCLUDE_DIR} + DEPENDS zlib-install brotli-install EXCLUDE_FROM_ALL YES ) @@ -166,6 +190,7 @@ function(_foundation_setup_windows_swiftpm_dependencies_target) message(STATUS "CURL_INCLUDE_PATH=${CURL_INCLUDE_DIR}") message(STATUS "CURL_LIBRARY_PATH=${CURL_LIBRARY_DIR}") message(STATUS "ZLIB_LIBRARY_PATH=${ZLIB_LIBRARY_DIR}") + message(STATUS "BROTLI_LIBRARY_PATH=${BROTLI_LIBRARY_DIR}") ExternalProject_Add_StepTargets(libxml install) ExternalProject_Add_StepTargets(curl install) @@ -186,5 +211,7 @@ function(_foundation_setup_windows_swiftpm_dependencies_target) COMMAND echo CURL_LIBRARY_PATH=${CURL_LIBRARY_DIR}) add_custom_command(TARGET WindowsSwiftPMDependencies POST_BUILD COMMAND echo ZLIB_LIBRARY_PATH=${ZLIB_LIBRARY_DIR}) + add_custom_command(TARGET WindowsSwiftPMDependencies POST_BUILD + COMMAND echo BROTLI_LIBRARY_PATH=${BROTLI_LIBRARY_DIR}) endfunction()