Loading...
Searching...
No Matches
multipart_body.hpp
Go to the documentation of this file.
/*
* RESTinio
*/
#pragma once
#include <restinio/helpers/string_algo.hpp>
#include <restinio/helpers/easy_parser.hpp>
#include <restinio/helpers/http_field_parsers/basics.hpp>
#include <restinio/helpers/http_field_parsers/content-type.hpp>
#include <restinio/http_headers.hpp>
#include <restinio/request_handler.hpp>
#include <restinio/expected.hpp>
#include <restinio/impl/string_caseless_compare.hpp>
#include <restinio/utils/metaprogramming.hpp>
#include <iostream>
namespace restinio
{
namespace multipart_body
{
//
// split_multipart_body
//
RESTINIO_NODISCARD
inline std::vector< string_view_t >
split_multipart_body(
string_view_t body,
string_view_t boundary )
{
using namespace restinio::string_algo;
std::vector< string_view_t > result;
std::vector< string_view_t > tmp_result;
const string_view_t eol{ "\r\n" };
const string_view_t last_separator{ "--\r\n" };
// Find the first boundary.
auto boundary_pos = body.find( boundary );
if( string_view_t::npos == boundary_pos )
// There is no initial separator in the body.
return result;
// The first body can be at the very begining of the body or
// there should be CRLF before the initial boundary.
if( boundary_pos != 0u &&
(boundary_pos < eol.size() ||
body.substr( boundary_pos - eol.size(), eol.size() ) != eol) )
return result;
auto remaining_body = body.substr( boundary_pos + boundary.size() );
if( starts_with( remaining_body, last_separator ) )
// The start boundary is the last boundary.
return result;
while( starts_with( remaining_body, eol ) )
{
remaining_body = remaining_body.substr( eol.size() );
boundary_pos = remaining_body.find( boundary );
if( string_view_t::npos == boundary_pos )
return result;
// There should be CRLF before the next boundary.
if( boundary_pos < eol.size() ||
remaining_body.substr( boundary_pos - eol.size(), eol.size() ) != eol )
return result;
tmp_result.push_back(
remaining_body.substr( 0u, boundary_pos - eol.size() ) );
remaining_body = remaining_body.substr( boundary_pos + boundary.size() );
// Is this boundary the last one?
if( starts_with( remaining_body, last_separator ) )
{
// Yes, our iteration can be stopped and we can return the result.
swap( tmp_result, result );
return result;
}
}
// We didn't find the last boundary. Or some error encountered in the format
// of the body.
//
// Empty result should be returned.
return result;
}
//
// parsed_part_t
//
struct parsed_part_t
{
http_header_fields_t fields;
string_view_t body;
};
namespace impl
{
namespace parser_details
{
using namespace restinio::http_field_parsers;
namespace easy_parser = restinio::easy_parser;
constexpr char CR = '\r';
constexpr char LF = '\n';
//
// body_producer_t
//
struct body_producer_t
: public easy_parser::impl::producer_tag< string_view_t >
{
RESTINIO_NODISCARD
expected_t< string_view_t, easy_parser::parse_error_t >
try_parse( easy_parser::impl::source_t & from ) const noexcept
{
// Return the whole content from the current position.
return from.fragment( from.current_position() );
}
};
//
// field_value_producer_t
//
struct field_value_producer_t
: public easy_parser::impl::producer_tag< std::string >
{
RESTINIO_NODISCARD
expected_t< std::string, easy_parser::parse_error_t >
try_parse( easy_parser::impl::source_t & from ) const
{
std::string accumulator;
auto ch = from.getch();
while( !ch.m_eof && ch.m_ch != CR && ch.m_ch != LF )
{
accumulator += ch.m_ch;
ch = from.getch();
}
if( ch.m_eof )
return make_unexpected( easy_parser::parse_error_t{
from.current_position(),
easy_parser::error_reason_t::unexpected_eof
} );
// CR or LF symbol should be returned back.
from.putback();
return { std::move(accumulator) };
}
};
} /* namespace parser_details */
//
// make_parser
//
RESTINIO_NODISCARD
auto
make_parser()
{
using namespace parser_details;
return produce< parsed_part_t >(
produce< http_header_fields_t >(
repeat( 0, N,
produce< http_header_field_t >(
token_p() >> to_lower() >> custom_consumer(
[](auto & f, std::string && v) {
f.name(std::move(v));
} ),
symbol(':'),
ows(),
field_value_producer_t{} >> custom_consumer(
[](auto & f, std::string && v) {
f.value(std::move(v));
} ),
symbol(CR), symbol(LF)
) >> custom_consumer(
[](auto & to, http_header_field_t && v) {
to.add_field( std::move(v) );
} )
)
) >> &parsed_part_t::fields,
symbol(CR), symbol(LF),
body_producer_t{} >> &parsed_part_t::body );
}
} /* namespace impl */
//
// try_parse_part
//
RESTINIO_NODISCARD
expected_t< parsed_part_t, restinio::easy_parser::parse_error_t >
try_parse_part( string_view_t part )
{
namespace easy_parser = restinio::easy_parser;
easy_parser::impl::source_t source{ part };
auto actual_producer = impl::make_parser();
return easy_parser::impl::top_level_clause_t< decltype(actual_producer) >{
std::move(actual_producer)
}.try_process( source );
}
//
// handling_result_t
//
enum class handling_result_t
{
continue_enumeration,
stop_enumeration,
terminate_enumeration
};
//
// enumeration_error_t
//
enum class enumeration_error_t
{
content_type_field_not_found,
content_type_field_parse_error,
content_type_field_inappropriate_value,
illegal_boundary_value,
no_parts_found,
terminated_by_handler,
unexpected_error
};
namespace impl
{
namespace boundary_value_checkers
{
// From https://tools.ietf.org/html/rfc1521:
//
// boundary := 0*69<bchars> bcharsnospace
//
// bchars := bcharsnospace / " "
//
// bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" /"_"
// / "," / "-" / "." / "/" / ":" / "=" / "?"
//
RESTINIO_NODISCARD
constexpr bool
is_bcharnospace( char ch )
{
return (ch >= '0' && ch <= '9') // DIGIT
|| ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) // ALPHA
|| ch == '\''
|| ch == '('
|| ch == ')'
|| ch == '+'
|| ch == '_'
|| ch == ','
|| ch == '-'
|| ch == '.'
|| ch == '/'
|| ch == ':'
|| ch == '='
|| ch == '?';
}
RESTINIO_NODISCARD
constexpr bool
is_bchar( char ch )
{
return is_bcharnospace(ch) || ch == ' ';
}
} /* namespace boundary_value_checkers */
} /* namespace impl */
//
// check_boundary_value
//
RESTINIO_NODISCARD
inline optional_t< enumeration_error_t >
check_boundary_value( string_view_t value )
{
using namespace impl::boundary_value_checkers;
if( value.size() >= 1u && value.size() <= 70u )
{
const std::size_t last_index = value.size() - 1u;
for( std::size_t i = 0u; i != last_index; ++i )
if( !is_bchar( value[i] ) )
return enumeration_error_t::illegal_boundary_value;
if( !is_bcharnospace( value[ last_index ] ) )
return enumeration_error_t::illegal_boundary_value;
}
else
return enumeration_error_t::illegal_boundary_value;
return nullopt;
}
//
// detect_boundary_for_multipart_body
//
template< typename Extra_Data >
RESTINIO_NODISCARD
expected_t< std::string, enumeration_error_t >
detect_boundary_for_multipart_body(
const generic_request_t< Extra_Data > & req,
string_view_t expected_media_type,
optional_t< string_view_t > expected_media_subtype )
{
namespace hfp = restinio::http_field_parsers;
using restinio::impl::is_equal_caseless;
// Content-Type header file should be present.
const auto content_type = req.header().opt_value_of(
restinio::http_field::content_type );
if( !content_type )
return make_unexpected(
enumeration_error_t::content_type_field_not_found );
// Content-Type field should successfuly parsed and should
// contain value that correspond to expected media-type.
const auto parse_result = hfp::content_type_value_t::try_parse(
*content_type );
if( !parse_result )
return make_unexpected(
enumeration_error_t::content_type_field_parse_error );
const auto & media_type = parse_result->media_type;
if( !is_equal_caseless( expected_media_type, media_type.type ) )
{
return make_unexpected(
enumeration_error_t::content_type_field_inappropriate_value );
}
if( expected_media_subtype &&
!is_equal_caseless( *expected_media_subtype, media_type.subtype ) )
{
return make_unexpected(
enumeration_error_t::content_type_field_inappropriate_value );
}
// `boundary` param should be present in parsed Content-Type value.
const auto boundary = hfp::find_first(
parse_result->media_type.parameters,
"boundary" );
if( !boundary )
return make_unexpected(
enumeration_error_t::content_type_field_inappropriate_value );
// `boundary` should have valid value.
const auto boundary_check_result = check_boundary_value( *boundary );
if( boundary_check_result )
return make_unexpected( *boundary_check_result );
// Actual value of boundary mark can be created.
std::string actual_boundary_mark;
actual_boundary_mark.reserve( 2 + boundary->size() );
actual_boundary_mark.append( "--" );
actual_boundary_mark.append( boundary->data(), boundary->size() );
return { std::move(actual_boundary_mark) };
}
namespace impl
{
template< typename Handler >
RESTINIO_NODISCARD
expected_t< std::size_t, enumeration_error_t >
enumerate_parts_of_request_body(
const std::vector< string_view_t > & parts,
Handler && handler )
{
std::size_t parts_processed{ 0u };
optional_t< enumeration_error_t > error;
for( auto current_part : parts )
{
// The current part should be parsed to headers and the body.
auto part_parse_result = try_parse_part( current_part );
if( !part_parse_result )
return make_unexpected( enumeration_error_t::unexpected_error );
// NOTE: parsed_part is passed as rvalue reference!
const handling_result_t handler_ret_code = handler(
std::move(*part_parse_result) );
if( handling_result_t::terminate_enumeration != handler_ret_code )
++parts_processed;
else
error = enumeration_error_t::terminated_by_handler;
if( handling_result_t::continue_enumeration != handler_ret_code )
break;
}
if( error )
return make_unexpected( *error );
return parts_processed;
}
//
// valid_handler_type
//
template< typename, typename = restinio::utils::metaprogramming::void_t<> >
struct valid_handler_type : public std::false_type {};
template< typename T >
struct valid_handler_type<
T,
restinio::utils::metaprogramming::void_t<
std::enable_if_t<
std::is_same<
handling_result_t,
decltype(std::declval<T>()(std::declval<parsed_part_t>()))
>::value,
bool
>
>
> : public std::true_type
{};
} /* namespace impl */
//
// enumerate_parts
//
template< typename User_Type, typename Handler >
RESTINIO_NODISCARD
expected_t< std::size_t, enumeration_error_t >
enumerate_parts(
const generic_request_t< User_Type > & req,
Handler && handler,
string_view_t expected_media_type = string_view_t{ "multipart" },
optional_t< string_view_t > expected_media_subtype = nullopt )
{
static_assert(
impl::valid_handler_type< std::decay_t<Handler> >::value,
"Handler should be callable object, "
"should accept parsed_part_t by value, const or rvalue reference, "
"and should return handling_result_t" );
const auto boundary = detect_boundary_for_multipart_body(
req,
expected_media_type,
expected_media_subtype );
if( boundary )
{
const auto parts = split_multipart_body( req.body(), *boundary );
if( parts.empty() )
return make_unexpected(
enumeration_error_t::no_parts_found );
return impl::enumerate_parts_of_request_body(
parts,
std::forward<Handler>(handler) );
}
return make_unexpected( boundary.error() );
}
} /* namespace multipart_body */
} /* namespace restinio */
basics.hpp
Utilities for parsing values of http-fields.
nonstd::optional_lite::optional
class optional
Definition: optional.hpp:932
restinio::easy_parser::impl::source_t
The class that implements "input stream".
Definition: easy_parser.hpp:740
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::impl::source_t::putback
void putback() noexcept
Return one character back to the input stream.
Definition: easy_parser.hpp:777
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::impl::top_level_clause_t
A special class to be used as the top level clause in parser.
Definition: easy_parser.hpp:1533
restinio::easy_parser::impl::top_level_clause_t::try_process
RESTINIO_NODISCARD auto try_process(source_t &from)
Definition: easy_parser.hpp:1546
restinio::easy_parser::parse_error_t
Information about parsing error.
Definition: easy_parser.hpp:93
restinio::generic_request_t
HTTP Request data.
Definition: request_handler.hpp:209
restinio::generic_request_t::header
const http_request_header_t & header() const noexcept
Get request header.
Definition: request_handler.hpp:264
restinio::generic_request_t::body
const std::string & body() const noexcept
Get request body.
Definition: request_handler.hpp:271
restinio::http_header_field_t
A single header field.
Definition: http_headers.hpp:583
restinio::http_header_fields_t
Header fields map.
Definition: http_headers.hpp:703
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:1301
RESTINIO_NODISCARD
#define RESTINIO_NODISCARD
Definition: compiler_features.hpp:36
content-type.hpp
Stuff related to value of Content-Type HTTP-field.
easy_parser.hpp
An very small, simple and somewhat limited implementation of recursive-descent parser.
expected.hpp
http_headers.hpp
metaprogramming.hpp
Various tools for C++ metaprogramming.
restinio::easy_parser
Definition: easy_parser.hpp:39
restinio::easy_parser::error_reason_t::unexpected_eof
@ unexpected_eof
Unexpected end of input is encontered when some character expected.
restinio::http_field_parsers
Definition: accept-charset.hpp:20
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::impl::boundary_value_checkers::is_bcharnospace
RESTINIO_NODISCARD constexpr bool is_bcharnospace(char ch)
Definition: multipart_body.hpp:403
restinio::multipart_body::impl::boundary_value_checkers::is_bchar
RESTINIO_NODISCARD constexpr bool is_bchar(char ch)
Definition: multipart_body.hpp:423
restinio::multipart_body::impl::parser_details::CR
constexpr char CR
Definition: multipart_body.hpp:165
restinio::multipart_body::impl::parser_details::LF
constexpr char LF
Definition: multipart_body.hpp:166
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::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:570
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::multipart_body::handling_result_t::stop_enumeration
@ stop_enumeration
Enumeration of parts should be stopped. All remaining parts of multipart body will be skipped....
restinio::multipart_body::handling_result_t::terminate_enumeration
@ terminate_enumeration
Enumeration of parts should be ignored. All remaining parts of multipart body will be skipped and the...
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::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::enumerate_parts
RESTINIO_NODISCARD expected_t< std::size_t, enumeration_error_t > enumerate_parts(const generic_request_t< User_Type > &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:686
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::multipart_body::enumeration_error_t::content_type_field_inappropriate_value
@ content_type_field_inappropriate_value
Content-Type field value parsed but doesn't contain an appropriate value. For example there can be me...
restinio::multipart_body::enumeration_error_t::no_parts_found
@ no_parts_found
No parts of a multipart body actually found.
restinio::multipart_body::enumeration_error_t::illegal_boundary_value
@ illegal_boundary_value
Value of 'boundary' parameter is invalid (for example it contains some illegal characters).
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...
restinio::multipart_body::enumeration_error_t::terminated_by_handler
@ terminated_by_handler
Enumeration of parts was aborted by user-provided handler. This code is returned when user-provided h...
restinio::multipart_body::enumeration_error_t::content_type_field_parse_error
@ content_type_field_parse_error
Unable to parse Content-Type field value.
restinio::multipart_body::enumeration_error_t::unexpected_error
@ unexpected_error
Some unexpected error encountered during the enumeration.
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::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
restinio::multipart_body::detect_boundary_for_multipart_body
RESTINIO_NODISCARD expected_t< std::string, enumeration_error_t > detect_boundary_for_multipart_body(const generic_request_t< Extra_Data > &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:497
restinio::string_algo
Definition: string_algo.hpp:21
restinio::utils::metaprogramming::void_t
typename make_void< Ts... >::type void_t
Definition: metaprogramming.hpp:28
restinio
Definition: asio_include.hpp:21
restinio::string_view_t
nonstd::string_view string_view_t
Definition: string_view.hpp:19
restinio::expected_t
nonstd::expected< T, E > expected_t
Definition: expected.hpp:22
request_handler.hpp
string_algo.hpp
Various string-related algorithms.
string_caseless_compare.hpp
Helpers for caseless comparison of strings.
restinio::easy_parser::impl::character_t::m_ch
char m_ch
Definition: easy_parser.hpp:595
restinio::easy_parser::impl::producer_tag
A special base class to be used with producers.
Definition: easy_parser.hpp:945
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::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::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::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::multipart_body::impl::valid_handler_type
Definition: multipart_body.hpp:607
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::parsed_part_t::fields
http_header_fields_t fields
HTTP-fields local for that part.
Definition: multipart_body.hpp:150
restinio::multipart_body::parsed_part_t::body
string_view_t body
The body of that part.
Definition: multipart_body.hpp:152