RESTinio
Loading...
Searching...
No Matches
file_upload.hpp
Go to the documentation of this file.
1/*
2 * RESTinio
3 */
4
5/*!
6 * @file
7 * @brief Various tools for simplification of file uploading.
8 *
9 * @since v.0.6.1
10 */
11
12#pragma once
13
14#include <restinio/helpers/http_field_parsers/content-type.hpp>
15#include <restinio/helpers/http_field_parsers/content-disposition.hpp>
16#include <restinio/helpers/multipart_body.hpp>
17
18#include <restinio/http_headers.hpp>
19#include <restinio/request_handler.hpp>
20#include <restinio/expected.hpp>
21
22#include <iostream>
23
24namespace restinio
25{
26
27namespace file_upload
28{
29
30//
31// enumeration_error_t
32//
33/*!
34 * @brief The result of an attempt to enumerate parts of a multipart body
35 * that contains uploaded file.
36 *
37 * @since v.0.6.1
38 */
40{
41 //! Content-Type field is not found.
42 //! If Content-Type is absent there is no way to detect 'boundary'
43 //! parameter.
45 //! Unable to parse Content-Type field value.
47 //! Content-Type field value parsed but doesn't contain an appropriate
48 //! value. For example there can be media-type different from 'multipart'
49 //! or 'boundary' parameter can be absent.
51 //! Value of 'boundary' parameter is invalid (for example it contains
52 //! some illegal characters).
54 //! Unable to parse Content-Disposition field.
56 //! Content-Disposition field value parsed but doesn't contain an
57 //! appropriate value. For example, there is no 'name' parameter.
59 //! No parts of a multipart body actually found.
61 //! No files found in the current part.
62 //! For example, there is no Content-Disposition field for that part,
63 //! or Content-Disposition hasn't 'filename' and 'filename*'
64 //! parameters.
66 //! Enumeration of parts was aborted by user-provided handler.
67 //! This code is returned when user-provided handler returns
68 //! handling_result_t::terminate_enumeration.
70 //! Some unexpected error encountered during the enumeration.
72};
73
74namespace impl
75{
76
77/*!
78 * @brief Helper function for conversion from one enumeration_error
79 * to another.
80 *
81 * @since v.0.6.1
82 */
83[[nodiscard]]
84constexpr enumeration_error_t
87{
89 using dest = enumeration_error_t;
90
91 dest result = dest::unexpected_error;
92
93 switch( original )
94 {
96 result = dest::content_type_field_not_found; break;
97
99 result = dest::content_type_field_parse_error; break;
100
103
104 case source::illegal_boundary_value:
105 result = dest::illegal_boundary_value; break;
106
107 case source::no_parts_found:
108 result = dest::no_parts_found; break;
109
110 case source::terminated_by_handler:
111 result = dest::terminated_by_handler; break;
112
113 case source::unexpected_error:
114 /* nothing to do */ break;
115 }
116
117 return result;
118}
119
120} /* namespace impl */
121
122//
123// handling_result_t
124//
125/*!
126 * @brief The result to be returned from user-provided handler of
127 * parts of multipart body.
128 *
129 * @since v.0.6.1
130 */
132
133//
134// part_description_t
135//
136/*!
137 * @brief A description of one part with an uploaded file.
138 *
139 * @note
140 * Values of @a filename_star and @a filename are optional.
141 * But at least one of them won't be empty.
142 * Both of them can be non-empty.
143 *
144 * @since v.0.6.1
145 */
147{
148 //! HTTP-fields local for that part.
149 /*!
150 * @note
151 * It can be empty if no HTTP-fields are found for that part.
152 */
154 //! The body of that part.
156 //! The value of Content-Disposition's 'name' parameter.
157 std::string name;
158 //! The value of Content-Disposition's 'filename*' parameter.
159 /*!
160 * This field has the value only of 'filename*' parameter was
161 * found in Content-Disposition field.
162 *
163 * @attention
164 * If that field is presend then it is the original value extracted
165 * from Content-Disposition without any transformation. It means
166 * that this field will hold values defined in RFC5987 like:
167 * `utf-8'en-US'A%20some%20filename.txt`
168 */
169 std::optional< std::string > filename_star;
170 //! The value of Content-Disposition's 'filename' parameter.
171 /*!
172 * This field has the value only of 'filename' parameter was
173 * found in Content-Disposition field.
174 */
175 std::optional< std::string > filename;
176};
177
178//
179// analyze_part
180//
181/*!
182 * @brief Helper function for analyzing an already parsed part of
183 * a multipart body for presence of an uploaded file.
184 *
185 * This function returns an instance of part_description_t if an
186 * uploaded file is found in @a parsed_part.
187 *
188 * If an uploaded file isn't found or any error detected during analysis
189 * of @a parsed_part then enumeration_error_t returned.
190 *
191 * Usage example:
192 * @code
193 * auto on_post(const restinio::request_handle_t & req) {
194 * using namespace restinio::multipart_body;
195 * using namespace restinio::file_upload;
196 *
197 * const auto result = enumerate_parts( *req,
198 * [](parsed_part_t part) {
199 * // Try to find an uploaded file in that part.
200 * const auto uploaded_file = analyze_part(part);
201 * if(uploaded_file) {
202 * ... // Some handling of the file content.
203 * }
204 * return handling_result_t::continue_enumeration;
205 * },
206 * "multipart", "form-data" );
207 * if(result) {
208 * ... // Producing positive response.
209 * }
210 * else {
211 * ... // Producing negative response.
212 * }
213 * return restinio::request_accepted();
214 * }
215 * @endcode
216 *
217 * @since v.0.6.1
218 */
219[[nodiscard]]
222{
223 namespace hfp = restinio::http_field_parsers;
224
225 // Content-Disposition field should be present.
226 const auto disposition_field = parsed_part.fields.opt_value_of(
227 restinio::http_field::content_disposition );
228 if( !disposition_field )
229 return make_unexpected( enumeration_error_t::no_files_found );
230
231 // Content-Disposition should have value `form-data` with
232 // `name` and `filename*`/`filename` parameters.
233 const auto parsed_disposition = hfp::content_disposition_value_t::
234 try_parse( *disposition_field );
235 if( !parsed_disposition )
236 return make_unexpected(
238 if( "form-data" != parsed_disposition->value )
239 return make_unexpected( enumeration_error_t::no_files_found );
240
241 const auto name = hfp::find_first(
242 parsed_disposition->parameters, "name" );
243 if( !name )
244 return make_unexpected(
246 const auto expected_to_optional = []( auto expected ) {
247 return expected ?
248 std::optional< std::string >{ std::string{
249 expected->data(),
250 expected->size()
251 } }
252 : std::optional< std::string >{};
253 };
254
255 auto filename_star = expected_to_optional( hfp::find_first(
256 parsed_disposition->parameters, "filename*" ) );
257 auto filename = expected_to_optional( hfp::find_first(
258 parsed_disposition->parameters, "filename" ) );
259
260 // If there is no `filename*` nor `filename` then there is no file.
261 if( !filename_star && !filename )
262 return make_unexpected( enumeration_error_t::no_files_found );
263
264 return part_description_t{
265 std::move( parsed_part.fields ),
266 parsed_part.body,
267 std::string{ name->data(), name->size() },
268 std::move(filename_star),
269 std::move(filename)
270 };
271}
272
273namespace impl
274{
275
276//
277// valid_handler_type
278//
279template< typename, typename = restinio::utils::metaprogramming::void_t<> >
280struct valid_handler_type : public std::false_type {};
281
282template< typename T >
284 T,
287 std::is_same<
289 decltype(std::declval<T>()(std::declval<part_description_t>()))
290 >::value,
291 bool
292 >
293 >
294 > : public std::true_type
295{};
296
297} /* namespace impl */
298
299/*!
300 * @brief A helper function for enumeration of parts of a multipart body
301 * those contain uploaded files.
302 *
303 * This function:
304 *
305 * - finds Content-Type field for @a req;
306 * - parses Content-Type field, checks the media-type and extracts
307 * the value of 'boundary' parameter. The extracted 'boundary'
308 * parameter is checked for validity;
309 * - splits the body of @a req using value of 'boundary' parameter;
310 * - enumerates every part of body, parses every part and tries to
311 * find a Content-Disposition field with appropriate 'name' and
312 * 'filename*'/'filename' parameters;
313 * - if a part with appropriate Content-Disposition is found the
314 * @a handler is called for it.
315 *
316 * Enumeration stops if @a handler returns handling_result_t::stop_enumeration
317 * or handling_result_t::terminate_enumeration. If @a handler returns
318 * handling_result_t::terminate_enumeration the enumerate_parts() returns
319 * enumeration_error_t::terminated_by_handler error code.
320 *
321 * A handler passed as @a handler argument should be a function or
322 * lambda/functor with one of the following formats:
323 * @code
324 * handling_result_t(part_description_t part);
325 * handling_result_t(part_description_t && part);
326 * handling_result_t(const part_description_t & part);
327 * @endcode
328 * Note that enumerate_parts_with_files() passes part_description_t instance to
329 * @a handler as rvalue reference. And this reference will be invalidaded after
330 * the return from @a handler.
331 *
332 * Usage example:
333 * @code
334 * auto on_post(const restinio::request_handle_t & req) {
335 * using namespace restinio::file_upload;
336 *
337 * const auto result = enumerate_parts_with_files( *req,
338 * [](part_description_t part) {
339 * ... // Some actions with the current part.
340 * return handling_result_t::continue_enumeration;
341 * },
342 * "multipart", "form-data" );
343 * if(result) {
344 * ... // Producing positive response.
345 * }
346 * else {
347 * ... // Producing negative response.
348 * }
349 * return restinio::request_accepted();
350 * }
351 * @endcode
352 *
353 * @return the count of parts passed to @a handler or
354 * error code in the case if some error is detected.
355 *
356 * @since v.0.6.1
357 */
358template< typename Extra_Data, typename Handler >
361 //! Request to be processed.
362 const generic_request_t< Extra_Data > & req,
363 //! Handler to be called for every part with uploaded file.
364 Handler && handler,
365 //! The value of 'type' part of media-type in Content-Type field.
366 //! Please note: the special value '*' is not supported here.
367 string_view_t expected_media_type = string_view_t{"multipart"},
368 //! The value of 'subtype' part of media-type in Content-Type field.
369 string_view_t expected_media_subtype = string_view_t{"form-data"} )
370{
371 static_assert(
372 impl::valid_handler_type< std::decay_t<Handler> >::value,
373 "Handler should be callable object, "
374 "should accept part_description_t by value, const or rvalue reference, "
375 "and should return handling_result_t" );
376
377 std::size_t files_found{ 0u };
378 std::optional< enumeration_error_t > error;
379
380 const auto result = restinio::multipart_body::enumerate_parts( req,
381 [&handler, &files_found, &error]
383 {
384 auto part_description = analyze_part( std::move(part) );
385 if( part_description )
386 {
387 ++files_found;
388
389 return handler( std::move(*part_description) );
390 }
392 part_description.error() )
393 {
395 }
396 else
397 {
398 error = part_description.error();
400 }
401 },
402 expected_media_type,
403 expected_media_subtype );
404
405 if( error )
406 return make_unexpected( *error );
407 else if( !result )
408 return make_unexpected(
409 impl::translate_enumeration_error( result.error() ) );
410 else
411 return files_found;
412}
413
414} /* namespace file_upload */
415
416} /* namespace restinio */
constexpr enumeration_error_t translate_enumeration_error(restinio::multipart_body::enumeration_error_t original)
Helper function for conversion from one enumeration_error to another.
expected_t< part_description_t, enumeration_error_t > analyze_part(restinio::multipart_body::parsed_part_t parsed_part)
Helper function for analyzing an already parsed part of a multipart body for presence of an uploaded ...
restinio::multipart_body::handling_result_t handling_result_t
The result to be returned from user-provided handler of parts of multipart body.
expected_t< std::size_t, enumeration_error_t > enumerate_parts_with_files(const generic_request_t< Extra_Data > &req, Handler &&handler, string_view_t expected_media_type=string_view_t{"multipart"}, string_view_t expected_media_subtype=string_view_t{"form-data"})
A helper function for enumeration of parts of a multipart body those contain uploaded files.
enumeration_error_t
The result of an attempt to enumerate parts of a multipart body that contains uploaded file.
@ content_type_field_inappropriate_value
Content-Type field value parsed but doesn't contain an appropriate value. For example there can be me...
@ no_files_found
No files found in the current part. For example, there is no Content-Disposition field for that part,...
@ no_parts_found
No parts of a multipart body actually found.
@ illegal_boundary_value
Value of 'boundary' parameter is invalid (for example it contains some illegal characters).
@ content_type_field_not_found
Content-Type field is not found. If Content-Type is absent there is no way to detect 'boundary' param...
@ terminated_by_handler
Enumeration of parts was aborted by user-provided handler. This code is returned when user-provided h...
@ content_type_field_parse_error
Unable to parse Content-Type field value.
@ content_disposition_field_inappropriate_value
Content-Disposition field value parsed but doesn't contain an appropriate value. For example,...
@ unexpected_error
Some unexpected error encountered during the enumeration.
@ content_disposition_field_parse_error
Unable to parse Content-Disposition field.
handling_result_t
The result to be returned from user-provided handler of parts of multipart body.
@ terminate_enumeration
Enumeration of parts should be ignored. All remaining parts of multipart body will be skipped and the...
@ continue_enumeration
Enumeration of parts should be continued. If there is another part the user-provided handler will be ...
enumeration_error_t
The result of an attempt to enumerate parts of a multipart body.
@ content_type_field_inappropriate_value
Content-Type field value parsed but doesn't contain an appropriate value. For example there can be me...
@ no_parts_found
No parts of a multipart body actually found.
@ illegal_boundary_value
Value of 'boundary' parameter is invalid (for example it contains some illegal characters).
@ content_type_field_not_found
Content-Type field is not found. If Content-Type is absent there is no way to detect 'boundary' param...
@ terminated_by_handler
Enumeration of parts was aborted by user-provided handler. This code is returned when user-provided h...
@ content_type_field_parse_error
Unable to parse Content-Type field value.
@ unexpected_error
Some unexpected error encountered during the enumeration.
http_field_t http_field
Helper alies to omitt _t suffix.
A description of one part with an uploaded file.
http_header_fields_t fields
HTTP-fields local for that part.
std::optional< std::string > filename
The value of Content-Disposition's 'filename' parameter.
std::string name
The value of Content-Disposition's 'name' parameter.
string_view_t body
The body of that part.
std::optional< std::string > filename_star
The value of Content-Disposition's 'filename*' parameter.
A description of parsed content of one part of a multipart body.