blob: 0696b7e96b2a419eaa8d7d4167ca0b259673aa6d [file] [log] [blame]
/*
* Copyright (C) 2019 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#import <Network/Network.h>
#import <experimental/coroutine>
#import <wtf/CompletionHandler.h>
#import <wtf/Forward.h>
#import <wtf/HashMap.h>
#import <wtf/RetainPtr.h>
#import <wtf/text/StringHash.h>
OBJC_CLASS NSURLRequest;
namespace TestWebKitAPI {
class Connection;
struct HTTPResponse;
template<typename PromiseType>
struct CoroutineHandle {
CoroutineHandle(std::experimental::coroutine_handle<PromiseType>&& handle)
: handle(WTFMove(handle)) { }
CoroutineHandle(const CoroutineHandle&) = delete;
CoroutineHandle(CoroutineHandle&& other)
: handle(std::exchange(other.handle, nullptr)) { }
~CoroutineHandle()
{
if (handle)
handle.destroy();
}
std::experimental::coroutine_handle<PromiseType> handle;
};
struct Task {
struct promise_type {
Task get_return_object() { return { std::experimental::coroutine_handle<promise_type>::from_promise(*this) }; }
std::experimental::suspend_never initial_suspend() { return { }; }
std::experimental::suspend_never final_suspend() noexcept { return { }; }
void unhandled_exception() { }
void return_void() { }
};
CoroutineHandle<promise_type> handle;
};
class ReceiveOperation;
class SendOperation;
class HTTPServer {
WTF_MAKE_FAST_ALLOCATED;
public:
struct RequestData;
enum class Protocol : uint8_t { Http, Https, HttpsWithLegacyTLS, Http2, HttpsProxy, HttpsProxyWithAuthentication };
using CertificateVerifier = Function<void(sec_protocol_metadata_t, sec_trust_t, sec_protocol_verify_complete_t)>;
HTTPServer(std::initializer_list<std::pair<String, HTTPResponse>>, Protocol = Protocol::Http, CertificateVerifier&& = nullptr, RetainPtr<SecIdentityRef>&& = nullptr, std::optional<uint16_t> port = { });
HTTPServer(Function<void(Connection)>&&, Protocol = Protocol::Http);
enum class UseCoroutines : bool { Yes };
HTTPServer(UseCoroutines, Function<Task(Connection)>&&, Protocol = Protocol::Http);
~HTTPServer();
uint16_t port() const;
String origin() const;
NSURLRequest *request(StringView path = "/"_s) const;
NSURLRequest *requestWithLocalhost(StringView path = "/"_s) const;
size_t totalRequests() const;
void cancel();
void addResponse(String&& path, HTTPResponse&&);
void setResponse(String&& path, HTTPResponse&&);
static void respondWithOK(Connection);
static void respondWithChallengeThenOK(Connection);
static String parsePath(const Vector<char>& request);
static String parseBody(const Vector<char>&);
static Vector<uint8_t> testPrivateKey();
static Vector<uint8_t> testCertificate();
private:
static RetainPtr<nw_parameters_t> listenerParameters(Protocol, CertificateVerifier&&, RetainPtr<SecIdentityRef>&&, std::optional<uint16_t> port);
static void respondToRequests(Connection, Ref<RequestData>);
const char* scheme() const;
Ref<RequestData> m_requestData;
RetainPtr<nw_listener_t> m_listener;
Protocol m_protocol { Protocol::Http };
};
class Connection {
public:
void send(String&&, CompletionHandler<void()>&& = nullptr) const;
void send(Vector<uint8_t>&&, CompletionHandler<void()>&& = nullptr) const;
void send(RetainPtr<dispatch_data_t>&&, CompletionHandler<void(bool)>&& = nullptr) const;
SendOperation awaitableSend(Vector<uint8_t>&&);
SendOperation awaitableSend(String&&);
void sendAndReportError(Vector<uint8_t>&&, CompletionHandler<void(bool)>&&) const;
void receiveBytes(CompletionHandler<void(Vector<uint8_t>&&)>&&, size_t minimumSize = 1) const;
void receiveHTTPRequest(CompletionHandler<void(Vector<char>&&)>&&, Vector<char>&& buffer = { }) const;
ReceiveOperation awaitableReceiveHTTPRequest() const;
void webSocketHandshake(CompletionHandler<void()>&& = { });
void terminate(CompletionHandler<void()>&& = { });
void cancel();
private:
friend class HTTPServer;
Connection(nw_connection_t connection)
: m_connection(connection) { }
RetainPtr<nw_connection_t> m_connection;
};
class ReceiveOperation : public std::experimental::suspend_never {
public:
ReceiveOperation(const Connection& connection)
: m_connection(connection) { }
bool await_ready() { return false; }
void await_suspend(std::experimental::coroutine_handle<>);
Vector<char> await_resume() { return WTFMove(m_result); }
private:
Connection m_connection;
Vector<char> m_result;
};
class SendOperation : public std::experimental::suspend_never {
public:
SendOperation(RetainPtr<dispatch_data_t>&& data, const Connection& connection)
: m_data(WTFMove(data))
, m_connection(connection) { }
bool await_ready() { return false; }
void await_suspend(std::experimental::coroutine_handle<>);
private:
RetainPtr<dispatch_data_t> m_data;
Connection m_connection;
};
struct HTTPResponse {
enum class TerminateConnection { No, Yes };
HTTPResponse(Vector<uint8_t>&& body)
: body(WTFMove(body)) { }
HTTPResponse(const String& body)
: body(bodyFromString(body)) { }
HTTPResponse(HashMap<String, String>&& headerFields, const String& body)
: headerFields(WTFMove(headerFields))
, body(bodyFromString(body)) { }
HTTPResponse(unsigned statusCode, HashMap<String, String>&& headerFields = { }, const String& body = { })
: statusCode(statusCode)
, headerFields(WTFMove(headerFields))
, body(bodyFromString(body)) { }
HTTPResponse(TerminateConnection terminateConnection)
: terminateConnection(terminateConnection) { }
HTTPResponse(const HTTPResponse&) = default;
HTTPResponse(HTTPResponse&&) = default;
HTTPResponse() = default;
HTTPResponse& operator=(const HTTPResponse&) = default;
HTTPResponse& operator=(HTTPResponse&&) = default;
enum class IncludeContentLength : bool { No, Yes };
Vector<uint8_t> serialize(IncludeContentLength = IncludeContentLength::Yes) const;
static Vector<uint8_t> bodyFromString(const String&);
unsigned statusCode { 200 };
HashMap<String, String> headerFields;
Vector<uint8_t> body;
TerminateConnection terminateConnection { TerminateConnection::No };
};
namespace H2 {
// https://http2.github.io/http2-spec/#rfc.section.4.1
class Frame {
public:
// https://http2.github.io/http2-spec/#rfc.section.6
enum class Type : uint8_t {
Data = 0x0,
Headers = 0x1,
Priority = 0x2,
RSTStream = 0x3,
Settings = 0x4,
PushPromise = 0x5,
Ping = 0x6,
GoAway = 0x7,
WindowUpdate = 0x8,
Continuation = 0x9,
};
Frame(Type type, uint8_t flags, uint32_t streamID, Vector<uint8_t> payload)
: m_type(type)
, m_flags(flags)
, m_streamID(streamID)
, m_payload(WTFMove(payload)) { }
Type type() const { return m_type; }
uint8_t flags() const { return m_flags; }
uint32_t streamID() const { return m_streamID; }
const Vector<uint8_t>& payload() const { return m_payload; }
private:
Type m_type;
uint8_t m_flags;
uint32_t m_streamID;
Vector<uint8_t> m_payload;
};
class Connection : public RefCounted<Connection> {
public:
static Ref<Connection> create(TestWebKitAPI::Connection tlsConnection) { return adoptRef(*new Connection(tlsConnection)); }
void send(Frame&&, CompletionHandler<void()>&& = nullptr) const;
void receive(CompletionHandler<void(Frame&&)>&&) const;
private:
Connection(TestWebKitAPI::Connection tlsConnection)
: m_tlsConnection(tlsConnection) { }
TestWebKitAPI::Connection m_tlsConnection;
mutable bool m_expectClientConnectionPreface { true };
mutable bool m_sendServerConnectionPreface { true };
mutable Vector<uint8_t> m_receiveBuffer;
};
} // namespace H2
} // namespace TestWebKitAPI
RetainPtr<SecCertificateRef> testCertificate();
RetainPtr<SecIdentityRef> testIdentity();
RetainPtr<SecIdentityRef> testIdentity2();