RESTinio
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 
24 namespace restinio
25 {
26 
27 namespace 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 
74 namespace 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  */
84 constexpr enumeration_error_t
87 {
89  using dest = enumeration_error_t;
90 
92 
93  switch( original )
94  {
97 
100 
103 
106 
107  case source::no_parts_found:
108  result = dest::no_parts_found; break;
109 
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.
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  */
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  */
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  */
222 {
223  namespace hfp = restinio::http_field_parsers;
224 
225  // Content-Disposition field should be present.
228  if( !disposition_field )
230 
231  // Content-Disposition should have value `form-data` with
232  // `name` and `filename*`/`filename` parameters.
235  if( !parsed_disposition )
236  return make_unexpected(
238  if( "form-data" != parsed_disposition->value )
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 ?
249  expected->data(),
250  expected->size()
251  } }
252  : optional_t< std::string >{};
253  };
254 
256  parsed_disposition->parameters, "filename*" ) );
258  parsed_disposition->parameters, "filename" ) );
259 
260  // If there is no `filename*` nor `filename` then there is no file.
261  if( !filename_star && !filename )
263 
264  return part_description_t{
267  std::string{ name->data(), name->size() },
269  std::move(filename)
270  };
271 }
272 
273 namespace impl
274 {
275 
276 //
277 // valid_handler_type
278 //
279 template< typename, typename = restinio::utils::metaprogramming::void_t<> >
280 struct valid_handler_type : public std::false_type {};
281 
282 template< typename T >
284  T,
286  std::enable_if_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  */
358 template< 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.
368  //! The value of 'subtype' part of media-type in Content-Type field.
370 {
371  static_assert(
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 };
379 
381  [&handler, &files_found, &error]
383  {
385  if( part_description )
386  {
387  ++files_found;
388 
389  return handler( std::move(*part_description) );
390  }
393  {
395  }
396  else
397  {
400  }
401  },
404 
405  if( error )
406  return make_unexpected( *error );
407  else if( !result )
408  return make_unexpected(
410  else
411  return files_found;
412 }
413 
414 } /* namespace file_upload */
415 
416 } /* namespace restinio */
std::string name
The value of Content-Disposition&#39;s &#39;name&#39; parameter.
RESTINIO_NODISCARD 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 ...
A description of one part with an uploaded file.
optional_t< std::string > filename
The value of Content-Disposition&#39;s &#39;filename&#39; parameter.
Content-Type field is not found. If Content-Type is absent there is no way to detect &#39;boundary&#39; param...
Content-Type field value parsed but doesn&#39;t contain an appropriate value. For example there can be me...
string_view_t body
The body of that part.
Enumeration of parts was aborted by user-provided handler. This code is returned when user-provided h...
RESTINIO_NODISCARD constexpr enumeration_error_t translate_enumeration_error(restinio::multipart_body::enumeration_error_t original)
Helper function for conversion from one enumeration_error to another.
Definition: file_upload.hpp:85
optional_t< std::string > filename_star
The value of Content-Disposition&#39;s &#39;filename*&#39; parameter.
No files found in the current part. For example, there is no Content-Disposition field for that part...
Content-Disposition field value parsed but doesn&#39;t contain an appropriate value. For example...
enumeration_error_t
The result of an attempt to enumerate parts of a multipart body that contains uploaded file...
Definition: file_upload.hpp:39
Some unexpected error encountered during the enumeration.
http_header_fields_t fields
HTTP-fields local for that part.
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...
No parts of a multipart body actually found.
Value of &#39;boundary&#39; parameter is invalid (for example it contains some illegal characters).
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, optional_t< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in optional_t<Value_Type> if parameter exists ...
Definition: value_or.hpp:64