RESTinio
multipart_body.hpp
Go to the documentation of this file.
1 /*
2  * RESTinio
3  */
4 
12 #pragma once
13 
18 
21 #include <restinio/expected.hpp>
22 
24 
26 
27 #include <iostream>
28 
29 namespace restinio
30 {
31 
32 namespace multipart_body
33 {
34 
35 //
36 // split_multipart_body
37 //
71 inline std::vector< string_view_t >
73  string_view_t body,
74  string_view_t boundary )
75 {
76  using namespace restinio::string_algo;
77 
78  std::vector< string_view_t > result;
79  std::vector< string_view_t > tmp_result;
80 
81  const string_view_t eol{ "\r\n" };
82  const string_view_t last_separator{ "--\r\n" };
83 
84  // Find the first boundary.
85  auto boundary_pos = body.find( boundary );
86  if( string_view_t::npos == boundary_pos )
87  // There is no initial separator in the body.
88  return result;
89 
90  // The first body can be at the very begining of the body or
91  // there should be CRLF before the initial boundary.
92  if( boundary_pos != 0u &&
93  (boundary_pos < eol.size() ||
94  body.substr( boundary_pos - eol.size(), eol.size() ) != eol) )
95  return result;
96 
97  auto remaining_body = body.substr( boundary_pos + boundary.size() );
98  if( starts_with( remaining_body, last_separator ) )
99  // The start boundary is the last boundary.
100  return result;
101 
102  while( starts_with( remaining_body, eol ) )
103  {
104  remaining_body = remaining_body.substr( eol.size() );
105 
106  boundary_pos = remaining_body.find( boundary );
107  if( string_view_t::npos == boundary_pos )
108  return result;
109 
110  // There should be CRLF before the next boundary.
111  if( boundary_pos < eol.size() ||
112  remaining_body.substr( boundary_pos - eol.size(), eol.size() ) != eol )
113  return result;
114 
115  tmp_result.push_back(
116  remaining_body.substr( 0u, boundary_pos - eol.size() ) );
117 
118  remaining_body = remaining_body.substr( boundary_pos + boundary.size() );
119  // Is this boundary the last one?
120  if( starts_with( remaining_body, last_separator ) )
121  {
122  // Yes, our iteration can be stopped and we can return the result.
123  swap( tmp_result, result );
124  return result;
125  }
126  }
127 
128  // We didn't find the last boundary. Or some error encountered in the format
129  // of the body.
130  //
131  // Empty result should be returned.
132  return result;
133 }
134 
135 //
136 // parsed_part_t
137 //
144 {
146 
153 };
154 
155 namespace impl
156 {
157 
158 namespace parser_details
159 {
160 
161 using namespace restinio::http_field_parsers;
162 
164 
165 constexpr char CR = '\r';
166 constexpr char LF = '\n';
167 
168 //
169 // body_producer_t
170 //
184  : public easy_parser::impl::producer_tag< string_view_t >
185 {
188  try_parse( easy_parser::impl::source_t & from ) const noexcept
189  {
190  // Return the whole content from the current position.
191  return from.fragment( from.current_position() );
192  }
193 };
194 
195 //
196 // field_value_producer_t
197 //
208  : public easy_parser::impl::producer_tag< std::string >
209 {
213  {
214  std::string accumulator;
215  auto ch = from.getch();
216  while( !ch.m_eof && ch.m_ch != CR && ch.m_ch != LF )
217  {
218  accumulator += ch.m_ch;
219  ch = from.getch();
220  }
221 
222  if( ch.m_eof )
223  return make_unexpected( easy_parser::parse_error_t{
224  from.current_position(),
225  easy_parser::error_reason_t::unexpected_eof
226  } );
227 
228  // CR or LF symbol should be returned back.
229  from.putback();
230 
231  return std::move(accumulator);
232  }
233 };
234 
235 } /* namespace parser_details */
236 
237 //
238 // make_parser
239 //
253 auto
255 {
256  using namespace parser_details;
257 
258  return produce< parsed_part_t >(
259  produce< http_header_fields_t >(
260  repeat( 0, N,
261  produce< http_header_field_t >(
262  token_p() >> to_lower() >> custom_consumer(
263  [](auto & f, std::string && v) {
264  f.name(std::move(v));
265  } ),
266  symbol(':'),
267  ows(),
268  field_value_producer_t{} >> custom_consumer(
269  [](auto & f, std::string && v) {
270  f.value(std::move(v));
271  } ),
272  symbol(CR), symbol(LF)
273  ) >> custom_consumer(
274  [](auto & to, http_header_field_t && v) {
275  to.add_field( std::move(v) );
276  } )
277  )
278  ) >> &parsed_part_t::fields,
279  symbol(CR), symbol(LF),
280  body_producer_t{} >> &parsed_part_t::body );
281 }
282 
283 } /* namespace impl */
284 
285 //
286 // try_parse_part
287 //
316 {
318 
319  easy_parser::impl::source_t source{ part };
320 
321  auto actual_producer = impl::make_parser();
322 
323  return easy_parser::impl::top_level_clause_t< decltype(actual_producer) >{
324  std::move(actual_producer)
325  }.try_process( source );
326 }
327 
328 //
329 // handling_result_t
330 //
338 {
342  continue_enumeration,
346  stop_enumeration,
351 };
352 
353 //
354 // enumeration_error_t
355 //
362 {
366  content_type_field_not_found,
368  content_type_field_parse_error,
372  content_type_field_inappropriate_value,
375  illegal_boundary_value,
377  no_parts_found,
381  terminated_by_handler,
383  unexpected_error
384 };
385 
386 namespace impl
387 {
388 
389 namespace boundary_value_checkers
390 {
391 
392 // From https://tools.ietf.org/html/rfc1521:
393 //
394 // boundary := 0*69<bchars> bcharsnospace
395 //
396 // bchars := bcharsnospace / " "
397 //
398 // bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" /"_"
399 // / "," / "-" / "." / "/" / ":" / "=" / "?"
400 //
402 constexpr bool
403 is_bcharnospace( char ch )
404 {
405  return (ch >= '0' && ch <= '9') // DIGIT
406  || ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) // ALPHA
407  || ch == '\''
408  || ch == '('
409  || ch == ')'
410  || ch == '+'
411  || ch == '_'
412  || ch == ','
413  || ch == '-'
414  || ch == '.'
415  || ch == '/'
416  || ch == ':'
417  || ch == '='
418  || ch == '?';
419 }
420 
422 constexpr bool
423 is_bchar( char ch )
424 {
425  return is_bcharnospace(ch) || ch == ' ';
426 }
427 
428 } /* namespace boundary_value_checkers */
429 
430 } /* namespace impl */
431 
432 //
433 // check_boundary_value
434 //
458 {
459  using namespace impl::boundary_value_checkers;
460 
461  if( value.size() >= 1u && value.size() <= 70u )
462  {
463  const std::size_t last_index = value.size() - 1u;
464  for( std::size_t i = 0u; i != last_index; ++i )
465  if( !is_bchar( value[i] ) )
466  return enumeration_error_t::illegal_boundary_value;
467 
468  if( !is_bcharnospace( value[ last_index ] ) )
469  return enumeration_error_t::illegal_boundary_value;
470  }
471  else
472  return enumeration_error_t::illegal_boundary_value;
473 
474  return nullopt;
475 }
476 
477 //
478 // detect_boundary_for_multipart_body
479 //
497  const request_t & req,
498  string_view_t expected_media_type,
499  optional_t< string_view_t > expected_media_subtype )
500 {
501  namespace hfp = restinio::http_field_parsers;
503 
504  // Content-Type header file should be present.
505  const auto content_type = req.header().opt_value_of(
506  restinio::http_field::content_type );
507  if( !content_type )
508  return make_unexpected(
509  enumeration_error_t::content_type_field_not_found );
510 
511  // Content-Type field should successfuly parsed and should
512  // contain value that correspond to expected media-type.
513  const auto parse_result = hfp::content_type_value_t::try_parse(
514  *content_type );
515  if( !parse_result )
516  return make_unexpected(
517  enumeration_error_t::content_type_field_parse_error );
518 
519  const auto & media_type = parse_result->media_type;
520  if( !is_equal_caseless( expected_media_type, media_type.type ) )
521  {
522  return make_unexpected(
523  enumeration_error_t::content_type_field_inappropriate_value );
524  }
525  if( expected_media_subtype &&
526  !is_equal_caseless( *expected_media_subtype, media_type.subtype ) )
527  {
528  return make_unexpected(
529  enumeration_error_t::content_type_field_inappropriate_value );
530  }
531 
532  // `boundary` param should be present in parsed Content-Type value.
533  const auto boundary = hfp::find_first(
534  parse_result->media_type.parameters,
535  "boundary" );
536  if( !boundary )
537  return make_unexpected(
538  enumeration_error_t::content_type_field_inappropriate_value );
539 
540  // `boundary` should have valid value.
541  const auto boundary_check_result = check_boundary_value( *boundary );
542  if( boundary_check_result )
543  return make_unexpected( *boundary_check_result );
544 
545  // Actual value of boundary mark can be created.
546  std::string actual_boundary_mark;
547  actual_boundary_mark.reserve( 2 + boundary->size() );
548  actual_boundary_mark.append( "--" );
549  actual_boundary_mark.append( boundary->data(), boundary->size() );
550 
551  return std::move(actual_boundary_mark);
552 }
553 
554 namespace impl
555 {
556 
566 template< typename Handler >
570  const std::vector< string_view_t > & parts,
571  Handler && handler )
572 {
573  std::size_t parts_processed{ 0u };
575 
576  for( auto current_part : parts )
577  {
578  // The current part should be parsed to headers and the body.
579  auto part_parse_result = try_parse_part( current_part );
580  if( !part_parse_result )
581  return make_unexpected( enumeration_error_t::unexpected_error );
582 
583  // NOTE: parsed_part is passed as rvalue reference!
584  const handling_result_t handler_ret_code = handler(
585  std::move(*part_parse_result) );
586 
587  if( handling_result_t::terminate_enumeration != handler_ret_code )
588  ++parts_processed;
589  else
590  error = enumeration_error_t::terminated_by_handler;
591 
592  if( handling_result_t::continue_enumeration != handler_ret_code )
593  break;
594  }
595 
596  if( error )
597  return make_unexpected( *error );
598 
599  return parts_processed;
600 }
601 
602 //
603 // valid_handler_type
604 //
605 template< typename, typename = restinio::utils::metaprogramming::void_t<> >
606 struct valid_handler_type : public std::false_type {};
607 
608 template< typename T >
610  T,
612  std::enable_if_t<
613  std::is_same<
614  handling_result_t,
615  decltype(std::declval<T>()(std::declval<parsed_part_t>()))
616  >::value,
617  bool
618  >
619  >
620  > : public std::true_type
621 {};
622 
623 } /* namespace impl */
624 
625 //
626 // enumerate_parts
627 //
682 template< typename Handler >
687  const request_t & req,
689  Handler && handler,
696  string_view_t expected_media_type = string_view_t{ "multipart" },
703  optional_t< string_view_t > expected_media_subtype = nullopt )
704 {
705  static_assert(
706  impl::valid_handler_type< std::decay_t<Handler> >::value,
707  "Handler should be callable object, "
708  "should accept parsed_part_t by value, const or rvalue reference, "
709  "and should return handling_result_t" );
710 
711  const auto boundary = detect_boundary_for_multipart_body(
712  req,
713  expected_media_type,
714  expected_media_subtype );
715  if( boundary )
716  {
717  const auto parts = split_multipart_body( req.body(), *boundary );
718 
719  if( parts.empty() )
720  return make_unexpected(
721  enumeration_error_t::no_parts_found );
722 
724  parts,
725  std::forward<Handler>(handler) );
726  }
727 
728  return make_unexpected( boundary.error() );
729 }
730 
731 } /* namespace multipart_body */
732 
733 } /* namespace restinio */
734 
RESTINIO_NODISCARD
#define RESTINIO_NODISCARD
Definition: compiler_features.hpp:33
restinio::easy_parser::impl::source_t::current_position
RESTINIO_NODISCARD position_t current_position() const noexcept
Get the current position in the stream.
Definition: easy_parser.hpp:786
restinio::easy_parser::symbol
RESTINIO_NODISCARD auto symbol(char expected) noexcept
A factory function to create a clause that expects the speficied symbol, extracts it and then skips i...
Definition: easy_parser.hpp:4032
restinio::http_field_parsers::token_p
RESTINIO_NODISCARD auto token_p() noexcept
A factory function to create a token_producer.
Definition: basics.hpp:985
nonstd::optional_lite::std11::move
T & move(T &t)
Definition: optional.hpp:421
restinio::http_header_field_t
A single header field.
Definition: http_headers.hpp:583
restinio::multipart_body::impl::boundary_value_checkers::is_bchar
constexpr RESTINIO_NODISCARD bool is_bchar(char ch)
Definition: multipart_body.hpp:423
request_handler.hpp
restinio::multipart_body::impl::parser_details::LF
constexpr char LF
Definition: multipart_body.hpp:166
restinio::http_field_parsers::find_first
RESTINIO_NODISCARD expected_t< string_view_t, not_found_t > find_first(const parameter_with_mandatory_value_container_t &where, string_view_t what)
A helper function to find the first occurence of a parameter with the specified value.
Definition: basics.hpp:1568
restinio::multipart_body::impl::enumerate_parts_of_request_body
RESTINIO_NODISCARD expected_t< std::size_t, enumeration_error_t > enumerate_parts_of_request_body(const std::vector< string_view_t > &parts, Handler &&handler)
A function that parses every part of a multipart body and calls a user-provided handler for every par...
Definition: multipart_body.hpp:569
restinio::multipart_body::handling_result_t
handling_result_t
The result to be returned from user-provided handler of parts of multipart body.
Definition: multipart_body.hpp:338
restinio::easy_parser::N
constexpr std::size_t N
A special marker that means infinite repetitions.
Definition: easy_parser.hpp:455
restinio::easy_parser::impl::source_t::getch
RESTINIO_NODISCARD character_t getch() noexcept
Get the next character from the input stream.
Definition: easy_parser.hpp:765
restinio::easy_parser::parse_error_t
Information about parsing error.
Definition: easy_parser.hpp:93
restinio::string_algo::starts_with
RESTINIO_NODISCARD bool starts_with(const string_view_t &where, const string_view_t &what) noexcept
Definition: string_algo.hpp:24
restinio::string_view_t
nonstd::string_view string_view_t
Definition: string_view.hpp:19
restinio::multipart_body::impl::boundary_value_checkers::is_bcharnospace
constexpr RESTINIO_NODISCARD bool is_bcharnospace(char ch)
Definition: multipart_body.hpp:403
restinio::multipart_body::impl::parser_details::body_producer_t::try_parse
RESTINIO_NODISCARD expected_t< string_view_t, easy_parser::parse_error_t > try_parse(easy_parser::impl::source_t &from) const noexcept
Definition: multipart_body.hpp:188
restinio::request_t::body
const std::string & body() const noexcept
Get request body.
Definition: request_handler.hpp:99
restinio::http_field_parsers
Definition: accept-charset.hpp:20
restinio::multipart_body::try_parse_part
RESTINIO_NODISCARD expected_t< parsed_part_t, restinio::easy_parser::parse_error_t > try_parse_part(string_view_t part)
Helper function for parsing content of one part of a multipart body.
Definition: multipart_body.hpp:315
restinio::multipart_body::impl::parser_details::CR
constexpr char CR
Definition: multipart_body.hpp:165
restinio::easy_parser::impl::character_t::m_ch
char m_ch
Definition: easy_parser.hpp:595
restinio::http_header_fields_t
Header fields map.
Definition: http_headers.hpp:703
restinio::http_field_parsers::ows
RESTINIO_NODISCARD auto ows() noexcept
A factory function to create an OWS clause.
Definition: basics.hpp:939
restinio::multipart_body::enumeration_error_t::content_type_field_not_found
@ content_type_field_not_found
Content-Type field is not found. If Content-Type is absent there is no way to detect 'boundary' param...
easy_parser.hpp
An very small, simple and somewhat limited implementation of recursive-descent parser.
restinio::multipart_body::parsed_part_t
A description of parsed content of one part of a multipart body.
Definition: multipart_body.hpp:144
restinio::multipart_body::impl::parser_details::field_value_producer_t
A special producer that consumes the rest of the current line in the input stream until CR/LF will be...
Definition: multipart_body.hpp:209
restinio::easy_parser::try_parse
RESTINIO_NODISCARD expected_t< typename Producer::result_type, parse_error_t > try_parse(string_view_t from, Producer producer)
Perform the parsing of the specified content by using specified value producer.
Definition: easy_parser.hpp:5042
nonstd::optional_lite::optional
class optional
Definition: optional.hpp:839
restinio::multipart_body::enumerate_parts
RESTINIO_NODISCARD expected_t< std::size_t, enumeration_error_t > enumerate_parts(const request_t &req, Handler &&handler, string_view_t expected_media_type=string_view_t{ "multipart" }, optional_t< string_view_t > expected_media_subtype=nullopt)
A helper function for enumeration of parts of a multipart body.
Definition: multipart_body.hpp:685
restinio::utils::metaprogramming::void_t
typename make_void< Ts... >::type void_t
Definition: metaprogramming.hpp:28
nonstd::optional_lite::nullopt
const nullopt_t nullopt((nullopt_t::init()))
restinio::expected_t
nonstd::expected< T, E > expected_t
Definition: expected.hpp:22
basics.hpp
Utilities for parsing values of http-fields.
restinio::request_t::header
const http_request_header_t & header() const noexcept
Get request header.
Definition: request_handler.hpp:92
restinio::easy_parser::impl::producer_tag
A special base class to be used with producers.
Definition: easy_parser.hpp:945
restinio::impl::is_equal_caseless
bool is_equal_caseless(const char *a, const char *b, std::size_t size) noexcept
Comparator for fields names.
Definition: string_caseless_compare.hpp:40
restinio::multipart_body::parsed_part_t::body
string_view_t body
The body of that part.
Definition: multipart_body.hpp:152
restinio::multipart_body::impl::parser_details::body_producer_t
A special producer that consumes the whole remaining content from the input stream.
Definition: multipart_body.hpp:185
restinio::http_header_fields_t::opt_value_of
optional_t< string_view_t > opt_value_of(string_view_t name) const noexcept
Get optional value of a field.
Definition: http_headers.hpp:1300
restinio::multipart_body::check_boundary_value
RESTINIO_NODISCARD optional_t< enumeration_error_t > check_boundary_value(string_view_t value)
A helper function for checking the validity of 'boundary' value.
Definition: multipart_body.hpp:457
restinio::multipart_body::handling_result_t::continue_enumeration
@ continue_enumeration
Enumeration of parts should be continued. If there is another part the user-provided handler will be ...
restinio
Definition: asio_include.hpp:21
restinio::multipart_body::detect_boundary_for_multipart_body
RESTINIO_NODISCARD expected_t< std::string, enumeration_error_t > detect_boundary_for_multipart_body(const request_t &req, string_view_t expected_media_type, optional_t< string_view_t > expected_media_subtype)
Helper function for parsing Content-Type field and extracting the value of 'boundary' parameter.
Definition: multipart_body.hpp:496
restinio::multipart_body::impl::parser_details::field_value_producer_t::try_parse
RESTINIO_NODISCARD expected_t< std::string, easy_parser::parse_error_t > try_parse(easy_parser::impl::source_t &from) const
Definition: multipart_body.hpp:212
restinio::easy_parser::impl::top_level_clause_t
A special class to be used as the top level clause in parser.
Definition: easy_parser.hpp:1533
restinio::multipart_body::impl::valid_handler_type
Definition: multipart_body.hpp:606
restinio::multipart_body::enumeration_error_t
enumeration_error_t
The result of an attempt to enumerate parts of a multipart body.
Definition: multipart_body.hpp:362
restinio::string_algo
Definition: string_algo.hpp:21
restinio::easy_parser::to_lower
RESTINIO_NODISCARD auto to_lower() noexcept
A factory function to create a to_lower_transformer.
Definition: easy_parser.hpp:4610
metaprogramming.hpp
Various tools for C++ metaprogramming.
string_caseless_compare.hpp
Helpers for caseless comparison of strings.
expected.hpp
string_algo.hpp
Various string-related algorithms.
nonstd::optional_lite::swap
void swap(optional< T > &x, optional< T > &y)
Definition: optional.hpp:1619
restinio::easy_parser::impl::source_t
The class that implements "input stream".
Definition: easy_parser.hpp:740
content-type.hpp
Stuff related to value of Content-Type HTTP-field.
restinio::easy_parser::impl::source_t::putback
void putback() noexcept
Return one character back to the input stream.
Definition: easy_parser.hpp:777
restinio::multipart_body::split_multipart_body
RESTINIO_NODISCARD std::vector< string_view_t > split_multipart_body(string_view_t body, string_view_t boundary)
Helper function for spliting a multipart body into a serie of separate parts.
Definition: multipart_body.hpp:72
http_headers.hpp
restinio::multipart_body::impl::make_parser
RESTINIO_NODISCARD auto make_parser()
A factory function for a parser of a part of multipart message.
Definition: multipart_body.hpp:254
restinio::easy_parser
Definition: easy_parser.hpp:39
restinio::multipart_body::parsed_part_t::fields
http_header_fields_t fields
HTTP-fields local for that part.
Definition: multipart_body.hpp:150
restinio::easy_parser::custom_consumer
RESTINIO_NODISCARD auto custom_consumer(F consumer)
A factory function to create a custom_consumer.
Definition: easy_parser.hpp:4516
restinio::request_t
HTTP Request data.
Definition: request_handler.hpp:44
restinio::easy_parser::repeat
RESTINIO_NODISCARD auto repeat(std::size_t min_occurences, std::size_t max_occurences, Clauses &&... clauses)
A factory function to create repetitor of subclauses.
Definition: easy_parser.hpp:3876