Skip to content

Commit

Permalink
[k2] implement setcookie & setrawcookie (#1186)
Browse files Browse the repository at this point in the history
  • Loading branch information
apolyakov authored Dec 13, 2024
1 parent ed08ea6 commit 6dc98a4
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 40 deletions.
4 changes: 4 additions & 0 deletions builtin-functions/kphp-light/server.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ function parse_url ($str ::: string, $component ::: int = -1): mixed;
function header ($str ::: string, $replace ::: bool = true, $http_response_code ::: int = 0): void;

function numa_get_bound_node(): int;

function setcookie ($name ::: string, $value ::: string, $expire_or_options ::: int = 0, $path ::: string = '', $domain ::: string = '', $secure ::: bool = false, $http_only ::: bool = false): void;

function setrawcookie ($name ::: string, $value ::: string, $expire_or_options ::: int = 0, $path ::: string = '', $domain ::: string = '', $secure ::: bool = false, $http_only ::: bool = false): void;
13 changes: 13 additions & 0 deletions runtime-light/server/http/http-server-state.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@

enum class HttpMethod : uint8_t { GET, POST, HEAD, OTHER };

namespace HttpHeader {

inline constexpr std::string_view HOST = "host";
inline constexpr std::string_view COOKIE = "cookie";
inline constexpr std::string_view SET_COOKIE = "set-cookie";
inline constexpr std::string_view CONNECTION = "connection";
inline constexpr std::string_view CONTENT_TYPE = "content-type";
inline constexpr std::string_view CONTENT_LENGTH = "content-length";
inline constexpr std::string_view AUTHORIZATION = "authorization";
inline constexpr std::string_view ACCEPT_ENCODING = "accept-encoding";

} // namespace HttpHeader

enum class HttpConnectionKind : uint8_t { KEEP_ALIVE, CLOSE };

enum HttpStatus : uint16_t {
Expand Down
67 changes: 30 additions & 37 deletions runtime-light/server/http/init-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,7 @@ constexpr std::string_view HTTP_X_REAL_REQUEST = "HTTP_X_REAL_REQUEST";
constexpr std::string_view SCHEME_SUFFIX = "://";
constexpr std::string_view GATEWAY_INTERFACE_VALUE = "CGI/1.1";

constexpr std::string_view HEADER_HOST = "host";
constexpr std::string_view HEADER_COOKIE = "cookie";
constexpr std::string_view HEADER_CONNECTION = "connection";
constexpr std::string_view HEADER_CONTENT_TYPE = "content-type";
constexpr std::string_view HEADER_CONTENT_LENGTH = "content-length";
constexpr std::string_view HEADER_AUTHORIZATION = "authorization";
constexpr std::string_view HEADER_ACCEPT_ENCODING = "accept-encoding";
constexpr std::string_view AUTHORIZATION_BASIC = "Basic";
constexpr std::string_view AUTHORIZATION_BASIC = "basic";
constexpr std::string_view CONNECTION_CLOSE = "close";
constexpr std::string_view CONNECTION_KEEP_ALIVE = "keep-alive";
constexpr std::string_view ENCODING_GZIP = "gzip";
Expand Down Expand Up @@ -145,38 +138,38 @@ std::string_view process_headers(tl::K2InvokeHttp &invoke_http, PhpScriptBuiltIn

std::string_view content_type{CONTENT_TYPE_APP_FORM_URLENCODED};
// platform provides headers that are already in lowercase
for (auto &[header_name, header] : invoke_http.headers) {
const std::string_view header_name_view{header_name.c_str(), header_name.size()};
const std::string_view header_view{header.value.c_str(), header.value.size()};
for (auto &[_, h_name, h_value] : invoke_http.headers) {
const std::string_view h_name_view{h_name.c_str(), h_name.size()};
const std::string_view h_value_view{h_value.c_str(), h_value.size()};

using namespace PhpServerSuperGlobalIndices;
if (header_name_view == HEADER_ACCEPT_ENCODING) {
if (absl::StrContains(header_view, ENCODING_GZIP)) {
if (h_name_view == HttpHeader::ACCEPT_ENCODING) {
if (absl::StrContains(h_value_view, ENCODING_GZIP)) {
http_server_instance_st.encoding |= HttpServerInstanceState::ENCODING_GZIP;
}
if (absl::StrContains(header_view, ENCODING_DEFLATE)) {
if (absl::StrContains(h_value_view, ENCODING_DEFLATE)) {
http_server_instance_st.encoding |= HttpServerInstanceState::ENCODING_DEFLATE;
}
} else if (header_name_view == HEADER_CONNECTION) {
if (header_view == CONNECTION_KEEP_ALIVE) [[likely]] {
} else if (h_name_view == HttpHeader::CONNECTION) {
if (h_value_view == CONNECTION_KEEP_ALIVE) [[likely]] {
http_server_instance_st.connection_kind = HttpConnectionKind::KEEP_ALIVE;
} else if (header_view == CONNECTION_CLOSE) [[likely]] {
} else if (h_value_view == CONNECTION_CLOSE) [[likely]] {
http_server_instance_st.connection_kind = HttpConnectionKind::CLOSE;
} else {
php_error("unexpected connection header: %s", header_view.data());
php_error("unexpected connection header: %s", h_value_view.data());
}
} else if (header_name_view == HEADER_COOKIE) {
process_cookie_header(header.value, superglobals);
} else if (header_name_view == HEADER_HOST) {
server.set_value(string{SERVER_NAME.data(), SERVER_NAME.size()}, header.value);
} else if (header_name_view == HEADER_AUTHORIZATION) {
process_authorization_header(header.value, superglobals);
} else if (header_name_view == HEADER_CONTENT_TYPE) {
content_type = header_view;
} else if (h_name_view == HttpHeader::COOKIE) {
process_cookie_header(h_value, superglobals);
} else if (h_name_view == HttpHeader::HOST) {
server.set_value(string{SERVER_NAME.data(), SERVER_NAME.size()}, h_value);
} else if (h_name_view == HttpHeader::AUTHORIZATION) {
process_authorization_header(h_value, superglobals);
} else if (h_name_view == HttpHeader::CONTENT_TYPE) {
content_type = h_value_view;
continue;
} else if (header_name_view == HEADER_CONTENT_LENGTH) {
} else if (h_name_view == HttpHeader::CONTENT_LENGTH) {
int32_t content_length{};
const auto [_, ec]{std::from_chars(header_view.data(), header_view.data() + header_view.size(), content_length)};
const auto [_, ec]{std::from_chars(h_value_view.data(), h_value_view.data() + h_value_view.size(), content_length)};
if (ec != std::errc{} || content_length != invoke_http.body.size()) [[unlikely]] {
php_error("content-length expected to be %d, but it's %u", content_length, invoke_http.body.size());
}
Expand All @@ -185,14 +178,14 @@ std::string_view process_headers(tl::K2InvokeHttp &invoke_http, PhpScriptBuiltIn

// add header entries
string key{};
key.reserve_at_least(HTTP_HEADER_PREFIX.size() + header_name.size());
key.reserve_at_least(HTTP_HEADER_PREFIX.size() + h_name.size());
key.append(HTTP_HEADER_PREFIX.data());
key.append(header_name_view.data(), header_name_view.size());
key.append(h_name_view.data(), h_name_view.size());
// to uppercase inplace
for (int64_t i = HTTP_HEADER_PREFIX.size(); i < key.size(); ++i) {
key[i] = key[i] != '-' ? std::toupper(key[i]) : '_';
}
server.set_value(key, std::move(header.value));
server.set_value(key, std::move(h_value));
}

return content_type;
Expand Down Expand Up @@ -300,11 +293,11 @@ void init_http_server(tl::K2InvokeHttp &&invoke_http) noexcept {

// add content-type header
auto &static_SB{RuntimeContext::get().static_SB};
static_SB.clean() << HEADER_CONTENT_TYPE.data() << ": " << CONTENT_TYPE_TEXT_WIN1251.data();
static_SB.clean() << HttpHeader::CONTENT_TYPE.data() << ": " << CONTENT_TYPE_TEXT_WIN1251.data();
header({static_SB.c_str(), static_SB.size()}, true, HttpStatus::NO_STATUS);
// add connection kind header
const auto connection_kind{http_server_instance_st.connection_kind == HttpConnectionKind::KEEP_ALIVE ? CONNECTION_KEEP_ALIVE : CONNECTION_CLOSE};
static_SB.clean() << HEADER_CONNECTION.data() << ": " << connection_kind.data();
static_SB.clean() << HttpHeader::CONNECTION.data() << ": " << connection_kind.data();
}

task_t<void> finalize_http_server(const string_buffer &output) noexcept {
Expand All @@ -319,13 +312,13 @@ task_t<void> finalize_http_server(const string_buffer &output) noexcept {
.headers = {},
.body = std::move(body)};
// fill headers
http_response.headers.data.data.reserve(http_server_instance_st.headers().size());
std::transform(http_server_instance_st.headers().cbegin(), http_server_instance_st.headers().cend(), std::back_inserter(http_response.headers.data.data),
http_response.headers.data.reserve(http_server_instance_st.headers().size());
std::transform(http_server_instance_st.headers().cbegin(), http_server_instance_st.headers().cend(), std::back_inserter(http_response.headers.data),
[](const auto &header_entry) noexcept {
const auto &[name, value]{header_entry};
string header_name{name.data(), static_cast<string::size_type>(name.size())};
tl::httpHeaderValue header_value{.value = {value.data(), static_cast<string::size_type>(value.size())}};
return tl::dictionaryField<tl::httpHeaderValue>{.key = std::move(header_name), .value = std::move(header_value)};
string header_value{value.data(), static_cast<string::size_type>(value.size())};
return tl::httpHeaderEntry{.is_sensitive = {}, .name = std::move(header_name), .value = std::move(header_value)};
});

tl::TLBuffer tlb{};
Expand Down
31 changes: 31 additions & 0 deletions runtime-light/stdlib/server/http-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
#include "absl/strings/ascii.h"
#include "absl/strings/str_split.h"

#include "runtime-common/core/runtime-core.h"
#include "runtime-common/core/utils/kphp-assert-core.h"
#include "runtime-light/server/http/http-server-state.h"
#include "runtime-light/stdlib/time/time-functions.h"

namespace {

constexpr std::string_view HTTP_DATE = R"(D, d M Y H:i:s \G\M\T)";
constexpr std::string_view HTTP_STATUS_PREFIX = "http/";
constexpr std::string_view HTTP_LOCATION_HEADER_PREFIX = "location:";

Expand Down Expand Up @@ -135,3 +138,31 @@ void header(std::string_view header_view, bool replace, int64_t response_code) n
http_server_instance_st.status_code = response_code;
}
}

void f$setrawcookie(const string &name, const string &value, int64_t expire_or_options, const string &path, const string &domain, bool secure,
bool http_only) noexcept {
auto &static_SB_spare{RuntimeContext::get().static_SB_spare};

static_SB_spare.clean() << HttpHeader::SET_COOKIE.data() << ": " << name << '=';
if (value.empty()) {
static_SB_spare << "DELETED; expires=Thu, 01 Jan 1970 00:00:01 GMT";
} else {
static_SB_spare << value;
if (expire_or_options != 0) {
static_SB_spare << "; expires=" << f$gmdate({HTTP_DATE.data(), HTTP_DATE.size()}, expire_or_options);
}
}
if (!path.empty()) {
static_SB_spare << "; path=" << path;
}
if (!domain.empty()) {
static_SB_spare << "; domain=" << domain;
}
if (secure) {
static_SB_spare << "; secure";
}
if (http_only) {
static_SB_spare << "; HttpOnly";
}
header({static_SB_spare.c_str(), static_SB_spare.size()}, false, HttpStatus::NO_STATUS);
}
9 changes: 9 additions & 0 deletions runtime-light/stdlib/server/http-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@
#include <string_view>

#include "runtime-common/core/runtime-core.h"
#include "runtime-common/stdlib/server/url-functions.h"
#include "runtime-light/server/http/http-server-state.h"

void header(std::string_view header, bool replace, int64_t response_code) noexcept;

inline void f$header(const string &str, bool replace = true, int64_t response_code = HttpStatus::NO_STATUS) noexcept {
header({str.c_str(), str.size()}, replace, response_code);
}

void f$setrawcookie(const string &name, const string &value, int64_t expire_or_options = 0, const string &path = {}, const string &domain = {},
bool secure = false, bool http_only = false) noexcept;

inline void f$setcookie(const string &name, const string &value = {}, int64_t expire_or_options = 0, const string &path = {}, const string &domain = {},
bool secure = false, bool http_only = false) noexcept {
f$setrawcookie(name, f$urlencode(value), expire_or_options, path, domain, secure, http_only);
}
2 changes: 1 addition & 1 deletion runtime-light/tl/tl-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class K2InvokeHttp final {
HttpVersion version{};
string method;
httpUri uri{};
dictionary<httpHeaderValue> headers{};
vector<httpHeaderEntry> headers{};
string body;

bool fetch(TLBuffer &tlb) noexcept;
Expand Down
8 changes: 6 additions & 2 deletions runtime-light/tl/tl-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,19 +454,23 @@ class httpUri final {
}
};

struct httpHeaderValue final {
struct httpHeaderEntry final {
Bool is_sensitive{};
string name;
string value;

bool fetch(TLBuffer &tlb) noexcept {
const auto ok{is_sensitive.fetch(tlb)};
const auto name_view{tlb.fetch_string()};
const auto value_view{tlb.fetch_string()};
name = {name_view.data(), static_cast<string::size_type>(name_view.size())};
value = {value_view.data(), static_cast<string::size_type>(value_view.size())};
return ok;
}

void store(TLBuffer &tlb) const noexcept {
is_sensitive.store(tlb);
tlb.store_string({name.c_str(), static_cast<size_t>(name.size())});
tlb.store_string({value.c_str(), static_cast<size_t>(value.size())});
}
};
Expand Down Expand Up @@ -498,7 +502,7 @@ struct httpConnection final {
struct httpResponse final {
HttpVersion version{};
int32_t status_code{};
dictionary<httpHeaderValue> headers{};
vector<httpHeaderEntry> headers{};
string body;

void store(TLBuffer &tlb) const noexcept {
Expand Down

0 comments on commit 6dc98a4

Please sign in to comment.