From c40b555241aad59af4f65fa19f76c2124d7af85d Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sun, 13 May 2018 15:25:17 +0100 Subject: Move Accept header parsing into a testable place and test it --- icespider/common/http.ice | 2 + icespider/core/ihttpRequest.cpp | 53 +++++++++++---------- icespider/core/ihttpRequest.h | 1 + icespider/unittests/Jamfile.jam | 13 ++++++ icespider/unittests/testAccept.cpp | 94 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 23 deletions(-) create mode 100644 icespider/unittests/testAccept.cpp diff --git a/icespider/common/http.ice b/icespider/common/http.ice index 1b51da7..d535ee2 100644 --- a/icespider/common/http.ice +++ b/icespider/common/http.ice @@ -30,6 +30,8 @@ module IceSpider { optional(1) string type; float q = 1.0; }; + ["slicer:ignore"] + local sequence Accepted; ["slicer:json:object", "cpp:type:std::map>"] diff --git a/icespider/core/ihttpRequest.cpp b/icespider/core/ihttpRequest.cpp index 7df6084..21506e5 100644 --- a/icespider/core/ihttpRequest.cpp +++ b/icespider/core/ihttpRequest.cpp @@ -33,34 +33,41 @@ namespace IceSpider { } } + Accepted + IHttpRequest::parseAccept(const std::string & acceptHdr) + { + auto accept = acceptHdr.c_str(); + Accepted accepts; + accepts.reserve(5); + char grp[BUFSIZ], type[BUFSIZ]; + float pri = 0.0f; + int chars, v; + while ((v = sscanf(accept, " %[^ /] / %[^ ;,] %n , %n", grp, type, &chars, &chars)) == 2) { + accept += chars; + chars = 0; + auto a = std::make_shared(); + if ((v = sscanf(accept, " ; q = %f %n , %n", &pri, &chars, &chars)) == 1) { + a->q = pri; + } + if (strcmp(grp, "*")) { + a->group.emplace(grp); + } + if (strcmp(type, "*")) { + a->type.emplace(type); + } + accept += chars; + accepts.push_back(a); + } + std::stable_sort(accepts.begin(), accepts.end(), [](const auto & a, const auto & b) { return a->q > b->q; }); + return accepts; + } + ContentTypeSerializer IHttpRequest::getSerializer(const IRouteHandler * handler) const { auto acceptHdr = getHeaderParam(H::ACCEPT); if (acceptHdr) { - auto accept = acceptHdr->c_str(); - std::vector accepts; - accepts.reserve(5); - char grp[BUFSIZ], type[BUFSIZ]; - float pri = 0.0f; - int chars, v; - while ((v = sscanf(accept, " %[^ /] / %[^ ;,] %n , %n", grp, type, &chars, &chars)) == 2) { - accept += chars; - chars = 0; - auto a = std::make_shared(); - if ((v = sscanf(accept, " ; q = %f %n , %n", &pri, &chars, &chars)) == 1) { - a->q = pri; - } - if (strcmp(grp, "*")) { - a->group.emplace(grp); - } - if (strcmp(type, "*")) { - a->type.emplace(type); - } - accept += chars; - accepts.push_back(a); - } - std::stable_sort(accepts.begin(), accepts.end(), [](const auto & a, const auto & b) { return a->q > b->q; }); + auto accepts = parseAccept(*acceptHdr); auto & strm = getOutputStream(); for(auto & a : accepts) { ContentTypeSerializer serializer = handler->getSerializer(a, strm); diff --git a/icespider/core/ihttpRequest.h b/icespider/core/ihttpRequest.h index 6e3be55..be3a0e0 100644 --- a/icespider/core/ihttpRequest.h +++ b/icespider/core/ihttpRequest.h @@ -32,6 +32,7 @@ namespace IceSpider { virtual OptionalString getHeaderParam(const std::string_view &) const = 0; virtual OptionalString getCookieParam(const std::string_view &) const = 0; virtual OptionalString getEnv(const std::string_view &) const = 0; + static Accepted parseAccept(const std::string &); virtual Slicer::DeserializerPtr getDeserializer() const; virtual ContentTypeSerializer getSerializer(const IRouteHandler *) const; virtual std::istream & getInputStream() const = 0; diff --git a/icespider/unittests/Jamfile.jam b/icespider/unittests/Jamfile.jam index aa8d879..43e3d82 100644 --- a/icespider/unittests/Jamfile.jam +++ b/icespider/unittests/Jamfile.jam @@ -58,6 +58,19 @@ run : testCompile : ; +run + testAccept.cpp + : : : + BOOST_TEST_DYN_LINK + testCommon + adhocutil + ../common//icespider-common + ../core//icespider-core + ../core//icespider-core + ../testing//icespider-testing + ../common//icespider-common + ; + run testApp.cpp base2.cpp diff --git a/icespider/unittests/testAccept.cpp b/icespider/unittests/testAccept.cpp new file mode 100644 index 0000000..fb5705b --- /dev/null +++ b/icespider/unittests/testAccept.cpp @@ -0,0 +1,94 @@ +#define BOOST_TEST_MODULE TestAccept +#include +#include + +#include + +auto parse = IceSpider::IHttpRequest::parseAccept; +using namespace boost::unit_test::data; + +namespace std { + ostream & operator<<(ostream & s, const IceSpider::Accept & a) + { + s << (a.group ? *a.group : "*") + << "/" + << (a.type ? *a.type : "*") + << ";q=" << a.q; + return s; + } +} + +BOOST_DATA_TEST_CASE(empty, make({ + "", + " ", +}), a) +{ + BOOST_REQUIRE(parse(a).empty()); +} + +BOOST_DATA_TEST_CASE(texthtml, make({ + "text/html", + "image/png;q=0, text/html", + "image/png;q=1.0, text/html;q=1.1", + "image/png;q=1.0, text/html;q=1.1, something/else;q=1.1", +}), a) +{ + auto front = parse(a).front(); + BOOST_REQUIRE(front->group); + BOOST_REQUIRE_EQUAL(*front->group, "text"); + BOOST_REQUIRE(front->type); + BOOST_REQUIRE_EQUAL(*front->type, "html"); +} + +BOOST_DATA_TEST_CASE(textany, make({ + "text/*", + "image/png;q=0, text/*", + "image/png;q=1.0, text/*;q=1.1", + "image/png;q=1.0, text/*;q=1.1, something/else;q=1.1", +}), a) +{ + auto front = parse(a).front(); + BOOST_REQUIRE(front->group); + BOOST_REQUIRE_EQUAL(*front->group, "text"); + BOOST_REQUIRE(!front->type); +} + +BOOST_DATA_TEST_CASE(anyhtml, make({ + "*/html", + "image/png;q=0, */html", + "image/png;q=1.0, */html;q=1.1", + "image/png;q=1.0, */html;q=1.1, something/else;q=1.1", +}), a) +{ + auto front = parse(a).front(); + BOOST_REQUIRE(!front->group); + BOOST_REQUIRE(front->type); + BOOST_REQUIRE_EQUAL(*front->type, "html"); +} + +BOOST_DATA_TEST_CASE(anyany, make({ + "*/*", + "image/png;q=0, */*", + "image/png;q=1.0, */*;q=1.1", + "image/png;q=1.0, */*;q=1.1, something/else;q=1.1", +}), a) +{ + auto front = parse(a).front(); + BOOST_REQUIRE(!front->group); + BOOST_REQUIRE(!front->type); +} + +BOOST_DATA_TEST_CASE(q1, make({ + "*/*", + "image/png, */*", + "image/png;q=1.0, */*", +}), a) +{ + auto all = parse(a); + for(const auto & accept : all) { + BOOST_TEST_CONTEXT(*accept) { + BOOST_CHECK_CLOSE(accept->q, 1.0, 0.1); + } + } +} + -- cgit v1.2.3