Commit feb47116 authored by octal's avatar octal

Reimplemented mime type parsing using the Stream* classes

parent 0d0d82fd
...@@ -53,14 +53,6 @@ MediaType::fromRaw(const char* str, size_t len) { ...@@ -53,14 +53,6 @@ MediaType::fromRaw(const char* str, size_t len) {
void void
MediaType::parseRaw(const char* str, size_t len) { MediaType::parseRaw(const char* str, size_t len) {
auto eof = [&](const char *p) {
return p - str == len;
};
auto offset = [&](const char* ptr) {
return static_cast<size_t>(ptr - str);
};
auto raise = [&](const char* str) { auto raise = [&](const char* str) {
// TODO: eventually, we should throw a more generic exception // TODO: eventually, we should throw a more generic exception
// that could then be catched in lower stack frames to rethrow // that could then be catched in lower stack frames to rethrow
...@@ -68,17 +60,8 @@ MediaType::parseRaw(const char* str, size_t len) { ...@@ -68,17 +60,8 @@ MediaType::parseRaw(const char* str, size_t len) {
throw HttpError(Http::Code::Unsupported_Media_Type, str); throw HttpError(Http::Code::Unsupported_Media_Type, str);
}; };
// Macro to ensure that we do not overflow when comparing strings StreamBuf buf(str, len);
// The trick here is to use sizeof on a raw string literal of type StreamCursor cursor(buf);
// const char[N] instead of strlen to avoid additional
// runtime computation
#define MAX_SIZE(s) std::min(sizeof(s) - 1, len - offset(p))
// Parse type
const char *p = static_cast<const char *>(memchr(str, '/', len));
if (p == NULL) {
raise("Malformated Media Type");
}
raw_ = string(str, len); raw_ = string(str, len);
...@@ -93,10 +76,10 @@ MediaType::parseRaw(const char* str, size_t len) { ...@@ -93,10 +76,10 @@ MediaType::parseRaw(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 (match_raw(s, sizeof s - 1, cursor)) { \
top = Type::val; \ top = Type::val; \
break; \ break; \
} }
MIME_TYPES MIME_TYPES
#undef TYPE #undef TYPE
...@@ -105,21 +88,25 @@ MediaType::parseRaw(const char* str, size_t len) { ...@@ -105,21 +88,25 @@ MediaType::parseRaw(const char* str, size_t len) {
top_ = top; top_ = top;
if (!match_literal('/', cursor))
raise("Malformed Media Type, expected a '/' after the top type");
if (cursor.eof())
raise("Malformed Media type, missing subtype");
// Parse subtype // Parse subtype
Mime::Subtype sub; Mime::Subtype sub;
++p;
if (eof(p)) raise("Malformed Media Type"); StreamCursor::Token subToken(cursor);
if (memcmp(p, "vnd.", MAX_SIZE("vnd.")) == 0) { if (match_raw("vnd.", 4, cursor)) {
sub = Subtype::Vendor; sub = Subtype::Vendor;
} else { } else {
do { do {
#define SUB_TYPE(val, s) \ #define SUB_TYPE(val, s) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \ if (match_raw(s, sizeof s - 1, cursor)) { \
sub = Subtype::val; \ sub = Subtype::val; \
p += sizeof(s) - 1; \ break; \
break; \
} }
MIME_SUBTYPES MIME_SUBTYPES
#undef SUB_TYPE #undef SUB_TYPE
...@@ -128,29 +115,28 @@ MediaType::parseRaw(const char* str, size_t len) { ...@@ -128,29 +115,28 @@ MediaType::parseRaw(const char* str, size_t len) {
} }
if (sub == Subtype::Ext || sub == Subtype::Vendor) { if (sub == Subtype::Ext || sub == Subtype::Vendor) {
rawSubIndex.beg = offset(p); (void) match_until({ ';', '+' }, cursor);
while (!eof(p) && (*p != ';' && *p != '+')) ++p; rawSubIndex.beg = subToken.start();
rawSubIndex.end = offset(p) - 1; rawSubIndex.end = subToken.end() - 1;
} }
sub_ = sub; sub_ = sub;
if (eof(p)) return; if (cursor.eof()) return;
// Parse suffix // Parse suffix
Mime::Suffix suffix = Suffix::None; Mime::Suffix suffix = Suffix::None;
if (*p == '+') { if (match_literal('+', cursor)) {
++p; if (cursor.eof()) raise("Malformed Media Type, expected suffix, got EOF");
if (eof(p)) raise("Malformed Media Type"); StreamCursor::Token suffixToken(cursor);
do { do {
#define SUFFIX(val, s, _) \ #define SUFFIX(val, s, _) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \ if (match_raw(s, sizeof s - 1, cursor)) { \
suffix = Suffix::val; \ suffix = Suffix::val; \
p += sizeof(s) - 1; \ break; \
break; \
} }
MIME_SUFFIXES MIME_SUFFIXES
#undef SUFFIX #undef SUFFIX
...@@ -158,66 +144,54 @@ MediaType::parseRaw(const char* str, size_t len) { ...@@ -158,66 +144,54 @@ MediaType::parseRaw(const char* str, size_t len) {
} while (0); } while (0);
if (suffix == Suffix::Ext) { if (suffix == Suffix::Ext) {
rawSuffixIndex.beg = offset(p); (void) match_until({ ';', '+' }, cursor);
while (!eof(p) && (*p != ';' && *p != '+')) ++p; rawSuffixIndex.beg = suffixToken.start();
rawSuffixIndex.end = offset(p) - 1; rawSuffixIndex.end = suffixToken.end() - 1;
} }
suffix_ = suffix; suffix_ = suffix;
} }
// Parse parameters // Parse parameters
while (!eof(p)) { while (!cursor.eof()) {
if (*p == ';' || *p == ' ') { if (cursor.current() == ';' || cursor.current() == ' ') {
if (eof(p + 1)) raise("Malformed Media Type"); if (cursor.next() == StreamCursor::Eof)
++p; raise("Malformed Media Type, expected parameter got EOF");
cursor.advance(1);
} }
else if (*p == 'q') { else if (match_literal('q', cursor)) {
++p;
if (eof(p)) { if (cursor.eof()) raise("Invalid quality factor");
raise("Invalid quality factor");
}
if (*p == '=') { if (match_literal('=', cursor)) {
char *end; double val;
double val = strtod(p + 1, &end); if (!match_double(&val, cursor))
if (!eof(end) && *end != ';' && *end != ' ') {
raise("Invalid quality factor"); raise("Invalid quality factor");
}
q_ = Some(Q::fromFloat(val)); q_ = Some(Q::fromFloat(val));
p = end;
} }
else { else {
raise("Invalid quality factor"); raise("Missing quality factor");
} }
} }
else { else {
const char *begin = p; StreamCursor::Token keyToken(cursor);
while (!eof(p) && *p != '=') ++p; (void) match_until('=', cursor);
if (eof(p)) raise("Unfinished Media Type parameter");
const char *end = p;
++p;
if (eof(p)) raise("Unfinished Media Type parameter");
std::string key(begin, end); if (cursor.eof() || cursor.next() == StreamCursor::Eof)
raise("Unfinished Media Type parameter");
begin = p; std::string key = keyToken.text();
while (!eof(p) && *p != ' ' && *p != ';') ++p; cursor.advance(1);
std::string value(begin, p);
params.insert(std::make_pair(std::move(key), std::move(value)));
StreamCursor::Token valueToken(cursor);
(void) match_until({ ' ', ';' }, cursor);
params.insert(std::make_pair(std::move(key), valueToken.text()));
} }
} }
#undef MAX_SIZE
} }
void void
...@@ -275,8 +249,8 @@ MediaType::toString() const { ...@@ -275,8 +249,8 @@ MediaType::toString() const {
} }
}; };
// @Improvement: allocating and concatenating many small strings is probably slow
std::string res; std::string res;
res.reserve(128);
res += topString(top_); res += topString(top_);
res += "/"; res += "/";
res += subString(sub_); res += subString(sub_);
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
*/ */
#include "stream.h" #include "stream.h"
#include <algorithm>
#include <iostream>
void void
BasicStreamBuf::setArea(const char* begin, const char* end, const char* brk) { BasicStreamBuf::setArea(const char* begin, const char* end, const char* brk) {
...@@ -104,3 +106,61 @@ match_raw(const void* buf, size_t len, StreamCursor& cursor) { ...@@ -104,3 +106,61 @@ match_raw(const void* buf, size_t len, StreamCursor& cursor) {
return false; return false;
} }
bool
match_literal(char c, StreamCursor& cursor, CaseSensitivity cs) {
if (cursor.eof())
return false;
char lhs = (cs == CaseSensitivity::Sensitive ? c : std::tolower(c));
char rhs = (cs == CaseSensitivity::Insensitive ? cursor.current() : std::tolower(cursor.current()));
if (lhs == rhs) {
cursor.advance(1);
return true;
}
return false;
}
bool
match_until(char c, StreamCursor& cursor, CaseSensitivity cs) {
return match_until( { c }, cursor, cs);
}
bool
match_until(std::initializer_list<char> chars, StreamCursor& cursor, CaseSensitivity cs) {
if (cursor.eof())
return false;
auto find = [&](char val) {
for (auto c: chars) {
char lhs = cs == CaseSensitivity::Sensitive ? c : std::tolower(c);
char rhs = cs == CaseSensitivity::Insensitive ? val : std::tolower(val);
if (lhs == rhs) return true;
}
return false;
};
while (!cursor.eof()) {
const char c = cursor.current();
if (find(c)) return true;
cursor.advance(1);
}
return false;
}
bool
match_double(double* val, StreamCursor &cursor) {
// @Todo: strtod does not support a length argument
char *end;
*val = strtod(cursor.offset(), &end);
if (end == cursor.offset())
return false;
cursor.advance(static_cast<ptrdiff_t>(end - cursor.offset()));
return true;
}
...@@ -87,6 +87,27 @@ public: ...@@ -87,6 +87,27 @@ public:
static constexpr int Eof = -1; static constexpr int Eof = -1;
struct Token {
Token(StreamCursor& cursor)
: cursor(cursor)
, pos(cursor.value)
{ }
size_t start() const { return pos; }
size_t end() const {
return cursor.value;
}
std::string text() {
const char *beg = cursor.offset(pos);
return std::string(beg, end() - start());
}
private:
StreamCursor& cursor;
size_t pos;
};
struct Revert { struct Revert {
Revert(StreamCursor& cursor) Revert(StreamCursor& cursor)
: cursor(cursor) : cursor(cursor)
...@@ -136,4 +157,12 @@ private: ...@@ -136,4 +157,12 @@ private:
size_t value; size_t value;
}; };
enum class CaseSensitivity {
Sensitive, Insensitive
};
bool match_raw(const void* buf, size_t len, StreamCursor& cursor); bool match_raw(const void* buf, size_t len, StreamCursor& cursor);
bool match_literal(char c, StreamCursor& cursor, CaseSensitivity cs = CaseSensitivity::Insensitive);
bool match_until(char c, StreamCursor& cursor, CaseSensitivity cs = CaseSensitivity::Insensitive);
bool match_until(std::initializer_list<char> chars, StreamCursor& cursor, CaseSensitivity cs = CaseSensitivity::Insensitive);
bool match_double(double* val, StreamCursor& cursor);
...@@ -116,7 +116,6 @@ TEST(mime_test, valid_parsing_test) { ...@@ -116,7 +116,6 @@ TEST(mime_test, valid_parsing_test) {
}); });
} }
TEST(mime_test, invalid_parsing) { TEST(mime_test, invalid_parsing) {
ASSERT_THROW(MediaType::fromString("applicationjson"), HttpError); ASSERT_THROW(MediaType::fromString("applicationjson"), HttpError);
ASSERT_THROW(MediaType::fromString("my/json"), HttpError); ASSERT_THROW(MediaType::fromString("my/json"), 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