src/multipart_form.cpp

98.8% Lines (80/81) 100.0% List of functions (11/11) 74.6% Branches (53/71)
multipart_form.cpp
f(x) 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