LibCore: Allow constructing a Core::HttpRequest from a raw HTTP request

This patch adds a very simple HTTP request parser that can be useful
for implementing say.. a web server. :^)
This commit is contained in:
Andreas Kling 2020-02-09 11:27:36 +01:00
parent a189285658
commit 3a7e49fe07
Notes: sideshowbarker 2024-07-19 09:31:09 +09:00
2 changed files with 119 additions and 0 deletions

View file

@ -71,4 +71,110 @@ ByteBuffer HttpRequest::to_raw_request() const
return builder.to_byte_buffer();
}
Optional<HttpRequest> HttpRequest::from_raw_request(const ByteBuffer& raw_request)
{
enum class State {
InMethod,
InResource,
InProtocol,
InHeaderName,
InHeaderValue,
};
State state { State::InMethod };
int index = 0;
auto peek = [&](int offset = 0) -> u8 {
if (index + offset >= raw_request.size())
return 0;
return raw_request[index + offset];
};
auto consume = [&]() -> u8 {
ASSERT(index < raw_request.size());
return raw_request[index++];
};
Vector<u8, 256> buffer;
String method;
String resource;
String protocol;
Vector<Header> headers;
Header current_header;
auto commit_and_advance_to = [&](auto& output, State new_state) {
output = String::copy(buffer);
buffer.clear();
state = new_state;
};
while (index < raw_request.size()) {
// FIXME: Figure out what the appropriate limitations should be.
if (buffer.size() > 65536)
return {};
switch (state) {
case State::InMethod:
if (peek() == ' ') {
consume();
commit_and_advance_to(method, State::InResource);
break;
}
buffer.append(consume());
break;
case State::InResource:
if (peek() == ' ') {
consume();
commit_and_advance_to(resource, State::InProtocol);
break;
}
buffer.append(consume());
break;
case State::InProtocol:
if (peek(0) == '\r' && peek(1) == '\n') {
consume();
consume();
commit_and_advance_to(protocol, State::InHeaderName);
break;
}
buffer.append(consume());
break;
case State::InHeaderName:
if (peek(0) == ':' && peek(1) == ' ') {
consume();
consume();
commit_and_advance_to(current_header.name, State::InHeaderValue);
break;
}
buffer.append(consume());
break;
case State::InHeaderValue:
if (peek(0) == '\r' && peek(1) == '\n') {
consume();
consume();
commit_and_advance_to(current_header.value, State::InHeaderName);
headers.append(move(current_header));
break;
}
buffer.append(consume());
break;
}
}
HttpRequest request;
if (method == "GET")
request.m_method = Method::GET;
else if (method == "HEAD")
request.m_method = Method::HEAD;
else if (method == "POST")
request.m_method = Method::POST;
else
return {};
request.m_resource = resource;
request.m_headers = move(headers);
return request;
}
}

View file

@ -26,6 +26,7 @@
#pragma once
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/URL.h>
@ -42,9 +43,17 @@ public:
POST
};
struct Header {
String name;
String value;
};
HttpRequest();
~HttpRequest();
const String& resource() const { return m_resource; }
const Vector<Header>& headers() const { return m_headers; }
const URL& url() const { return m_url; }
void set_url(const URL& url) { m_url = url; }
@ -56,9 +65,13 @@ public:
RefPtr<NetworkJob> schedule();
static Optional<HttpRequest> from_raw_request(const ByteBuffer&);
private:
URL m_url;
String m_resource;
Method m_method { GET };
Vector<Header> m_headers;
};
}