Commit bb9909a2 authored by Mathieu STEFANI's avatar Mathieu STEFANI

Mimes are now in their own header / source files

parent 5465db8b
...@@ -7,6 +7,7 @@ set(SOURCE_FILES ...@@ -7,6 +7,7 @@ set(SOURCE_FILES
http.cc http.cc
http_header.cc http_header.cc
http_headers.cc http_headers.cc
mime.cc
) )
add_library(net ${SOURCE_FILES}) add_library(net ${SOURCE_FILES})
......
...@@ -18,291 +18,6 @@ namespace Net { ...@@ -18,291 +18,6 @@ namespace Net {
namespace Http { namespace Http {
namespace Mime {
std::string
Q::toString() const {
if (val_ == 0)
return "q=0";
else if (val_ == 100)
return "q=1";
char buff[sizeof("q=0.99")];
memset(buff, sizeof buff, 0);
if (val_ % 10 == 0)
snprintf(buff, sizeof buff, "q=%.1f", val_ / 100.0);
else
snprintf(buff, sizeof buff, "q=%.2f", val_ / 100.0);
return std::string(buff);
}
MediaType
MediaType::fromString(const std::string& str) {
return fromRaw(str.c_str(), str.size());
}
MediaType
MediaType::fromString(std::string&& str) {
return fromRaw(str.c_str(), str.size());
}
MediaType
MediaType::fromRaw(const char* str, size_t len) {
MediaType res;
res.parseRaw(str, len);
return res;
}
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
// an HttpError
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 = strchr(str, '/');
if (p == NULL) {
raise("Malformated Media Type");
}
raw_ = string(str, len);
Mime::Type top;
// The reason we are using a do { } while (0); syntax construct here is to emulate
// if / else-if. Since we are using items-list macros to compare the strings,
// we want to avoid evaluating all the branches when one of them evaluates to true.
//
// Instead, we break the loop when a branch evaluates to true so that we do
// not evaluate all the subsequent ones.
//
// Watch out, this pattern is repeated throughout the function
do {
#define TYPE(val, s) \
if (memcmp(str, s, MAX_SIZE(s)) == 0) { \
top = Type::val; \
break; \
}
MIME_TYPES
#undef TYPE
raise("Unknown Media Type");
} while (0);
top_ = top;
// Parse subtype
Mime::Subtype sub;
++p;
if (eof(p)) raise("Malformed Media Type");
if (memcmp(p, "vnd.", MAX_SIZE("vnd.")) == 0) {
sub = Subtype::Vendor;
} else {
do {
#define SUB_TYPE(val, s) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \
sub = Subtype::val; \
p += sizeof(s) - 1; \
break; \
}
MIME_SUBTYPES
#undef SUB_TYPE
sub = Subtype::Ext;
} while (0);
}
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
Mime::Suffix suffix = Suffix::None;
if (*p == '+') {
++p;
if (eof(p)) raise("Malformed Media Type");
do {
#define SUFFIX(val, s, _) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \
suffix = Suffix::val; \
p += sizeof(s) - 1; \
break; \
}
MIME_SUFFIXES
#undef SUFFIX
suffix = Suffix::Ext;
} while (0);
if (suffix == Suffix::Ext) {
rawSuffixIndex.beg = offset(p);
while (!eof(p) && (*p != ';' && *p != '+')) ++p;
rawSuffixIndex.end = offset(p) - 1;
}
suffix_ = suffix;
}
// Parse parameters
while (!eof(p)) {
if (*p == ';' || *p == ' ') {
if (eof(p + 1)) raise("Malformed Media Type");
++p;
}
else if (*p == 'q') {
++p;
if (eof(p)) {
raise("Invalid quality factor");
}
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");
}
}
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);
params.insert(std::make_pair(std::move(key), std::move(value)));
}
}
#undef MAX_SIZE
}
void
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 {
if (!raw_.empty()) return raw_;
auto topString = [](Mime::Type top) -> const char * {
switch (top) {
#define TYPE(val, str) \
case Mime::Type::val: \
return str;
MIME_TYPES
#undef TYPE
}
};
auto subString = [](Mime::Subtype sub) -> const char * {
switch (sub) {
#define SUB_TYPE(val, str) \
case Mime::Subtype::val: \
return str;
MIME_SUBTYPES
#undef TYPE
}
};
auto suffixString = [](Mime::Suffix suffix) -> const char * {
switch (suffix) {
#define SUFFIX(val, str, _) \
case Mime::Suffix::val: \
return "+" str;
MIME_SUFFIXES
#undef SUFFIX
}
};
// @Improvement: allocating and concatenating many small strings is probably slow
std::string res;
res += topString(top_);
res += "/";
res += subString(sub_);
if (suffix_ != Suffix::None) {
res += suffixString(suffix_);
}
optionally_do(q_, [&res](Q quality) {
res += "; ";
res += quality.toString();
});
for (const auto& param: params) {
res += "; ";
res += param.first + "=" + param.second;
}
return res;
}
} // namespace Mime
const char* encodingString(Encoding encoding) { const char* encodingString(Encoding encoding) {
switch (encoding) { switch (encoding) {
case Encoding::Gzip: case Encoding::Gzip:
......
...@@ -6,15 +6,12 @@ ...@@ -6,15 +6,12 @@
#pragma once #pragma once
#include "optional.h" #include "mime.h"
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <memory> #include <memory>
#include <ostream> #include <ostream>
#include <vector> #include <vector>
#include <stdexcept>
#include <cassert>
#include <unordered_map>
#define SAFE_HEADER_CAST #define SAFE_HEADER_CAST
...@@ -65,208 +62,6 @@ enum class Encoding { ...@@ -65,208 +62,6 @@ enum class Encoding {
const char* encodingString(Encoding encoding); const char* encodingString(Encoding encoding);
namespace Mime {
#define MIME_TYPES \
TYPE(Star , "*") \
TYPE(Text , "text") \
TYPE(Image , "image") \
TYPE(Audio , "audio") \
TYPE(Video , "video") \
TYPE(Application, "application") \
TYPE(Message , "message") \
TYPE(Multipart , "multipart")
#define MIME_SUBTYPES \
SUB_TYPE(Star , "*") \
SUB_TYPE(Plain , "plain") \
SUB_TYPE(Html , "html") \
SUB_TYPE(Xhtml , "xhtml") \
SUB_TYPE(Xml , "xml") \
SUB_TYPE(Javascript, "javascript") \
SUB_TYPE(Css , "css") \
\
SUB_TYPE(Json , "json") \
SUB_TYPE(FormUrlEncoded, "x-www-form-urlencoded") \
\
SUB_TYPE(Png, "png") \
SUB_TYPE(Gif, "gif") \
SUB_TYPE(Bmp, "bmp") \
SUB_TYPE(Jpeg, "jpeg")
#define MIME_SUFFIXES \
SUFFIX(Json , "json" , "JavaScript Object Notation") \
SUFFIX(Ber , "ber" , "Basic Encoding Rules") \
SUFFIX(Der , "der" , "Distinguished Encoding Rules") \
SUFFIX(Fastinfoset, "fastinfoset", "Fast Infoset") \
SUFFIX(Wbxml , "wbxml" , "WAP Binary XML") \
SUFFIX(Zip , "zip" , "ZIP file storage") \
SUFFIX(Xml , "xml" , "Extensible Markup Language")
enum class Type {
#define TYPE(val, _) val,
MIME_TYPES
#undef TYPE
None
};
enum class Subtype {
#define SUB_TYPE(val, _) val,
MIME_SUBTYPES
#undef SUB_TYPE
Vendor,
Ext,
None
};
enum class Suffix {
#define SUFFIX(val, _, __) val,
MIME_SUFFIXES
#undef SUFFIX
None,
Ext
};
// 3.9 Quality Values
class Q {
public:
// typedef uint8_t Type;
typedef uint16_t Type;
explicit Q(Type val)
{
if (val > 100) {
throw std::runtime_error("Invalid quality value, must be in the [0; 100] range");
}
val_ = val;
}
static Q fromFloat(double f) {
return Q(static_cast<Type>(f * 100.0));
}
Type value() const { return val_; }
operator Type() const { return val_; }
std::string toString() const;
private:
Type val_;
};
inline bool operator==(Q lhs, Q rhs) {
return lhs.value() == rhs.value();
}
// 3.7 Media Types
class MediaType {
public:
enum Parse { DoParse, DontParse };
MediaType()
: top_(Type::None)
, sub_(Subtype::None)
, suffix_(Suffix::None)
{ }
MediaType(std::string raw, Parse parse = DontParse)
: top_(Type::None)
, sub_(Subtype::None)
, suffix_(Suffix::None)
{
if (parse == DoParse) {
parseRaw(raw.c_str(), raw.length());
}
else {
raw_ = std::move(raw);
}
}
MediaType(Mime::Type top, Mime::Subtype sub)
: top_(top)
, sub_(sub)
, suffix_(Suffix::None)
{ }
MediaType(Mime::Type top, Mime::Subtype sub, Mime::Suffix suffix)
: top_(top)
, sub_(sub)
, suffix_(suffix)
{ }
static MediaType fromRaw(const char* str, size_t len);
static MediaType fromString(const std::string& str);
static MediaType fromString(std::string&& str);
Mime::Type top() const { return top_; }
Mime::Subtype sub() const { return sub_; }
Mime::Suffix suffix() const { return suffix_; }
std::string rawSub() const {
return rawSubIndex.splice(raw_);
}
std::string raw() const { return raw_; }
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);
Mime::Type top_;
Mime::Subtype sub_;
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_;
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;
std::unordered_map<std::string, std::string> params;
Optional<Q> q_;
};
inline bool operator==(const MediaType& lhs, const MediaType& rhs) {
return lhs.top() == rhs.top() &&
lhs.sub() == rhs.sub() &&
lhs.suffix() == rhs.suffix();
}
} // namespace Mime
#define MIME(top, sub) \
Net::Http::Mime::MediaType(Net::Http::Mime::Type::top, Net::Http::Mime::Subtype::sub)
#define MIME3(top, sub, suffix) \
Net::Http::Mime::MediaType(Net::Http::Mime::Type::top, Net::Http::Mime::Subtype::sub, Net::Http::Mime::Suffix::suffix);
class Header { class Header {
public: public:
virtual const char *name() const = 0; virtual const char *name() const = 0;
......
/* mime.cc
Mathieu Stefani, 29 August 2015
Implementaton of MIME Type parsing
*/
#include "mime.h"
#include "http.h"
#include <cstring>
using namespace std;
namespace Net {
namespace Http {
namespace Mime {
std::string
Q::toString() const {
if (val_ == 0)
return "q=0";
else if (val_ == 100)
return "q=1";
char buff[sizeof("q=0.99")];
memset(buff, sizeof buff, 0);
if (val_ % 10 == 0)
snprintf(buff, sizeof buff, "q=%.1f", val_ / 100.0);
else
snprintf(buff, sizeof buff, "q=%.2f", val_ / 100.0);
return std::string(buff);
}
MediaType
MediaType::fromString(const std::string& str) {
return fromRaw(str.c_str(), str.size());
}
MediaType
MediaType::fromString(std::string&& str) {
return fromRaw(str.c_str(), str.size());
}
MediaType
MediaType::fromRaw(const char* str, size_t len) {
MediaType res;
res.parseRaw(str, len);
return res;
}
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
// an HttpError
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 = strchr(str, '/');
if (p == NULL) {
raise("Malformated Media Type");
}
raw_ = string(str, len);
Mime::Type top;
// The reason we are using a do { } while (0); syntax construct here is to emulate
// if / else-if. Since we are using items-list macros to compare the strings,
// we want to avoid evaluating all the branches when one of them evaluates to true.
//
// Instead, we break the loop when a branch evaluates to true so that we do
// not evaluate all the subsequent ones.
//
// Watch out, this pattern is repeated throughout the function
do {
#define TYPE(val, s) \
if (memcmp(str, s, MAX_SIZE(s)) == 0) { \
top = Type::val; \
break; \
}
MIME_TYPES
#undef TYPE
raise("Unknown Media Type");
} while (0);
top_ = top;
// Parse subtype
Mime::Subtype sub;
++p;
if (eof(p)) raise("Malformed Media Type");
if (memcmp(p, "vnd.", MAX_SIZE("vnd.")) == 0) {
sub = Subtype::Vendor;
} else {
do {
#define SUB_TYPE(val, s) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \
sub = Subtype::val; \
p += sizeof(s) - 1; \
break; \
}
MIME_SUBTYPES
#undef SUB_TYPE
sub = Subtype::Ext;
} while (0);
}
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
Mime::Suffix suffix = Suffix::None;
if (*p == '+') {
++p;
if (eof(p)) raise("Malformed Media Type");
do {
#define SUFFIX(val, s, _) \
if (memcmp(p, s, MAX_SIZE(s)) == 0) { \
suffix = Suffix::val; \
p += sizeof(s) - 1; \
break; \
}
MIME_SUFFIXES
#undef SUFFIX
suffix = Suffix::Ext;
} while (0);
if (suffix == Suffix::Ext) {
rawSuffixIndex.beg = offset(p);
while (!eof(p) && (*p != ';' && *p != '+')) ++p;
rawSuffixIndex.end = offset(p) - 1;
}
suffix_ = suffix;
}
// Parse parameters
while (!eof(p)) {
if (*p == ';' || *p == ' ') {
if (eof(p + 1)) raise("Malformed Media Type");
++p;
}
else if (*p == 'q') {
++p;
if (eof(p)) {
raise("Invalid quality factor");
}
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");
}
}
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);
params.insert(std::make_pair(std::move(key), std::move(value)));
}
}
#undef MAX_SIZE
}
void
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 {
if (!raw_.empty()) return raw_;
auto topString = [](Mime::Type top) -> const char * {
switch (top) {
#define TYPE(val, str) \
case Mime::Type::val: \
return str;
MIME_TYPES
#undef TYPE
}
};
auto subString = [](Mime::Subtype sub) -> const char * {
switch (sub) {
#define SUB_TYPE(val, str) \
case Mime::Subtype::val: \
return str;
MIME_SUBTYPES
#undef TYPE
}
};
auto suffixString = [](Mime::Suffix suffix) -> const char * {
switch (suffix) {
#define SUFFIX(val, str, _) \
case Mime::Suffix::val: \
return "+" str;
MIME_SUFFIXES
#undef SUFFIX
}
};
// @Improvement: allocating and concatenating many small strings is probably slow
std::string res;
res += topString(top_);
res += "/";
res += subString(sub_);
if (suffix_ != Suffix::None) {
res += suffixString(suffix_);
}
optionally_do(q_, [&res](Q quality) {
res += "; ";
res += quality.toString();
});
for (const auto& param: params) {
res += "; ";
res += param.first + "=" + param.second;
}
return res;
}
} // namespace Mime
} // namespace Http
} // namespace Net
/* mime.h
Mathieu Stefani, 29 August 2015
Type safe representation of a MIME Type (RFC 1590)
*/
#pragma once
#include "optional.h"
#include <string>
#include <unordered_map>
#include <stdexcept>
#include <cassert>
namespace Net {
namespace Http {
namespace Mime {
#define MIME_TYPES \
TYPE(Star , "*") \
TYPE(Text , "text") \
TYPE(Image , "image") \
TYPE(Audio , "audio") \
TYPE(Video , "video") \
TYPE(Application, "application") \
TYPE(Message , "message") \
TYPE(Multipart , "multipart")
#define MIME_SUBTYPES \
SUB_TYPE(Star , "*") \
SUB_TYPE(Plain , "plain") \
SUB_TYPE(Html , "html") \
SUB_TYPE(Xhtml , "xhtml") \
SUB_TYPE(Xml , "xml") \
SUB_TYPE(Javascript, "javascript") \
SUB_TYPE(Css , "css") \
\
SUB_TYPE(Json , "json") \
SUB_TYPE(FormUrlEncoded, "x-www-form-urlencoded") \
\
SUB_TYPE(Png, "png") \
SUB_TYPE(Gif, "gif") \
SUB_TYPE(Bmp, "bmp") \
SUB_TYPE(Jpeg, "jpeg")
#define MIME_SUFFIXES \
SUFFIX(Json , "json" , "JavaScript Object Notation") \
SUFFIX(Ber , "ber" , "Basic Encoding Rules") \
SUFFIX(Der , "der" , "Distinguished Encoding Rules") \
SUFFIX(Fastinfoset, "fastinfoset", "Fast Infoset") \
SUFFIX(Wbxml , "wbxml" , "WAP Binary XML") \
SUFFIX(Zip , "zip" , "ZIP file storage") \
SUFFIX(Xml , "xml" , "Extensible Markup Language")
enum class Type {
#define TYPE(val, _) val,
MIME_TYPES
#undef TYPE
None
};
enum class Subtype {
#define SUB_TYPE(val, _) val,
MIME_SUBTYPES
#undef SUB_TYPE
Vendor,
Ext,
None
};
enum class Suffix {
#define SUFFIX(val, _, __) val,
MIME_SUFFIXES
#undef SUFFIX
None,
Ext
};
// 3.9 Quality Values
class Q {
public:
// typedef uint8_t Type;
typedef uint16_t Type;
explicit Q(Type val)
{
if (val > 100) {
throw std::runtime_error("Invalid quality value, must be in the [0; 100] range");
}
val_ = val;
}
static Q fromFloat(double f) {
return Q(static_cast<Type>(f * 100.0));
}
Type value() const { return val_; }
operator Type() const { return val_; }
std::string toString() const;
private:
Type val_;
};
inline bool operator==(Q lhs, Q rhs) {
return lhs.value() == rhs.value();
}
// 3.7 Media Types
class MediaType {
public:
enum Parse { DoParse, DontParse };
MediaType()
: top_(Type::None)
, sub_(Subtype::None)
, suffix_(Suffix::None)
{ }
MediaType(std::string raw, Parse parse = DontParse)
: top_(Type::None)
, sub_(Subtype::None)
, suffix_(Suffix::None)
{
if (parse == DoParse) {
parseRaw(raw.c_str(), raw.length());
}
else {
raw_ = std::move(raw);
}
}
MediaType(Mime::Type top, Mime::Subtype sub)
: top_(top)
, sub_(sub)
, suffix_(Suffix::None)
{ }
MediaType(Mime::Type top, Mime::Subtype sub, Mime::Suffix suffix)
: top_(top)
, sub_(sub)
, suffix_(suffix)
{ }
static MediaType fromRaw(const char* str, size_t len);
static MediaType fromString(const std::string& str);
static MediaType fromString(std::string&& str);
Mime::Type top() const { return top_; }
Mime::Subtype sub() const { return sub_; }
Mime::Suffix suffix() const { return suffix_; }
std::string rawSub() const {
return rawSubIndex.splice(raw_);
}
std::string raw() const { return raw_; }
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);
Mime::Type top_;
Mime::Subtype sub_;
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_;
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;
std::unordered_map<std::string, std::string> params;
Optional<Q> q_;
};
inline bool operator==(const MediaType& lhs, const MediaType& rhs) {
return lhs.top() == rhs.top() &&
lhs.sub() == rhs.sub() &&
lhs.suffix() == rhs.suffix();
}
} // namespace Mime
} // namespace Http
} // namespace Net
#define MIME(top, sub) \
Net::Http::Mime::MediaType(Net::Http::Mime::Type::top, Net::Http::Mime::Subtype::sub)
#define MIME3(top, sub, suffix) \
Net::Http::Mime::MediaType(Net::Http::Mime::Type::top, Net::Http::Mime::Subtype::sub, Net::Http::Mime::Suffix::suffix);
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "http_header.h" #include "mime.h"
#include "http.h" #include "http.h"
using namespace Net::Http; using namespace Net::Http;
......
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