Skip to content

Conversation

@nekishdev
Copy link

No description provided.

#include "runtime-common/core/runtime-core.h"
#include "runtime-common/core/std/containers.h"
#include "runtime-common/stdlib/server/url-functions.h"
#include "runtime-common/stdlib/string/string-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.

Looks like these includes are actually unused

f$parse_str(body_str, superglobals.v$_POST);
} else if (content_type.starts_with(CONTENT_TYPE_MULTIPART_FORM_DATA)) {
kphp::log::error("unsupported content-type: {}", CONTENT_TYPE_MULTIPART_FORM_DATA);
std::string_view boundary = parse_boundary(content_type);
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor comments:

  1. We use braced initialization throughout the codebase. Please, update the initialization to use braces: std::string_view boundary{parse_boundary};.
  2. Ensure consistency in namespace usage by either consistently applying the kphp::http:: prefix or omitting it across the codebase. Let's only use single approach for clarity and maintainability.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please, add missing copyright header


namespace kphp::http {

void parse_multipart(const std::string_view &body, const std::string_view &boundary, mixed &v$_POST, mixed &v$_FILES);
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, there is no need to passing a std::string_view object by reference as it's quite small. Passing by reference introduces an indirection on the other hand


void parse_multipart(const std::string_view &body, const std::string_view &boundary, mixed &v$_POST, mixed &v$_FILES);

std::string_view parse_boundary(const std::string_view &content_type);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please, mark all functions noexcept

constexpr std::string_view HEADER_CONTENT_DISPOSITION_FORM_DATA = "form-data;";
constexpr std::string_view MULTIPART_BOUNDARY_EQ = "boundary=";

struct Header {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to follow the code style of the runtime-light, let's name types in lowercase

struct Header {
std::string_view header_str, name, value;

Header() {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Please, use = default if you want a trivial constructor

: header_str{header_str_} {}

void parse() {
char delim = ':';
Copy link
Contributor

Choose a reason for hiding this comment

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

There is vk::split_string_view function. Here is the usage reference: runtime-light/server/http/init-functions.cpp:119

}

size_t
attrStart = header.find_first_not_of(' ', pos),
Copy link
Contributor

Choose a reason for hiding this comment

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

Please, use separate lines to initialize/assign multiple variables

file[string("size")].push_back(file_size);
file[string("tmp_name")].push_back(string(tmp_name.data(), tmp_name.size()));
file[string("error")].push_back(0);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it really possible that file_size < 0?

@nekishdev nekishdev force-pushed the ngoryanoy/k2/http-multipart branch 3 times, most recently from 9a96972 to e22a5e9 Compare October 15, 2025 15:45
@nekishdev nekishdev force-pushed the ngoryanoy/k2/http-multipart branch 2 times, most recently from 65da1f4 to 8374d13 Compare February 11, 2026 11:14
@nekishdev nekishdev force-pushed the ngoryanoy/k2/http-multipart branch from 8374d13 to f1a82c7 Compare February 11, 2026 11:17

void parse_multipart(const std::string_view body, const std::string_view boundary, mixed &v$_POST, mixed &v$_FILES);

std::string_view parse_boundary(const std::string_view content_type);
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's change return type to std::optional<std::string_view> to indicate that result can be none. Also const in function arg type isn't meaningful

}
}

bool is_valid() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make object header to be valid always? For example it can take two arguments name and value and create object by static method.
example

struct header {
  std::string_view m_name;
  std::string_view m_value;

header() = delete;

static std::optional<header> new(std::string_view name, std::string_view value) noexcept{...}

bool name_is(const std::string_view name) const noexcept {...}
};

I think it will simplify work this with type

// 1) attr = "name", value = "avatar"
// 2) attr = "filename", value = "my_avatar.png"
struct partAttr {
std::string_view attr, value;
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's separate the field declarations into different lines

struct partAttr {
std::string_view attr, value;

partAttr() = default;
Copy link
Contributor

Choose a reason for hiding this comment

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

In fact, default constructor used only in next_attr. We can get rid of it if we use optional

std::string_view attr, value;

partAttr() = default;
partAttr(const std::string_view attr_, const std::string_view value_) : attr{attr_}, value{value_} {};
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's remove const from args and add it to type fields, this will be more useful

};

// Represents a parser of Content-Disposition header string.
struct attrParser {
Copy link
Contributor

Choose a reason for hiding this comment

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

We can use use class here to reduce private/public keywords
class attrParser { // private data public: // public data }

// Represents a parser of Content-Disposition header string.
struct attrParser {
private:
std::string_view header;
Copy link
Contributor

Choose a reason for hiding this comment

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

This name is a bit confusing. We’ve declared a header type above, but this variable has a different type. As I understand it from how it’s used, it’s the value field of header. Maybe we should call it header_value?

public:
attrParser(const std::string_view header_) : header{header_} {}
partAttr next_attr();
bool end() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this method actually useful? If we make next_attr return an optional type, then when using it we could keep calling it until it starts returning None

}

private:
void markEnd() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this method can be removed. It’s used only once in the implementation, but make class more complex

}

// Represents one part of multipart content
struct part {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same about different lines

std::string_view name, filename, content_type, data;
};

struct MultipartBody {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same about class and public/private keywords


namespace {

const int TMP_FILENAME_LENGTH = 10;
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use constexpr and bounded int type int8_t, int16_t, etc.


// Returns true if current pos refers to one of \r, \n, \r\n
bool is_crlf() {
return body[pos] == '\r' || body[pos] == '\n' || (body[pos] == '\r' && body[pos+1] == '\n');
Copy link
Contributor

Choose a reason for hiding this comment

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

Third condition is unreachable since body[pos] == '\r' since already checked


namespace kphp::http {

void parse_multipart(const std::string_view body, const std::string_view boundary, mixed &v$_POST, mixed &v$_FILES);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this interface could be improved, but I can’t give a precise opinion. Maybe we should think in the direction of parse_multipart(body, boundary) -> ranges<part>?

};

partAttr attrParser::next_attr() {
if (pos == 0) {
Copy link
Contributor

@astrophysik astrophysik Feb 12, 2026

Choose a reason for hiding this comment

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

Generally about this function. As I understand, code use it like

while(!end()) {
   next_attr()
}

I think it can be simplified by c++ ranges, something like:

  auto res = std::views::split(header, ";") | std::views::transform([](auto part) {
               std::string_view part_view{vk::trim(std::string_view(part))};
               auto [name_view, value_view]{vk::split_string_view(part_view, '=')};
               if (value_view.size() >= 2 && value_view.starts_with('"') && value_view.ends_with('"')) {
                 value_view = value_view.substr(1, value_view.size()-2);
               }

               return partAttr{name_view, value_view};
             });
return res;

Case for pos == 0 and HEADER_CONTENT_DISPOSITION_FORM_DATA can be processed in constructor. If it can be rewrited by ranges, we also can simplify this logic to one function parse_part_attr(header_value) -> ranges<partAttr>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants