Commit 326dcc29 authored by Mathieu Stefani's avatar Mathieu Stefani

Extended the MimeType support to throw an error when parsing an invalid string

parent 8cf7501d
......@@ -6,6 +6,7 @@
#include "http_header.h"
#include "common.h"
#include "http.h"
#include <stdexcept>
#include <iterator>
#include <cstring>
......@@ -50,17 +51,33 @@ MediaType
MediaType::fromRaw(const char* str, size_t len) {
MediaType res;
// HUGE @Todo: Validate the input when parsing to avoid overflow
res.parseRaw(str, len);
return res;
}
void
MediaType::parseRaw(const char* str, size_t len) {
auto eof = [&](const char *p) {
return p - str == len;
};
#define MAX_SIZE(s) std::min(sizeof(s) - 1, static_cast<size_t>(p - str))
auto offset = [&](const char* ptr) {
return static_cast<size_t>(ptr - str);
};
auto raise = [&](const char* str) {
throw HttpError(Http::Code::Unsupported_Media_Type, str);
};
#define MAX_SIZE(s) std::min(sizeof(s) - 1, offset(p))
// Parse type
const char *p = strchr(str, '/');
if (p == NULL) return res;
if (p == NULL) {
raise("Malformated Media Type");
}
raw_ = string(str, len);
Mime::Type top;
......@@ -73,27 +90,26 @@ MediaType::fromRaw(const char* str, size_t len) {
//
// Watch out, this pattern is repeated throughout the function
do {
#define TYPE(val, s) \
#define TYPE(val, s) \
if (memcmp(str, s, MAX_SIZE(s)) == 0) { \
top = Type::val; \
break; \
top = Type::val; \
break; \
}
MIME_TYPES
#undef TYPE
top = Type::Ext;
throw HttpError(Http::Code::Unsupported_Media_Type, "Unknown Media Type");
} while (0);
res.top_ = top;
if (top == Type::Ext) return res;
top_ = top;
// Parse subtype
Mime::Subtype sub;
++p;
if (eof(p)) raise("Malformed Media Type");
if (memcmp(p, "vnd.", 4) == 0) {
sub = Subtype::Vendor;
while (!eof(p) && (*p != ';' && *p != '+')) ++p;
} else {
do {
#define SUB_TYPE(val, s) \
......@@ -108,13 +124,24 @@ MediaType::fromRaw(const char* str, size_t len) {
} while (0);
}
res.sub_ = sub;
if (sub == Subtype::Ext || sub == Subtype::Vendor) {
rawSubIndex.beg = offset(p);
while (!eof(p) && (*p != ';' && *p != '+')) ++p;
rawSubIndex.end = offset(p) - 1;
}
sub_ = sub;
if (eof(p)) return;
// Parse suffix
Mime::Suffix suffix = Suffix::None;
if (*p == '+') {
++p;
if (eof(p)) raise("Malformed Media Type");
do {
#define SUFFIX(val, s, _) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \
......@@ -127,33 +154,55 @@ MediaType::fromRaw(const char* str, size_t len) {
suffix = Suffix::Ext;
} while (0);
res.suffix_ = suffix;
if (suffix == Suffix::Ext) {
rawSuffixIndex.beg = offset(p);
while (!eof(p) && (*p != ';' && *p != '+')) ++p;
rawSuffixIndex.end = offset(p) - 1;
}
suffix_ = suffix;
}
if (eof(p)) return res;
if (eof(p)) return;
if (*p == ';') ++p;
if (eof(p)) {
raise("Malformed Media Type");
}
while (*p == ' ') ++p;
if (eof(p)) {
raise("Malformed Media Type");
};
Optional<Q> q = None();
if (*p == 'q') {
++p;
if (eof(p)) {
raise("Invalid quality factor");
}
if (*p == '=') {
char *end;
double val = strtod(p + 1, &end);
if (!eof(end) && *end != ';' && *end != ' ') {
raise("Invalid quality factor");
}
q = Some(Q::fromFloat(val));
}
else {
raise("Invalid quality factor");
}
}
res.q_ = std::move(q);
q_ = std::move(q);
#undef MAX_SIZE
return res;
}
void
......@@ -164,6 +213,8 @@ MediaType::setQuality(Q quality) {
std::string
MediaType::toString() const {
if (!raw_.empty()) return raw_;
auto topString = [](Mime::Type top) -> const char * {
switch (top) {
#define TYPE(val, str) \
......
......@@ -13,6 +13,7 @@
#include <ostream>
#include <vector>
#include <stdexcept>
#include <cassert>
#define SAFE_HEADER_CAST
......@@ -106,7 +107,7 @@ enum class Type {
#define TYPE(val, _) val,
MIME_TYPES
#undef TYPE
Ext
None
};
enum class Subtype {
......@@ -114,7 +115,8 @@ enum class Subtype {
MIME_SUBTYPES
#undef SUB_TYPE
Vendor,
Ext
Ext,
None
};
enum class Suffix {
......@@ -162,18 +164,26 @@ inline bool operator==(Q lhs, Q rhs) {
// 3.7 Media Types
class MediaType {
public:
enum Parse { DoParse, DontParse };
MediaType()
: top_(Type::Ext)
, sub_(Subtype::Ext)
: top_(Type::None)
, sub_(Subtype::None)
, suffix_(Suffix::None)
{ }
MediaType(std::string raw)
: top_(Type::Ext)
, sub_(Subtype::Ext)
, suffix_(Suffix::Ext)
, raw_(std::move(raw))
{ }
MediaType(std::string raw, Parse parse = DontParse)
: top_(Type::None)
, sub_(Subtype::None)
, suffix_(Suffix::None)
{
if (parse == DoParse) {
parseRaw(raw.c_str(), raw.length());
}
else {
raw_ = std::move(raw);
}
}
MediaType(Mime::Type top, Mime::Subtype sub)
: top_(top)
......@@ -197,6 +207,10 @@ public:
Mime::Subtype sub() const { return sub_; }
Mime::Suffix suffix() const { return suffix_; }
std::string rawSub() const {
return rawSubIndex.splice(raw_);
}
std::string raw() const { return raw_; }
const Optional<Q>& q() const { return q_; }
......@@ -204,11 +218,31 @@ public:
std::string toString() const;
private:
void parseRaw(const char* str, size_t len);
Mime::Type top_;
Mime::Subtype sub_;
Mime::Suffix suffix_;
/* Let's save some extra memory allocations by only storing the
raw MediaType along with indexes of the relevant parts
Note: experimental for now as it might not be a good idea
*/
std::string raw_;
struct Index {
size_t beg;
size_t end;
std::string splice(const std::string& str) const {
assert(end >= beg);
return str.substr(beg, end - beg + 1);
}
};
Index rawSubIndex;
Index rawSuffixIndex;
Optional<Q> q_;
};
......
#include "gtest/gtest.h"
#include "http_header.h"
#include "http.h"
using namespace Net::Http;
using namespace Net::Http::Mime;
TEST(mime_test, basic_test) {
......@@ -54,9 +56,40 @@ TEST(mime_test, valid_parsing_test) {
ASSERT_EQ(m6.top(), Type::Application);
ASSERT_EQ(m6.sub(), Subtype::Vendor);
ASSERT_EQ(m6.suffix(), Suffix::None);
ASSERT_EQ(m6.rawSub(), "vnd.adobe.flash-movie");
auto m7 = MediaType::fromString("application/vnd.mycompany.myapp-v2+json");
ASSERT_EQ(m7.top(), Type::Application);
ASSERT_EQ(m7.sub(), Subtype::Vendor);
ASSERT_EQ(m7.suffix(), Suffix::Json);
ASSERT_EQ(m7.rawSub(), "vnd.mycompany.myapp-v2");
auto m8 = MediaType::fromString("application/x-myapp-v1+json");
ASSERT_EQ(m8.top(), Type::Application);
ASSERT_EQ(m8.sub(), Subtype::Ext);
ASSERT_EQ(m8.suffix(), Suffix::Json);
ASSERT_EQ(m8.rawSub(), "x-myapp-v1");
auto m9 = MediaType::fromString("audio/x-my-codec");
ASSERT_EQ(m9.top(), Type::Audio);
ASSERT_EQ(m9.sub(), Subtype::Ext);
ASSERT_EQ(m9.suffix(), Suffix::None);
ASSERT_EQ(m9.rawSub(), "x-my-codec");
}
TEST(mime_test, invalid_parsing) {
ASSERT_THROW(MediaType::fromString("applicationjson"), HttpError);
ASSERT_THROW(MediaType::fromString("my/json"), HttpError);
ASSERT_THROW(MediaType::fromString("text/"), HttpError);
ASSERT_THROW(MediaType::fromString("text/plain+"), HttpError);
ASSERT_THROW(MediaType::fromString("video/mp4;"), HttpError);
ASSERT_THROW(MediaType::fromString("image/png; "), HttpError);
ASSERT_THROW(MediaType::fromString("text/plain; q"), HttpError);
ASSERT_THROW(MediaType::fromString("text/plain; q"), HttpError);
ASSERT_THROW(MediaType::fromString("application/xhtml+xml; q=a0.2"), HttpError);
ASSERT_THROW(MediaType::fromString("application/xhtml+xml; q=0.2b"), HttpError);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment