Skip to content

[k2] add support http multipart content-type#1545

Open
astrophysik wants to merge 26 commits intomasterfrom
vsadokhov/k2-http-multipart
Open

[k2] add support http multipart content-type#1545
astrophysik wants to merge 26 commits intomasterfrom
vsadokhov/k2-http-multipart

Conversation

@astrophysik
Copy link
Contributor

No description provided.

@astrophysik astrophysik added this to the next milestone Mar 6, 2026
@astrophysik astrophysik self-assigned this Mar 6, 2026
@astrophysik astrophysik added the k2 k2 related label Mar 6, 2026
#include "runtime-light/state/instance-state.h"
#include "runtime-light/stdlib/component/component-api.h"
#include "runtime-light/stdlib/diagnostics/logs.h"
#include "runtime-light/stdlib/file/file-system-functions.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused include

#include "runtime-light/tl/tl-functions.h"
#include "runtime-light/tl/tl-types.h"

#include "runtime-light/stdlib/output/print-functions.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused include

http_server_instance_st.opt_raw_post_data.emplace(std::move(body));
} else if (!std::ranges::search(content_type, CONTENT_TYPE_MULTIPART_FORM_DATA).empty()) {
kphp::log::error("unsupported content-type: {}", CONTENT_TYPE_MULTIPART_FORM_DATA);
if (auto boundary_opt{kphp::http::multipart::extract_boundary(content_type)}; boundary_opt.has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like an error that we do nothing in case we couldn't extract boundary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add warning in this case


kphp::coro::task<> finalize_server() noexcept {
auto& http_server_instance_st{HttpServerInstanceState::get()};
auto& superglobals{InstanceState::get().php_script_mutable_globals_singleton.get_superglobals()};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is only used in a single switch-case. Let's declare it there to not waste CPU cycles unless it's really necessary

nit: you may use PhpScriptMutableGlobals::current() instead of InstanceState::get().php_script_mutable_globals_singleton

const array<mixed> files{superglobals.v$_FILES.to_array()};
for (const auto& files_it : files) {
const mixed& file{files_it.get_value()};
const mixed& tmp_filenames{file.get_value(string{"tmp_name"})};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string{"tmp_name"} constructs a new string object every iteration. Since this is a constant key, it could be cached

return std::nullopt;
}

std::expected<size_t, int32_t> write_temporary_file(std::string_view tmp_name, std::span<const std::byte> content) noexcept {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider following implementation:

std::expected<size_t, int32_t> write_temporary_file(std::string_view tmp_name, std::span<const std::byte> content) noexcept {                                                                                                                                
    auto file_res{kphp::fs::file::open(tmp_name, "w")};
    if (!file_res.has_value()) {
      return std::unexpected{UPLOAD_ERR_NO_FILE};
    }

    auto written_res{(*file_res).write(content)};
    if (!written_res.has_value()) {
      return std::unexpected{UPLOAD_ERR_CANT_WRITE};
    }

    size_t file_size{*written_res};
    if (file_size < content.size()) {
      return std::unexpected{UPLOAD_ERR_PARTIAL};
    }

    return file_size;
  }

Moreover, I don't see a reason to return std::expected<size_t, int32_t> instead of std::expected<void, size_t>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks
Error code and size are used in mixed file{}; initialization

const string name{part.name_attribute.data(), static_cast<string::size_type>(part.name_attribute.size())};
const string body{part.body.data(), static_cast<string::size_type>(part.body.size())};
if (part.content_type.has_value() && !std::ranges::search(*part.content_type, CONTENT_TYPE_APP_FORM_URLENCODED).empty()) {
f$parse_str(body, post[name]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be more systematic to use get_value here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, but it leads to extra allocation

void process_post_multipart(const kphp::http::multipart::details::part& part, mixed& post) noexcept {
const string name{part.name_attribute.data(), static_cast<string::size_type>(part.name_attribute.size())};
const string body{part.body.data(), static_cast<string::size_type>(part.body.size())};
if (part.content_type.has_value() && !std::ranges::search(*part.content_type, CONTENT_TYPE_APP_FORM_URLENCODED).empty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not *part.content_type == CONTENT_TYPE_APP_FORM_URLENCODED?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because content-type may looks like Content-type: application/x-www-form-urlencoded; charset=UTF-8


auto tmp_name_opt{generate_temporary_name()};
if (!tmp_name_opt.has_value()) {
kphp::log::warning("cannot generate unique name for multipart temporary file");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it dangerous that we can't create a file and just emit a warning? It looks like this may lead to some state corruption

auto tmp_name{*tmp_name_opt};
auto write_res{write_temporary_file(tmp_name, {reinterpret_cast<const std::byte*>(part.body.data()), part.body.size()})};

mixed file{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it simpler to declare it as array<mixed>?

@astrophysik astrophysik added the enhancement New feature or request label Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request k2 k2 related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants