866 lines
22 KiB
C++
866 lines
22 KiB
C++
|
#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;
|
||
|
}
|