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) {
void
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) {
// TODO: eventually, we should throw a more generic exception
// that could then be catched in lower stack frames to rethrow
......@@ -68,17 +60,8 @@ MediaType::parseRaw(const char* str, size_t len) {
throw HttpError(Http::Code::Unsupported_Media_Type, str);
};
// Macro to ensure that we do not overflow when comparing strings
// The trick here is to use sizeof on a raw string literal of type
// 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");
}
StreamBuf buf(str, len);
StreamCursor cursor(buf);
raw_ = string(str, len);
......@@ -94,7 +77,7 @@ MediaType::parseRaw(const char* str, size_t len) {
// Watch out, this pattern is repeated throughout the function
do {
#define TYPE(val, s) \
if (memcmp(str, s, MAX_SIZE(s)) == 0) { \
if (match_raw(s, sizeof s - 1, cursor)) { \
top = Type::val; \
break; \
}
......@@ -105,20 +88,24 @@ MediaType::parseRaw(const char* str, size_t len) {
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
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;
} else {
do {
#define SUB_TYPE(val, s) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \
if (match_raw(s, sizeof s - 1, cursor)) { \
sub = Subtype::val; \
p += sizeof(s) - 1; \
break; \
}
MIME_SUBTYPES
......@@ -128,28 +115,27 @@ MediaType::parseRaw(const char* str, size_t len) {
}
if (sub == Subtype::Ext || sub == Subtype::Vendor) {
rawSubIndex.beg = offset(p);
while (!eof(p) && (*p != ';' && *p != '+')) ++p;
rawSubIndex.end = offset(p) - 1;
(void) match_until({ ';', '+' }, cursor);
rawSubIndex.beg = subToken.start();
rawSubIndex.end = subToken.end() - 1;
}
sub_ = sub;
if (eof(p)) return;
if (cursor.eof()) return;
// Parse suffix
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 {
#define SUFFIX(val, s, _) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \
if (match_raw(s, sizeof s - 1, cursor)) { \
suffix = Suffix::val; \
p += sizeof(s) - 1; \
break; \
}
MIME_SUFFIXES
......@@ -158,66 +144,54 @@ MediaType::parseRaw(const char* str, size_t len) {
} while (0);
if (suffix == Suffix::Ext) {
rawSuffixIndex.beg = offset(p);
while (!eof(p) && (*p != ';' && *p != '+')) ++p;
rawSuffixIndex.end = offset(p) - 1;
(void) match_until({ ';', '+' }, cursor);
rawSuffixIndex.beg = suffixToken.start();
rawSuffixIndex.end = suffixToken.end() - 1;
}
suffix_ = suffix;
}
// Parse parameters
while (!eof(p)) {
while (!cursor.eof()) {
if (*p == ';' || *p == ' ') {
if (eof(p + 1)) raise("Malformed Media Type");
++p;
if (cursor.current() == ';' || cursor.current() == ' ') {
if (cursor.next() == StreamCursor::Eof)
raise("Malformed Media Type, expected parameter got EOF");
cursor.advance(1);
}
else if (*p == 'q') {
++p;
else if (match_literal('q', cursor)) {
if (eof(p)) {
raise("Invalid quality factor");
}
if (cursor.eof()) raise("Invalid quality factor");
if (*p == '=') {
char *end;
double val = strtod(p + 1, &end);
if (!eof(end) && *end != ';' && *end != ' ') {
if (match_literal('=', cursor)) {
double val;
if (!match_double(&val, cursor))
raise("Invalid quality factor");
}
q_ = Some(Q::fromFloat(val));
p = end;
}
else {
raise("Invalid quality factor");
raise("Missing quality factor");
}
}
else {
const char *begin = p;
while (!eof(p) && *p != '=') ++p;
StreamCursor::Token keyToken(cursor);
(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");
if (cursor.eof() || cursor.next() == StreamCursor::Eof)
raise("Unfinished Media Type parameter");
std::string key(begin, end);
begin = p;
while (!eof(p) && *p != ' ' && *p != ';') ++p;
std::string value(begin, p);
params.insert(std::make_pair(std::move(key), std::move(value)));
std::string key = keyToken.text();
cursor.advance(1);
StreamCursor::Token valueToken(cursor);
(void) match_until({ ' ', ';' }, cursor);
params.insert(std::make_pair(std::move(key), valueToken.text()));
}
}
#undef MAX_SIZE
}
void
......@@ -275,8 +249,8 @@ MediaType::toString() const {
}
};
// @Improvement: allocating and concatenating many small strings is probably slow
std::string res;
res.reserve(128);
res += topString(top_);
res += "/";
res += subString(sub_);
......
......@@ -4,6 +4,8 @@
*/
#include "stream.h"
#include <algorithm>
#include <iostream>
void
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) {
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:
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 {
Revert(StreamCursor& cursor)
: cursor(cursor)
......@@ -136,4 +157,12 @@ private:
size_t value;
};
enum class CaseSensitivity {
Sensitive, Insensitive
};
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) {
});
}
TEST(mime_test, invalid_parsing) {
ASSERT_THROW(MediaType::fromString("applicationjson"), 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