Line data Source code
1 : #ifndef THINGER_HTTP_SERVER_MULTIPART_HPP
2 : #define THINGER_HTTP_SERVER_MULTIPART_HPP
3 :
4 : #include <cstdint>
5 : #include <filesystem>
6 : #include <functional>
7 : #include <limits>
8 : #include <memory>
9 : #include <optional>
10 : #include <string>
11 : #include <string_view>
12 : #include <system_error>
13 : #include <utility>
14 : #include <vector>
15 : #include "../common/multipart_parser.hpp"
16 : #include "../../util/types.hpp"
17 :
18 : namespace thinger::http {
19 :
20 : class request;
21 :
22 : /**
23 : * Reads a multipart body (multipart/form-data, or any other multipart type) part by part
24 : * as it arrives, like Go's multipart.Reader: the headers of each part, then its content in
25 : * pieces, never buffering more than its read buffer and the header section of a part.
26 : *
27 : * http::multipart_reader reader(req);
28 : * while (const http::multipart_part* part = co_await reader.next()) {
29 : * for (auto data = co_await reader.read(); !data.empty(); data = co_await reader.read()) { ... }
30 : * }
31 : * if (reader.failed()) res.error(reader.status(), reader.message());
32 : *
33 : * The body is read with request::read_some(), within the maximum body size of the route
34 : * (body_too_large), or from req.body() if the server already read it (routes that are not
35 : * deferred). A body with a Content-Encoding is not decoded: not_multipart (415). A read that
36 : * reaches the read deadline of the request (request::set_read_deadline()) fails with
37 : * invalid_body (400): req.get_body_error() is body_error::timeout, to answer 408 instead.
38 : */
39 : class multipart_reader {
40 : public:
41 : // Bytes read from the body at once
42 : static constexpr size_t default_buffer_size = 64 * 1024;
43 :
44 : /// Reader of the body of `req`, which must outlive it
45 : explicit multipart_reader(request& req, multipart_limits limits = {}, size_t buffer_size = default_buffer_size);
46 :
47 : /// The next part, skipping what is left of the current one; null at the end of the
48 : /// body, on error (see failed()), or if the read was cancelled. Valid until the next call.
49 : thinger::awaitable<const multipart_part*> next();
50 :
51 : /// The next piece of the content of the current part, of `max_size` bytes at most (what
52 : /// is left of it stays for the next call, as Go's Part.Read): a view into the buffer of
53 : /// the reader, no copy, valid until the next call. Empty at the end of the part, on
54 : /// error or if the read was cancelled. read(0) returns empty at once without advancing
55 : /// or reading: it does not mean the end of the part.
56 : thinger::awaitable<std::string_view> read(size_t max_size = std::numeric_limits<size_t>::max());
57 :
58 : /// Append the rest of the content of the current part to `value` (bounded by the
59 : /// limits of the part); false on error or if the read was cancelled
60 : thinger::awaitable<bool> read_all(std::string& value);
61 :
62 : /// Media type of the body, lowercase ("multipart/form-data"), empty if not multipart
63 84 : const std::string& media_type() const { return media_type_; }
64 :
65 : /// The current part, null before the first one and after the last
66 3 : const multipart_part* part() const { return in_part_ ? &parser_->part() : nullptr; }
67 :
68 : /// The whole body was read, up to its closing delimiter (and its epilogue)
69 117 : bool done() const { return done_; }
70 222 : bool failed() const { return error_ != multipart_error::none; }
71 66 : multipart_error error() const { return error_; }
72 :
73 : /// Status and message answering the error (400, 413, 415 or 500)
74 0 : http_response::status status() const { return multipart_error_status(error_); }
75 0 : const char* message() const { return multipart_error_message(error_); }
76 :
77 : private:
78 : request& req_;
79 : std::optional<multipart_parser> parser_;
80 : std::string media_type_;
81 : std::unique_ptr<uint8_t[]> buffer_;
82 : size_t buffer_size_;
83 : std::string_view input_;
84 : std::string_view pending_; // what read() left of the last piece of the parser
85 : multipart_error error_ = multipart_error::none;
86 : bool in_memory_ = false; // the body was read by the server: parse req.body()
87 : bool finished_ = false; // the whole body was read
88 : bool in_part_ = false;
89 : bool done_ = false;
90 :
91 : void fail(multipart_error error);
92 :
93 : /// The next event of the parser, reading the body as needed; need_more if a read was
94 : /// cancelled
95 : thinger::awaitable<multipart_parser::event> advance();
96 :
97 : /// Read more of the body into the input; false if the read was cancelled
98 : thinger::awaitable<bool> fill();
99 : };
100 :
101 : /**
102 : * A file of a multipart form, stored in a temporary file that is deleted with this object
103 : * (at the end of the request handler, usually) unless it is moved to its destination with
104 : * move_to() or kept with release().
105 : */
106 : class multipart_file {
107 : public:
108 : multipart_file(multipart_part part, std::filesystem::path path, std::uint64_t size);
109 : ~multipart_file();
110 :
111 : multipart_file(multipart_file&& other) noexcept;
112 : multipart_file& operator=(multipart_file&& other) noexcept;
113 : multipart_file(const multipart_file&) = delete;
114 : multipart_file& operator=(const multipart_file&) = delete;
115 :
116 : /// Field name
117 27 : const std::string& name() const { return part_.name; }
118 : /// Filename as the client sent it: never use it as a path (see safe_filename())
119 12 : const std::string& filename() const { return part_.filename; }
120 : /// The filename reduced to a name to store it with (see multipart_part::safe_filename())
121 15 : std::string safe_filename() const { return part_.safe_filename(); }
122 : /// Content-Type of the part ("text/plain" if it had none), as the client says
123 12 : const std::string& content_type() const { return part_.content_type; }
124 : /// Every header of the part
125 : const multipart_part& part() const { return part_; }
126 :
127 : /// Bytes of content
128 15 : std::uint64_t size() const { return size_; }
129 :
130 : /// The file with the content: the temporary file (readable by its owner only), or
131 : /// wherever move_to() moved it
132 30 : const std::filesystem::path& path() const { return path_; }
133 :
134 : /// Move the file to `destination` (renamed, or copied if it is in another file
135 : /// system), replacing any file there: it is no longer deleted. False on error.
136 : bool move_to(const std::filesystem::path& destination, std::error_code& error);
137 : bool move_to(const std::filesystem::path& destination);
138 :
139 : /// Keep the temporary file (no longer deleted), returning its path
140 : std::filesystem::path release();
141 :
142 : private:
143 : multipart_part part_;
144 : std::filesystem::path path_;
145 : std::uint64_t size_ = 0;
146 : bool owned_ = true; // the temporary file is deleted with this object
147 :
148 : void remove();
149 : };
150 :
151 : // How request::read_multipart() reads a form
152 : struct multipart_options {
153 : multipart_limits limits;
154 : // Directory of the temporary files (the system one, std::filesystem::temp_directory_path(), if empty)
155 : std::filesystem::path temp_directory;
156 : // Handler of the file parts, instead of temporary files: it reads the content with
157 : // reader.read() (what it leaves unread is skipped) and stores it wherever it wants.
158 : // The form then has no files. What it throws ends the request (500).
159 : std::function<thinger::awaitable<void>(const multipart_part& part, multipart_reader& reader)> on_file;
160 : };
161 :
162 : class multipart_form;
163 :
164 : /// Read the multipart/form-data body of a request (see request::read_multipart())
165 : thinger::awaitable<multipart_form> read_multipart(request& req, multipart_options options = {});
166 :
167 : /**
168 : * A multipart/form-data body read whole (request::read_multipart()): its fields in memory
169 : * and its files in temporary files, deleted with the form. Parts without a name, or with a
170 : * disposition other than form-data, are skipped (as Go and busboy do).
171 : */
172 : class multipart_form {
173 : public:
174 : multipart_form() = default;
175 408 : multipart_form(multipart_form&&) noexcept = default;
176 : multipart_form& operator=(multipart_form&&) noexcept = default;
177 :
178 : /// Parts without filename: their names and values, in order
179 24 : const std::vector<std::pair<std::string, std::string>>& fields() const { return fields_; }
180 :
181 : /// Value of the first field with this name, null if none
182 : const std::string* field(std::string_view name) const;
183 :
184 : /// Value of the first field with this name, or `default_value`
185 : std::string field(std::string_view name, std::string_view default_value) const;
186 :
187 : /// Parts with a filename, in order
188 6 : std::vector<multipart_file>& files() { return files_; }
189 24 : const std::vector<multipart_file>& files() const { return files_; }
190 :
191 : /// The first file with this field name, null if none
192 : multipart_file* file(std::string_view name);
193 :
194 : /// Whether the whole body was read; if not, the error tells why, and the form is empty
195 102 : bool ok() const { return error_ == multipart_error::none; }
196 102 : explicit operator bool() const { return ok(); }
197 : multipart_error error() const { return error_; }
198 69 : http_response::status status() const { return multipart_error_status(error_); }
199 69 : const char* message() const { return multipart_error_message(error_); }
200 :
201 : private:
202 : std::vector<std::pair<std::string, std::string>> fields_;
203 : std::vector<multipart_file> files_;
204 : multipart_error error_ = multipart_error::none;
205 :
206 : friend thinger::awaitable<multipart_form> read_multipart(request& req, multipart_options options);
207 : };
208 :
209 : }
210 :
211 : #endif
|