/* Starshatter OpenSource Distribution Copyright (c) 1997-2004, Destroyer Studios LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name "Destroyer Studios" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SUBSYSTEM: NetEx.lib FILE: HttpServer.cpp AUTHOR: John DiCamillo OVERVIEW ======== Network Server Pump for HTTP Server */ #include "MemDebug.h" #include "HttpServer.h" #include "NetLayer.h" #include #include #include // +-------------------------------------------------------------------+ HttpServer::HttpServer(WORD port, int poolsize) : NetServer(port, poolsize) { http_server_name = "Generic HttpServer 1.0"; } HttpServer::~HttpServer() { } // +--------------------------------------------------------------------+ Text HttpServer::ProcessRequest(Text msg, const NetAddr& addr) { HttpRequest request(msg); HttpResponse response; request.SetClientAddr(addr); switch (request.Method()) { case HttpRequest::HTTP_GET: if (DoGet(request, response)) return response; case HttpRequest::HTTP_POST: if (DoPost(request, response)) return response; case HttpRequest::HTTP_HEAD: if (DoHead(request, response)) return response; } return ErrorResponse(); } // +--------------------------------------------------------------------+ Text HttpServer::GetServerName() { return http_server_name; } void HttpServer::SetServerName(const char* name) { http_server_name = name; } // +--------------------------------------------------------------------+ Text HttpServer::DefaultResponse() { Text response = "HTTP/1.1 200 OK\nServer: "; response += http_server_name; response += "\nMIME-Version: 1.0\nContent-Type: text/html\nConnection: close\n\n"; return response; } Text HttpServer::ErrorResponse() { Text response = "HTTP/1.1 500 Internal Server Error\nServer:"; response += http_server_name; response += "\nMIME-Version: 1.0\nContent-Type: text/html\nConnection: close\n\n"; response += ""; response += http_server_name; response += " Error\n"; response += "\n

"; response += http_server_name; response += "

\n

Veruca... sweetheart... angel... I'm not a magician!\n"; response += "\n\n"; return response; } // +--------------------------------------------------------------------+ bool HttpServer::DoGet(HttpRequest& request, HttpResponse& response) { char buffer[1024]; Text content; content = ""; content += http_server_name; content += "\n"; content += "\n

"; content += http_server_name; content += "

\n"; content += "

Client Address:

\n"; sprintf_s(buffer, "%d.%d.%d.%d:%d

\n", client_addr.B1(), client_addr.B2(), client_addr.B3(), client_addr.B4(), client_addr.Port()); content += buffer; content += "

Request Method:

\n"; switch (request.Method()) { case HttpRequest::HTTP_GET: content += "GET"; break; case HttpRequest::HTTP_POST: content += "POST"; break; case HttpRequest::HTTP_HEAD: content += "HEAD"; break; default: content += "(unsupported?)"; break; } content += "
\n"; content += "

URI Requested:

\n"; content += request.URI(); content += "
\n"; if (request.GetQuery().size() > 0) { content += "

Query Parameters:

\n"; ListIter q_iter = request.GetQuery(); while (++q_iter) { HttpParam* q = q_iter.value(); sprintf_s(buffer, "%s: %s
\n", q->name.data(), q->value.data()); content += buffer; } } content += "

Request Headers:

\n"; ListIter h_iter = request.GetHeaders(); while (++h_iter) { HttpParam* h = h_iter.value(); sprintf_s(buffer, "%s: %s
\n", h->name.data(), h->value.data()); content += buffer; } content += "\n\n"; response.SetStatus(HttpResponse::SC_OK); response.AddHeader("Server", http_server_name); response.AddHeader("MIME-Version", "1.0"); response.AddHeader("Content-Type", "text/html"); response.SetContent(content); return true; } // +--------------------------------------------------------------------+ bool HttpServer::DoPost(HttpRequest& request, HttpResponse& response) { return DoGet(request, response); } // +--------------------------------------------------------------------+ bool HttpServer::DoHead(HttpRequest& request, HttpResponse& response) { if (DoGet(request, response)) { int len = response.Content().length(); char buffer[256]; sprintf_s(buffer, "%d", len); response.SetHeader("Content-Length", buffer); response.SetContent(""); return true; } return false; } // +--------------------------------------------------------------------+ // +--------------------------------------------------------------------+ // +--------------------------------------------------------------------+ HttpRequest::HttpRequest(const char* r) : method(0) { if (r && *r) ParseRequest(r); } HttpRequest::~HttpRequest() { query.destroy(); headers.destroy(); cookies.destroy(); } // +--------------------------------------------------------------------+ void HttpRequest::ParseRequest(Text request) { if (request.length() <= 8) return; const char* pReq = 0; const char* pURI = 0; const char* pQuery = 0; switch (request[0]) { case 'G': if (request.indexOf("GET") == 0) method = HTTP_GET; break; case 'P': if (request.indexOf("POST") == 0) method = HTTP_POST; break; case 'H': if (request.indexOf("HEAD") == 0) method = HTTP_HEAD; break; default: break; } if (!method) return; char buffer[1024]; int i = 0; // save the request line: pReq = request.data(); while (*pReq && *pReq != '\n') buffer[i++] = *pReq++; buffer[i] = 0; request_line = buffer; i = 0; // find the URI: pURI = request.data(); while (*pURI && !isspace(*pURI)) pURI++; while (*pURI && isspace(*pURI)) pURI++; // copy the URI and find the query string: while (*pURI && *pURI != '?' && !isspace(*pURI)) { buffer[i++] = *pURI++; } buffer[i] = 0; uri = buffer; pQuery = pURI; // parse the query string: if (*pQuery == '?') { pQuery++; while (*pQuery && !isspace(*pQuery)) { char name_buf[1024]; char value_buf[1024]; i = 0; while (*pQuery && *pQuery != '=' && !isspace(*pQuery)) name_buf[i++] = *pQuery++; name_buf[i] = 0; if (*pQuery == '=') pQuery++; i = 0; while (*pQuery && *pQuery != '&' && !isspace(*pQuery)) value_buf[i++] = *pQuery++; value_buf[i] = 0; if (*pQuery == '&') pQuery++; HttpParam* param = new(__FILE__,__LINE__) HttpParam(name_buf, DecodeParam(value_buf)); if (param) query.append(param); } } // get the headers: const char* p = request.data(); while (*p && *p != '\n') p++; if (*p == '\n') p++; while (*p && *p != '\r' && *p != '\n') { char name_buf[1024]; char value_buf[1024]; i = 0; while (*p && *p != ':') name_buf[i++] = *p++; name_buf[i] = 0; p++; // skip ':' while (isspace(*p)) p++; // skip spaces i = 0; while (*p && *p != '\r' && *p != '\n') // read to end of header line value_buf[i++] = *p++; value_buf[i] = 0; if (!_stricmp(name_buf, "Cookie")) { ParseCookie(value_buf); } else { HttpParam* param = new(__FILE__,__LINE__) HttpParam(name_buf, value_buf); if (param) headers.append(param); } while (*p && *p != '\n') p++; if (*p == '\n') p++; } if (method == HTTP_POST && *p) { while (*p == '\n' || *p == '\r') p++; content = *p; pQuery = p; while (*pQuery && !isspace(*pQuery)) { char name_buf[1024]; char value_buf[1024]; i = 0; while (*pQuery && *pQuery != '=' && !isspace(*pQuery)) name_buf[i++] = *pQuery++; name_buf[i] = 0; if (*pQuery == '=') pQuery++; i = 0; while (*pQuery && *pQuery != '&' && !isspace(*pQuery)) value_buf[i++] = *pQuery++; value_buf[i] = 0; if (*pQuery == '&') pQuery++; HttpParam* param = new(__FILE__,__LINE__) HttpParam(name_buf, DecodeParam(value_buf)); if (param) query.append(param); } } } void HttpRequest::ParseCookie(const char* param) { const char* p = param; while (p && *p) { while (isspace(*p)) p++; // just ignore reserved attributes if (*p == '$') { while (*p && !isspace(*p) && *p != ';') p++; if (*p == ';') p++; } // found a cookie! else if (isalpha(*p)) { char name[1024]; char data[1024]; char* d = name; while (*p && *p != '=') *d++ = *p++; *d = 0; if (*p == '=') p++; if (*p == '"') p++; d = data; while (*p && *p != '"' && *p != ';') *d++ = *p++; *d = 0; if (*p == '"') p++; if (*p == ';') p++; HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, data); if (param) cookies.append(param); } // this shouldn't happen - abandon the parse else { return; } } } // +--------------------------------------------------------------------+ Text HttpRequest::GetParam(const char* name) { ListIter iter = query; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) return p->value; } return Text(); } // +--------------------------------------------------------------------+ Text HttpRequest::GetHeader(const char* name) { ListIter iter = headers; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) return p->value; } return Text(); } void HttpRequest::SetHeader(const char* name, const char* value) { ListIter iter = headers; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) { p->value = value; return; } } HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, value); if (param) headers.append(param); } void HttpRequest::AddHeader(const char* name, const char* value) { HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, value); if (param) headers.append(param); } // +--------------------------------------------------------------------+ Text HttpRequest::GetCookie(const char* name) { ListIter iter = cookies; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) return p->value; } return Text(); } void HttpRequest::SetCookie(const char* name, const char* value) { ListIter iter = cookies; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) { p->value = value; return; } } HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, value); if (param) cookies.append(param); } void HttpRequest::AddCookie(const char* name, const char* value) { HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, value); if (param) cookies.append(param); } // +--------------------------------------------------------------------+ Text HttpRequest::DecodeParam(const char* value) { if (!value || !*value) return ""; int size = strlen(value); char val = 0; char code[4]; char sbuf[256]; char* lbuf = 0; char* dst = sbuf; char* p = sbuf; if (size > 255) { lbuf = new(__FILE__,__LINE__) char[size+1]; dst = lbuf; p = lbuf; } if (p) { while (*value) { switch (*value) { default: *p++ = *value; break; case '+': *p++ = ' '; break; case '%': value++; code[0] = *value++; code[1] = *value; code[2] = 0; val = (char) strtol(code, 0, 16); *p++ = val; break; } value++; } *p = 0; } Text result = dst; if (lbuf) delete [] lbuf; return result; } // +--------------------------------------------------------------------+ Text HttpRequest::EncodeParam(const char* value) { if (!value || !*value) return ""; int size = strlen(value); char hex1 = 0; char hex2 = 0; char sbuf[1024]; char* lbuf = 0; char* dst = sbuf; char* p = sbuf; if (size > 255) { lbuf = new(__FILE__,__LINE__) char[4*size+1]; dst = lbuf; p = lbuf; } if (p) { while (*value) { switch (*value) { default: *p++ = *value; break; case ' ': *p++ = '+'; break; case '?': *p++ = '%'; *p++ = '3'; *p++ = 'F'; break; case '&': *p++ = '%'; *p++ = '2'; *p++ = '6'; break; case ':': *p++ = '%'; *p++ = '3'; *p++ = 'A'; break; case '/': *p++ = '%'; *p++ = '2'; *p++ = 'F'; break; case '\\': *p++ = '%'; *p++ = '5'; *p++ = 'C'; break; case '%': *p++ = '%'; *p++ = '2'; *p++ = '5'; break; case '|': *p++ = '%'; *p++ = '7'; *p++ = 'C'; break; case '<': *p++ = '%'; *p++ = '3'; *p++ = 'C'; break; case '>': *p++ = '%'; *p++ = '3'; *p++ = 'E'; break; case '[': *p++ = '%'; *p++ = '5'; *p++ = 'B'; break; case ']': *p++ = '%'; *p++ = '5'; *p++ = 'D'; break; case '{': *p++ = '%'; *p++ = '7'; *p++ = 'B'; break; case '}': *p++ = '%'; *p++ = '7'; *p++ = 'D'; break; case '"': *p++ = '%'; *p++ = '2'; *p++ = '2'; break; case '^': *p++ = '%'; *p++ = '5'; *p++ = 'E'; break; case '`': *p++ = '%'; *p++ = '6'; *p++ = '0'; break; case '\n': break; case '\r': break; case '\t': break; } value++; } *p = 0; } Text result = dst; if (lbuf) delete [] lbuf; return result; } // +--------------------------------------------------------------------+ HttpRequest::operator Text() { Text response = request_line.data(); response += "\n"; for (int i = 0; i < headers.size(); i++) { HttpParam* h = headers[i]; response += h->name; response += ": "; response += h->value; response += "\n"; } for (int i = 0; i < cookies.size(); i++) { HttpParam* c = cookies[i]; response += "Cookie: "; response += c->name; response += "=\""; response += c->value; response += "\"\n"; } response += "Connection: close\n\n"; response += content; return response; } // +--------------------------------------------------------------------+ // +--------------------------------------------------------------------+ // +--------------------------------------------------------------------+ HttpResponse::HttpResponse(int stat, const char* data) : status(stat), content(data) { } HttpResponse::HttpResponse(const char* r) : status(0), content(r) { if (r && *r) ParseResponse(r); } HttpResponse::~HttpResponse() { headers.destroy(); cookies.destroy(); } // +--------------------------------------------------------------------+ HttpResponse::operator Text() { Text response; switch (status) { case SC_CONTINUE : response = "HTTP/1.1 100 Continue\n"; break; case SC_SWITCHING_PROTOCOLS : response = "HTTP/1.1 101 Switching Protocols\n"; break; case SC_OK : response = "HTTP/1.1 200 OK\n"; break; case SC_CREATED : response = "HTTP/1.1 201 Created\n"; break; case SC_ACCEPTED : response = "HTTP/1.1 202 Accepted\n"; break; case SC_NON_AUTHORITATIVE : response = "HTTP/1.1 203 Non Authoritative\n"; break; case SC_NO_CONTENT : response = "HTTP/1.1 204 No Content\n"; break; case SC_RESET_CONTENT : response = "HTTP/1.1 205 Reset Content\n"; break; case SC_PARTIAL_CONTENT : response = "HTTP/1.1 206 Partial Content\n"; break; case SC_MULTIPLE_CHOICES : response = "HTTP/1.1 300 Multiple Choices\n"; break; case SC_MOVED_PERMANENTLY : response = "HTTP/1.1 301 Moved Permanently\n"; break; case SC_FOUND : response = "HTTP/1.1 302 Found\n"; break; case SC_SEE_OTHER : response = "HTTP/1.1 303 See Other\n"; break; case SC_NOT_MODIFIED : response = "HTTP/1.1 304 Not Modified\n"; break; case SC_USE_PROXY : response = "HTTP/1.1 305 Use Proxy\n"; break; case SC_TEMPORARY_REDIRECT : response = "HTTP/1.1 307 Temporary Redirect\n"; break; case SC_BAD_REQUEST : response = "HTTP/1.1 400 Bad Request\n"; break; case SC_UNAUTHORIZED : response = "HTTP/1.1 401 Unauthorized\n"; break; case SC_PAYMENT_REQUIRED : response = "HTTP/1.1 402 Payment Required\n"; break; case SC_FORBIDDEN : response = "HTTP/1.1 403 Forbidden\n"; break; case SC_NOT_FOUND : response = "HTTP/1.1 404 Not Found\n"; break; case SC_METHOD_NOT_ALLOWED : response = "HTTP/1.1 405 Method Not Allowed\n"; break; case SC_NOT_ACCEPTABLE : response = "HTTP/1.1 406 Not Acceptable\n"; break; case SC_PROXY_AUTH_REQ : response = "HTTP/1.1 407 Proxy Authorization Req\n"; break; case SC_REQUEST_TIME_OUT : response = "HTTP/1.1 408 Request Timeout\n"; break; case SC_CONFLICT : response = "HTTP/1.1 409 Conflict\n"; break; case SC_GONE : response = "HTTP/1.1 410 Gone\n"; break; case SC_LENGTH_REQUIRED : response = "HTTP/1.1 411 Length Required\n"; break; default: case SC_SERVER_ERROR : response = "HTTP/1.1 500 Internal Server Error\n"; break; case SC_NOT_IMPLEMENTED : response = "HTTP/1.1 501 Not Implemented\n"; break; case SC_BAD_GATEWAY : response = "HTTP/1.1 502 Bad Gateway\n"; break; case SC_SERVICE_UNAVAILABLE : response = "HTTP/1.1 503 Service Unavailable\n"; break; case SC_GATEWAY_TIMEOUT : response = "HTTP/1.1 504 Gateway Timeout\n"; break; case SC_VERSION_NOT_SUPPORTED: response = "HTTP/1.1 505 HTTP Version Not Supported\n"; break; } SetHeader("Connection", "close"); char buffer[256]; if (content.length()) { sprintf_s(buffer, "%d", content.length()); SetHeader("Content-Length", buffer); } for (int i = 0; i < cookies.size(); i++) { HttpParam* cookie = cookies.at(i); sprintf_s(buffer, "%s=\"%s\"; Version=\"1\"", cookie->name.data(), cookie->value.data()); AddHeader("Set-Cookie", buffer); } for (int i = 0; i < headers.size(); i++) { const HttpParam* p = headers.at(i); sprintf_s(buffer, "%s: %s\n", p->name.data(), p->value.data()); response += buffer; } response += "\n"; response += content; return response; } // +--------------------------------------------------------------------+ Text HttpResponse::GetHeader(const char* name) { ListIter iter = headers; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) return p->value; } return Text(); } void HttpResponse::SetHeader(const char* name, const char* value) { ListIter iter = headers; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) { p->value = value; return; } } HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, value); if (param) headers.append(param); } void HttpResponse::AddHeader(const char* name, const char* value) { HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, value); if (param) headers.append(param); } // +--------------------------------------------------------------------+ Text HttpResponse::GetCookie(const char* name) { ListIter iter = cookies; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) return p->value; } return Text(); } void HttpResponse::SetCookie(const char* name, const char* value) { ListIter iter = cookies; while (++iter) { HttpParam* p = iter.value(); if (p->name == name) { p->value = value; return; } } HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, value); if (param) cookies.append(param); } void HttpResponse::AddCookie(const char* name, const char* value) { HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, value); if (param) cookies.append(param); } // +--------------------------------------------------------------------+ void HttpResponse::SendRedirect(const char* url) { status = SC_TEMPORARY_REDIRECT; SetHeader("Location", url); } // +--------------------------------------------------------------------+ void HttpResponse::ParseResponse(Text response) { if (response.length() <= 12 || response.indexOf("HTTP/1.") != 0) return; const char* pStatus = response.data() + 9; sscanf_s(pStatus, "%d", &status); if (!status) return; int i = 0; // get the headers: const char* p = response.data(); while (*p && *p != '\n') p++; if (*p == '\n') p++; while (*p && *p != '\r' && *p != '\n') { char name_buf[1024]; char value_buf[1024]; i = 0; while (*p && *p != ':') name_buf[i++] = *p++; name_buf[i] = 0; p++; // skip ':' while (isspace(*p)) p++; // skip spaces i = 0; while (*p && *p != '\r' && *p != '\n') // read to end of header line value_buf[i++] = *p++; value_buf[i] = 0; if (!_stricmp(name_buf, "Set-Cookie")) { ParseCookie(value_buf); } else { HttpParam* param = new(__FILE__,__LINE__) HttpParam(name_buf, value_buf); if (param) headers.append(param); } while (*p && *p != '\n') p++; if (*p == '\n') p++; } if (*p == '\n') p++; content = p; } void HttpResponse::ParseCookie(const char* param) { const char* p = param; while (p && *p) { while (isspace(*p)) p++; // just ignore reserved attributes if (*p == '$') { while (*p && !isspace(*p) && *p != ';') p++; if (*p == ';') p++; } // found a cookie! else if (isalpha(*p)) { char name[1024]; char data[1024]; char* d = name; while (*p && *p != '=') *d++ = *p++; *d = 0; if (*p == '=') p++; if (*p == '"') p++; d = data; while (*p && *p != '"' && *p != ';') *d++ = *p++; *d = 0; if (*p == '"') p++; if (*p == ';') p++; // ignore the version attribute if (_stricmp(name, "version")) { HttpParam* param = new(__FILE__,__LINE__) HttpParam(name, data); if (param) cookies.append(param); } } // this shouldn't happen - abandon the parse else { return; } } }