Commit 5465db8b authored by Mathieu STEFANI's avatar Mathieu STEFANI

MediaType now parses extra parameters

parent cf87c78c
......@@ -170,43 +170,55 @@ MediaType::parseRaw(const char* str, size_t len) {
suffix_ = suffix;
}
if (eof(p)) return;
if (*p == ';') ++p;
if (eof(p)) {
raise("Malformed Media Type");
}
// Parse parameters
while (!eof(p)) {
while (*p == ' ') ++p;
if (eof(p)) {
raise("Malformed Media Type");
};
Optional<Q> q = None();
if (*p == ';' || *p == ' ') {
if (eof(p + 1)) raise("Malformed Media Type");
++p;
}
if (*p == 'q') {
++p;
else if (*p == 'q') {
++p;
if (eof(p)) {
raise("Invalid quality factor");
}
if (eof(p)) {
raise("Invalid quality factor");
}
if (*p == '=') {
char *end;
double val = strtod(p + 1, &end);
if (!eof(end) && *end != ';' && *end != ' ') {
if (*p == '=') {
char *end;
double val = strtod(p + 1, &end);
if (!eof(end) && *end != ';' && *end != ' ') {
raise("Invalid quality factor");
}
q_ = Some(Q::fromFloat(val));
p = end;
}
else {
raise("Invalid quality factor");
}
q = Some(Q::fromFloat(val));
}
else {
raise("Invalid quality factor");
const char *begin = p;
while (!eof(p) && *p != '=') ++p;
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);
begin = p;
while (!eof(p) && *p != ' ' && *p != ';') ++p;
std::string value(begin, p);
params.insert(std::make_pair(std::move(key), std::move(value)));
}
}
q_ = std::move(q);
}
#undef MAX_SIZE
......@@ -217,6 +229,21 @@ MediaType::setQuality(Q quality) {
q_ = Some(quality);
}
Optional<std::string>
MediaType::getParam(std::string name) const {
auto it = params.find(name);
if (it == std::end(params)) {
return None();
}
return Some(it->second);
}
void
MediaType::setParam(std::string name, std::string value) {
params[name] = value;
}
std::string
MediaType::toString() const {
......@@ -252,6 +279,7 @@ MediaType::toString() const {
}
};
// @Improvement: allocating and concatenating many small strings is probably slow
std::string res;
res += topString(top_);
res += "/";
......@@ -265,6 +293,11 @@ MediaType::toString() const {
res += quality.toString();
});
for (const auto& param: params) {
res += "; ";
res += param.first + "=" + param.second;
}
return res;
}
......
......@@ -14,6 +14,7 @@
#include <vector>
#include <stdexcept>
#include <cassert>
#include <unordered_map>
#define SAFE_HEADER_CAST
......@@ -216,6 +217,9 @@ public:
const Optional<Q>& q() const { return q_; }
void setQuality(Q quality);
Optional<std::string> getParam(std::string name) const;
void setParam(std::string name, std::string value);
std::string toString() const;
private:
void parseRaw(const char* str, size_t len);
......@@ -243,6 +247,8 @@ private:
Index rawSubIndex;
Index rawSuffixIndex;
std::unordered_map<std::string, std::string> params;
Optional<Q> q_;
};
......
......@@ -28,53 +28,92 @@ TEST(mime_test, basic_test) {
m4.setQuality(Q::fromFloat(0.79));
ASSERT_EQ(m4.toString(), "application/json+zip; q=0.79");
auto m5 = MIME(Text, Html);
m5.setQuality(Q::fromFloat(1.0));
m5.setParam("charset", "utf-8");
ASSERT_EQ(m5.toString(), "text/html; q=1; charset=utf-8");
}
void parse(const char* str, std::function<void (const MediaType&)> testFunc)
{
auto m1 = MediaType::fromString(std::string(str));
testFunc(m1);
auto m2 = MediaType::fromRaw(str, strlen(str));
testFunc(m2);
MediaType m3(str, MediaType::DoParse);
testFunc(m3);
}
TEST(mime_test, valid_parsing_test) {
auto m1 = MediaType::fromString("application/json");
ASSERT_EQ(m1, MIME(Application, Json));
ASSERT_TRUE(m1.q().isEmpty());
auto m2 = MediaType::fromString("application/xhtml+xml");
ASSERT_EQ(m2, MediaType(Type::Application, Subtype::Xhtml, Suffix::Xml));
ASSERT_TRUE(m2.q().isEmpty());
auto m3 = MediaType::fromString("application/json; q=0.3");
ASSERT_EQ(m3, MIME(Application, Json));
ASSERT_EQ(m3.q().getOrElse(Q(0)), Q::fromFloat(0.3));
auto m4 = MediaType::fromString("application/xhtml+xml; q=0.7");
ASSERT_EQ(m4.top(), Type::Application);
ASSERT_EQ(m4.sub(), Subtype::Xhtml);
ASSERT_EQ(m4.suffix(), Suffix::Xml);
ASSERT_EQ(m4.q().getOrElse(Q(0)), Q(70));
auto m5 = MediaType::fromString("application/xhtml+xml; q=0.78");
ASSERT_EQ(m5.q().getOrElse(Q(0)), Q(78));
auto m6 = MediaType::fromString("application/vnd.adobe.flash-movie");
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");
parse("application/json", [](const MediaType &m1) {
ASSERT_EQ(m1, MIME(Application, Json));
ASSERT_TRUE(m1.q().isEmpty());
});
parse("application/xhtml+xml", [](const MediaType &m2) {
ASSERT_EQ(m2, MediaType(Type::Application, Subtype::Xhtml, Suffix::Xml));
ASSERT_TRUE(m2.q().isEmpty());
});
parse("application/json; q=0.3", [](const MediaType& m3) {
ASSERT_EQ(m3, MIME(Application, Json));
ASSERT_EQ(m3.q().getOrElse(Q(0)), Q::fromFloat(0.3));
});
parse("application/xhtml+xml; q=0.7", [](const MediaType& m4) {
ASSERT_EQ(m4.top(), Type::Application);
ASSERT_EQ(m4.sub(), Subtype::Xhtml);
ASSERT_EQ(m4.suffix(), Suffix::Xml);
ASSERT_EQ(m4.q().getOrElse(Q(0)), Q(70));
});
parse("application/xhtml+xml; q=0.78", [](const MediaType& m5) {
ASSERT_EQ(m5.q().getOrElse(Q(0)), Q(78));
});
parse("application/vnd.adobe.flash-movie", [](const MediaType& m6) {
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");
});
parse("application/vnd.mycompany.myapp-v2+json", [](const MediaType& m7) {
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");
});
parse("application/x-myapp-v1+json", [](const MediaType& m8) {
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");
});
parse("audio/x-my-codec", [](const MediaType& m9) {
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");
});
parse("text/html; charset=ISO-8859-4", [](const MediaType& m10) {
ASSERT_EQ(m10, MIME(Text, Html));
ASSERT_TRUE(m10.q().isEmpty());
auto charset = m10.getParam("charset");
ASSERT_EQ(charset.getOrElse(""), "ISO-8859-4");
});
parse("text/html; q=0.83; charset=ISO-8859-4", [](const MediaType& m11) {
ASSERT_EQ(m11, MIME(Text, Html));
ASSERT_EQ(m11.q().getOrElse(Q(0)), Q(83));
ASSERT_EQ(m11.getParam("charset").getOrElse(""), "ISO-8859-4");
});
}
......@@ -92,4 +131,9 @@ TEST(mime_test, invalid_parsing) {
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);
ASSERT_THROW(MediaType::fromString("text/html; q=0.21;"), HttpError);
ASSERT_THROW(MediaType::fromString("text/html; q=0.21; charset"), HttpError);
ASSERT_THROW(MediaType::fromString("text/html; q=0.21; charset="), HttpError);
ASSERT_THROW(MediaType::fromString("text/html; q=0.21; charset=ISO-8859-4; "), 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