src/multipart_form.cpp
98.8% Lines (80/81)
100.0% List of functions (11/11)
74.6% Branches (53/71)
Functions (11)
Function
Calls
Lines
Branches
Blocks
boost::burl::multipart_form::multipart_form()
:31
15x
100.0%
–
100.0%
boost::burl::multipart_form::text(std::basic_string_view<char, std::char_traits<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string_view<char, std::char_traits<char> >)
:37
6x
100.0%
25.0%
48.0%
boost::burl::multipart_form::file(std::basic_string_view<char, std::char_traits<char> >, std::filesystem::__cxx11::path, std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >)
:53
6x
100.0%
68.4%
59.0%
boost::burl::multipart_form::bytes(std::basic_string_view<char, std::char_traits<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >)
:84
4x
100.0%
57.1%
58.0%
boost::burl::multipart_form::generate_boundary[abi:cxx11]()
:109
15x
100.0%
100.0%
71.0%
boost::burl::multipart_form::make_header[abi:cxx11](std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >) const
:121
16x
94.7%
100.0%
95.0%
boost::burl::multipart_form::body::body(boost::burl::multipart_form)
:154
15x
100.0%
–
100.0%
boost::burl::multipart_form::body::content_type[abi:cxx11]() const
:160
15x
100.0%
100.0%
100.0%
boost::burl::multipart_form::body::content_length() const
:166
15x
100.0%
100.0%
100.0%
boost::burl::multipart_form::body::write(boost::capy::any_buffer_sink&) const
:176
112x
100.0%
100.0%
44.0%
boost::burl::tag_invoke(boost::burl::body_from_tag<boost::burl::multipart_form>, boost::burl::multipart_form)
:215
15x
100.0%
100.0%
67.0%
| Line | Branch | TLA | Hits | Source Code |
|---|---|---|---|---|
| 1 | // | |||
| 2 | // Copyright (c) 2026 Mohammad Nejati | |||
| 3 | // | |||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| 6 | // | |||
| 7 | // Official repository: https://github.com/cppalliance/burl | |||
| 8 | // | |||
| 9 | ||||
| 10 | #include <boost/burl/multipart_form.hpp> | |||
| 11 | ||||
| 12 | #include "detail/send_file.hpp" | |||
| 13 | ||||
| 14 | #include <boost/capy/buffers.hpp> | |||
| 15 | #include <boost/capy/buffers/make_buffer.hpp> | |||
| 16 | #include <boost/capy/io/any_buffer_sink.hpp> | |||
| 17 | #include <boost/http/server/mime_types.hpp> | |||
| 18 | ||||
| 19 | #include <cstdint> | |||
| 20 | #include <optional> | |||
| 21 | #include <random> | |||
| 22 | #include <string> | |||
| 23 | #include <string_view> | |||
| 24 | #include <utility> | |||
| 25 | ||||
| 26 | namespace boost | |||
| 27 | { | |||
| 28 | namespace burl | |||
| 29 | { | |||
| 30 | ||||
| 31 | 15x | multipart_form::multipart_form() | ||
| 32 | 15x | : boundary_(generate_boundary()) | ||
| 33 | { | |||
| 34 | 15x | } | ||
| 35 | ||||
| 36 | multipart_form& | |||
| 37 | 6x | multipart_form::text( | ||
| 38 | std::string_view name, | |||
| 39 | std::string value, | |||
| 40 | std::string_view content_type) | |||
| 41 | { | |||
| 42 | 6x | auto size = value.size(); | ||
| 43 | 12x | parts_.push_back( | ||
| 44 |
2/8✓ Branch 2 taken 6 times.
✓ Branch 7 taken 6 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
12x | part{ .header = make_header(name, {}, content_type), | |
| 45 | .is_file = false, | |||
| 46 | 6x | .text = std::move(value), | ||
| 47 | .path = {}, | |||
| 48 | .size = size }); | |||
| 49 | 6x | return *this; | ||
| 50 | } | |||
| 51 | ||||
| 52 | multipart_form& | |||
| 53 | 6x | multipart_form::file( | ||
| 54 | std::string_view name, | |||
| 55 | std::filesystem::path path, | |||
| 56 | std::string_view filename, | |||
| 57 | std::string_view content_type) | |||
| 58 | { | |||
| 59 | 6x | std::string filename_buf; | ||
| 60 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
|
6x | if(filename.empty()) | |
| 61 | { | |||
| 62 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
|
4x | filename_buf = path.filename().string(); | |
| 63 | 4x | filename = filename_buf; | ||
| 64 | } | |||
| 65 | 6x | std::string content_type_buf; | ||
| 66 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
|
6x | if(content_type.empty()) | |
| 67 | { | |||
| 68 |
1/1✓ Branch 2 taken 4 times.
|
4x | content_type_buf = http::mime_types::content_type(filename); | |
| 69 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
|
4x | if(content_type_buf.empty()) | |
| 70 |
1/1✓ Branch 1 taken 2 times.
|
2x | content_type_buf = "application/octet-stream"; | |
| 71 | 4x | content_type = content_type_buf; | ||
| 72 | } | |||
| 73 |
1/1✓ Branch 1 taken 6 times.
|
6x | auto size = std::filesystem::file_size(path); | |
| 74 | 12x | parts_.push_back( | ||
| 75 |
2/8✓ Branch 1 taken 6 times.
✓ Branch 6 taken 6 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
12x | part{ .header = make_header(name, filename, content_type), | |
| 76 | .is_file = true, | |||
| 77 | .text = {}, | |||
| 78 | 6x | .path = std::move(path), | ||
| 79 | .size = size }); | |||
| 80 | 6x | return *this; | ||
| 81 | 6x | } | ||
| 82 | ||||
| 83 | multipart_form& | |||
| 84 | 4x | multipart_form::bytes( | ||
| 85 | std::string_view name, | |||
| 86 | std::string data, | |||
| 87 | std::string_view filename, | |||
| 88 | std::string_view content_type) | |||
| 89 | { | |||
| 90 | 4x | std::string content_type_buf; | ||
| 91 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
|
4x | if(content_type.empty()) | |
| 92 | { | |||
| 93 |
1/1✓ Branch 2 taken 3 times.
|
3x | content_type_buf = http::mime_types::content_type(filename); | |
| 94 |
2/2✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
|
3x | if(content_type_buf.empty()) | |
| 95 |
1/1✓ Branch 1 taken 1 time.
|
1x | content_type_buf = "application/octet-stream"; | |
| 96 | 3x | content_type = content_type_buf; | ||
| 97 | } | |||
| 98 | 4x | auto size = data.size(); | ||
| 99 | 8x | parts_.push_back( | ||
| 100 |
2/8✓ Branch 1 taken 4 times.
✓ Branch 6 taken 4 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
8x | part{ .header = make_header(name, filename, content_type), | |
| 101 | .is_file = false, | |||
| 102 | 4x | .text = std::move(data), | ||
| 103 | .path = {}, | |||
| 104 | .size = size }); | |||
| 105 | 4x | return *this; | ||
| 106 | 4x | } | ||
| 107 | ||||
| 108 | std::string | |||
| 109 | 15x | multipart_form::generate_boundary() | ||
| 110 | { | |||
| 111 | static constexpr char chars[] = "0123456789abcdef"; | |||
| 112 |
1/1✓ Branch 1 taken 15 times.
|
15x | std::random_device rd; | |
| 113 |
2/2✓ Branch 1 taken 15 times.
✓ Branch 4 taken 15 times.
|
15x | std::minstd_rand gen(rd()); | |
| 114 |
1/1✓ Branch 1 taken 15 times.
|
15x | std::string rs = "----BoostBurlFormBoundary"; | |
| 115 |
2/2✓ Branch 0 taken 360 times.
✓ Branch 1 taken 15 times.
|
375x | for(int i = 0; i < 24; ++i) | |
| 116 |
2/2✓ Branch 1 taken 360 times.
✓ Branch 4 taken 360 times.
|
360x | rs += chars[(gen() >> 12) & 15]; | |
| 117 | 30x | return rs; | ||
| 118 | 15x | } | ||
| 119 | ||||
| 120 | std::string | |||
| 121 | 16x | multipart_form::make_header( | ||
| 122 | std::string_view name, | |||
| 123 | std::string_view filename, | |||
| 124 | std::string_view content_type) const | |||
| 125 | { | |||
| 126 | 16x | std::string h; | ||
| 127 |
1/1✓ Branch 1 taken 16 times.
|
16x | h += "--"; | |
| 128 |
1/1✓ Branch 1 taken 16 times.
|
16x | h += boundary_; | |
| 129 |
1/1✓ Branch 1 taken 16 times.
|
16x | h += "\r\nContent-Disposition: form-data; name=\""; | |
| 130 |
1/1✓ Branch 1 taken 16 times.
|
16x | h += name; | |
| 131 |
1/1✓ Branch 1 taken 16 times.
|
16x | h += "\""; | |
| 132 |
2/2✓ Branch 1 taken 10 times.
✓ Branch 2 taken 6 times.
|
16x | if(!filename.empty()) | |
| 133 | { | |||
| 134 |
1/1✓ Branch 1 taken 10 times.
|
10x | h += "; filename=\""; | |
| 135 |
1/1✓ Branch 1 taken 10 times.
|
10x | h += filename; | |
| 136 |
1/1✓ Branch 1 taken 10 times.
|
10x | h += "\""; | |
| 137 | } | |||
| 138 |
1/1✓ Branch 1 taken 16 times.
|
16x | h += "\r\n"; | |
| 139 |
2/2✓ Branch 1 taken 12 times.
✓ Branch 2 taken 4 times.
|
16x | if(!content_type.empty()) | |
| 140 | { | |||
| 141 |
1/1✓ Branch 1 taken 12 times.
|
12x | h += "Content-Type: "; | |
| 142 |
1/1✓ Branch 1 taken 12 times.
|
12x | h += content_type; | |
| 143 |
1/1✓ Branch 1 taken 12 times.
|
12x | h += "\r\n"; | |
| 144 | } | |||
| 145 |
1/1✓ Branch 1 taken 16 times.
|
16x | h += "\r\n"; | |
| 146 | 16x | return h; | ||
| 147 | ✗ | } | ||
| 148 | ||||
| 149 | class multipart_form::body | |||
| 150 | { | |||
| 151 | multipart_form form_; | |||
| 152 | ||||
| 153 | public: | |||
| 154 | 15x | explicit body(multipart_form form) | ||
| 155 | 15x | : form_(std::move(form)) | ||
| 156 | { | |||
| 157 | 15x | } | ||
| 158 | ||||
| 159 | std::optional<std::string> | |||
| 160 | 15x | content_type() const | ||
| 161 | { | |||
| 162 |
1/1✓ Branch 1 taken 15 times.
|
15x | return "multipart/form-data; boundary=" + form_.boundary_; | |
| 163 | } | |||
| 164 | ||||
| 165 | std::optional<std::uint64_t> | |||
| 166 | 15x | content_length() const noexcept | ||
| 167 | { | |||
| 168 | 15x | std::uint64_t n = 0; | ||
| 169 |
2/2✓ Branch 4 taken 16 times.
✓ Branch 5 taken 15 times.
|
31x | for(auto const& p : form_.parts_) | |
| 170 | 16x | n += p.header.size() + p.size + 2; | ||
| 171 | 15x | n += form_.boundary_.size() + 6; | ||
| 172 | 15x | return n; | ||
| 173 | } | |||
| 174 | ||||
| 175 | capy::io_task<> | |||
| 176 |
1/1✓ Branch 1 taken 112 times.
|
112x | write(capy::any_buffer_sink& sink) const | |
| 177 | { | |||
| 178 | for(auto const& p : form_.parts_) | |||
| 179 | { | |||
| 180 | if(auto [ec, n] = co_await sink.write(capy::make_buffer(p.header)); | |||
| 181 | ec) | |||
| 182 | co_return { ec }; | |||
| 183 | ||||
| 184 | if(p.is_file) | |||
| 185 | { | |||
| 186 | if(auto [ec] = co_await detail::send_file(sink, p.path, p.size); | |||
| 187 | ec) | |||
| 188 | co_return { ec }; | |||
| 189 | } | |||
| 190 | else | |||
| 191 | { | |||
| 192 | if(auto [ec, n] = | |||
| 193 | co_await sink.write(capy::make_buffer(p.text)); | |||
| 194 | ec) | |||
| 195 | co_return { ec }; | |||
| 196 | } | |||
| 197 | ||||
| 198 | if(auto [ec, n] = co_await sink.write( | |||
| 199 | capy::make_buffer(std::string_view("\r\n"))); | |||
| 200 | ec) | |||
| 201 | co_return { ec }; | |||
| 202 | } | |||
| 203 | ||||
| 204 | auto trailer = "--" + form_.boundary_ + "--\r\n"; | |||
| 205 | if(auto [ec, n] = co_await sink.write( | |||
| 206 | capy::make_buffer(std::string_view(trailer))); | |||
| 207 | ec) | |||
| 208 | co_return { ec }; | |||
| 209 | ||||
| 210 | co_return {}; | |||
| 211 | 224x | } | ||
| 212 | }; | |||
| 213 | ||||
| 214 | any_request_body | |||
| 215 | 15x | tag_invoke(body_from_tag<multipart_form>, multipart_form form) | ||
| 216 | { | |||
| 217 |
1/1✓ Branch 4 taken 15 times.
|
15x | return multipart_form::body{ std::move(form) }; | |
| 218 | } | |||
| 219 | ||||
| 220 | } // namespace burl | |||
| 221 | } // namespace boost | |||
| 222 |