mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
1025 lines
28 KiB
C++
1025 lines
28 KiB
C++
#include "Web.h"
|
|
|
|
NAMESPACE_UPP
|
|
|
|
#define LLOG(x) // RLOG(x)
|
|
|
|
#define SLOWWRITE 4 // KB/s, comment out to turn off
|
|
|
|
String MIMECharsetName(byte charset)
|
|
{
|
|
if(charset == CHARSET_DEFAULT)
|
|
charset = GetDefaultCharset();
|
|
switch(charset) {
|
|
case CHARSET_ISO8859_1: return "ISO-8859-1";
|
|
case CHARSET_ISO8859_2: return "ISO-8859-2";
|
|
case CHARSET_ISO8859_3: return "ISO-8859-3";
|
|
case CHARSET_ISO8859_4: return "ISO-8859-4";
|
|
case CHARSET_ISO8859_5: return "ISO-8859-5";
|
|
case CHARSET_ISO8859_6: return "ISO-8859-6";
|
|
case CHARSET_ISO8859_7: return "ISO-8859-7";
|
|
case CHARSET_ISO8859_8: return "ISO-8859-8";
|
|
case CHARSET_ISO8859_9: return "ISO-8859-9";
|
|
case CHARSET_ISO8859_10: return "ISO-8859-10";
|
|
case CHARSET_ISO8859_13: return "ISO-8859-13";
|
|
case CHARSET_ISO8859_14: return "ISO-8859-14";
|
|
case CHARSET_ISO8859_15: return "ISO-8859-15";
|
|
case CHARSET_ISO8859_16: return "ISO-8859-16";
|
|
case CHARSET_WIN1250: return "windows-1250";
|
|
case CHARSET_WIN1251: return "windows-1251";
|
|
case CHARSET_WIN1252: return "windows-1252";
|
|
case CHARSET_WIN1253: return "windows-1253";
|
|
case CHARSET_WIN1254: return "windows-1254";
|
|
case CHARSET_WIN1255: return "windows-1255";
|
|
case CHARSET_WIN1256: return "windows-1256";
|
|
case CHARSET_WIN1257: return "windows-1257";
|
|
case CHARSET_WIN1258: return "windows-1258";
|
|
// case CHARSET_KOI8_R:
|
|
// case CHARSET_CP852:
|
|
// case CHARSET_MJK:
|
|
case CHARSET_TOASCII: return "us-ascii";
|
|
case CHARSET_UTF8: return "UTF-8";
|
|
// case CHARSET_UNICODE:
|
|
default: return Null;
|
|
}
|
|
}
|
|
|
|
HttpRequest::HttpRequest(HttpServer& server, pick_ Socket& _socket, HttpQuery query_)
|
|
: server(server), socket(_socket), query(query_)
|
|
, request_ticks(GetTickCount())
|
|
{
|
|
socket.Linger(1000);
|
|
#ifdef PLATFORM_WIN32
|
|
event.Read(socket);
|
|
#endif
|
|
String auth = query.GetString("$$AUTHORIZATION");
|
|
if(!IsNull(auth)) {
|
|
String dec = UrlDecode(auth);
|
|
if(dec.GetLength() >= 6 && !MemICmp(dec, "basic ", 6)) {
|
|
const char *p = dec.GetIter(6);
|
|
while(*p == ' ')
|
|
p++;
|
|
String b6d = Base64Decode(p);
|
|
const char *un = b6d, *unp = un;
|
|
while(*unp && *unp != ':')
|
|
unp++;
|
|
query.Set("$$AUTH_TYPE", "BASIC");
|
|
query.Set("$$AUTH_USERNAME", String(un, unp));
|
|
if(*unp == ':')
|
|
unp++;
|
|
query.Set("$$AUTH_PASSWORD", String(unp));
|
|
}
|
|
else if(dec.GetLength() >= 7 && !MemICmp(dec, "digest ", 7)) {
|
|
query.Set("$$AUTH_TYPE", "DIGEST");
|
|
const char *p = dec.GetIter(7);
|
|
while(*p)
|
|
if((byte)*p <= ' ' || *p == ',')
|
|
p++;
|
|
else {
|
|
const char *b = p;
|
|
while(*p && *p != '=')
|
|
p++;
|
|
const char *e = p;
|
|
while(e > b && (byte)e[-1] <= ' ')
|
|
e--;
|
|
String key(b, e);
|
|
String value;
|
|
if(*p == '=') {
|
|
p++;
|
|
if(*p == '\"') {
|
|
b = ++p;
|
|
while(*p && *p != '\"')
|
|
if(*p != '\\' || *++p)
|
|
value.Cat(*p++);
|
|
if(*p == '\"')
|
|
p++;
|
|
}
|
|
else {
|
|
while((byte)*p >= ' ' && *p != ',')
|
|
value.Cat(*p++);
|
|
}
|
|
}
|
|
query.Set("$$AUTH_" + key, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
LLOG("HttpRequest:\n" << query);
|
|
}
|
|
|
|
void HttpRequest::LogTime(const char *s, int level)
|
|
{
|
|
server.LogTime(NFormat("(ID:%d)%s", (int)(uintptr_t)this, s), level);
|
|
}
|
|
|
|
int HttpRequest::GetDuration() const
|
|
{
|
|
return GetTickCount() - request_ticks;
|
|
}
|
|
|
|
void HttpRequest::Write(String header, String body)
|
|
{
|
|
Write(header, body, 200, "OK");
|
|
}
|
|
|
|
void HttpRequest::Write(String header, String body, int result_code, String result_text)
|
|
{
|
|
int duration = GetDuration();
|
|
if(IsNull(header))
|
|
header = query.GetString("$$DEFAULT_HEADER");
|
|
|
|
LogTime(NFormat("HttpRequest::Write(%d): done in %d msecs: %d bytes\n%s",
|
|
result_code, duration, body.GetLength(), header), 2);
|
|
|
|
server.AddRequest(GetTickCount() - request_ticks);
|
|
|
|
String out;
|
|
int httpver = query.GetInt("$$HTTP_VERSION");
|
|
if(httpver >= 1000) { // send status line & headers
|
|
out << NFormat("HTTP/%d.%d %d %s\r\n", httpver / 1000, httpver % 1000, result_code, result_text)
|
|
<< header
|
|
<< "Content-Length: " << body.GetLength() << "\r\n"
|
|
"\r\n";
|
|
}
|
|
if(query.GetString("$$METHOD") != "HEAD")
|
|
out.Cat(body);
|
|
// out.Cat('\0', 8192);
|
|
ASSERT(socket.IsOpen());
|
|
server.AddWrite(socket, out);
|
|
LogTime(NFormat("HttpRequest::Write(%d): %d bytes added to delayed write list",
|
|
result_code, out.GetLength()), 2);
|
|
}
|
|
|
|
void HttpRequest::Redirect(String url)
|
|
{
|
|
Htmls body = NFormat(t_("If you're not redirected automatically please use %s."), HtmlLink(url) / t_("this link"));
|
|
body = HtmlPage(t_("Redirection to another web address"), body);
|
|
String header;
|
|
header << "Location: " << url << "\r\n";
|
|
Write(header, body, 302, "Object Moved");
|
|
/*
|
|
String out;
|
|
if(query.GetInt("$$HTTP_VERSION") >= 1000) { // send status line & headers
|
|
out <<
|
|
"HTTP/1.0 302 Object Moved\r\n"
|
|
<< "Location: " << url << "\r\n"
|
|
<< "Content-Length: " << body.GetLength() << "\r\n"
|
|
"\r\n";
|
|
}
|
|
out.Cat(body);
|
|
server.AddWrite(socket, out);
|
|
LogTime(NFormat("HttpRequest::Redirect() added to delayed write list: %s", url), 2);
|
|
*/
|
|
}
|
|
|
|
void HttpRequest::Error(String err)
|
|
{
|
|
LogTime(NFormat("error after %d msecs: %s", GetDuration(), err), 0);
|
|
Htmls body = GetHttpErrorPage(query, err, server.IsShowQuery());
|
|
Write(HttpContentType(HttpTextHtml()), body);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// HttpServer::
|
|
|
|
HttpServer::SocketWrite::SocketWrite(Socket socket_, String data_, int ticks_)
|
|
: socket(socket_), data(data_), done(0), ticks(ticks_)
|
|
{
|
|
#ifdef PLATFORM_WIN32
|
|
sock_event.Write(socket);
|
|
#endif
|
|
}
|
|
|
|
HttpServer::HttpServer()
|
|
{
|
|
show_headers = true;
|
|
server_port = 0;
|
|
default_header = HttpContentType(HttpTextHtml());
|
|
start_time = GetSysTime();
|
|
hit_count = 0;
|
|
total_response_msec = 0;
|
|
trailing_sum = 0;
|
|
trailing_count = 0;
|
|
max_post_size = DEFAULT_MAX_POST_SIZE;
|
|
max_request_time = DEFAULT_MAX_REQUEST_TIME;
|
|
}
|
|
|
|
void HttpServer::Logging(const char *log, int mls)
|
|
{
|
|
max_log_size = mls;
|
|
logfile = log;
|
|
if(logfile.IsEmpty())
|
|
logfile = ConfigFile("requests.txt");
|
|
}
|
|
|
|
void HttpServer::Log(const char *s, int level)
|
|
{
|
|
enum { MAX_LOG_SIZE = 500000 };
|
|
WhenLog(s, level);
|
|
if(logfile.IsEmpty())
|
|
return;
|
|
// VppLog() << s;
|
|
FileStream fs;
|
|
if(!fs.Open(logfile, FileStream::APPEND) && !fs.Open(logfile, FileStream::CREATE))
|
|
return;
|
|
fs.Put(s);
|
|
int len = (int)fs.GetSize();
|
|
fs.Close();
|
|
if(len < max_log_size)
|
|
return;
|
|
String bak = ForceExt(logfile, "") + ".old" + GetFileExt(logfile);
|
|
FileDelete(bak);
|
|
FileMove(logfile, bak);
|
|
/*
|
|
#if defined(PLATFORM_WIN32)
|
|
DeleteFile(bak);
|
|
MoveFile(logfile, bak);
|
|
#elif defined(PLATFORM_POSIX)
|
|
unlink(bak);
|
|
rename(logfile, bak);
|
|
#else
|
|
#error Unsupported platform
|
|
#endif
|
|
*/
|
|
if(fs.Open(logfile, FileStream::APPEND)) {
|
|
fs << "[" << Format(GetSysTime()) << "] old '" << logfile << "' backed up (" << len << " bytes)\n";
|
|
fs.Close();
|
|
}
|
|
}
|
|
|
|
void HttpServer::LogTime(const char *s, int level)
|
|
{
|
|
String str;
|
|
Log(str << '[' << Format(GetSysTime()) << " @ " << int(msecs() % 10000u) << "]: " << s << '\n', level);
|
|
}
|
|
|
|
bool HttpServer::Open(int port, int listen_count)
|
|
{
|
|
LogTime(NFormat("HttpServer::Open(port = %d)", port), 1);
|
|
if(!ServerSocket(socket, server_port = port, true, listen_count))
|
|
return false;
|
|
#ifdef PLATFORM_WIN32
|
|
sock_event.Accept(socket);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool HttpServer::Reopen()
|
|
{
|
|
Close();
|
|
return Open(server_port);
|
|
}
|
|
|
|
void HttpServer::Close()
|
|
{
|
|
LogTime("HttpServer::Close()", 1);
|
|
socket.Close();
|
|
}
|
|
|
|
bool HttpServer::DelayedWrite()
|
|
{
|
|
static const int MAX_SEND_CHUNK = 65536;
|
|
// if(!delayed_writes.IsEmpty())
|
|
// LogTime("HttpServer::DelayedWrite, #writes = " + FormatInt(delayed_writes.GetCount()), 2);
|
|
for(int i = delayed_writes.GetCount(); --i >= 0;) {
|
|
SocketWrite& sw = delayed_writes[i];
|
|
if(IsNull(sw.data)) {
|
|
sw.socket.ReadCount(1, 0);
|
|
bool rm = sw.socket.IsEof();
|
|
if(!rm && sw.socket.IsError()) {
|
|
LogTime("HttpServer::DelayedWrite: shutdown error: " + sw.socket.GetErrorText(), 0);
|
|
rm = true;
|
|
}
|
|
if(!rm && msecs(sw.ticks) >= max_request_time) {
|
|
LogTime("HttpServer::DelayedWrite: timed out waiting for socket shutdown", 0);
|
|
rm = true;
|
|
}
|
|
if(rm) {
|
|
sw.socket.Close();
|
|
delayed_writes.Remove(i);
|
|
}
|
|
continue;
|
|
}
|
|
// int part = sw.data.GetLength() - sw.done; Mirek:unused
|
|
int count = max(sw.socket.WriteWait(sw.data.Begin() + sw.done,
|
|
min(sw.data.GetLength() - sw.done, MAX_SEND_CHUNK), 0), 0);
|
|
if(sw.socket.IsError()) {
|
|
if(sw.socket.GetErrorCode() == SOCKERR(ENOBUFS)) {
|
|
sw.socket.ClearError();
|
|
continue;
|
|
}
|
|
LogTime(NFormat("HttpServer::DelayedWrite(): %s", Socket::GetErrorText()), 0);
|
|
delayed_writes.Remove(i);
|
|
continue;
|
|
}
|
|
if(count > 0)
|
|
LogTime(NFormat("HttpServer::DelayedWrite(): %d bytes written on %d", count, sw.socket.GetNumber()), 2);
|
|
if((sw.done += count) >= sw.data.GetLength()) {
|
|
LogTime(NFormat("HttpServer::DelayedWrite(): finished %d (%d left)",
|
|
sw.socket.GetNumber(), delayed_writes.GetCount() - 1), 2);
|
|
sw.ticks = msecs();
|
|
sw.data = Null;
|
|
sw.socket.StopWrite();
|
|
}
|
|
else if(msecs(sw.ticks) >= max_request_time) {
|
|
LogTime(NFormat("HttpServer::DelayedWrite(): timed out after sending %d out of %d bytes",
|
|
sw.done, sw.data.GetLength()), 0);
|
|
// sw.socket.StopWrite();
|
|
sw.socket.Close();
|
|
delayed_writes.Remove(i);
|
|
}
|
|
}
|
|
return !delayed_writes.IsEmpty();
|
|
}
|
|
|
|
HttpServer *HttpServer::Wait(const Vector<HttpServer *>& list, int msec)
|
|
{
|
|
Vector<Socket *> read, write;
|
|
for(int i = 0; i < list.GetCount(); i++) {
|
|
list[i]->GetReadSockets(read);
|
|
list[i]->GetWriteSockets(write);
|
|
}
|
|
if(msec > 0) {
|
|
LLOG(String() << "HttpServer::Wait(" << list.GetCount() << " servers for " << msec
|
|
<< " msecs) -> Socket::Wait(#read = " << read.GetCount() << ", #write = " << write.GetCount() << ")");
|
|
if(read.IsEmpty() && write.IsEmpty() || !Socket::Wait(read, write, msec))
|
|
return NULL;
|
|
}
|
|
for(int i = 0; i < list.GetCount(); i++)
|
|
if(list[i]->Accept())
|
|
return list[i];
|
|
return NULL;
|
|
}
|
|
|
|
bool HttpServer::Wait(int msec)
|
|
{
|
|
// LogTime(NFormat("wait(%d msec)", msec), 2);
|
|
Vector<HttpServer *> list;
|
|
list.SetCount(1);
|
|
list[0] = this;
|
|
bool ok = !!Wait(list, msec);
|
|
// LogTime(NFormat("//wait -> %d", ok), 2);
|
|
return ok;
|
|
}
|
|
|
|
void HttpServer::GetReadSockets(Vector<Socket *>& sockets)
|
|
{
|
|
if(socket.IsOpen())
|
|
sockets.Add(&socket);
|
|
if(connection.IsOpen())
|
|
sockets.Add(&connection);
|
|
}
|
|
|
|
void HttpServer::GetWriteSockets(Vector<Socket *>& sockets)
|
|
{
|
|
for(int i = 0; i < delayed_writes.GetCount(); i++)
|
|
sockets.Add(&delayed_writes[i].socket);
|
|
}
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
void HttpServer::GetWaitEvents(Vector<Event *>& events)
|
|
{
|
|
if(socket.IsOpen())
|
|
events.Add(&sock_event);
|
|
if(connection.IsOpen())
|
|
events.Add(&conn_event);
|
|
for(int i = 0; i < delayed_writes.GetCount(); i++)
|
|
events.Add(&delayed_writes[i].sock_event);
|
|
}
|
|
#endif
|
|
|
|
bool HttpServer::Accept()
|
|
{
|
|
if(connection.IsOpen())
|
|
return true;
|
|
if(socket.Accept(connection, &ipaddr, true, 0)) {
|
|
connection.NoBlock();
|
|
#ifdef PLATFORM_WIN32
|
|
conn_event.Read(connection);
|
|
#endif
|
|
first_line = Null;
|
|
sapi_request = Null;
|
|
post_data = Null;
|
|
headers_length = 0;
|
|
request_version = 0;
|
|
post_length = 0;
|
|
request_time = GetTickCount();
|
|
request_state = RS_FIRST;
|
|
request_query = HttpQuery();
|
|
LogTime(NFormat("HttpServer::Accept: accepted socket %d from %d.%d.%d.%d", connection.GetNumber(),
|
|
int(ipaddr >> 24) & 0xff, int(ipaddr >> 16) & 0xff, int(ipaddr >> 8) & 0xff, int(ipaddr >> 0) & 0xff), 2);
|
|
return true;
|
|
}
|
|
if(socket.IsError()) {
|
|
LogTime(NFormat("HttpServer::Accept: error %s", Socket::GetErrorText()), 0);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define FOURCHAR(a,b,c,d) ((int(a)) + (int(b) * 0x100) + (int(c) * 0x10000) + (int(d) * 0x1000000))
|
|
|
|
One<HttpRequest> HttpServer::GetRequest()
|
|
{
|
|
Socket conn = connection;
|
|
connection.Clear();
|
|
|
|
if(!conn.IsOpen())
|
|
return NULL;
|
|
if(conn.IsError()) {
|
|
LogTime(NFormat("HttpServer::GetRequest: error on socket %d: %s", conn.GetNumber(), Socket::GetErrorText()), 2);
|
|
return NULL;
|
|
}
|
|
int rtm = GetTickCount() - request_time;
|
|
if(rtm >= max_request_time) {
|
|
LogTime("request time elapsed, request trashed", 1);
|
|
return NULL;
|
|
}
|
|
String data = conn.Read(0, max_post_size);
|
|
if(data.IsVoid()) {
|
|
LogTime(NFormat("Socket %d closed by client.", conn.GetNumber()), 1);
|
|
return NULL;
|
|
}
|
|
if(conn.IsError()) {
|
|
LogTime(NFormat("Request transmission error, request trashed (after %d bytes)", data.GetLength()), 1);
|
|
return NULL;
|
|
}
|
|
LogTime(NFormat("%d bytes read: %s", data.GetLength(), data), 2);
|
|
const char *p = data.Begin(), *e = data.End();
|
|
while(p < e) {
|
|
const char *b = p;
|
|
switch(request_state) {
|
|
case RS_FIRST:
|
|
if(first_line.GetLength() < 4) {
|
|
int left = min<int>((int)(uintptr_t)(e - p), 4 - first_line.GetLength());
|
|
first_line.Cat(p, left);
|
|
p += left;
|
|
break;
|
|
} {
|
|
int four = Peek32le(first_line);
|
|
if(four == FOURCHAR('S', 'A', 'P', 'I')) {
|
|
if(first_line.GetLength() < 8) {
|
|
int left = min<int>((int)(uintptr_t)(e - p), 8 - first_line.GetLength());
|
|
first_line.Cat(p, left);
|
|
p += left;
|
|
break;
|
|
}
|
|
sapi_length = Peek32le(first_line.GetIter(4));
|
|
if(sapi_length < 0 || sapi_length > max_post_size) {
|
|
LogTime(NFormat("Invalid SAPI request length (%d), request trashed", sapi_length), 1);
|
|
conn.Close();
|
|
return NULL;
|
|
}
|
|
LogTime("SAPI request detected", 2);
|
|
request_state = RS_SAPI;
|
|
break;
|
|
}
|
|
if(four == FOURCHAR('S', 'T', 'A', 'T')) {
|
|
String out;
|
|
String cmdline;
|
|
#ifdef PLATFORM_WIN32
|
|
cmdline = GetCommandLine();
|
|
#endif
|
|
out << "READY " << cmdline << "\r\n";
|
|
conn.Write(out);
|
|
// conn.StopWrite();
|
|
conn.Close();
|
|
return NULL;
|
|
}
|
|
if(four != FOURCHAR('P', 'O', 'S', 'T') && four != FOURCHAR('G', 'E', 'T', ' ')
|
|
&& four != FOURCHAR('H', 'E', 'A', 'D')) {
|
|
LogTime(NFormat("Invalid HTTP request type (%s), request trashed", first_line), 1);
|
|
return NULL;
|
|
}
|
|
while(p < e && *p != '\n')
|
|
p++;
|
|
if(first_line.GetLength() + (int)(p - b) > max_post_size) {
|
|
LogTime("HTTP request length limit reached, request trashed", 1);
|
|
return NULL;
|
|
}
|
|
first_line.Cat(b, (int)(uintptr_t)(p - b));
|
|
if(p >= e)
|
|
break;
|
|
p++;
|
|
|
|
const char *r = first_line;
|
|
while(*r && *r != ' ' && *r != '\n')
|
|
r++;
|
|
if(*r == ' ') {
|
|
r++;
|
|
const char *s = r;
|
|
while(*r && *r != ' ' && *r != '?')
|
|
r++;
|
|
request_query.Set("$$PATH", UrlDecode(String(s, r)));
|
|
const char *e = r, *t = r;
|
|
while(e > s && e[-1] != '/')
|
|
if(*--e == '.')
|
|
t = e;
|
|
request_query.Set("$$TITLE", UrlDecode(String(e, t)));
|
|
if(e > s)
|
|
e--;
|
|
t = e;
|
|
while(e > s && e[-1] != '/')
|
|
e--;
|
|
request_query.Set("$$DIR", UrlDecode(String(e, t)));
|
|
if(s < r && *s == '/')
|
|
s++;
|
|
t = s;
|
|
while(s < r && *s != '/')
|
|
s++;
|
|
if(s < r)
|
|
request_query.Set("$$ROOT", UrlDecode(String(t, s)));
|
|
if(*r == '?') {
|
|
s = ++r;
|
|
while(*r && *r != ' ' && *r != '\n')
|
|
r++;
|
|
String raw_query(s, r);
|
|
request_query.Set("$$QUERY", raw_query);
|
|
request_query.Set(HttpQuery(raw_query));
|
|
}
|
|
if(!memcmp(r, " HTTP/", 6) && IsDigit(r[6])) { // HTTP version
|
|
int upper = stou(r + 6, &r);
|
|
int lower = 0;
|
|
if(*r == '.' && IsDigit(*++r))
|
|
lower = stou(r, &r);
|
|
request_version = 1000 * upper + lower;
|
|
request_query.SetInt("$$HTTP_VERSION", request_version);
|
|
}
|
|
}
|
|
request_query.Set("$$IPADDR", FormatIP(ipaddr));
|
|
request_query.Set("$$DEFAULT_HEADER", default_header);
|
|
request_query.Set("$$METHOD", four == FOURCHAR('P', 'O', 'S', 'T') ? "POST"
|
|
: four == FOURCHAR('H', 'E', 'A', 'D') ? "HEAD" : "GET");
|
|
if(request_version >= 1000)
|
|
request_state = (four == FOURCHAR('P', 'O', 'S', 'T') ? RS_POST_HEADERS : RS_GET_HEADERS);
|
|
else { // simple HTTP request without headers
|
|
One<HttpRequest> req = new HttpRequest(*this, conn, request_query);
|
|
req->LogTime(GetHttpURI(request_query), 1);
|
|
return req;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RS_SAPI:
|
|
if(sapi_request.GetLength() < sapi_length) {
|
|
int add = min<int>((int)(uintptr_t)(e - p), sapi_length - sapi_request.GetLength());
|
|
LogTime(NFormat("SAPI request length = %d, collected %d, adding %d",
|
|
sapi_length, sapi_request.GetLength(), add), 2);
|
|
sapi_request.Cat(p, add);
|
|
p += add;
|
|
}
|
|
if(sapi_request.GetLength() >= sapi_length) {
|
|
LogTime("Streaming SAPI request", 2);
|
|
StringStream strm(sapi_request);
|
|
strm % request_query;
|
|
if(strm.IsError()) {
|
|
LOG("Invalid SAPI request data, request trashed");
|
|
return NULL;
|
|
}
|
|
request_query.Set("$$DEFAULT_HEADER", default_header);
|
|
One<HttpRequest> req = new HttpRequest(*this, conn, request_query);
|
|
req->LogTime(GetHttpURI(request_query), 1);
|
|
return req;
|
|
}
|
|
break;
|
|
|
|
case RS_GET_HEADERS:
|
|
case RS_POST_HEADERS:
|
|
while(p < e && *p != '\n')
|
|
p++;
|
|
if(headers_length + header_line.GetLength() + (int)(p - b) > max_post_size) {
|
|
LogTime("Header line too long, request trashed", 1);
|
|
return NULL;
|
|
}
|
|
header_line.Cat(b, (int)(uintptr_t)(p - b));
|
|
LOG("b - b = " << (p - b) << ", header_line = " << header_line);
|
|
if(p >= e)
|
|
break;
|
|
p++;
|
|
|
|
{ // read header item
|
|
const char *r = header_line;
|
|
while(*r && (byte)*r <= ' ')
|
|
r++;
|
|
if(*r == 0) { // end of headers
|
|
if(request_state == RS_POST_HEADERS) {
|
|
post_length = request_query.GetInt("$$CONTENT_LENGTH");
|
|
if(!IsNull(post_length) && post_length > max_post_size) {
|
|
LogTime(NFormat("HTTP POST length limit exceeded (%d), request trashed", post_length), 1);
|
|
return NULL;
|
|
}
|
|
request_state = RS_POST_DATA;
|
|
break;
|
|
}
|
|
else {
|
|
One<HttpRequest> req = new HttpRequest(*this, conn, request_query);
|
|
req->LogTime(GetHttpURI(request_query), 1);
|
|
return req;
|
|
}
|
|
}
|
|
String var = "$$";
|
|
for(; *r && *r != ':'; r++)
|
|
if(IsAlNum(*r))
|
|
var.Cat(ToUpper(*r));
|
|
else if(*r == '-' && !var.IsEmpty() && *var.Last() != '_')
|
|
var.Cat('_');
|
|
if(var.GetLength() > 2 && *r == ':') {
|
|
while(*++r == ' ')
|
|
;
|
|
const char *s = r;
|
|
r = header_line.End();
|
|
while(r > s && (byte)r[-1] <= ' ')
|
|
r--;
|
|
String v = request_query.GetString(var);
|
|
if(!v.IsEmpty())
|
|
v.Cat(", ");
|
|
v.Cat(s, (int)(uintptr_t)(r - s));
|
|
request_query.Set(var, v);
|
|
headers_length += var.GetLength() + v.GetLength();
|
|
}
|
|
header_line = Null;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case RS_POST_DATA:
|
|
if(post_length == 0) {
|
|
while(p < e && *p != '\n')
|
|
p++;
|
|
if(post_data.GetLength() + (int)(p - b) > max_post_size) {
|
|
LogTime("HTTP POST length exceeded, request trashed", 1);
|
|
return NULL;
|
|
}
|
|
post_data.Cat(b, (int)(uintptr_t)(p - b));
|
|
if(p >= e)
|
|
break;
|
|
p++;
|
|
}
|
|
else if(post_data.GetLength() < post_length) {
|
|
int add = min<int>(post_length - post_data.GetLength(), (int)(uintptr_t)(e - p));
|
|
post_data.Cat(p, add);
|
|
p += add;
|
|
if(post_data.GetLength() < post_length)
|
|
break;
|
|
}
|
|
{
|
|
String content = request_query.GetString("$$CONTENT_TYPE");
|
|
static const char mtag[] = "multipart/";
|
|
request_query.Set("$$POSTDATA", post_data);
|
|
if(!socket.IsError() && strnicmp(content, mtag, 10))
|
|
request_query.SetURL(post_data);
|
|
else
|
|
GetHttpPostData(request_query, post_data);
|
|
One<HttpRequest> req = new HttpRequest(*this, conn, request_query);
|
|
req->LogTime(GetHttpURI(request_query), 1);
|
|
return req;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
NEVER();
|
|
break;
|
|
}
|
|
}
|
|
|
|
connection = conn;
|
|
return NULL;
|
|
|
|
/*
|
|
bool post = false;
|
|
HttpQuery query;
|
|
LOG((int)GetTickCount() << " HttpServer::GetRequest->PeekCount(4) on socket " << (SOCKET)sconn);
|
|
String request = sconn.PeekCount(4, 100);
|
|
LOG((int)GetTickCount() << " HttpServer::GetRequest on socket " << (SOCKET)sconn);
|
|
if(request.GetLength() == 4)
|
|
if(!memcmp(request, "GET ", 4))
|
|
post = false;
|
|
else if(!memcmp(request, "POST", 4))
|
|
post = true;
|
|
else if(!memcmp(request, "SAPI", 4)) {
|
|
request = sconn.ReadCount(8);
|
|
int len = PeekIL(request.Begin() + 4);
|
|
if(len < 0 || len > max_post_size)
|
|
return NULL;
|
|
StringStream strm(sconn.ReadCount(len));
|
|
if(sconn.IsError()) {
|
|
LogTime("HttpServer::ReadRequest: ISAPI request error: " + sconn.GetError());
|
|
return NULL;
|
|
}
|
|
strm % query;
|
|
if(strm.IsError())
|
|
return NULL;
|
|
query.Set(WID__DEFAULT_HEADER, default_header);
|
|
One<HttpRequest> req = new HttpRequest(*this, sconn, query);
|
|
req->LogTime(GetHttpURI(query));
|
|
return req;
|
|
}
|
|
else if(!memcmp(request, "STAT", 4)) {
|
|
String out;
|
|
String cmdline;
|
|
#ifdef PLATFORM_WIN32
|
|
cmdline = GetCommandLine();
|
|
#endif
|
|
out << "READY " << cmdline << "\r\n";
|
|
sconn.Write(out);
|
|
sconn.StopWrite();
|
|
sconn.Close();
|
|
return NULL;
|
|
}
|
|
else
|
|
return NULL;
|
|
int http_version = 9;
|
|
request = sconn.ReadUntil('\n');
|
|
const char *p = request;
|
|
while(*p && *p != ' ' && *p != '\n')
|
|
p++;
|
|
if(*p == ' ') {
|
|
p++;
|
|
const char *b = p;
|
|
while(*p && *p != ' ' && *p != '?')
|
|
p++;
|
|
query.Set(WID__PATH, String(b, p));
|
|
if(*p == '?') {
|
|
b = ++p;
|
|
while(*p && *p != ' ' && *p != '\n')
|
|
p++;
|
|
String raw_query(b, p);
|
|
query.Set(WID__QUERY, raw_query);
|
|
query.Set(HttpQuery(raw_query));
|
|
}
|
|
if(!memcmp(p, " HTTP/", 6) && IsDigit(p[6])) { // HTTP version
|
|
int upper = stou(p + 6, &p);
|
|
int lower = 0;
|
|
if(*p == '.' && IsDigit(*++p))
|
|
lower = stou(p, &p);
|
|
http_version = 1000 * upper + lower;
|
|
query.SetInt(WID__HTTP_VERSION, http_version);
|
|
}
|
|
}
|
|
query.Set(WID__DEFAULT_HEADER, default_header);
|
|
query.Set(WID__METHOD, post ? "POST" : "GET");
|
|
if(post && http_version < 1000)
|
|
return NULL; // must be at least HTTP/1.0 to handle POST
|
|
if(http_version >= 1000) { // read headers
|
|
while(!sconn.IsError()) {
|
|
request = sconn.ReadUntil('\n');
|
|
p = request;
|
|
while(*p && (byte)*p <= ' ')
|
|
p++;
|
|
if(*p == 0)
|
|
break;
|
|
String var = "$$";
|
|
for(; *p && *p != ':'; p++)
|
|
if(IsAlNum(*p))
|
|
var.Cat(ToUpper(*p));
|
|
else if(*p == '-' && !var.IsEmpty() && *var.Last() != '_')
|
|
var.Cat('_');
|
|
if(var.GetLength() > 2 && *p == ':') {
|
|
while(*++p == ' ')
|
|
;
|
|
const char *b = p;
|
|
p = request.End();
|
|
while((byte)p[-1] <= ' ')
|
|
p--;
|
|
String s = query(var);
|
|
if(!s.IsEmpty())
|
|
s.Cat(", ");
|
|
s.Cat(b, p - b);
|
|
query.Set(var, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(post)
|
|
ReadPostData(sconn, query);
|
|
if(sconn.IsError()) {
|
|
LogTime(String() << "HttpServer::GetRequest: " << sconn.GetError());
|
|
return NULL;
|
|
}
|
|
One<HttpRequest> req = new HttpRequest(*this, sconn, query);
|
|
req->LogTime(GetHttpURI(query));
|
|
return req;
|
|
*/
|
|
}
|
|
|
|
void HttpServer::ReadPostData(Socket& socket, HttpQuery& query)
|
|
{
|
|
int length = query.GetInt("$$CONTENT_LENGTH");
|
|
String buffer;
|
|
if(length > 0)
|
|
buffer = socket.ReadCount(min(length, max_post_size));
|
|
else // length unknown, read just 1 line (works for www-form-urlencoded)
|
|
buffer = socket.ReadUntil('\n', Null, max_post_size);
|
|
String content = query.GetString("$$CONTENT_TYPE");
|
|
static const String mtag = "multipart/";
|
|
if(!socket.IsError() && strnicmp(content, mtag, mtag.GetLength())) { // assume single-part data
|
|
query.SetURL(buffer);
|
|
return;
|
|
}
|
|
GetHttpPostData(query, buffer);
|
|
}
|
|
|
|
void HttpServer::AddWrite(Socket socket, String data)
|
|
{
|
|
if(data.GetLength() > 0)
|
|
delayed_writes.Add(new SocketWrite(socket, data, msecs()));
|
|
}
|
|
|
|
bool HttpServer::IsDelayedWrite() const
|
|
{
|
|
return !delayed_writes.IsEmpty();
|
|
}
|
|
|
|
double HttpServer::GetElapsedTime() const
|
|
{
|
|
return (double)((GetSysTime() - start_time) * 1000);
|
|
}
|
|
|
|
double HttpServer::GetAvgTime() const
|
|
{
|
|
return hit_count ? total_response_msec / hit_count : 0;
|
|
}
|
|
|
|
double HttpServer::GetAvgLagTime() const
|
|
{
|
|
if(trailing_count == 0)
|
|
return 0;
|
|
return (double)trailing_sum / trailing_count;
|
|
}
|
|
|
|
void HttpServer::AddRequest(int response_msec)
|
|
{
|
|
hit_count++;
|
|
total_response_msec += response_msec;
|
|
if(trailing_count >= TRAILER) {
|
|
trailing_sum -= trailing_times[0];
|
|
Copy(trailing_times, trailing_times + 1, trailing_times + TRAILER);
|
|
// memmove(&trailing_times[0], &trailing_times[1], (TRAILER - 1) * sizeof(*trailing_times));
|
|
trailing_count--;
|
|
}
|
|
trailing_sum += (trailing_times[trailing_count++] = response_msec);
|
|
}
|
|
|
|
String GetHttpURI(HttpQuery query)
|
|
{
|
|
String out = query.GetString("$$PATH");
|
|
if(!query.IsEmpty("$$QUERY"))
|
|
out << '?' << query.GetString("$$QUERY");
|
|
return out;
|
|
}
|
|
|
|
void GetHttpPostData(HttpQuery& query, String buffer)
|
|
{
|
|
query.Set("$$POSTDATA", buffer);
|
|
|
|
const char *p = buffer;
|
|
while(p[0] != '-' || p[1] != '-') {
|
|
while(*p != '\n')
|
|
if(*p++ == 0)
|
|
return; // end of file, boundary not found
|
|
p++;
|
|
}
|
|
String delimiter;
|
|
{ // read multipart delimiter
|
|
const char *b = (p += 2);
|
|
while(*p && *p++ != '\n')
|
|
;
|
|
const char *e = p;
|
|
while(e > b && (byte)e[-1] <= ' ')
|
|
e--;
|
|
delimiter = String(b, e);
|
|
}
|
|
// DUMP(delimiter);
|
|
|
|
int delta = 4 + delimiter.GetLength();
|
|
const char *e = buffer.End();
|
|
if(e - p < delta)
|
|
return;
|
|
e -= delta;
|
|
while(p < e) { // read individual parts
|
|
String filename, content_type, name;
|
|
while(!MemICmp(p, "content-", 8)) { // parse content specifiers
|
|
p += 8;
|
|
if(!MemICmp(p, "disposition:", 12)) {
|
|
p += 12;
|
|
while(*p && *p != '\n')
|
|
if((byte)*p <= ' ')
|
|
p++;
|
|
else { // fetch key-value pair
|
|
const char *kp = p;
|
|
while(*p && *p != '\n' && *p != '=' && *p != ';')
|
|
p++;
|
|
const char *ke = p;
|
|
String value;
|
|
if(*p == '=') {
|
|
const char *b = ++p;
|
|
if(*p == '\"') { // quoted value
|
|
b++;
|
|
while(*++p && *p != '\n' && *p != '\"')
|
|
;
|
|
value = String(b, p);
|
|
if(*p == '\"')
|
|
p++;
|
|
}
|
|
else {
|
|
while(*p && *p != '\n' && *p != ';')
|
|
p++;
|
|
value = String(b, p);
|
|
}
|
|
}
|
|
if(ke - kp == 4 && !MemICmp(kp, "name", 4))
|
|
name = value;
|
|
else if(ke - kp == 8 && !MemICmp(kp, "filename", 8))
|
|
filename = value;
|
|
if(*p == ';')
|
|
p++;
|
|
}
|
|
}
|
|
else if(!MemICmp(p, "type:", 5)) {
|
|
p += 5;
|
|
while(*p && *p != '\n' && (byte)*p <= ' ')
|
|
p++;
|
|
const char *b = p;
|
|
while(*p && *p != '\n')
|
|
p++;
|
|
const char *e = p;
|
|
while(e > b && (byte)e[-1] <= ' ')
|
|
e--;
|
|
content_type = String(b, e);
|
|
}
|
|
;
|
|
while(*p && *p++ != '\n')
|
|
;
|
|
}
|
|
if(*p++ != '\r' || *p++ != '\n')
|
|
return;
|
|
const char *b = p;
|
|
while(p < e) {
|
|
p = (const char *)memchr(p, '\r', e - p);
|
|
if(!p)
|
|
return;
|
|
if(p[0] == '\r' && p[1] == '\n' && p[2] == '-' && p[3] == '-'
|
|
&& !memcmp(p + 4, delimiter, delimiter.GetLength()))
|
|
break;
|
|
p++;
|
|
}
|
|
// DUMP(name);
|
|
// DUMP(content_type);
|
|
// DUMP(filename);
|
|
// DUMP(String(b, p));
|
|
if(!name.IsEmpty()) { // add variables
|
|
if(!filename.IsEmpty())
|
|
query.Set("$$FILENAME$$" + name, filename);
|
|
if(!content_type.IsEmpty())
|
|
query.Set("$$CONTENT_TYPE$$" + name, content_type);
|
|
query.Set(name, String(b, p));
|
|
}
|
|
p += delta;
|
|
while(*p && *p++ != '\n')
|
|
;
|
|
}
|
|
}
|
|
|
|
static HtmlTag HtmlSmallFont()
|
|
{
|
|
return HtmlFontSize(-2);
|
|
}
|
|
|
|
String GetHttpQueryDump(HttpQuery query)
|
|
{
|
|
Htmls table;
|
|
for(int i = 0; i < query.GetCount(); i++) {
|
|
Htmls row;
|
|
row << HtmlCell().VAlign(ALIGN_TOP) / HtmlSmallFont() / HtmlBold() / (ToHtml(query.GetKey(i)) + " ") << '\n';
|
|
String value = query.GetValue(i);
|
|
if(value.GetLength() >= 1000) {
|
|
value.Trim(1000);
|
|
value << NFormat(t_("... (total length: %d bytes)"), value.GetLength());
|
|
}
|
|
row << HtmlCell().VAlign(ALIGN_TOP) / HtmlSmallFont() / ToHtml(value);
|
|
table << HtmlRow() % row;
|
|
}
|
|
return HtmlTable().Border(0).CellSpacing(0).CellPadding(0) % table;
|
|
}
|
|
|
|
String GetHttpErrorPage(HttpQuery query, String err, bool show_query)
|
|
{
|
|
Htmls body;
|
|
body << t_("The web server is unable to satisfy your request:\n<p>\n");
|
|
String request = query.GetString("$$PATH");
|
|
if(!query.IsEmpty("$$QUERY"))
|
|
request << '?' << query.GetString("$$QUERY");
|
|
body << HtmlTag("TT") / ToHtml(request)
|
|
<< "\n<p>\n"
|
|
<< HtmlBold() / t_("Reason:") << err
|
|
<< "\n<p>\n";
|
|
if(show_query)
|
|
body
|
|
<< HtmlSmallFont() / HtmlBold() / t_("Detailed query data:")
|
|
<< "\n<br>\n"
|
|
<< GetHttpQueryDump(query);
|
|
|
|
return HtmlTitlePage(t_("Web server error"), body);
|
|
}
|
|
|
|
END_UPP_NAMESPACE
|