diff --git a/src/oro/api_nap.cpp b/src/oro/api_nap.cpp index 3b6e48c..3f9a9d1 100644 --- a/src/oro/api_nap.cpp +++ b/src/oro/api_nap.cpp @@ -18,6 +18,7 @@ #include "private/api_nap.hpp" #include "private/v1_endpoints.hpp" #include "private/dateconv.hpp" +#include "private/mime_split.hpp" #include "duckhandy/int_conv.hpp" #include "api_nap_exception.hpp" #include @@ -98,6 +99,14 @@ void fill_creator_list (const sjd::element& elem, std::vector& out) { } } +std::string_view header_entry (const nap::HttpResponse& resp, std::string_view name) { + for (const auto& head : resp.header_list) { + if (equal(head.first, name)) + return head.second; + } + return {""}; +} + } //unnamed namespace ApiNap::ApiNap ( @@ -265,6 +274,14 @@ ApiOutput ApiNap::fetch_and_parse (const char* endpoint, F&& data_fill, bool if (200 != resp.code) throw ServerError(resp); + { + bool ok; + int parsed_char; + auto mime = tawashi::string_to_mime(header_entry(resp, "content-type"), ok, parsed_char); + if (ok and (not equal(mime.type, "application") or not equal(mime.subtype, "json"))) + throw ContentTypeError(mime.type, mime.subtype); + } + T dataret; { std::unique_lock lock(m_json_mutex); diff --git a/src/oro/api_nap_exception.cpp b/src/oro/api_nap_exception.cpp index 39968c7..df461db 100644 --- a/src/oro/api_nap_exception.cpp +++ b/src/oro/api_nap_exception.cpp @@ -18,8 +18,21 @@ #include "api_nap_exception.hpp" #include "nap/http_response.hpp" #include +#include +#include namespace oro { +namespace { +std::size_t append_to_char_arary (std::string_view in, char* out, std::size_t offs, std::size_t capacity) { + assert(capacity >= offs + 1); + + const std::size_t out_size = std::min(in.size(), capacity - offs - 1); + std::copy(in.begin(), in.begin() + out_size, out + offs); + out[out_size] = '\0'; + + return offs + out_size; +} +} //unnamed namespace ServerError::ServerError (const nap::HttpResponse& resp) : std::runtime_error( @@ -35,4 +48,20 @@ int ServerError::error_code() const noexcept { return m_err_code; } +ContentTypeError::ContentTypeError (std::string_view type, std::string_view subtype) : + std::runtime_error( + std::string("Unexpected content-type received: \"") + + std::string(type) + "/" + std::string(subtype) + "\"" + ) +{ + std::size_t offs = 0; + offs = append_to_char_arary(type, m_content_type.data(), offs, m_content_type.size()); + offs = append_to_char_arary("/", m_content_type.data(), offs, m_content_type.size()); + offs = append_to_char_arary(subtype, m_content_type.data(), offs, m_content_type.size()); +} + +std::string_view ContentTypeError::content_type() const noexcept { + return {m_content_type.data()}; +} + } //namespace oro diff --git a/src/oro/api_nap_exception.hpp b/src/oro/api_nap_exception.hpp index 230bd99..f5eb70b 100644 --- a/src/oro/api_nap_exception.hpp +++ b/src/oro/api_nap_exception.hpp @@ -18,6 +18,8 @@ #pragma once #include +#include +#include namespace nap { struct HttpResponse; @@ -34,4 +36,13 @@ private: int m_err_code; }; +class ContentTypeError : public std::runtime_error { +public: + explicit ContentTypeError (std::string_view type, std::string_view subtype); + std::string_view content_type() const noexcept; + +private: + std::array m_content_type; +}; + } //namespace oro diff --git a/src/oro/private/mime_split.cpp b/src/oro/private/mime_split.cpp index 82865be..e50d934 100644 --- a/src/oro/private/mime_split.cpp +++ b/src/oro/private/mime_split.cpp @@ -81,8 +81,8 @@ BOOST_FUSION_ADAPT_STRUCT( tawashi::SplitMime, - (boost::string_view, type) - (boost::string_view, subtype) + (std::string_view, type) + (std::string_view, subtype) (tawashi::MimeParametersMapType, parameters) ); @@ -90,26 +90,26 @@ namespace tawashi { namespace { template struct MimeGrammar : boost::spirit::qi::grammar { - explicit MimeGrammar (const std::string* parString); + explicit MimeGrammar (std::string_view parString); boost::spirit::qi::rule content_type; boost::spirit::qi::rule media_type; - boost::spirit::qi::rule type; - boost::spirit::qi::rule subtype; + boost::spirit::qi::rule type; + boost::spirit::qi::rule subtype; boost::spirit::qi::rule parameter; - boost::spirit::qi::rule attribute; - boost::spirit::qi::rule value; - boost::spirit::qi::rule quoted_string; - boost::spirit::qi::rule token; - const std::string* m_master_string; + boost::spirit::qi::rule attribute; + boost::spirit::qi::rule value; + boost::spirit::qi::rule quoted_string; + boost::spirit::qi::rule token; + std::string_view m_master_string; Iterator m_begin; }; template - MimeGrammar::MimeGrammar (const std::string* parString) : + MimeGrammar::MimeGrammar (std::string_view parString) : MimeGrammar::base_type(content_type), m_master_string(parString), - m_begin(m_master_string->cbegin()) + m_begin(m_master_string.cbegin()) { namespace px = boost::phoenix; using boost::spirit::ascii::space; @@ -119,7 +119,7 @@ namespace tawashi { using boost::spirit::qi::raw; using boost::spirit::qi::_val; using boost::spirit::qi::lexeme; - using boost::string_view; + using std::string_view; using boost::spirit::_1; content_type = -media_type; @@ -132,7 +132,7 @@ namespace tawashi { token = raw[+(alnum | char_("_.-"))][ _val = px::bind( - &string_view::substr, px::construct(px::ref(*m_master_string)), + &string_view::substr, px::construct(px::ref(m_master_string)), px::begin(_1) - px::ref(m_begin), px::size(_1) ) @@ -144,7 +144,7 @@ namespace tawashi { '"' ] ][_val = px::bind( - &string_view::substr, px::construct(px::ref(*m_master_string)), + &string_view::substr, px::construct(px::ref(m_master_string)), px::begin(_1) + 1 - px::ref(m_begin), px::size(_1) - 2 )]; @@ -161,34 +161,34 @@ namespace tawashi { }; } //unnamed namespace - SplitMime string_to_mime (const std::string* parMime, bool& parParseOk, int& parParsedCharCount) { + SplitMime string_to_mime (std::string_view parMime, bool& parParseOk, int& parParsedCharCount) { using boost::spirit::qi::blank; using boost::spirit::qi::blank_type; - MimeGrammar gramm(parMime); + MimeGrammar gramm(parMime); SplitMime result; parParseOk = false; parParsedCharCount = 0; - std::string::const_iterator start_it = parMime->cbegin(); + std::string_view::const_iterator start_it = parMime.cbegin(); const bool parse_ok = boost::spirit::qi::phrase_parse( start_it, - parMime->cend(), + parMime.cend(), gramm, blank, result ); - parParseOk = parse_ok and (parMime->cend() == start_it); - parParsedCharCount = std::distance(parMime->cbegin(), start_it); + parParseOk = parse_ok and (parMime.cend() == start_it); + parParsedCharCount = std::distance(parMime.cbegin(), start_it); assert(parParsedCharCount >= 0); return result; } std::string mime_to_string (const SplitMime& parMime, bool& parWriteOk) { namespace px = boost::phoenix; - using boost::string_view; + using std::string_view; using boost::spirit::karma::generate; using boost::spirit::karma::char_; using boost::spirit::karma::string; diff --git a/src/oro/private/mime_split.hpp b/src/oro/private/mime_split.hpp index f13e9e2..e4b7549 100644 --- a/src/oro/private/mime_split.hpp +++ b/src/oro/private/mime_split.hpp @@ -17,19 +17,19 @@ #pragma once -#include #include #include +#include namespace tawashi { - typedef boost::container::flat_map MimeParametersMapType; + typedef boost::container::flat_map MimeParametersMapType; struct SplitMime { - boost::string_view type; - boost::string_view subtype; + std::string_view type; + std::string_view subtype; MimeParametersMapType parameters; }; - SplitMime string_to_mime (const std::string* parMime, bool& parParseOk, int& parParsedCharCount); + SplitMime string_to_mime (std::string_view parMime, bool& parParseOk, int& parParsedCharCount); std::string mime_to_string (const SplitMime& parMime, bool& parWriteOk); } //namespace tawashi