initial commit
This commit is contained in:
865
third_party/libhv/http/HttpMessage.cpp
vendored
Executable file
865
third_party/libhv/http/HttpMessage.cpp
vendored
Executable file
@@ -0,0 +1,865 @@
|
||||
#include "HttpMessage.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "htime.h"
|
||||
#include "hlog.h"
|
||||
#include "hurl.h"
|
||||
#include "base64.h"
|
||||
|
||||
using namespace hv;
|
||||
|
||||
http_headers DefaultHeaders;
|
||||
http_body NoBody;
|
||||
HttpCookie NoCookie;
|
||||
char HttpMessage::s_date[32] = {0};
|
||||
|
||||
HttpCookie::HttpCookie() {
|
||||
init();
|
||||
}
|
||||
|
||||
void HttpCookie::init() {
|
||||
max_age = 0;
|
||||
secure = false;
|
||||
httponly = false;
|
||||
samesite = Default;
|
||||
priority = NotSet;
|
||||
}
|
||||
|
||||
void HttpCookie::reset() {
|
||||
init();
|
||||
name.clear();
|
||||
value.clear();
|
||||
domain.clear();
|
||||
path.clear();
|
||||
expires.clear();
|
||||
kv.clear();
|
||||
}
|
||||
|
||||
bool HttpCookie::parse(const std::string& str) {
|
||||
std::stringstream ss;
|
||||
ss << str;
|
||||
std::string line;
|
||||
std::string::size_type pos;
|
||||
std::string key;
|
||||
std::string val;
|
||||
|
||||
reset();
|
||||
while (std::getline(ss, line, ';')) {
|
||||
pos = line.find_first_of('=');
|
||||
if (pos != std::string::npos) {
|
||||
key = trim(line.substr(0, pos));
|
||||
val = trim(line.substr(pos+1));
|
||||
const char* pkey = key.c_str();
|
||||
if (stricmp(pkey, "Domain") == 0) {
|
||||
domain = val;
|
||||
}
|
||||
else if (stricmp(pkey, "Path") == 0) {
|
||||
path = val;
|
||||
}
|
||||
else if (stricmp(pkey, "Expires") == 0) {
|
||||
expires = val;
|
||||
}
|
||||
else if (stricmp(pkey, "Max-Age") == 0) {
|
||||
max_age = atoi(val.c_str());
|
||||
}
|
||||
else if (stricmp(pkey, "SameSite") == 0) {
|
||||
samesite = stricmp(val.c_str(), "Strict") == 0 ? HttpCookie::SameSite::Strict :
|
||||
stricmp(val.c_str(), "Lax") == 0 ? HttpCookie::SameSite::Lax :
|
||||
stricmp(val.c_str(), "None") == 0 ? HttpCookie::SameSite::None :
|
||||
HttpCookie::SameSite::Default;
|
||||
}
|
||||
else if (stricmp(pkey, "Priority") == 0) {
|
||||
priority = stricmp(val.c_str(), "Low") == 0 ? HttpCookie::Priority::Low :
|
||||
stricmp(val.c_str(), "Medium") == 0 ? HttpCookie::Priority::Medium :
|
||||
stricmp(val.c_str(), "High") == 0 ? HttpCookie::Priority::High :
|
||||
HttpCookie::Priority::NotSet ;
|
||||
}
|
||||
else {
|
||||
if (name.empty()) {
|
||||
name = key;
|
||||
value = val;
|
||||
}
|
||||
kv[key] = val;
|
||||
}
|
||||
} else {
|
||||
key = trim(line);
|
||||
const char* pkey = key.c_str();
|
||||
if (stricmp(pkey, "Secure") == 0) {
|
||||
secure = true;
|
||||
}
|
||||
else if (stricmp(pkey, "HttpOnly") == 0) {
|
||||
httponly = true;
|
||||
}
|
||||
else {
|
||||
hlogw("Unrecognized key '%s'", key.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return !name.empty();
|
||||
}
|
||||
|
||||
std::string HttpCookie::dump() const {
|
||||
assert(!name.empty() || !kv.empty());
|
||||
std::string res;
|
||||
|
||||
if (!name.empty()) {
|
||||
res = name;
|
||||
res += "=";
|
||||
res += value;
|
||||
}
|
||||
|
||||
for (auto& pair : kv) {
|
||||
if (pair.first == name) continue;
|
||||
if (!res.empty()) res += "; ";
|
||||
res += pair.first;
|
||||
res += "=";
|
||||
res += pair.second;
|
||||
}
|
||||
|
||||
if (!domain.empty()) {
|
||||
res += "; Domain=";
|
||||
res += domain;
|
||||
}
|
||||
|
||||
if (!path.empty()) {
|
||||
res += "; Path=";
|
||||
res += path;
|
||||
}
|
||||
|
||||
if (max_age > 0) {
|
||||
res += "; Max-Age=";
|
||||
res += hv::to_string(max_age);
|
||||
} else if (!expires.empty()) {
|
||||
res += "; Expires=";
|
||||
res += expires;
|
||||
}
|
||||
|
||||
if (samesite != HttpCookie::SameSite::Default) {
|
||||
res += "; SameSite=";
|
||||
res += samesite == HttpCookie::SameSite::Strict ? "Strict" :
|
||||
samesite == HttpCookie::SameSite::Lax ? "Lax" :
|
||||
"None" ;
|
||||
}
|
||||
|
||||
if (priority != HttpCookie::Priority::NotSet) {
|
||||
res += "; Priority=";
|
||||
res += priority == HttpCookie::Priority::Low ? "Low" :
|
||||
priority == HttpCookie::Priority::Medium ? "Medium" :
|
||||
"High" ;
|
||||
}
|
||||
|
||||
if (secure) {
|
||||
res += "; Secure";
|
||||
}
|
||||
|
||||
if (httponly) {
|
||||
res += "; HttpOnly";
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
HttpMessage::HttpMessage() {
|
||||
type = HTTP_BOTH;
|
||||
Init();
|
||||
}
|
||||
|
||||
HttpMessage::~HttpMessage() {
|
||||
|
||||
}
|
||||
|
||||
void HttpMessage::Init() {
|
||||
http_major = 1;
|
||||
http_minor = 1;
|
||||
content = NULL;
|
||||
content_length = 0;
|
||||
content_type = CONTENT_TYPE_NONE;
|
||||
}
|
||||
|
||||
void HttpMessage::Reset() {
|
||||
Init();
|
||||
headers.clear();
|
||||
cookies.clear();
|
||||
body.clear();
|
||||
#ifndef WITHOUT_HTTP_CONTENT
|
||||
json.clear();
|
||||
form.clear();
|
||||
kv.clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef WITHOUT_HTTP_CONTENT
|
||||
// NOTE: json ignore number/string, 123/"123"
|
||||
|
||||
std::string HttpMessage::GetString(const char* key, const std::string& defvalue) {
|
||||
switch (ContentType()) {
|
||||
case APPLICATION_JSON:
|
||||
{
|
||||
if (json.empty()) {
|
||||
ParseBody();
|
||||
}
|
||||
if (!json.is_object()) {
|
||||
return defvalue;
|
||||
}
|
||||
const auto& value = json[key];
|
||||
if (value.is_string()) {
|
||||
return value;
|
||||
}
|
||||
else if (value.is_number()) {
|
||||
return hv::to_string(value);
|
||||
}
|
||||
else if (value.is_boolean()) {
|
||||
bool b = value;
|
||||
return b ? "true" : "false";
|
||||
}
|
||||
else {
|
||||
return defvalue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MULTIPART_FORM_DATA:
|
||||
{
|
||||
if (form.empty()) {
|
||||
ParseBody();
|
||||
}
|
||||
auto iter = form.find(key);
|
||||
if (iter != form.end()) {
|
||||
return iter->second.content;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case APPLICATION_URLENCODED:
|
||||
{
|
||||
if (kv.empty()) {
|
||||
ParseBody();
|
||||
}
|
||||
auto iter = kv.find(key);
|
||||
if (iter != kv.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return defvalue;
|
||||
}
|
||||
|
||||
template<>
|
||||
HV_EXPORT int64_t HttpMessage::Get(const char* key, int64_t defvalue) {
|
||||
if (ContentType() == APPLICATION_JSON) {
|
||||
if (json.empty()) {
|
||||
ParseBody();
|
||||
}
|
||||
if (!json.is_object()) {
|
||||
return defvalue;
|
||||
}
|
||||
const auto& value = json[key];
|
||||
if (value.is_number()) {
|
||||
return value;
|
||||
}
|
||||
else if (value.is_string()) {
|
||||
std::string str = value;
|
||||
return atoll(str.c_str());
|
||||
}
|
||||
else if (value.is_boolean()) {
|
||||
bool b = value;
|
||||
return b ? 1 : 0;
|
||||
}
|
||||
else {
|
||||
return defvalue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::string str = GetString(key);
|
||||
return str.empty() ? defvalue : atoll(str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
HV_EXPORT int HttpMessage::Get(const char* key, int defvalue) {
|
||||
return (int)Get<int64_t>(key, defvalue);
|
||||
}
|
||||
|
||||
template<>
|
||||
HV_EXPORT double HttpMessage::Get(const char* key, double defvalue) {
|
||||
if (ContentType() == APPLICATION_JSON) {
|
||||
if (json.empty()) {
|
||||
ParseBody();
|
||||
}
|
||||
if (!json.is_object()) {
|
||||
return defvalue;
|
||||
}
|
||||
const auto& value = json[key];
|
||||
if (value.is_number()) {
|
||||
return value;
|
||||
}
|
||||
else if (value.is_string()) {
|
||||
std::string str = value;
|
||||
return atof(str.c_str());
|
||||
}
|
||||
else {
|
||||
return defvalue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::string str = GetString(key);
|
||||
return str.empty() ? defvalue : atof(str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
HV_EXPORT float HttpMessage::Get(const char* key, float defvalue) {
|
||||
return (float)Get<double>(key, defvalue);
|
||||
}
|
||||
|
||||
template<>
|
||||
HV_EXPORT bool HttpMessage::Get(const char* key, bool defvalue) {
|
||||
if (ContentType() == APPLICATION_JSON) {
|
||||
if (json.empty()) {
|
||||
ParseBody();
|
||||
}
|
||||
if (!json.is_object()) {
|
||||
return defvalue;
|
||||
}
|
||||
const auto& value = json[key];
|
||||
if (value.is_boolean()) {
|
||||
return value;
|
||||
}
|
||||
else if (value.is_string()) {
|
||||
std::string str = value;
|
||||
return hv_getboolean(str.c_str());
|
||||
}
|
||||
else if (value.is_number()) {
|
||||
return value != 0;
|
||||
}
|
||||
else {
|
||||
return defvalue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::string str = GetString(key);
|
||||
return str.empty() ? defvalue : hv_getboolean(str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpMessage::GetBool(const char* key, bool defvalue) {
|
||||
return Get<bool>(key, defvalue);
|
||||
}
|
||||
int64_t HttpMessage::GetInt(const char* key, int64_t defvalue) {
|
||||
return Get<int64_t>(key, defvalue);
|
||||
}
|
||||
double HttpMessage::GetFloat(const char* key, double defvalue) {
|
||||
return Get<double>(key, defvalue);
|
||||
}
|
||||
#endif
|
||||
|
||||
void HttpMessage::FillContentType() {
|
||||
auto iter = headers.find("Content-Type");
|
||||
if (iter != headers.end()) {
|
||||
content_type = http_content_type_enum(iter->second.c_str());
|
||||
goto append;
|
||||
}
|
||||
|
||||
#ifndef WITHOUT_HTTP_CONTENT
|
||||
if (content_type == CONTENT_TYPE_NONE) {
|
||||
if (json.size() != 0) {
|
||||
content_type = APPLICATION_JSON;
|
||||
}
|
||||
else if (form.size() != 0) {
|
||||
content_type = MULTIPART_FORM_DATA;
|
||||
}
|
||||
else if (kv.size() != 0) {
|
||||
content_type = X_WWW_FORM_URLENCODED;
|
||||
}
|
||||
else if (body.size() != 0) {
|
||||
content_type = TEXT_PLAIN;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (content_type != CONTENT_TYPE_NONE) {
|
||||
headers["Content-Type"] = http_content_type_str(content_type);
|
||||
}
|
||||
|
||||
append:
|
||||
#ifndef WITHOUT_HTTP_CONTENT
|
||||
if (content_type == MULTIPART_FORM_DATA) {
|
||||
auto iter = headers.find("Content-Type");
|
||||
if (iter != headers.end()) {
|
||||
const char* boundary = strstr(iter->second.c_str(), "boundary=");
|
||||
if (boundary == NULL) {
|
||||
boundary = DEFAULT_MULTIPART_BOUNDARY;
|
||||
iter->second += "; boundary=";
|
||||
iter->second += boundary;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
void HttpMessage::FillContentLength() {
|
||||
auto iter = headers.find("Content-Length");
|
||||
if (iter != headers.end()) {
|
||||
content_length = atoll(iter->second.c_str());
|
||||
}
|
||||
if (content_length == 0) {
|
||||
DumpBody();
|
||||
content_length = body.size();
|
||||
}
|
||||
if (iter == headers.end() && !IsChunked() && content_type != TEXT_EVENT_STREAM) {
|
||||
if (content_length != 0 || type == HTTP_RESPONSE) {
|
||||
headers["Content-Length"] = hv::to_string(content_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpMessage::IsChunked() {
|
||||
auto iter = headers.find("Transfer-Encoding");
|
||||
return iter != headers.end() && stricmp(iter->second.c_str(), "chunked") == 0;
|
||||
}
|
||||
|
||||
bool HttpMessage::IsKeepAlive() {
|
||||
bool keepalive = true;
|
||||
auto iter = headers.find("connection");
|
||||
if (iter != headers.end()) {
|
||||
const char* keepalive_value = iter->second.c_str();
|
||||
if (stricmp(keepalive_value, "keep-alive") == 0) {
|
||||
keepalive = true;
|
||||
}
|
||||
else if (stricmp(keepalive_value, "close") == 0) {
|
||||
keepalive = false;
|
||||
}
|
||||
else if (stricmp(keepalive_value, "upgrade") == 0) {
|
||||
keepalive = true;
|
||||
}
|
||||
}
|
||||
else if (http_major == 1 && http_minor == 0) {
|
||||
keepalive = false;
|
||||
}
|
||||
return keepalive;
|
||||
}
|
||||
|
||||
bool HttpMessage::IsUpgrade() {
|
||||
auto iter = headers.find("upgrade");
|
||||
return iter != headers.end();
|
||||
}
|
||||
|
||||
// headers
|
||||
void HttpMessage::SetHeader(const char* key, const std::string& value) {
|
||||
headers[key] = value;
|
||||
}
|
||||
std::string HttpMessage::GetHeader(const char* key, const std::string& defvalue) {
|
||||
auto iter = headers.find(key);
|
||||
return iter == headers.end() ? defvalue : iter->second;
|
||||
}
|
||||
|
||||
// cookies
|
||||
void HttpMessage::AddCookie(const HttpCookie& cookie) {
|
||||
cookies.push_back(cookie);
|
||||
}
|
||||
const HttpCookie& HttpMessage::GetCookie(const std::string& name) {
|
||||
for (auto iter = cookies.begin(); iter != cookies.end(); ++iter) {
|
||||
if (iter->name == name) {
|
||||
return *iter;
|
||||
}
|
||||
auto kv_iter = iter->kv.find(name);
|
||||
if (kv_iter != iter->kv.end()) {
|
||||
iter->name = name;
|
||||
iter->value = kv_iter->second;
|
||||
return *iter;
|
||||
}
|
||||
}
|
||||
return NoCookie;
|
||||
}
|
||||
|
||||
// body
|
||||
void HttpMessage::SetBody(const std::string& body) {
|
||||
this->body = body;
|
||||
}
|
||||
const std::string& HttpMessage::Body() {
|
||||
return this->body;
|
||||
}
|
||||
|
||||
void HttpMessage::DumpHeaders(std::string& str) {
|
||||
FillContentType();
|
||||
FillContentLength();
|
||||
|
||||
// headers
|
||||
for (auto& header: headers) {
|
||||
// http2 :method :path :scheme :authority :status
|
||||
if (*str.c_str() != ':') {
|
||||
// %s: %s\r\n
|
||||
str += header.first;
|
||||
str += ": ";
|
||||
// fix CVE-2023-26148
|
||||
// if the value has \r\n, translate to \\r\\n
|
||||
if (header.second.find("\r") != std::string::npos ||
|
||||
header.second.find("\n") != std::string::npos) {
|
||||
std::string newStr = "";
|
||||
for (size_t i = 0; i < header.second.size(); ++i) {
|
||||
if (header.second[i] == '\r') {
|
||||
newStr += "\\r";
|
||||
} else if (header.second[i] == '\n') {
|
||||
newStr += "\\n";
|
||||
} else {
|
||||
newStr += header.second[i];
|
||||
}
|
||||
}
|
||||
str += newStr;
|
||||
} else {
|
||||
str += header.second;
|
||||
}
|
||||
str += "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
// cookies
|
||||
const char* cookie_field = "Cookie";
|
||||
if (type == HTTP_RESPONSE) {
|
||||
cookie_field = "Set-Cookie";
|
||||
}
|
||||
for (auto& cookie : cookies) {
|
||||
str += cookie_field;
|
||||
str += ": ";
|
||||
str += cookie.dump();
|
||||
str += "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
void HttpMessage::DumpBody() {
|
||||
if (body.size() != 0) {
|
||||
return;
|
||||
}
|
||||
FillContentType();
|
||||
#ifndef WITHOUT_HTTP_CONTENT
|
||||
switch(content_type) {
|
||||
case APPLICATION_JSON:
|
||||
body = dump_json(json, 2);
|
||||
break;
|
||||
case MULTIPART_FORM_DATA:
|
||||
{
|
||||
auto iter = headers.find("Content-Type");
|
||||
if (iter == headers.end()) {
|
||||
return;
|
||||
}
|
||||
const char* boundary = strstr(iter->second.c_str(), "boundary=");
|
||||
if (boundary == NULL) {
|
||||
return;
|
||||
}
|
||||
boundary += strlen("boundary=");
|
||||
body = dump_multipart(form, boundary);
|
||||
}
|
||||
break;
|
||||
case X_WWW_FORM_URLENCODED:
|
||||
body = dump_query_params(kv);
|
||||
break;
|
||||
default:
|
||||
// nothing to do
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void HttpMessage::DumpBody(std::string& str) {
|
||||
DumpBody();
|
||||
const char* content = (const char*)Content();
|
||||
size_t content_length = ContentLength();
|
||||
if (content && content_length) {
|
||||
str.append(content, content_length);
|
||||
}
|
||||
}
|
||||
|
||||
int HttpMessage::ParseBody() {
|
||||
if (body.size() == 0) {
|
||||
return -1;
|
||||
}
|
||||
FillContentType();
|
||||
#ifndef WITHOUT_HTTP_CONTENT
|
||||
switch(content_type) {
|
||||
case APPLICATION_JSON:
|
||||
{
|
||||
std::string errmsg;
|
||||
int ret = parse_json(body.c_str(), json, errmsg);
|
||||
if (ret != 0 && errmsg.size() != 0) {
|
||||
hloge("%s", errmsg.c_str());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
case MULTIPART_FORM_DATA:
|
||||
{
|
||||
auto iter = headers.find("Content-Type");
|
||||
if (iter == headers.end()) {
|
||||
return -1;
|
||||
}
|
||||
const char* boundary = strstr(iter->second.c_str(), "boundary=");
|
||||
if (boundary == NULL) {
|
||||
return -1;
|
||||
}
|
||||
boundary += strlen("boundary=");
|
||||
std::string strBoundary(boundary);
|
||||
strBoundary = trim_pairs(strBoundary, "\"\"\'\'");
|
||||
return parse_multipart(body, form, strBoundary.c_str());
|
||||
}
|
||||
case X_WWW_FORM_URLENCODED:
|
||||
return parse_query_params(body.c_str(), kv);
|
||||
default:
|
||||
// nothing to do
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string HttpMessage::Dump(bool is_dump_headers, bool is_dump_body) {
|
||||
std::string str;
|
||||
if (is_dump_headers) {
|
||||
DumpHeaders(str);
|
||||
}
|
||||
str += "\r\n";
|
||||
if (is_dump_body) {
|
||||
DumpBody(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
HttpRequest::HttpRequest() : HttpMessage() {
|
||||
type = HTTP_REQUEST;
|
||||
Init();
|
||||
}
|
||||
|
||||
void HttpRequest::Init() {
|
||||
headers["User-Agent"] = DEFAULT_HTTP_USER_AGENT;
|
||||
headers["Accept"] = "*/*";
|
||||
method = HTTP_GET;
|
||||
scheme = "http";
|
||||
host = "127.0.0.1";
|
||||
port = DEFAULT_HTTP_PORT;
|
||||
path = "/";
|
||||
timeout = DEFAULT_HTTP_TIMEOUT;
|
||||
connect_timeout = DEFAULT_HTTP_CONNECT_TIMEOUT;
|
||||
retry_count = DEFAULT_HTTP_FAIL_RETRY_COUNT;
|
||||
retry_delay = DEFAULT_HTTP_FAIL_RETRY_DELAY;
|
||||
redirect = 1;
|
||||
proxy = 0;
|
||||
cancel = 0;
|
||||
}
|
||||
|
||||
void HttpRequest::Reset() {
|
||||
HttpMessage::Reset();
|
||||
Init();
|
||||
url.clear();
|
||||
query_params.clear();
|
||||
}
|
||||
|
||||
void HttpRequest::DumpUrl() {
|
||||
std::string str;
|
||||
if (url.size() != 0 &&
|
||||
*url.c_str() != '/' &&
|
||||
strstr(url.c_str(), "://") != NULL) {
|
||||
// have been complete url
|
||||
goto query;
|
||||
}
|
||||
// scheme://
|
||||
str = scheme;
|
||||
str += "://";
|
||||
// host:port
|
||||
if (url.size() != 0 && *url.c_str() != '/') {
|
||||
// url begin with host
|
||||
str += url;
|
||||
}
|
||||
else {
|
||||
if (port == 0 ||
|
||||
port == DEFAULT_HTTP_PORT ||
|
||||
port == DEFAULT_HTTPS_PORT) {
|
||||
str += Host();
|
||||
}
|
||||
else {
|
||||
str += hv::asprintf("%s:%d", host.c_str(), port);
|
||||
}
|
||||
}
|
||||
// /path
|
||||
if (url.size() != 0 && *url.c_str() == '/') {
|
||||
// url begin with path
|
||||
str += url;
|
||||
}
|
||||
else if (path.size() > 1 && *path.c_str() == '/') {
|
||||
str += path;
|
||||
}
|
||||
else if (url.size() == 0) {
|
||||
str += '/';
|
||||
}
|
||||
url = str;
|
||||
query:
|
||||
// ?query
|
||||
if (strchr(url.c_str(), '?') == NULL &&
|
||||
query_params.size() != 0) {
|
||||
url += '?';
|
||||
url += dump_query_params(query_params);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::ParseUrl() {
|
||||
DumpUrl();
|
||||
hurl_t parser;
|
||||
hv_parse_url(&parser, url.c_str());
|
||||
// scheme
|
||||
std::string scheme_ = url.substr(parser.fields[HV_URL_SCHEME].off, parser.fields[HV_URL_SCHEME].len);
|
||||
// host
|
||||
std::string host_(host);
|
||||
if (parser.fields[HV_URL_HOST].len > 0) {
|
||||
host_ = url.substr(parser.fields[HV_URL_HOST].off, parser.fields[HV_URL_HOST].len);
|
||||
}
|
||||
// port
|
||||
int port_ = parser.port ? parser.port : strcmp(scheme_.c_str(), "https") ? DEFAULT_HTTP_PORT : DEFAULT_HTTPS_PORT;
|
||||
if (!proxy) {
|
||||
scheme = scheme_;
|
||||
host = host_;
|
||||
port = port_;
|
||||
}
|
||||
FillHost(host_.c_str(), port_);
|
||||
// path
|
||||
if (parser.fields[HV_URL_PATH].len > 0) {
|
||||
path = url.substr(parser.fields[HV_URL_PATH].off);
|
||||
}
|
||||
// query
|
||||
if (parser.fields[HV_URL_QUERY].len > 0) {
|
||||
parse_query_params(url.c_str()+parser.fields[HV_URL_QUERY].off, query_params);
|
||||
}
|
||||
}
|
||||
|
||||
std::string HttpRequest::Path() {
|
||||
const char* s = path.c_str();
|
||||
const char* e = s;
|
||||
while (*e && *e != '?' && *e != '#') ++e;
|
||||
return HUrl::unescape(std::string(s, e));
|
||||
}
|
||||
|
||||
void HttpRequest::FillHost(const char* host, int port) {
|
||||
if (headers.find("Host") == headers.end()) {
|
||||
if (port == 0 ||
|
||||
port == DEFAULT_HTTP_PORT ||
|
||||
port == DEFAULT_HTTPS_PORT) {
|
||||
headers["Host"] = host;
|
||||
} else {
|
||||
headers["Host"] = asprintf("%s:%d", host, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::SetHost(const char* host, int port) {
|
||||
this->host = host;
|
||||
this->port = port;
|
||||
FillHost(host, port);
|
||||
}
|
||||
|
||||
void HttpRequest::SetProxy(const char* host, int port) {
|
||||
this->scheme = "http";
|
||||
this->host = host;
|
||||
this->port = port;
|
||||
proxy = 1;
|
||||
}
|
||||
|
||||
void HttpRequest::SetAuth(const std::string& auth) {
|
||||
SetHeader("Authorization", auth);
|
||||
}
|
||||
|
||||
void HttpRequest::SetBasicAuth(const std::string& username, const std::string& password) {
|
||||
std::string strAuth = hv::asprintf("%s:%s", username.c_str(), password.c_str());
|
||||
std::string base64Auth = hv::Base64Encode((const unsigned char*)strAuth.c_str(), strAuth.size());
|
||||
SetAuth(std::string("Basic ") + base64Auth);
|
||||
}
|
||||
|
||||
void HttpRequest::SetBearerTokenAuth(const std::string& token) {
|
||||
SetAuth(std::string("Bearer ") + token);
|
||||
}
|
||||
|
||||
std::string HttpRequest::Dump(bool is_dump_headers, bool is_dump_body) {
|
||||
ParseUrl();
|
||||
|
||||
std::string str;
|
||||
str.reserve(MAX(512, path.size() + 128));
|
||||
// GET / HTTP/1.1\r\n
|
||||
str = asprintf("%s %s HTTP/%d.%d\r\n",
|
||||
http_method_str(method),
|
||||
proxy ? url.c_str() : path.c_str(),
|
||||
(int)http_major, (int)http_minor);
|
||||
if (is_dump_headers) {
|
||||
DumpHeaders(str);
|
||||
}
|
||||
str += "\r\n";
|
||||
if (is_dump_body) {
|
||||
DumpBody(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void HttpRequest::SetRange(long from, long to) {
|
||||
SetHeader("Range", hv::asprintf("bytes=%ld-%ld", from, to));
|
||||
}
|
||||
|
||||
bool HttpRequest::GetRange(long& from, long& to) {
|
||||
auto iter = headers.find("Range");
|
||||
if (iter != headers.end()) {
|
||||
sscanf(iter->second.c_str(), "bytes=%ld-%ld", &from, &to);
|
||||
return true;
|
||||
}
|
||||
from = to = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpResponse::HttpResponse() : HttpMessage() {
|
||||
type = HTTP_RESPONSE;
|
||||
Init();
|
||||
}
|
||||
|
||||
void HttpResponse::Init() {
|
||||
status_code = HTTP_STATUS_OK;
|
||||
}
|
||||
|
||||
void HttpResponse::Reset() {
|
||||
HttpMessage::Reset();
|
||||
Init();
|
||||
}
|
||||
|
||||
std::string HttpResponse::Dump(bool is_dump_headers, bool is_dump_body) {
|
||||
char c_str[256] = {0};
|
||||
std::string str;
|
||||
str.reserve(512);
|
||||
// HTTP/1.1 200 OK\r\n
|
||||
snprintf(c_str, sizeof(c_str), "HTTP/%d.%d %d %s\r\n",
|
||||
(int)http_major, (int)http_minor,
|
||||
(int)status_code, http_status_str(status_code));
|
||||
str = c_str;
|
||||
if (is_dump_headers) {
|
||||
if (*s_date) {
|
||||
headers["Date"] = s_date;
|
||||
} else {
|
||||
headers["Date"] = gmtime_fmt(time(NULL), c_str);
|
||||
}
|
||||
DumpHeaders(str);
|
||||
}
|
||||
str += "\r\n";
|
||||
if (is_dump_body) {
|
||||
DumpBody(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void HttpResponse::SetRange(long from, long to, long total) {
|
||||
SetHeader("Content-Range", hv::asprintf("bytes %ld-%ld/%ld", from, to, total));
|
||||
}
|
||||
|
||||
bool HttpResponse::GetRange(long& from, long& to, long& total) {
|
||||
auto iter = headers.find("Content-Range");
|
||||
if (iter != headers.end()) {
|
||||
sscanf(iter->second.c_str(), "bytes %ld-%ld/%ld", &from, &to, &total);
|
||||
return true;
|
||||
}
|
||||
from = to = total = 0;
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user