Line data Source code
1 : #include "multipart.hpp"
2 : #include "request.hpp"
3 : #include "body_decoders.hpp"
4 : #include "../../util/ascii.hpp"
5 : #include "../../util/logger.hpp"
6 : #include <algorithm>
7 : #include <cerrno>
8 : #include <cstring>
9 : #include <cstdlib>
10 : #include <fcntl.h>
11 : #include <unistd.h>
12 :
13 : namespace thinger::http {
14 :
15 : // --- multipart_reader ---
16 :
17 123 : multipart_reader::multipart_reader(request& req, multipart_limits limits, size_t buffer_size)
18 123 : : req_(req), buffer_size_(std::max<size_t>(buffer_size, 1)) {
19 123 : auto http_request = req.get_http_request();
20 123 : if (!http_request) {
21 0 : fail(multipart_error::not_multipart);
22 0 : return;
23 : }
24 :
25 : // A single Content-Type of a multipart type
26 123 : const std::string* content_type = nullptr;
27 627 : for (const auto& [key, value] : http_request->get_headers()) {
28 507 : if (!headers::is_header(key, header::content_type)) continue;
29 126 : if (content_type && value != *content_type) {
30 3 : fail(multipart_error::not_multipart);
31 3 : return;
32 : }
33 123 : content_type = &value;
34 : }
35 120 : auto type = content_type ? media_type::parse(*content_type) : std::nullopt;
36 120 : if (!type || !type->type.starts_with("multipart/")) {
37 6 : fail(multipart_error::not_multipart);
38 6 : return;
39 : }
40 114 : media_type_ = type->type;
41 :
42 : // A compressed body would have to be decoded first (the server decodes the bodies it
43 : // reads, removing their Content-Encoding)
44 120 : if (http_request->has_header("Content-Encoding") &&
45 120 : !::thinger::util::ascii::iequals(http_request->get_header("Content-Encoding"), "identity")) {
46 3 : fail(multipart_error::not_multipart);
47 3 : return;
48 : }
49 :
50 : // Exactly one boundary: a repeated one is ambiguous
51 111 : std::string_view boundary;
52 111 : size_t boundaries = 0;
53 222 : for (const auto& [name, value] : type->parameters) {
54 111 : if (name != "boundary") continue;
55 111 : boundary = value;
56 111 : ++boundaries;
57 : }
58 111 : if (boundaries != 1) {
59 6 : fail(multipart_error::invalid_boundary);
60 6 : return;
61 : }
62 105 : parser_.emplace(boundary, limits);
63 105 : if (parser_->failed()) {
64 9 : fail(parser_->error());
65 9 : return;
66 : }
67 :
68 : // Body already read by the server (routes that are not deferred), or still to read
69 96 : if (req.get_body_error() != body_error::none) {
70 0 : fail(req.get_body_error() == body_error::too_large ? multipart_error::body_too_large
71 : : multipart_error::invalid_body);
72 0 : return;
73 : }
74 96 : in_memory_ = !req.has_pending_body();
75 96 : if (!in_memory_) buffer_ = std::make_unique<uint8_t[]>(buffer_size_);
76 147 : }
77 :
78 66 : void multipart_reader::fail(multipart_error error) {
79 66 : if (error_ == multipart_error::none) error_ = error;
80 66 : in_part_ = false;
81 66 : }
82 :
83 24734 : thinger::awaitable<bool> multipart_reader::fill() {
84 : if (in_memory_) {
85 : input_ = req_.get_http_request()->get_body();
86 : finished_ = true;
87 : co_return true;
88 : }
89 : size_t bytes = co_await req_.read_some(buffer_.get(), buffer_size_);
90 : if (bytes > 0) {
91 : input_ = std::string_view(reinterpret_cast<const char*>(buffer_.get()), bytes);
92 : co_return true;
93 : }
94 : auto error = req_.get_body_error();
95 : if (error == body_error::too_large) {
96 : fail(multipart_error::body_too_large);
97 : } else if (error != body_error::none) {
98 : fail(multipart_error::invalid_body);
99 : } else if (req_.has_pending_body()) {
100 : co_return false; // cancelled: the body is still there
101 : } else {
102 : finished_ = true;
103 : }
104 : co_return true;
105 49468 : }
106 :
107 25172 : thinger::awaitable<multipart_parser::event> multipart_reader::advance() {
108 : using event = multipart_parser::event;
109 : for (;;) {
110 : if (error_ != multipart_error::none) co_return event::error;
111 : auto result = parser_->parse(input_);
112 : if (result == event::error) {
113 : fail(parser_->error());
114 : co_return result;
115 : }
116 : // The end once the epilogue is read too, so the whole body is read (and checked)
117 : if (result != event::need_more && (result != event::end || finished_)) co_return result;
118 : if (finished_) {
119 : result = parser_->finish();
120 : if (result == event::error) fail(parser_->error());
121 : co_return result;
122 : }
123 : if (!co_await fill()) co_return event::need_more;
124 : }
125 50344 : }
126 :
127 279 : thinger::awaitable<const multipart_part*> multipart_reader::next() {
128 : using event = multipart_parser::event;
129 : if (!parser_ || error_ != multipart_error::none) co_return nullptr;
130 : pending_ = {};
131 : for (;;) {
132 : switch (co_await advance()) {
133 : case event::part:
134 : in_part_ = true;
135 : co_return &parser_->part();
136 : case event::data:
137 : continue; // the rest of the current part, skipped
138 : case event::part_end:
139 : in_part_ = false;
140 : continue;
141 : case event::end:
142 : in_part_ = false;
143 : done_ = true;
144 : co_return nullptr;
145 : default:
146 : co_return nullptr;
147 : }
148 : }
149 558 : }
150 :
151 24962 : thinger::awaitable<std::string_view> multipart_reader::read(size_t max_size) {
152 : using event = multipart_parser::event;
153 : if (max_size == 0 || !in_part_ || error_ != multipart_error::none) co_return std::string_view{};
154 : if (pending_.empty()) {
155 : auto result = co_await advance();
156 : if (result != event::data) {
157 : if (result == event::part_end) in_part_ = false;
158 : co_return std::string_view{};
159 : }
160 : pending_ = parser_->data();
161 : }
162 : auto data = pending_.substr(0, max_size);
163 : pending_.remove_prefix(data.size());
164 : co_return data;
165 49924 : }
166 :
167 93 : thinger::awaitable<bool> multipart_reader::read_all(std::string& value) {
168 : while (in_part_) {
169 : auto data = co_await read();
170 : if (data.empty()) break;
171 : value.append(data);
172 : }
173 : co_return !in_part_ && error_ == multipart_error::none;
174 186 : }
175 :
176 : // --- multipart_file ---
177 :
178 30 : multipart_file::multipart_file(multipart_part part, std::filesystem::path path, std::uint64_t size)
179 30 : : part_(std::move(part)), path_(std::move(path)), size_(size) {}
180 :
181 39 : multipart_file::~multipart_file() {
182 39 : remove();
183 39 : }
184 :
185 9 : multipart_file::multipart_file(multipart_file&& other) noexcept
186 9 : : part_(std::move(other.part_)), path_(std::move(other.path_)), size_(other.size_), owned_(other.owned_) {
187 9 : other.owned_ = false;
188 9 : }
189 :
190 0 : multipart_file& multipart_file::operator=(multipart_file&& other) noexcept {
191 0 : if (this != &other) {
192 0 : remove();
193 0 : part_ = std::move(other.part_);
194 0 : path_ = std::move(other.path_);
195 0 : size_ = other.size_;
196 0 : owned_ = other.owned_;
197 0 : other.owned_ = false;
198 : }
199 0 : return *this;
200 : }
201 :
202 39 : void multipart_file::remove() {
203 39 : if (!owned_ || path_.empty()) return;
204 24 : owned_ = false;
205 24 : std::error_code ignored;
206 24 : std::filesystem::remove(path_, ignored);
207 : }
208 :
209 3 : bool multipart_file::move_to(const std::filesystem::path& destination, std::error_code& error) {
210 3 : error.clear();
211 3 : std::filesystem::rename(path_, destination, error);
212 3 : if (error == std::errc::cross_device_link) {
213 0 : error.clear();
214 0 : std::filesystem::copy_file(path_, destination, std::filesystem::copy_options::overwrite_existing, error);
215 0 : if (error) return false;
216 0 : std::error_code ignored;
217 0 : std::filesystem::remove(path_, ignored);
218 3 : } else if (error) {
219 0 : return false;
220 : }
221 3 : path_ = destination;
222 3 : owned_ = false;
223 3 : return true;
224 : }
225 :
226 3 : bool multipart_file::move_to(const std::filesystem::path& destination) {
227 3 : std::error_code error;
228 6 : return move_to(destination, error);
229 : }
230 :
231 3 : std::filesystem::path multipart_file::release() {
232 3 : owned_ = false;
233 3 : return path_;
234 : }
235 :
236 : // --- multipart_form ---
237 :
238 0 : const std::string* multipart_form::field(std::string_view name) const {
239 0 : for (const auto& [key, value] : fields_) {
240 0 : if (key == name) return &value;
241 : }
242 0 : return nullptr;
243 : }
244 :
245 0 : std::string multipart_form::field(std::string_view name, std::string_view default_value) const {
246 0 : auto value = field(name);
247 0 : return value ? *value : std::string(default_value);
248 : }
249 :
250 9 : multipart_file* multipart_form::file(std::string_view name) {
251 18 : for (auto& file : files_) {
252 15 : if (file.name() == name) return &file;
253 : }
254 3 : return nullptr;
255 : }
256 :
257 : namespace {
258 :
259 : // A temporary file being written, deleted unless released
260 : class temporary_file {
261 : public:
262 36 : explicit temporary_file(const std::filesystem::path& directory) {
263 36 : std::string pattern = (directory / "thinger-upload-XXXXXX").string();
264 : // (readable by its owner only, created exclusively)
265 36 : fd_ = ::mkstemp(pattern.data());
266 36 : if (fd_ >= 0) path_ = std::move(pattern);
267 36 : }
268 :
269 36 : ~temporary_file() {
270 36 : close();
271 36 : if (!path_.empty()) ::unlink(path_.c_str());
272 36 : }
273 :
274 : temporary_file(const temporary_file&) = delete;
275 : temporary_file& operator=(const temporary_file&) = delete;
276 :
277 36 : bool is_open() const { return fd_ >= 0; }
278 :
279 9851 : bool write(std::string_view data) {
280 19702 : while (!data.empty()) {
281 9851 : auto written = ::write(fd_, data.data(), data.size());
282 9851 : if (written < 0) {
283 0 : if (errno == EINTR) continue;
284 0 : return false;
285 : }
286 9851 : data.remove_prefix(static_cast<size_t>(written));
287 : }
288 9851 : return true;
289 : }
290 :
291 72 : bool close() {
292 72 : if (fd_ < 0) return true;
293 36 : bool closed = ::close(fd_) == 0;
294 36 : fd_ = -1;
295 36 : return closed;
296 : }
297 :
298 : // The file, no longer deleted
299 30 : std::string release() {
300 30 : return std::exchange(path_, {});
301 : }
302 :
303 : private:
304 : int fd_ = -1;
305 : std::string path_;
306 : };
307 :
308 : // Where uploads are written: the directory of the options, or the system's
309 99 : std::filesystem::path upload_directory(const multipart_options& options) {
310 99 : if (!options.temp_directory.empty()) return options.temp_directory;
311 9 : std::error_code error;
312 9 : auto directory = std::filesystem::temp_directory_path(error);
313 9 : return error ? std::filesystem::path("/tmp") : directory;
314 9 : }
315 :
316 : // A part stored in a temporary file: file_failed if it could not be written, and no
317 : // path if the body failed first (the file is deleted then)
318 : struct stored_part {
319 : multipart_error error = multipart_error::none;
320 : std::string path;
321 : std::uint64_t size = 0;
322 : };
323 :
324 : // (blocking writes, as Go's ParseMultipartForm: there is no portable asynchronous
325 : // file I/O, and the parts arrive slower than a disk takes them)
326 36 : thinger::awaitable<stored_part> store_part(multipart_reader& reader, const std::filesystem::path& directory) {
327 : temporary_file file(directory);
328 : if (!file.is_open()) {
329 : LOG_ERROR("Cannot create a temporary file for an upload in {}: {}", directory.string(), std::strerror(errno));
330 : co_return stored_part{multipart_error::file_failed};
331 : }
332 : std::uint64_t size = 0;
333 : bool written = true;
334 : for (auto data = co_await reader.read(); !data.empty(); data = co_await reader.read()) {
335 : if (!file.write(data)) {
336 : written = false;
337 : break;
338 : }
339 : size += data.size();
340 : }
341 : if (!written || !file.close()) {
342 : LOG_ERROR("Cannot write the temporary file of an upload: {}", std::strerror(errno));
343 : co_return stored_part{multipart_error::file_failed};
344 : }
345 : if (reader.failed()) co_return stored_part{};
346 : co_return stored_part{multipart_error::none, file.release(), size};
347 72 : }
348 :
349 : }
350 :
351 102 : thinger::awaitable<multipart_form> read_multipart(request& req, multipart_options options) {
352 : multipart_form form;
353 : multipart_reader reader(req, options.limits);
354 : if (!reader.failed() && reader.media_type() != "multipart/form-data") {
355 : form.error_ = multipart_error::not_multipart;
356 : co_return form;
357 : }
358 :
359 : auto directory = upload_directory(options);
360 : multipart_error failure = multipart_error::none;
361 : while (const multipart_part* part = co_await reader.next()) {
362 : // Parts that are not form fields are skipped, as Go and busboy do
363 : if (part->disposition != "form-data" || part->name.empty()) continue;
364 :
365 : if (!part->is_file()) {
366 : std::string value;
367 : if (!co_await reader.read_all(value)) break;
368 : form.fields_.emplace_back(part->name, std::move(value));
369 : continue;
370 : }
371 :
372 : if (options.on_file) {
373 : co_await options.on_file(*part, reader);
374 : continue;
375 : }
376 :
377 : auto stored = co_await store_part(reader, directory);
378 : if (stored.error != multipart_error::none) {
379 : failure = stored.error;
380 : break;
381 : }
382 : // The body failed while the part was being written
383 : if (stored.path.empty()) break;
384 : form.files_.emplace_back(*part, std::move(stored.path), stored.size);
385 : }
386 :
387 : if (failure == multipart_error::none && !reader.done()) {
388 : // An error, or a read cancelled: the body was not read whole
389 : failure = reader.failed() ? reader.error() : multipart_error::truncated;
390 : }
391 : if (failure != multipart_error::none) {
392 : form.error_ = failure;
393 : form.fields_.clear();
394 : form.files_.clear();
395 : }
396 : co_return form;
397 204 : }
398 :
399 : }
|