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 @@ ...@@ -6,6 +6,7 @@
#include "http_header.h" #include "http_header.h"
#include "common.h" #include "common.h"
#include "http.h"
#include <stdexcept> #include <stdexcept>
#include <iterator> #include <iterator>
#include <cstring> #include <cstring>
...@@ -50,17 +51,33 @@ MediaType ...@@ -50,17 +51,33 @@ MediaType
MediaType::fromRaw(const char* str, size_t len) { MediaType::fromRaw(const char* str, size_t len) {
MediaType res; 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) { auto eof = [&](const char *p) {
return p - str == len; 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 // Parse type
const char *p = strchr(str, '/'); const char *p = strchr(str, '/');
if (p == NULL) return res; if (p == NULL) {
raise("Malformated Media Type");
}
raw_ = string(str, len);
Mime::Type top; Mime::Type top;
...@@ -73,27 +90,26 @@ MediaType::fromRaw(const char* str, size_t len) { ...@@ -73,27 +90,26 @@ MediaType::fromRaw(const char* str, size_t len) {
// //
// Watch out, this pattern is repeated throughout the function // Watch out, this pattern is repeated throughout the function
do { do {
#define TYPE(val, s) \ #define TYPE(val, s) \
if (memcmp(str, s, MAX_SIZE(s)) == 0) { \ if (memcmp(str, s, MAX_SIZE(s)) == 0) { \
top = Type::val; \ top = Type::val; \
break; \ break; \
} }
MIME_TYPES MIME_TYPES
#undef TYPE #undef TYPE
top = Type::Ext; throw HttpError(Http::Code::Unsupported_Media_Type, "Unknown Media Type");
} while (0); } while (0);
res.top_ = top; top_ = top;
if (top == Type::Ext) return res;
// Parse subtype // Parse subtype
Mime::Subtype sub; Mime::Subtype sub;
++p; ++p;
if (eof(p)) raise("Malformed Media Type");
if (memcmp(p, "vnd.", 4) == 0) { if (memcmp(p, "vnd.", 4) == 0) {
sub = Subtype::Vendor; sub = Subtype::Vendor;
while (!eof(p) && (*p != ';' && *p != '+')) ++p;
} else { } else {
do { do {
#define SUB_TYPE(val, s) \ #define SUB_TYPE(val, s) \
...@@ -108,13 +124,24 @@ MediaType::fromRaw(const char* str, size_t len) { ...@@ -108,13 +124,24 @@ MediaType::fromRaw(const char* str, size_t len) {
} while (0); } 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 // Parse suffix
Mime::Suffix suffix = Suffix::None; Mime::Suffix suffix = Suffix::None;
if (*p == '+') { if (*p == '+') {
++p; ++p;
if (eof(p)) raise("Malformed Media Type");
do { do {
#define SUFFIX(val, s, _) \ #define SUFFIX(val, s, _) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \ if (memcmp(p, s, MAX_SIZE(s)) == 0) { \
...@@ -127,33 +154,55 @@ MediaType::fromRaw(const char* str, size_t len) { ...@@ -127,33 +154,55 @@ MediaType::fromRaw(const char* str, size_t len) {
suffix = Suffix::Ext; suffix = Suffix::Ext;
} while (0); } 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 (*p == ';') ++p;
if (eof(p)) {
raise("Malformed Media Type");
}
while (*p == ' ') ++p; while (*p == ' ') ++p;
if (eof(p)) {
raise("Malformed Media Type");
};
Optional<Q> q = None(); Optional<Q> q = None();
if (*p == 'q') { if (*p == 'q') {
++p; ++p;
if (eof(p)) {
raise("Invalid quality factor");
}
if (*p == '=') { if (*p == '=') {
char *end; char *end;
double val = strtod(p + 1, &end); double val = strtod(p + 1, &end);
if (!eof(end) && *end != ';' && *end != ' ') {
raise("Invalid quality factor");
}
q = Some(Q::fromFloat(val)); q = Some(Q::fromFloat(val));
} }
else {
raise("Invalid quality factor");
}
} }
res.q_ = std::move(q); q_ = std::move(q);
#undef MAX_SIZE #undef MAX_SIZE
return res;
} }
void void
...@@ -164,6 +213,8 @@ MediaType::setQuality(Q quality) { ...@@ -164,6 +213,8 @@ MediaType::setQuality(Q quality) {
std::string std::string
MediaType::toString() const { MediaType::toString() const {
if (!raw_.empty()) return raw_;
auto topString = [](Mime::Type top) -> const char * { auto topString = [](Mime::Type top) -> const char * {
switch (top) { switch (top) {
#define TYPE(val, str) \ #define TYPE(val, str) \
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <ostream> #include <ostream>
#include <vector> #include <vector>
#include <stdexcept> #include <stdexcept>
#include <cassert>
#define SAFE_HEADER_CAST #define SAFE_HEADER_CAST
...@@ -106,7 +107,7 @@ enum class Type { ...@@ -106,7 +107,7 @@ enum class Type {
#define TYPE(val, _) val, #define TYPE(val, _) val,
MIME_TYPES MIME_TYPES
#undef TYPE #undef TYPE
Ext None
}; };
enum class Subtype { enum class Subtype {
...@@ -114,7 +115,8 @@ enum class Subtype { ...@@ -114,7 +115,8 @@ enum class Subtype {
MIME_SUBTYPES MIME_SUBTYPES
#undef SUB_TYPE #undef SUB_TYPE
Vendor, Vendor,
Ext Ext,
None
}; };
enum class Suffix { enum class Suffix {
...@@ -162,18 +164,26 @@ inline bool operator==(Q lhs, Q rhs) { ...@@ -162,18 +164,26 @@ inline bool operator==(Q lhs, Q rhs) {
// 3.7 Media Types // 3.7 Media Types
class MediaType { class MediaType {
public: public:
enum Parse { DoParse, DontParse };
MediaType() MediaType()
: top_(Type::Ext) : top_(Type::None)
, sub_(Subtype::Ext) , sub_(Subtype::None)
, suffix_(Suffix::None) , suffix_(Suffix::None)
{ } { }
MediaType(std::string raw) MediaType(std::string raw, Parse parse = DontParse)
: top_(Type::Ext) : top_(Type::None)
, sub_(Subtype::Ext) , sub_(Subtype::None)
, suffix_(Suffix::Ext) , suffix_(Suffix::None)
, raw_(std::move(raw)) {
{ } if (parse == DoParse) {
parseRaw(raw.c_str(), raw.length());
}
else {
raw_ = std::move(raw);
}
}
MediaType(Mime::Type top, Mime::Subtype sub) MediaType(Mime::Type top, Mime::Subtype sub)
: top_(top) : top_(top)
...@@ -197,6 +207,10 @@ public: ...@@ -197,6 +207,10 @@ public:
Mime::Subtype sub() const { return sub_; } Mime::Subtype sub() const { return sub_; }
Mime::Suffix suffix() const { return suffix_; } Mime::Suffix suffix() const { return suffix_; }
std::string rawSub() const {
return rawSubIndex.splice(raw_);
}
std::string raw() const { return raw_; } std::string raw() const { return raw_; }
const Optional<Q>& q() const { return q_; } const Optional<Q>& q() const { return q_; }
...@@ -204,11 +218,31 @@ public: ...@@ -204,11 +218,31 @@ public:
std::string toString() const; std::string toString() const;
private: private:
void parseRaw(const char* str, size_t len);
Mime::Type top_; Mime::Type top_;
Mime::Subtype sub_; Mime::Subtype sub_;
Mime::Suffix suffix_; 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_; 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_; Optional<Q> q_;
}; };
......
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "http_header.h" #include "http_header.h"
#include "http.h"
using namespace Net::Http;
using namespace Net::Http::Mime; using namespace Net::Http::Mime;
TEST(mime_test, basic_test) { TEST(mime_test, basic_test) {
...@@ -54,9 +56,40 @@ TEST(mime_test, valid_parsing_test) { ...@@ -54,9 +56,40 @@ TEST(mime_test, valid_parsing_test) {
ASSERT_EQ(m6.top(), Type::Application); ASSERT_EQ(m6.top(), Type::Application);
ASSERT_EQ(m6.sub(), Subtype::Vendor); ASSERT_EQ(m6.sub(), Subtype::Vendor);
ASSERT_EQ(m6.suffix(), Suffix::None); ASSERT_EQ(m6.suffix(), Suffix::None);
ASSERT_EQ(m6.rawSub(), "vnd.adobe.flash-movie");
auto m7 = MediaType::fromString("application/vnd.mycompany.myapp-v2+json"); auto m7 = MediaType::fromString("application/vnd.mycompany.myapp-v2+json");
ASSERT_EQ(m7.top(), Type::Application); ASSERT_EQ(m7.top(), Type::Application);
ASSERT_EQ(m7.sub(), Subtype::Vendor); ASSERT_EQ(m7.sub(), Subtype::Vendor);
ASSERT_EQ(m7.suffix(), Suffix::Json); 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