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

MediaType now parses extra parameters

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