mirror of
https://github.com/rajkosto/mxoemu
synced 2026-08-14 02:26:05 -04:00
519 lines
11 KiB
C++
519 lines
11 KiB
C++
/** \file HTTPSocket.cpp
|
|
** \date 2004-04-06
|
|
** \author grymse@alhem.net
|
|
**/
|
|
/*
|
|
Copyright (C) 2004-2010 Anders Hedstrom
|
|
|
|
This library is made available under the terms of the GNU GPL, with
|
|
the additional exemption that compiling, linking, and/or using OpenSSL
|
|
is allowed.
|
|
|
|
If you would like to use this library in a closed-source application,
|
|
a separate license agreement is available. For information about
|
|
the closed-source license agreement for the C++ sockets library,
|
|
please visit http://www.alhem.net/Sockets/license.html and/or
|
|
email license@alhem.net.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
#ifdef _MSC_VER
|
|
#pragma warning(disable:4786)
|
|
#endif
|
|
#include "HTTPSocket.h"
|
|
#include "Parse.h"
|
|
#include "ISocketHandler.h"
|
|
#include <stdarg.h>
|
|
#include <cstdio>
|
|
|
|
#ifdef SOCKETS_NAMESPACE
|
|
namespace SOCKETS_NAMESPACE {
|
|
#endif
|
|
|
|
|
|
HTTPSocket::HTTPSocket(ISocketHandler& h)
|
|
:TcpSocket(h)
|
|
,m_first(true)
|
|
,m_header(true)
|
|
,m_http_version("HTTP/1.0")
|
|
,m_request(false)
|
|
,m_response(false)
|
|
,m_body_size_left(0)
|
|
,m_b_http_1_1(false)
|
|
,m_b_keepalive(false)
|
|
,m_b_chunked(false)
|
|
,m_chunk_size(0)
|
|
,m_chunk_state(0)
|
|
,m_header_count(0)
|
|
,m_max_header_count(MAX_HTTP_HEADER_COUNT)
|
|
{
|
|
SetLineProtocol();
|
|
DisableInputBuffer();
|
|
}
|
|
|
|
|
|
HTTPSocket::~HTTPSocket()
|
|
{
|
|
}
|
|
|
|
|
|
void HTTPSocket::OnRawData(const char *buf,size_t len)
|
|
{
|
|
if (!m_header)
|
|
{
|
|
if (m_b_chunked)
|
|
{
|
|
size_t ptr = 0;
|
|
while (ptr < len)
|
|
{
|
|
switch (m_chunk_state)
|
|
{
|
|
case 4:
|
|
while (ptr < len && (m_chunk_line.size() < 2 || m_chunk_line.substr(m_chunk_line.size() - 2) != "\r\n"))
|
|
m_chunk_line += buf[ptr++];
|
|
if (m_chunk_line.size() > 1 && m_chunk_line.substr(m_chunk_line.size() - 2) == "\r\n")
|
|
{
|
|
OnDataComplete();
|
|
// prepare for next request(or response)
|
|
m_b_chunked = false;
|
|
SetLineProtocol( true );
|
|
m_first = true;
|
|
m_header = true;
|
|
m_body_size_left = 0;
|
|
if (len - ptr > 0)
|
|
{
|
|
char tmp[TCP_BUFSIZE_READ];
|
|
memcpy(tmp, buf + ptr, len - ptr);
|
|
tmp[len - ptr] = 0;
|
|
OnRead( tmp, len - ptr );
|
|
ptr = len;
|
|
}
|
|
}
|
|
break;
|
|
case 0:
|
|
while (ptr < len && (m_chunk_line.size() < 2 || m_chunk_line.substr(m_chunk_line.size() - 2) != "\r\n"))
|
|
m_chunk_line += buf[ptr++];
|
|
if (m_chunk_line.size() > 1 && m_chunk_line.substr(m_chunk_line.size() - 2) == "\r\n")
|
|
{
|
|
m_chunk_line.resize(m_chunk_line.size() - 2);
|
|
Parse pa(m_chunk_line, ";");
|
|
std::string size_str = pa.getword();
|
|
m_chunk_size = Utility::hex2unsigned(size_str);
|
|
if (!m_chunk_size)
|
|
{
|
|
m_chunk_state = 4;
|
|
m_chunk_line = "";
|
|
}
|
|
else
|
|
{
|
|
m_chunk_state = 1;
|
|
m_chunk_line = "";
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
{
|
|
size_t left = len - ptr;
|
|
size_t sz = m_chunk_size < left ? m_chunk_size : left;
|
|
OnData(buf + ptr, sz);
|
|
m_chunk_size -= sz;
|
|
ptr += sz;
|
|
if (!m_chunk_size)
|
|
{
|
|
m_chunk_state = 2;
|
|
}
|
|
}
|
|
break;
|
|
case 2: // skip CR
|
|
ptr++;
|
|
m_chunk_state = 3;
|
|
break;
|
|
case 3: // skip LF
|
|
ptr++;
|
|
m_chunk_state = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
if (!m_b_http_1_1 || !m_b_keepalive)
|
|
{
|
|
OnData(buf, len);
|
|
/*
|
|
request is HTTP/1.0 _or_ HTTP/1.1 and not keep-alive
|
|
|
|
This means we destroy the connection after the response has been delivered,
|
|
hence no need to reset all internal state variables for a new incoming
|
|
request.
|
|
*/
|
|
m_body_size_left -= len;
|
|
if (!m_body_size_left)
|
|
{
|
|
OnDataComplete();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
size_t sz = m_body_size_left < len ? m_body_size_left : len;
|
|
OnData(buf, sz);
|
|
m_body_size_left -= sz;
|
|
if (!m_body_size_left)
|
|
{
|
|
OnDataComplete();
|
|
// prepare for next request(or response)
|
|
SetLineProtocol( true );
|
|
m_first = true;
|
|
m_header = true;
|
|
m_body_size_left = 0;
|
|
if (len - sz > 0)
|
|
{
|
|
char tmp[TCP_BUFSIZE_READ];
|
|
memcpy(tmp, buf + sz, len - sz);
|
|
tmp[len - sz] = 0;
|
|
OnRead( tmp, len - sz );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void HTTPSocket::OnLine(const std::string& line)
|
|
{
|
|
if (m_first)
|
|
{
|
|
Parse pa(line);
|
|
std::string str = pa.getword();
|
|
if (str.size() > 4 && Utility::ToLower(str.substr(0,5)) == "http/") // response
|
|
{
|
|
m_http_version = str;
|
|
m_status = pa.getword();
|
|
m_status_text = pa.getrest();
|
|
m_response = true;
|
|
}
|
|
else // request
|
|
{
|
|
m_method = str;
|
|
m_url = pa.getword();
|
|
size_t spl = m_url.find("?");
|
|
if (spl != std::string::npos)
|
|
{
|
|
m_uri = m_url.substr(0,spl);
|
|
m_query_string = m_url.substr(spl + 1);
|
|
}
|
|
else
|
|
{
|
|
m_uri = m_url;
|
|
m_query_string = "";
|
|
}
|
|
m_http_version = pa.getword();
|
|
m_b_http_1_1 = m_http_version.size() > 4 && m_http_version.substr(4) == "/1.1";
|
|
m_b_keepalive = m_b_http_1_1;
|
|
m_request = true;
|
|
}
|
|
m_first = false;
|
|
OnFirst();
|
|
return;
|
|
}
|
|
if (!line.size())
|
|
{
|
|
if (m_body_size_left || !m_b_http_1_1 || !m_b_keepalive || m_b_chunked)
|
|
{
|
|
SetLineProtocol(false);
|
|
m_header = false;
|
|
}
|
|
OnHeaderComplete();
|
|
if (!m_body_size_left && !m_b_chunked)
|
|
{
|
|
OnDataComplete();
|
|
}
|
|
return;
|
|
}
|
|
Parse pa(line,":");
|
|
std::string key = pa.getword();
|
|
std::string value = pa.getrest();
|
|
OnHeader(key,value);
|
|
if (Utility::ToLower(key) == "content-length")
|
|
{
|
|
m_body_size_left = atol(value.c_str());
|
|
}
|
|
if (m_b_http_1_1 && Utility::ToLower(key) == "connection")
|
|
{
|
|
m_b_keepalive = Utility::ToLower(value) != "close";
|
|
}
|
|
if (Utility::ToLower(key) == "transfer-encoding" && Utility::ToLower(value) == "chunked")
|
|
{
|
|
m_b_chunked = true;
|
|
}
|
|
/* If remote end tells us to keep connection alive, and we're operating
|
|
in http/1.1 mode (not http/1.0 mode), then we mark the socket to be
|
|
retained. */
|
|
#ifdef ENABLE_POOL
|
|
if (m_b_http_1_1 && m_b_keepalive)
|
|
{
|
|
SetRetain();
|
|
}
|
|
#endif
|
|
if (m_header_count++ > m_max_header_count)
|
|
{
|
|
SetCloseAndDelete();
|
|
Handler().LogError(this, "OnLine", m_header_count, "http header count exceeds builtin limit of (" + Utility::l2string(m_max_header_count) + ")", LOG_LEVEL_FATAL);
|
|
}
|
|
}
|
|
|
|
|
|
void HTTPSocket::SendResponse()
|
|
{
|
|
std::string msg;
|
|
msg = m_http_version + " " + m_status + " " + m_status_text + "\r\n";
|
|
for (string_m::iterator it = m_response_header.begin(); it != m_response_header.end(); ++it)
|
|
{
|
|
std::string key = (*it).first;
|
|
std::string val = (*it).second;
|
|
msg += key + ": " + val + "\r\n";
|
|
}
|
|
for (std::list<std::pair<std::string, std::string> >::iterator it2 = m_response_header_append.begin(); it2 != m_response_header_append.end(); ++it2)
|
|
{
|
|
msg += it2 -> first + ": " + it2 -> second + "\r\n";
|
|
}
|
|
msg += "\r\n";
|
|
Send( msg );
|
|
}
|
|
|
|
|
|
void HTTPSocket::AddResponseHeader(const std::string& header, const char *format, ...)
|
|
{
|
|
char slask[8192]; // temporary for vsprintf / vsnprintf
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
vsnprintf(slask, sizeof(slask), format, ap);
|
|
va_end(ap);
|
|
|
|
m_response_header[header] = slask;
|
|
}
|
|
|
|
|
|
void HTTPSocket::SendRequest()
|
|
{
|
|
std::string msg;
|
|
msg = m_method + " " + m_url + " " + m_http_version + "\r\n";
|
|
for (string_m::iterator it = m_response_header.begin(); it != m_response_header.end(); ++it)
|
|
{
|
|
std::string key = (*it).first;
|
|
std::string val = (*it).second;
|
|
msg += key + ": " + val + "\r\n";
|
|
}
|
|
msg += "\r\n";
|
|
Send( msg );
|
|
}
|
|
|
|
|
|
std::string HTTPSocket::MyUseragent()
|
|
{
|
|
std::string version = "C++Sockets/";
|
|
#ifdef _VERSION
|
|
version += _VERSION;
|
|
#endif
|
|
return version;
|
|
}
|
|
|
|
|
|
void HTTPSocket::Reset()
|
|
{
|
|
m_first = true;
|
|
m_header = true;
|
|
m_request = false;
|
|
m_response = false;
|
|
SetLineProtocol(true);
|
|
while (m_response_header.size())
|
|
{
|
|
string_m::iterator it = m_response_header.begin();
|
|
m_response_header.erase(it);
|
|
}
|
|
while (m_response_header_append.size())
|
|
{
|
|
std::list<std::pair<std::string, std::string> >::iterator it = m_response_header_append.begin();
|
|
m_response_header_append.erase(it);
|
|
}
|
|
m_header_count = 0;
|
|
|
|
}
|
|
|
|
|
|
const std::string& HTTPSocket::GetMethod()
|
|
{
|
|
return m_method;
|
|
}
|
|
|
|
|
|
void HTTPSocket::SetMethod(const std::string& x)
|
|
{
|
|
m_method = x;
|
|
}
|
|
|
|
|
|
const std::string& HTTPSocket::GetUrl()
|
|
{
|
|
return m_url;
|
|
}
|
|
|
|
|
|
void HTTPSocket::SetUrl(const std::string& x)
|
|
{
|
|
m_url = x;
|
|
}
|
|
|
|
|
|
const std::string& HTTPSocket::GetUri()
|
|
{
|
|
return m_uri;
|
|
}
|
|
|
|
|
|
const std::string& HTTPSocket::GetQueryString()
|
|
{
|
|
return m_query_string;
|
|
}
|
|
|
|
|
|
const std::string& HTTPSocket::GetHttpVersion()
|
|
{
|
|
return m_http_version;
|
|
}
|
|
|
|
|
|
const std::string& HTTPSocket::GetStatus()
|
|
{
|
|
return m_status;
|
|
}
|
|
|
|
|
|
const std::string& HTTPSocket::GetStatusText()
|
|
{
|
|
return m_status_text;
|
|
}
|
|
|
|
|
|
bool HTTPSocket::IsRequest()
|
|
{
|
|
return m_request;
|
|
}
|
|
|
|
|
|
bool HTTPSocket::IsResponse()
|
|
{
|
|
return m_response;
|
|
}
|
|
|
|
|
|
void HTTPSocket::SetHttpVersion(const std::string& x)
|
|
{
|
|
m_http_version = x;
|
|
}
|
|
|
|
|
|
void HTTPSocket::SetStatus(const std::string& x)
|
|
{
|
|
m_status = x;
|
|
}
|
|
|
|
|
|
void HTTPSocket::SetStatusText(const std::string& x)
|
|
{
|
|
m_status_text = x;
|
|
}
|
|
|
|
|
|
void HTTPSocket::AddResponseHeader(const std::string& x,const std::string& y)
|
|
{
|
|
m_response_header[x] = y;
|
|
}
|
|
|
|
|
|
void HTTPSocket::AppendResponseHeader(const std::string& x,const std::string& y)
|
|
{
|
|
m_response_header_append.push_back(std::pair<std::string, std::string>(x,y));
|
|
}
|
|
|
|
|
|
void HTTPSocket::SetUri(const std::string& x)
|
|
{
|
|
m_uri = x;
|
|
}
|
|
|
|
|
|
void HTTPSocket::url_this(const std::string& url_in,std::string& protocol,std::string& host,port_t& port,std::string& url,std::string& file)
|
|
{
|
|
Parse pa(url_in,"/");
|
|
protocol = pa.getword(); // http
|
|
if (!strcasecmp(protocol.c_str(), "https:"))
|
|
{
|
|
#ifdef HAVE_OPENSSL
|
|
EnableSSL();
|
|
#else
|
|
Handler().LogError(this, "url_this", -1, "SSL not available", LOG_LEVEL_WARNING);
|
|
#endif
|
|
port = 443;
|
|
}
|
|
else
|
|
{
|
|
port = 80;
|
|
}
|
|
host = pa.getword();
|
|
if (strstr(host.c_str(),":"))
|
|
{
|
|
Parse pa(host,":");
|
|
pa.getword(host);
|
|
port = static_cast<port_t>(pa.getvalue());
|
|
}
|
|
url = "/" + pa.getrest();
|
|
{
|
|
Parse pa(url,"/");
|
|
std::string tmp = pa.getword();
|
|
while (tmp.size())
|
|
{
|
|
file = tmp;
|
|
tmp = pa.getword();
|
|
}
|
|
}
|
|
} // url_this
|
|
|
|
|
|
bool HTTPSocket::ResponseHeaderIsSet(const std::string& name)
|
|
{
|
|
string_m::iterator it = m_response_header.find( name );
|
|
if (it != m_response_header.end())
|
|
{
|
|
return true;
|
|
}
|
|
std::list<std::pair<std::string, std::string> >::iterator it2;
|
|
for (it2 = m_response_header_append.begin(); it2 != m_response_header_append.end(); ++it2)
|
|
{
|
|
std::pair<std::string, std::string>& ref = *it2;
|
|
if (!strcasecmp(ref.first.c_str(), name.c_str()) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
#ifdef SOCKETS_NAMESPACE
|
|
}
|
|
#endif
|
|
|
|
|