Line data Source code
1 : //
2 : // Copyright (c) 2025 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/http_proto
8 : //
9 :
10 : #include <boost/http_proto/multipart_form_sink.hpp>
11 : #include <boost/http_proto/rfc/content_disposition_rule.hpp>
12 :
13 : #include "src/rfc/detail/rules.hpp"
14 :
15 : #include <boost/url/grammar/ci_string.hpp>
16 :
17 : namespace boost {
18 : namespace http_proto {
19 :
20 : namespace {
21 : std::string
22 1 : unquote(quoted_token_view qtv)
23 : {
24 1 : if(qtv.has_escapes())
25 0 : return { qtv.begin(), qtv.end() };
26 :
27 1 : auto rs = std::string{};
28 9 : for(auto it = qtv.begin(); it != qtv.end(); it++)
29 : {
30 8 : if(*it == '\\')
31 0 : it++;
32 8 : rs.push_back(*it);
33 : }
34 1 : return rs;
35 1 : }
36 : } // namespace
37 :
38 1 : multipart_form_sink::
39 : multipart_form_sink(
40 1 : core::string_view boundary)
41 : {
42 1 : leftover_.append("\r\n");
43 1 : needle_.append("\r\n--");
44 1 : needle_.append(boundary);
45 1 : }
46 :
47 : auto
48 0 : multipart_form_sink::
49 : parts() const noexcept
50 : -> boost::span<part const>
51 : {
52 0 : return parts_;
53 : }
54 :
55 : auto
56 1 : multipart_form_sink::
57 : on_write(
58 : buffers::const_buffer b,
59 : bool more) -> results
60 : {
61 1 : system::error_code ec;
62 : core::string_view sv(
63 1 : static_cast<char const*>(b.data()), b.size());
64 :
65 1 : if(!leftover_.empty())
66 : {
67 1 : for(std::size_t i = 0; i < leftover_.size(); ++i)
68 : {
69 1 : core::string_view nd(needle_);
70 1 : if(!nd.starts_with({ &leftover_[i], &*leftover_.cend() }))
71 0 : continue;
72 1 : nd.remove_prefix(leftover_.size() - i);
73 1 : if(sv.size() >= nd.size())
74 : {
75 1 : if(!sv.starts_with(nd))
76 0 : continue;
77 1 : parse(true, { leftover_.data(), i }, ec);
78 1 : if(ec.failed())
79 0 : goto upcall;
80 1 : leftover_.clear();
81 1 : sv.remove_prefix(nd.size());
82 1 : goto loop_sv;
83 : }
84 0 : if(!more)
85 0 : break;
86 0 : if(!nd.starts_with(sv))
87 0 : continue;
88 0 : parse(false, { leftover_.data(), i }, ec);
89 0 : if(ec.failed())
90 0 : goto upcall;
91 0 : leftover_.erase(0, i);
92 0 : leftover_.append(sv);
93 0 : return { ec , sv.size() };
94 : }
95 : // leftover_ cannot contain a needle
96 0 : parse(false, leftover_, ec);
97 0 : if(ec.failed())
98 0 : goto upcall;
99 0 : leftover_.clear();
100 : }
101 :
102 1 : loop_sv:
103 62 : for(char const* it = sv.begin(); it != sv.end(); ++it)
104 : {
105 62 : if(*it == '\r')
106 : {
107 5 : core::string_view rm(it, sv.end());
108 5 : if(rm.size() >= needle_.size())
109 : {
110 4 : if(std::equal(
111 4 : needle_.rbegin(),
112 4 : needle_.rend(),
113 8 : rm.rend() - needle_.size()))
114 : {
115 1 : parse(true, { sv.begin(), it }, ec);
116 1 : if(ec.failed())
117 0 : goto upcall;
118 1 : sv = { it + needle_.size(), sv.end() };
119 1 : goto loop_sv;
120 : }
121 3 : continue;
122 : }
123 1 : if(!more)
124 1 : break;
125 0 : if(!core::string_view(needle_).starts_with(rm))
126 0 : continue;
127 0 : parse(false, { sv.begin(), it }, ec);
128 0 : if(ec.failed())
129 0 : goto upcall;
130 0 : leftover_.append(it, sv.end());
131 0 : sv = {};
132 0 : goto upcall;
133 : }
134 : }
135 1 : parse(false, sv, ec);
136 1 : if(!ec.failed())
137 1 : sv= {};
138 :
139 0 : upcall:
140 1 : return { ec, b.size() - sv.size() };
141 : }
142 :
143 : void
144 3 : multipart_form_sink::
145 : parse(
146 : bool match,
147 : core::string_view b,
148 : system::error_code& ec)
149 : {
150 3 : loop:
151 6 : switch(state_)
152 : {
153 1 : case state::preamble:
154 1 : if(match)
155 1 : state_ = state::post_boundary0;
156 1 : break;
157 2 : case state::post_boundary0:
158 2 : if(b.empty())
159 0 : break;
160 2 : switch(b[0])
161 : {
162 1 : case '\r':
163 1 : state_ = state::post_boundary1;
164 1 : break;
165 1 : case '-':
166 1 : state_ = state::post_boundary2;
167 1 : break;
168 0 : default:
169 0 : ec = http_proto::error::bad_payload;
170 0 : return;
171 : }
172 2 : b.remove_prefix(1);
173 2 : goto loop;
174 1 : case state::post_boundary1:
175 1 : if(b.empty())
176 0 : break;
177 1 : if(b[0] != '\n')
178 : {
179 0 : ec = http_proto::error::bad_payload;
180 0 : return;
181 : }
182 1 : b.remove_prefix(1);
183 1 : state_ = state::header;
184 1 : goto loop;
185 1 : case state::post_boundary2:
186 1 : if(b.empty())
187 0 : break;
188 1 : if(b[0] != '-')
189 : {
190 0 : ec = http_proto::error::bad_payload;
191 0 : return;
192 : }
193 1 : b.remove_prefix(1);
194 1 : state_ = state::finished;
195 1 : break;
196 1 : case state::header:
197 : {
198 1 : auto const s0 = header_.size();
199 1 : header_.append(b);
200 1 : auto const pos = header_.find("\r\n\r\n");
201 1 : if(pos == std::string::npos)
202 0 : break;
203 1 : header_.erase(pos + 4);
204 1 : b.remove_prefix(header_.size() - s0);
205 :
206 : // parse fields
207 :
208 : auto const fields = grammar::parse(
209 1 : header_,
210 1 : grammar::range_rule(detail::field_rule));
211 :
212 1 : if(!fields)
213 : {
214 0 : ec = http_proto::error::bad_payload;
215 0 : return;
216 : }
217 :
218 1 : part part{};
219 :
220 2 : for(auto&& field : fields.value())
221 : {
222 1 : if(field.has_obs_fold)
223 0 : continue; // TODO
224 :
225 1 : if(grammar::ci_is_equal(field.name, "Content-Disposition"))
226 : {
227 : // parse Content-Disposition
228 1 : auto cd = grammar::parse(field.value, content_disposition_rule);
229 1 : if(!cd || !grammar::ci_is_equal(cd->type, "form-data"))
230 : {
231 0 : ec = http_proto::error::bad_payload;
232 0 : return;
233 : }
234 2 : for(auto && param : cd->params)
235 : {
236 1 : if(grammar::ci_is_equal(param.name, "name"))
237 : {
238 1 : part.name = unquote(param.value);
239 : }
240 0 : else if(grammar::ci_is_equal(param.name, "filename"))
241 : {
242 0 : auto& ff = part.content.emplace<file_field>();
243 0 : ff.name = unquote(param.value);
244 :
245 : // TODO
246 0 : ff.path = ff.name;
247 0 : file_.open(
248 : ff.path.c_str(),
249 : capy::file_mode::write_new,
250 : ec);
251 0 : if(ec.failed())
252 0 : return;
253 : }
254 : }
255 1 : }
256 0 : else if(grammar::ci_is_equal(field.name, "Content-Type"))
257 : {
258 0 : part.content_type = field.value;
259 : }
260 : }
261 :
262 1 : if(part.name.empty())
263 : {
264 0 : ec = http_proto::error::bad_payload;
265 0 : return;
266 : }
267 :
268 1 : header_.clear();
269 1 : parts_.push_back(std::move(part));
270 1 : state_ = state::content;
271 : BOOST_FALLTHROUGH;
272 1 : }
273 : case state::content:
274 1 : if(auto* p = variant2::get_if<text_field>(
275 1 : &parts_.back().content))
276 : {
277 1 : p->data.append(b);
278 : }
279 : else
280 : {
281 0 : file_.write(b.data(), b.size(), ec);
282 0 : if(ec.failed())
283 0 : return;
284 0 : if(match)
285 : {
286 0 : file_.close(ec);
287 0 : if(ec.failed())
288 0 : return;
289 : }
290 : }
291 1 : if(match)
292 1 : state_ = state::post_boundary0;
293 1 : break;
294 0 : case state::finished:
295 0 : break;
296 : }
297 : }
298 :
299 : } // http_proto
300 : } // boost
|