-
Notifications
You must be signed in to change notification settings - Fork 110
[k2] add support multipart/form-data to HTTP server #1423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
| #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" |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comments:
- We use braced initialization throughout the codebase. Please, update the initialization to use braces:
std::string_view boundary{parse_boundary};. - 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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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() {} |
There was a problem hiding this comment.
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 = ':'; |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
9a96972 to
e22a5e9
Compare
65da1f4 to
8374d13
Compare
8374d13 to
f1a82c7
Compare
|
|
||
| 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); |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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_} {}; |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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>
No description provided.