// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//


//This is a very rudimentary HTTP server written plainly for testing URLSession. 
//It is not concurrent. It listens on a port, reads once and writes back only once.
//We can make it better everytime we need more functionality to test different aspects of URLSession.

import Dispatch

#if canImport(MSVCRT)
    import MSVCRT
    import WinSDK
#elseif canImport(Darwin)
    import Darwin
#elseif canImport(Glibc)
    import Glibc
#endif

#if !os(Windows)
typealias SOCKET = Int32
#endif


public let globalDispatchQueue = DispatchQueue.global()
public let dispatchQueueMake: (String) -> DispatchQueue = { DispatchQueue.init(label: $0) }
public let dispatchGroupMake: () -> DispatchGroup = DispatchGroup.init

struct _HTTPUtils {
    static let CRLF = "\r\n"
    static let VERSION = "HTTP/1.1"
    static let SPACE = " "
    static let CRLF2 = CRLF + CRLF
    static let EMPTY = ""
}

extension UInt16 {
    public init(networkByteOrder input: UInt16) {
        self.init(bigEndian: input)
    }
}

class _TCPSocket {
#if !os(Windows)
    private let sendFlags: CInt
#endif
    private var listenSocket: SOCKET!
    private var socketAddress = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: 1) 
    private var connectionSocket: SOCKET?
    
    private func isNotNegative(r: CInt) -> Bool {
        return r != -1
    }

    private func isZero(r: CInt) -> Bool {
        return r == 0
    }

    private func attempt<T>(_ name: String, file: String = #file, line: UInt = #line, valid: (T) -> Bool,  _ b: @autoclosure () -> T) throws -> T {
        let r = b()
        guard valid(r) else {
            throw ServerError(operation: name, errno: errno, file: file, line: line)
        }
        return r
    }

    public private(set) var port: UInt16

    init(port: UInt16?) throws {
#if !os(Windows)
#if os(Linux) || os(Android) || os(FreeBSD)
        sendFlags = CInt(MSG_NOSIGNAL)
#else
        sendFlags = 0
#endif
#endif

        self.port = port ?? 0

#if os(Windows)
        listenSocket = try attempt("WSASocketW", valid: { $0 != INVALID_SOCKET }, WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP.rawValue, nil, 0, DWORD(WSA_FLAG_OVERLAPPED)))

        var value: Int8 = 1
        _ = try attempt("setsockopt", valid: { $0 == 0 }, setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &value, Int32(MemoryLayout.size(ofValue: value))))
#else
#if os(Linux) && !os(Android)
        let SOCKSTREAM = Int32(SOCK_STREAM.rawValue)
#else
        let SOCKSTREAM = SOCK_STREAM
#endif
        listenSocket = try attempt("socket", valid: { $0 >= 0 }, socket(AF_INET, SOCKSTREAM, Int32(IPPROTO_TCP)))
        var on: CInt = 1
        _ = try attempt("setsockopt", valid: { $0 == 0 }, setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &on, socklen_t(MemoryLayout<CInt>.size)))
#endif

        let sa = createSockaddr(port)
        socketAddress.initialize(to: sa)
        try socketAddress.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size, { 
            let addr = UnsafePointer<sockaddr>($0)
            _ = try attempt("bind", valid: isZero, bind(listenSocket, addr, socklen_t(MemoryLayout<sockaddr>.size)))
            _ = try attempt("listen", valid: isZero, listen(listenSocket, SOMAXCONN))
        })

        var actualSA = sockaddr_in()
        withUnsafeMutablePointer(to: &actualSA) { ptr in
            ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { (ptr: UnsafeMutablePointer<sockaddr>) in
                var len = socklen_t(MemoryLayout<sockaddr>.size)
                getsockname(listenSocket, ptr, &len)
            }
        }

        self.port = UInt16(networkByteOrder: actualSA.sin_port)
    }

    private func createSockaddr(_ port: UInt16?) -> sockaddr_in {
        // Listen on the loopback address so that OSX doesnt pop up a dialog
        // asking to accept incoming connections if the firewall is enabled.
        let addr = UInt32(INADDR_LOOPBACK).bigEndian
        let netPort = UInt16(bigEndian: port ?? 0)
        #if os(Android)
            return sockaddr_in(sin_family: sa_family_t(AF_INET), sin_port: netPort, sin_addr: in_addr(s_addr: addr), __pad: (0,0,0,0,0,0,0,0))
        #elseif os(Linux)
            return sockaddr_in(sin_family: sa_family_t(AF_INET), sin_port: netPort, sin_addr: in_addr(s_addr: addr), sin_zero: (0,0,0,0,0,0,0,0))
        #elseif os(Windows)
            return sockaddr_in(sin_family: ADDRESS_FAMILY(AF_INET), sin_port: USHORT(netPort), sin_addr: IN_ADDR(S_un: in_addr.__Unnamed_union_S_un(S_addr: addr)), sin_zero: (CHAR(0), CHAR(0), CHAR(0), CHAR(0), CHAR(0), CHAR(0), CHAR(0), CHAR(0)))
        #else
            return sockaddr_in(sin_len: 0, sin_family: sa_family_t(AF_INET), sin_port: netPort, sin_addr: in_addr(s_addr: addr), sin_zero: (0,0,0,0,0,0,0,0))
        #endif
    }

    func acceptConnection(notify: ServerSemaphore) throws {
        try socketAddress.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size, {
            let addr = UnsafeMutablePointer<sockaddr>($0)
            var sockLen = socklen_t(MemoryLayout<sockaddr>.size) 
#if os(Windows)
            connectionSocket = try attempt("WSAAccept", valid: { $0 != INVALID_SOCKET }, WSAAccept(listenSocket, addr, &sockLen, nil, 0))
#else
            connectionSocket = try attempt("accept", valid: { $0 >= 0 }, accept(listenSocket, addr, &sockLen))
#endif
#if canImport(Darwin)
            // Disable SIGPIPEs when writing to closed sockets
            var on: CInt = 1
            if let connectionSocket = connectionSocket {
                _ = try attempt("setsockopt", valid: isZero, setsockopt(connectionSocket, SOL_SOCKET, SO_NOSIGPIPE, &on, socklen_t(MemoryLayout<CInt>.size)))
            }
#endif
        })
    }
 
    func readData() throws -> String {
        guard let connectionSocket = connectionSocket else {
            throw InternalServerError.socketAlreadyClosed
        }

        var buffer = [CChar](repeating: 0, count: 4096)
#if os(Windows)
        var dwNumberOfBytesRecieved: DWORD = 0;
        try buffer.withUnsafeMutableBufferPointer {
            var wsaBuffer: WSABUF = WSABUF(len: ULONG($0.count), buf: $0.baseAddress)
            var flags: DWORD = 0
            _ = try attempt("WSARecv", valid: { $0 != SOCKET_ERROR }, WSARecv(connectionSocket, &wsaBuffer, 1, &dwNumberOfBytesRecieved, &flags, nil, nil))
        }
#else
        _ = try attempt("read", valid: { $0 >= 0 }, read(connectionSocket, &buffer, buffer.count))
#endif
        return String(cString: &buffer)
    }

    func writeRawData(_ data: Data) throws {
        guard let connectionSocket = connectionSocket else {
            throw InternalServerError.socketAlreadyClosed
        }

#if os(Windows)
        _ = try data.withUnsafeBytes {
            var dwNumberOfBytesSent: DWORD = 0
            var wsaBuffer: WSABUF = WSABUF(len: ULONG(data.count), buf: UnsafeMutablePointer<CHAR>(mutating: $0.bindMemory(to: CHAR.self).baseAddress))
            _ = try attempt("WSASend", valid: { $0 != SOCKET_ERROR }, WSASend(connectionSocket, &wsaBuffer, 1, &dwNumberOfBytesSent, 0, nil, nil))
        }
#else
        _ = try data.withUnsafeBytes { ptr in
            try attempt("send", valid: isNotNegative, CInt(send(connectionSocket, ptr.baseAddress!, data.count, sendFlags)))
        }
#endif
    }

    private func _send(_ bytes: [UInt8]) throws -> Int {
        guard let connectionSocket = connectionSocket else {
            throw InternalServerError.socketAlreadyClosed
        }

#if os(Windows)
        return try bytes.withUnsafeBytes {
            var dwNumberOfBytesSent: DWORD = 0
            var wsaBuffer: WSABUF = WSABUF(len: ULONG(bytes.count), buf: UnsafeMutablePointer<CHAR>(mutating: $0.bindMemory(to: CHAR.self).baseAddress))
            return try Int(attempt("WSASend", valid: { $0 != SOCKET_ERROR }, WSASend(connectionSocket, &wsaBuffer, 1, &dwNumberOfBytesSent, 0, nil, nil)))
        }
#else
        return try bytes.withUnsafeBufferPointer {
            try attempt("send", valid: { $0 >= 0 }, send(connectionSocket, $0.baseAddress, $0.count, sendFlags))
        }
#endif
    }

    func writeData(header: String, bodyData: Data, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
        _ = try _send(Array(header.utf8))

        if let sendDelay = sendDelay, let bodyChunks = bodyChunks {
            let count = max(1, Int(Double(bodyData.count) / Double(bodyChunks)))
            for startIndex in stride(from: 0, to: bodyData.count, by: count) {
                Thread.sleep(forTimeInterval: sendDelay)
                let endIndex = min(startIndex + count, bodyData.count)
                try bodyData.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Void in
                    let chunk = UnsafeRawBufferPointer(rebasing: ptr[startIndex..<endIndex])
                    _ = try _send(Array(chunk.bindMemory(to: UInt8.self)))
                }
            }
        } else {
            try bodyData.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Void in
                _ = try _send(Array(ptr.bindMemory(to: UInt8.self)))
            }
        }
    }

    func closeClient() {
        if let connectionSocket = self.connectionSocket {
#if os(Windows)
            closesocket(connectionSocket)
#else
            close(connectionSocket)
#endif
            self.connectionSocket = nil
        }
    }

    func shutdownListener() {
        closeClient()
#if os(Windows)
        shutdown(listenSocket, SD_BOTH)
        closesocket(listenSocket)
#else
        shutdown(listenSocket, CInt(SHUT_RDWR))
        close(listenSocket)
#endif
    }
}

class _HTTPServer {

    let socket: _TCPSocket
    var willReadAgain = false
    var port: UInt16 {
        get {
            return self.socket.port
        }
    }
    
    init(port: UInt16?) throws {
        socket = try _TCPSocket(port: port)
    }

    public class func create(port: UInt16?) throws -> _HTTPServer {
        return try _HTTPServer(port: port)
    }

    public func listen(notify: ServerSemaphore) throws {
        try socket.acceptConnection(notify: notify)
    }

    public func stop() {
        if !willReadAgain {
            socket.closeClient()
            socket.shutdownListener()
	}
    }
   
    public func request() throws -> _HTTPRequest {
        var request = try _HTTPRequest(request: socket.readData())
       
        if Int(request.getHeader(for: "Content-Length") ?? "0") ?? 0 > 0
            || (request.getHeader(for: "Transfer-Encoding") ?? "").lowercased() == "chunked" {
            
            // According to RFC7230 https://tools.ietf.org/html/rfc7230#section-3
            // We receive messageBody after the headers, so we need read from socket minimum 2 times
            //
            // HTTP-message structure
            //
            // start-line
            // *( header-field CRLF )
            // CRLF
            // [ message-body ]
            // We receives '{numofbytes}\r\n{data}\r\n'
            // TODO read data until the end
            
            let substr = try socket.readData().split(separator: "\r\n")
            if substr.count >= 2 {
                request.messageBody = String(substr[1])
            }
        }
        
        return request
    }

    public func respond(with response: _HTTPResponse, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
        if let delay = startDelay {
            Thread.sleep(forTimeInterval: delay)
        }
        do {
            try self.socket.writeData(header: response.header, bodyData: response.bodyData, sendDelay: sendDelay, bodyChunks: bodyChunks)
        } catch {
        }
    }

    func respondWithBrokenResponses(uri: String) throws {
        let responseData: Data
        switch uri {
            case "/LandOfTheLostCities/Pompeii":
                /* this is an example of what you get if you connect to an HTTP2
                 server using HTTP/1.1. Curl interprets that as a HTTP/0.9
                 simple-response and therefore sends this back as a response
                 body. Go figure! */
                responseData = Data([
                    0x00, 0x00, 0x18, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
                    0x01, 0x00, 0x05, 0x00, 0x00, 0x40, 0x00, 0x00, 0x06, 0x00,
                    0x00, 0x1f, 0x40, 0x00, 0x00, 0x86, 0x07, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
                    0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x20, 0x63, 0x6c, 0x69,
                    0x65, 0x6e, 0x74, 0x20, 0x70, 0x72, 0x65, 0x66, 0x61, 0x63,
                    0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6d,
                    0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x72, 0x20,
                    0x63, 0x6f, 0x72, 0x72, 0x75, 0x70, 0x74, 0x2e, 0x20, 0x48,
                    0x65, 0x78, 0x20, 0x64, 0x75, 0x6d, 0x70, 0x20, 0x66, 0x6f,
                    0x72, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64,
                    0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x3a, 0x20, 0x34, 0x37,
                    0x34, 0x35, 0x35, 0x34, 0x32, 0x30, 0x32, 0x66, 0x33, 0x33,
                    0x32, 0x66, 0x36, 0x34, 0x36, 0x35, 0x37, 0x36, 0x36, 0x39,
                    0x36, 0x33, 0x36, 0x35, 0x32, 0x66, 0x33, 0x31, 0x33, 0x32,
                    0x33, 0x33, 0x33, 0x34, 0x33, 0x35, 0x33, 0x36, 0x33, 0x37,
                    0x33, 0x38, 0x33, 0x39, 0x33, 0x30])
            case "/LandOfTheLostCities/Sodom":
                /* a technically valid HTTP/0.9 simple-response */
                responseData = ("technically, this is a valid HTTP/0.9 " +
                    "simple-response. I know it's odd but CURL supports it " +
                    "still...\r\nFind out more in those URLs:\r\n " +
                    " - https://www.w3.org/Protocols/HTTP/1.0/spec.html#Message-Types\r\n" +
                    " - https://github.com/curl/curl/issues/467\r\n").data(using: .utf8)!
            case "/LandOfTheLostCities/Gomorrah":
                /* just broken, hope that's not officially HTTP/0.9 :p */
                responseData = "HTTP/1.1\r\n\r\n\r\n".data(using: .utf8)!
            case "/LandOfTheLostCities/Myndus":
                responseData = ("HTTP/1.1 200 OK\r\n" +
                               "\r\n" +
                               "this is a body that isn't legal as it's " +
                               "neither chunked encoding nor any Content-Length\r\n").data(using: .utf8)!
            case "/LandOfTheLostCities/Kameiros":
                responseData = ("HTTP/1.1 999 Wrong Code\r\n" +
                               "illegal: status code (too large)\r\n" +
                               "\r\n").data(using: .utf8)!
            case "/LandOfTheLostCities/Dinavar":
                responseData = ("HTTP/1.1 20 Too Few Digits\r\n" +
                               "illegal: status code (too few digits)\r\n" +
                               "\r\n").data(using: .utf8)!
            case "/LandOfTheLostCities/Kuhikugu":
                responseData = ("HTTP/1.1 2000 Too Many Digits\r\n" +
                               "illegal: status code (too many digits)\r\n" +
                               "\r\n").data(using: .utf8)!
            default:
                responseData = ("HTTP/1.1 500 Internal Server Error\r\n" +
                               "case-missing-in: TestFoundation/HTTPServer.swift\r\n" +
                               "\r\n").data(using: .utf8)!
        }
        try self.socket.writeRawData(responseData)
    }

    func respondWithAuthResponse(uri: String, firstRead: Bool) throws {
        let responseData: Data
        if firstRead {
            responseData = ("HTTP/1.1 401 UNAUTHORIZED \r\n" +
                        "Content-Length: 0\r\n" +
                        "WWW-Authenticate: Basic realm=\"Fake Relam\"\r\n" +
                        "Access-Control-Allow-Origin: *\r\n" +
                        "Access-Control-Allow-Credentials: true\r\n" +
                        "Via: 1.1 vegur\r\n" +
                        "Cache-Control: proxy-revalidate\r\n" +
                        "Connection: keep-Alive\r\n" +
                        "\r\n").data(using: .utf8)!
        } else {
            responseData = ("HTTP/1.1 200 OK \r\n" +
                "Content-Length: 37\r\n" +
                "Content-Type: application/json\r\n" +
                "Access-Control-Allow-Origin: *\r\n" +
                "Access-Control-Allow-Credentials: true\r\n" +
                "Via: 1.1 vegur\r\n" +
                "Cache-Control: proxy-revalidate\r\n" +
                "Connection: keep-Alive\r\n" +
                "\r\n" +
                "{\"authenticated\":true,\"user\":\"user\"}\n").data(using: .utf8)!
        }
        try self.socket.writeRawData(responseData)
    }

    func respondWithUnauthorizedHeader() throws{
        let responseData = ("HTTP/1.1 401 UNAUTHORIZED \r\n" +
                "Content-Length: 0\r\n" +
                "Connection: keep-Alive\r\n" +
                "\r\n").data(using: .utf8)!
        try self.socket.writeRawData(responseData)
    }
}

struct _HTTPRequest {
    enum Method : String {
        case GET
        case POST
        case PUT
    }
    let method: Method
    let uri: String 
    let body: String
    var messageBody: String?
    let headers: [String]

    enum Error: Swift.Error {
        case headerEndNotFound
    }
    
    public init(request: String) throws {
        let headerEnd = (request as NSString).range(of: _HTTPUtils.CRLF2)
        guard headerEnd.location != NSNotFound else { throw Error.headerEndNotFound }
        let header = (request as NSString).substring(to: headerEnd.location)
        headers = header.components(separatedBy: _HTTPUtils.CRLF)
        let action = headers[0]
        method = Method(rawValue: action.components(separatedBy: " ")[0])!
        uri = action.components(separatedBy: " ")[1]
        body = (request as NSString).substring(from: headerEnd.location + headerEnd.length)
    }

    public func getCommaSeparatedHeaders() -> String {
        var allHeaders = ""
        for header in headers {
            allHeaders += header + ","
        }
        return allHeaders
    }

    public func getHeader(for key: String) -> String? {
        let lookup = key.lowercased()
        for header in headers {
            let parts = header.components(separatedBy: ":")
            if parts[0].lowercased() == lookup {
                return parts[1].trimmingCharacters(in: CharacterSet(charactersIn: " "))
            }
        }
        return nil
    }
}

struct _HTTPResponse {
    enum Response : Int {
        case OK = 200
        case REDIRECT = 302
        case NOTFOUND = 404
    }
    private let responseCode: Response
    private let headers: String
    public let bodyData: Data

    public init(response: Response, headers: String = _HTTPUtils.EMPTY, bodyData: Data) {
        self.responseCode = response
        self.headers = headers
        self.bodyData = bodyData
    }

    public init(response: Response, headers: String = _HTTPUtils.EMPTY, body: String) {
        self.init(response: response, headers: headers, bodyData: body.data(using: .utf8)!)
    }

    public var header: String {
        let statusLine = _HTTPUtils.VERSION + _HTTPUtils.SPACE + "\(responseCode.rawValue)" + _HTTPUtils.SPACE + "\(responseCode)"
        return statusLine + (headers != _HTTPUtils.EMPTY ? _HTTPUtils.CRLF + headers : _HTTPUtils.EMPTY) + _HTTPUtils.CRLF2
    }
}

public class TestURLSessionServer {
    let capitals: [String:String] = ["Nepal": "Kathmandu",
                                     "Peru": "Lima",
                                     "Italy": "Rome",
                                     "USA": "Washington, D.C.",
                                     "UnitedStates": "USA",
                                     "UnitedKingdom": "UK",
                                     "UK": "London",
                                     "country.txt": "A country is a region that is identified as a distinct national entity in political geography"]
    let httpServer: _HTTPServer
    let startDelay: TimeInterval?
    let sendDelay: TimeInterval?
    let bodyChunks: Int?
    var port: UInt16 {
        get {
            return self.httpServer.port
        }
    }
    
    public init (port: UInt16?, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
        httpServer = try _HTTPServer.create(port: port)
        self.startDelay = startDelay
        self.sendDelay = sendDelay
        self.bodyChunks = bodyChunks
    }

    public func readAndRespond() throws {
        let req = try httpServer.request()

        if let value = req.getHeader(for: "x-pause") {
            if let wait = Double(value), wait > 0 {
                Thread.sleep(forTimeInterval: wait)
            }
        }

        if req.uri.hasPrefix("/LandOfTheLostCities/") {
            /* these are all misbehaving servers */
            try httpServer.respondWithBrokenResponses(uri: req.uri)
        } else if req.uri == "/NSString-ISO-8859-1-data.txt" {
            // Serve this directly as binary data to avoid any String encoding conversions.
            if let url = testBundle().url(forResource: "NSString-ISO-8859-1-data", withExtension: "txt"),
                let content = try? Data(contentsOf: url) {
                var 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: .ascii)!
                responseData.append(content)
                try httpServer.socket.writeRawData(responseData)
            } else {
                try httpServer.respond(with: _HTTPResponse(response: .NOTFOUND, body: "Not Found"))
            }
        } else if req.uri.hasPrefix("/auth") {
            httpServer.willReadAgain = true
            try httpServer.respondWithAuthResponse(uri: req.uri, firstRead: true)
        } else if req.uri.hasPrefix("/unauthorized") {
            try httpServer.respondWithUnauthorizedHeader()
        } else {
            try httpServer.respond(with: process(request: req), startDelay: self.startDelay, sendDelay: self.sendDelay, bodyChunks: self.bodyChunks)
        }
    }

    public func readAndRespondAgain() throws {
        let req = try httpServer.request()
        if req.uri.hasPrefix("/auth/") {
            try httpServer.respondWithAuthResponse(uri: req.uri, firstRead: false)
        }
        httpServer.willReadAgain = false
    }

    func process(request: _HTTPRequest) -> _HTTPResponse {
        if request.method == .GET || request.method == .POST || request.method == .PUT {
            return getResponse(request: request)
        } else {
            fatalError("Unsupported method!")
        }
    }

    func getResponse(request: _HTTPRequest) -> _HTTPResponse {
        let uri = request.uri

        if uri == "/upload" {
            let text = "Upload completed!"
            return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.data(using: .utf8)!.count)", body: text)
        }

        if uri == "/country.txt" {
            let text = capitals[String(uri.dropFirst())]!
            return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.data(using: .utf8)!.count)", body: text)
        }

        if uri == "/requestHeaders" {
            let text = request.getCommaSeparatedHeaders()
            return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.data(using: .utf8)!.count)", body: text)
        }

        if uri == "/emptyPost" {
            if request.body.count == 0 && request.getHeader(for: "Content-Type") == nil {
                return _HTTPResponse(response: .OK, body: "")
            }
            return _HTTPResponse(response: .NOTFOUND, body: "")
        }

        if uri == "/requestCookies" {
            let text = request.getCommaSeparatedHeaders()
            return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.data(using: .utf8)!.count)\r\nSet-Cookie: fr=anjd&232; Max-Age=7776000; path=/\r\nSet-Cookie: nm=sddf&232; Max-Age=7776000; path=/; domain=.swift.org; secure; httponly\r\n", body: text)
        }

        if uri == "/setCookies" {
            let text = request.getCommaSeparatedHeaders()
            return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.data(using: .utf8)!.count)", body: text)
        }
        
        if uri == "/redirectSetCookies" {
            return _HTTPResponse(response: .REDIRECT, headers: "Location: /setCookies\r\nSet-Cookie: redirect=true; Max-Age=7776000; path=/", body: "")
        }

        if uri == "/UnitedStates" {
            let value = capitals[String(uri.dropFirst())]!
            let text = request.getCommaSeparatedHeaders()
            let host = request.headers[1].components(separatedBy: " ")[1]
            let ip = host.components(separatedBy: ":")[0]
            let port = host.components(separatedBy: ":")[1]
            let newPort = Int(port)! + 1
            let newHost = ip + ":" + String(newPort)
            let httpResponse = _HTTPResponse(response: .REDIRECT, headers: "Location: http://\(newHost + "/" + value)", body: text)
            return httpResponse 
        }

        if uri == "/DTDs/PropertyList-1.0.dtd" {
            let dtd = """
    <!ENTITY % plistObject "(array | data | date | dict | real | integer | string | true | false )" >
    <!ELEMENT plist %plistObject;>
    <!ATTLIST plist version CDATA "1.0" >

    <!-- Collections -->
    <!ELEMENT array (%plistObject;)*>
    <!ELEMENT dict (key, %plistObject;)*>
    <!ELEMENT key (#PCDATA)>

    <!--- Primitive types -->
    <!ELEMENT string (#PCDATA)>
    <!ELEMENT data (#PCDATA)> <!-- Contents interpreted as Base-64 encoded -->
    <!ELEMENT date (#PCDATA)> <!-- Contents should conform to a subset of ISO 8601 (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'.  Smaller units may be omitted with a loss of precision) -->

    <!-- Numerical primitives -->
    <!ELEMENT true EMPTY>  <!-- Boolean constant true -->
    <!ELEMENT false EMPTY> <!-- Boolean constant false -->
    <!ELEMENT real (#PCDATA)> <!-- Contents should represent a floating point number matching ("+" | "-")? d+ ("."d*)? ("E" ("+" | "-") d+)? where d is a digit 0-9.  -->
    <!ELEMENT integer (#PCDATA)> <!-- Contents should represent a (possibly signed) integer number in base 10 -->
"""
            return _HTTPResponse(response: .OK, body: dtd)
        }

        if uri == "/UnitedKingdom" {
            let value = capitals[String(uri.dropFirst())]!
            let text = request.getCommaSeparatedHeaders()
            //Response header with only path to the location to redirect.
            let httpResponse = _HTTPResponse(response: .REDIRECT, headers: "Location: \(value)", body: text)
            return httpResponse
        }
        
        if uri == "/echo" {
            return _HTTPResponse(response: .OK, body: request.messageBody ?? request.body)
        }
        
        if uri == "/redirect-with-default-port" {
            let text = request.getCommaSeparatedHeaders()
            let host = request.headers[1].components(separatedBy: " ")[1]
            let ip = host.components(separatedBy: ":")[0]
            let httpResponse = _HTTPResponse(response: .REDIRECT, headers: "Location: http://\(ip)/redirected-with-default-port", body: text)
            return httpResponse

        }

        if uri == "/gzipped-response" {
            // This is "Hello World!" gzipped.
            let helloWorld = Data([0x1f, 0x8b, 0x08, 0x00, 0x6d, 0xca, 0xb2, 0x5c,
                                   0x00, 0x03, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57,
                                   0x08, 0xcf, 0x2f, 0xca, 0x49, 0x51, 0x04, 0x00,
                                   0xa3, 0x1c, 0x29, 0x1c, 0x0c, 0x00, 0x00, 0x00])
            return _HTTPResponse(response: .OK,
                                 headers: ["Content-Length: \(helloWorld.count)",
                                           "Content-Encoding: gzip"].joined(separator: _HTTPUtils.CRLF),
                                 bodyData: helloWorld)
        }

        return _HTTPResponse(response: .OK, body: capitals[String(uri.dropFirst())]!)
    }

    func stop() {
        httpServer.stop()
    }
}

struct ServerError : Error {
    let operation: String
    let errno: CInt
    let file: String
    let line: UInt
    var _code: Int { return Int(errno) }
    var _domain: String { return NSPOSIXErrorDomain }
}


extension ServerError : CustomStringConvertible {
    var description: String {
        let s = String(validatingUTF8: strerror(errno)) ?? ""
        return "\(operation) failed: \(s) (\(_code))"
    }
}

enum InternalServerError : Error {
    case socketAlreadyClosed
}

public class ServerSemaphore {
    let dispatchSemaphore = DispatchSemaphore(value: 0)

    public func wait(timeout: DispatchTime) -> DispatchTimeoutResult {
        return dispatchSemaphore.wait(timeout: timeout)
    }

    public func signal() {
        dispatchSemaphore.signal()
    }
}

class LoopbackServerTest : XCTestCase {
    private static let staticSyncQ = DispatchQueue(label: "org.swift.TestFoundation.HTTPServer.StaticSyncQ")

    private static var _serverPort: Int = -1
    private static let serverReady = ServerSemaphore()
    private static var _serverActive = false
    private static var testServer: TestURLSessionServer? = nil


    static var serverPort: Int {
        get {
            return staticSyncQ.sync { _serverPort }
        }
        set {
            staticSyncQ.sync { _serverPort = newValue }
        }
    }

    static var serverActive: Bool {
        get { return staticSyncQ.sync { _serverActive } }
        set { staticSyncQ.sync { _serverActive = newValue }}
    }

    static func terminateServer() {
        serverActive = false
        testServer?.stop()
        testServer = nil
    }

    override class func setUp() {
        super.setUp()
        func runServer(with condition: ServerSemaphore, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
            let server = try TestURLSessionServer(port: nil, startDelay: startDelay, sendDelay: sendDelay, bodyChunks: bodyChunks)
            testServer = server
            serverPort = Int(server.port)
            serverReady.signal()
            serverActive = true

            while serverActive {
                do {
                    try server.httpServer.listen(notify: condition)
                    try server.readAndRespond()
                    if server.httpServer.willReadAgain {
                        try server.httpServer.listen(notify: condition)
                        try server.readAndRespondAgain()
                    }
                    server.httpServer.socket.closeClient()
                } catch {
                }
            }
            serverPort = -2
        }

        globalDispatchQueue.async {
            do {
                try runServer(with: serverReady)
            } catch {
            }
        }

        let timeout = DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + 2_000_000_000)

        while serverPort == -1 {
            guard serverReady.wait(timeout: timeout) == .success else {
                fatalError("Timedout waiting for server to be ready")
            }
        }
    }

    override class func tearDown() {
        super.tearDown()
        terminateServer()
    }
}