diff options
Diffstat (limited to 'lib/libUPnP/Platinum/Source/Core/PltHttpServerTask.cpp')
-rw-r--r-- | lib/libUPnP/Platinum/Source/Core/PltHttpServerTask.cpp | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/lib/libUPnP/Platinum/Source/Core/PltHttpServerTask.cpp b/lib/libUPnP/Platinum/Source/Core/PltHttpServerTask.cpp new file mode 100644 index 0000000..ed23d47 --- /dev/null +++ b/lib/libUPnP/Platinum/Source/Core/PltHttpServerTask.cpp @@ -0,0 +1,443 @@ +/***************************************************************** +| +| Platinum - HTTP Server Tasks +| +| Copyright (c) 2004-2010, Plutinosoft, LLC. +| All rights reserved. +| http://www.plutinosoft.com +| +| 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. +| +| OEMs, ISVs, VARs and other distributors that combine and +| distribute commercially licensed software with Platinum software +| and do not wish to distribute the source code for the commercially +| licensed software under version 2, or (at your option) any later +| version, of the GNU General Public License (the "GPL") must enter +| into a commercial license agreement with Plutinosoft, LLC. +| licensing@plutinosoft.com +| +| 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; see the file LICENSE.txt. If not, write to +| the Free Software Foundation, Inc., +| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +| http://www.gnu.org/licenses/gpl-2.0.html +| +****************************************************************/ + +/*---------------------------------------------------------------------- +| includes ++---------------------------------------------------------------------*/ +#include "PltHttpServerTask.h" +#include "PltHttp.h" +#include "PltVersion.h" + +NPT_SET_LOCAL_LOGGER("platinum.core.http.servertask") + +/*---------------------------------------------------------------------- +| external references ++---------------------------------------------------------------------*/ +extern NPT_String HttpServerHeader; +const char* const PLT_HTTP_DEFAULT_403_HTML = "<html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>Access to this URL is forbidden.</p></html>"; +const char* const PLT_HTTP_DEFAULT_404_HTML = "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></html>"; +const char* const PLT_HTTP_DEFAULT_500_HTML = "<html><head><title>500 Internal Error</title></head><body><h1>Internal Error</h1><p>The server encountered an unexpected condition which prevented it from fulfilling the request.</p></html>"; + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::PLT_HttpServerSocketTask ++---------------------------------------------------------------------*/ +PLT_HttpServerSocketTask::PLT_HttpServerSocketTask(NPT_Socket* socket, + bool stay_alive_forever) : + m_Socket(socket), + m_StayAliveForever(stay_alive_forever) +{ + // needed for PS3 that is some case will request data every 35 secs and + // won't like it if server disconnected too early + m_Socket->SetReadTimeout(60000); + m_Socket->SetWriteTimeout(600000); +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask ++---------------------------------------------------------------------*/ +PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask() +{ + if (m_Socket) { + m_Socket->Cancel(); + delete m_Socket; + } +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::DoRun ++---------------------------------------------------------------------*/ +void +PLT_HttpServerSocketTask::DoRun() +{ + NPT_BufferedInputStreamReference buffered_input_stream; + NPT_HttpRequestContext context; + NPT_Result res = NPT_SUCCESS; + bool headers_only; + bool keep_alive = false; + + // create a buffered input stream to parse HTTP request + NPT_InputStreamReference input_stream; + NPT_CHECK_LABEL_SEVERE(GetInputStream(input_stream), done); + NPT_CHECK_POINTER_LABEL_FATAL(input_stream.AsPointer(), done); + buffered_input_stream = new NPT_BufferedInputStream(input_stream); + + while (!IsAborting(0)) { + NPT_HttpRequest* request = NULL; + NPT_HttpResponse* response = NULL; + + // reset keep-alive to exit task on read failure + keep_alive = false; + + // wait for a request + res = Read(buffered_input_stream, request, &context); + if (NPT_FAILED(res) || (request == NULL)) + goto cleanup; + + // process request and setup response + res = RespondToClient(*request, context, response); + if (NPT_FAILED(res) || (response == NULL)) + goto cleanup; + + // check if client requested keep-alive + keep_alive = PLT_HttpHelper::IsConnectionKeepAlive(*request); + headers_only = request->GetMethod() == NPT_HTTP_METHOD_HEAD; + + // send response, pass keep-alive request from client + // (it can be overridden if response handler did not allow it) + res = Write(response, keep_alive, headers_only); + + // on write error, reset keep_alive so we can close this connection + if (NPT_FAILED(res)) keep_alive = false; + +cleanup: + // cleanup + delete request; + delete response; + + if (!keep_alive && !m_StayAliveForever) { + return; + } + } +done: + return; +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::GetInputStream ++---------------------------------------------------------------------*/ +NPT_Result +PLT_HttpServerSocketTask::GetInputStream(NPT_InputStreamReference& stream) +{ + return m_Socket->GetInputStream(stream); +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::GetInfo ++---------------------------------------------------------------------*/ +NPT_Result +PLT_HttpServerSocketTask::GetInfo(NPT_SocketInfo& info) +{ + return m_Socket->GetInfo(info); +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::Read ++---------------------------------------------------------------------*/ +NPT_Result +PLT_HttpServerSocketTask::Read(NPT_BufferedInputStreamReference& buffered_input_stream, + NPT_HttpRequest*& request, + NPT_HttpRequestContext* context) +{ + NPT_SocketInfo info; + GetInfo(info); + + // update context with socket info if needed + if (context) { + context->SetLocalAddress(info.local_address); + context->SetRemoteAddress(info.remote_address); + } + + // put back in buffered mode to be able to parse HTTP request properly + buffered_input_stream->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE); + + // parse request + NPT_Result res = NPT_HttpRequest::Parse(*buffered_input_stream, &info.local_address, request); + if (NPT_FAILED(res) || !request) { + // only log if not timeout + res = NPT_FAILED(res)?res:NPT_FAILURE; + if (res != NPT_ERROR_TIMEOUT && res != NPT_ERROR_EOS) NPT_CHECK_WARNING(res); + return res; + } + + // update context with socket info again + // to refresh the remote address in case it was a non connected udp socket + GetInfo(info); + if (context) { + context->SetLocalAddress(info.local_address); + context->SetRemoteAddress(info.remote_address); + } + + // return right away if no body is expected + if (request->GetMethod() == NPT_HTTP_METHOD_GET || + request->GetMethod() == NPT_HTTP_METHOD_HEAD) { + return NPT_SUCCESS; + } + + // create an entity + NPT_HttpEntity* request_entity = new NPT_HttpEntity(request->GetHeaders()); + request->SetEntity(request_entity); + + NPT_MemoryStream* body_stream = new NPT_MemoryStream(); + request_entity->SetInputStream((NPT_InputStreamReference)body_stream); + + // unbuffer the stream to read body fast + buffered_input_stream->SetBufferSize(0); + + // check for chunked Transfer-Encoding + if (request_entity->GetTransferEncoding() == "chunked") { + NPT_CHECK_SEVERE(NPT_StreamToStreamCopy( + *NPT_InputStreamReference(new NPT_HttpChunkedInputStream(buffered_input_stream)).AsPointer(), + *body_stream)); + + request_entity->SetTransferEncoding(NULL); + } else if (request_entity->GetContentLength()) { + // a request with a body must always have a content length if not chunked + NPT_CHECK_SEVERE(NPT_StreamToStreamCopy( + *buffered_input_stream.AsPointer(), + *body_stream, + 0, + request_entity->GetContentLength())); + } else { + request->SetEntity(NULL); + } + + // rebuffer the stream + buffered_input_stream->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE); + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::RespondToClient ++---------------------------------------------------------------------*/ +NPT_Result +PLT_HttpServerSocketTask::RespondToClient(NPT_HttpRequest& request, + const NPT_HttpRequestContext& context, + NPT_HttpResponse*& response) +{ + NPT_Result result = NPT_ERROR_NO_SUCH_ITEM; + + // reset output params first + response = NULL; + + // prepare the response body + NPT_HttpEntity* body = new NPT_HttpEntity(); + response = new NPT_HttpResponse(200, "OK", NPT_HTTP_PROTOCOL_1_1); + response->SetEntity(body); + + // ask to setup the response + result = SetupResponse(request, context, *response); + + // handle result + if (result == NPT_ERROR_NO_SUCH_ITEM) { + body->SetInputStream(PLT_HTTP_DEFAULT_404_HTML); + body->SetContentType("text/html"); + response->SetStatus(404, "Not Found"); + } else if (result == NPT_ERROR_PERMISSION_DENIED) { + body->SetInputStream(PLT_HTTP_DEFAULT_403_HTML); + body->SetContentType("text/html"); + response->SetStatus(403, "Forbidden"); + } else if (result == NPT_ERROR_TERMINATED) { + // mark that we want to exit + delete response; + response = NULL; + } else if (NPT_FAILED(result)) { + body->SetInputStream(PLT_HTTP_DEFAULT_500_HTML); + body->SetContentType("text/html"); + response->SetStatus(500, "Internal Error"); + } + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::SendResponseHeaders ++---------------------------------------------------------------------*/ +NPT_Result +PLT_HttpServerSocketTask::SendResponseHeaders(NPT_HttpResponse* response, + NPT_OutputStream& output_stream, + bool& keep_alive) +{ + // add any headers that may be missing + NPT_HttpHeaders& headers = response->GetHeaders(); + + // get the request entity to set additional headers + NPT_InputStreamReference body_stream; + NPT_HttpEntity* entity = response->GetEntity(); + if (entity && NPT_SUCCEEDED(entity->GetInputStream(body_stream))) { + // set the content length if known + if (entity->ContentLengthIsKnown()) { + headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, + NPT_String::FromIntegerU(entity->GetContentLength())); + } + + // content type + NPT_String content_type = entity->GetContentType(); + if (!content_type.IsEmpty()) { + headers.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, content_type); + } + + // content encoding + NPT_String content_encoding = entity->GetContentEncoding(); + if (!content_encoding.IsEmpty()) { + headers.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING, content_encoding); + } + + // transfer encoding + const NPT_String& transfer_encoding = entity->GetTransferEncoding(); + if (!transfer_encoding.IsEmpty()) { + headers.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING, transfer_encoding); + } + + } else if (!headers.GetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH)) { + // force content length to 0 if there is no message body + // (necessary for 1.1 or 1.0 with keep-alive connections) + headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, "0"); + } + + const NPT_String* content_length = headers.GetHeaderValue(NPT_HTTP_HEADER_CONTENT_LENGTH); + const NPT_String* transfer_encoding = headers.GetHeaderValue(NPT_HTTP_HEADER_TRANSFER_ENCODING); + const NPT_String* connection_header = headers.GetHeaderValue(NPT_HTTP_HEADER_CONNECTION); + if (keep_alive) { + if (connection_header && connection_header->Compare("close") == 0) { + keep_alive = false; + } else { + // the request says client supports keep-alive + // but override if response has content-length header or + // transfer chunked encoding + keep_alive = content_length || + (transfer_encoding && transfer_encoding->Compare(NPT_HTTP_TRANSFER_ENCODING_CHUNKED) == 0); + } + } + + // only write keep-alive header for 1.1 if it's close + NPT_String protocol = response->GetProtocol(); + if (protocol.Compare(NPT_HTTP_PROTOCOL_1_0, true) == 0 || !keep_alive) { + headers.SetHeader(NPT_HTTP_HEADER_CONNECTION, keep_alive?"keep-alive":"close", true); + } + headers.SetHeader(NPT_HTTP_HEADER_SERVER, PLT_HTTP_DEFAULT_SERVER, false); // set but don't replace + + PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINE, "PLT_HttpServerSocketTask::Write", response); + + // create a memory stream to buffer the headers + NPT_MemoryStream header_stream; + response->Emit(header_stream); + + // send the headers + NPT_CHECK_WARNING(output_stream.WriteFully(header_stream.GetData(), header_stream.GetDataSize())); + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::SendResponseBody ++---------------------------------------------------------------------*/ +NPT_Result +PLT_HttpServerSocketTask::SendResponseBody(NPT_HttpResponse* response, + NPT_OutputStream& output_stream) +{ + NPT_HttpEntity* entity = response->GetEntity(); + if (!entity) return NPT_SUCCESS; + + NPT_InputStreamReference body_stream; + entity->GetInputStream(body_stream); + if (body_stream.IsNull()) return NPT_SUCCESS; + + // check for chunked transfer encoding + NPT_OutputStream* dest = &output_stream; + if (entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) { + dest = new NPT_HttpChunkedOutputStream(output_stream); + } + + // send body + NPT_LOG_FINE_1("sending body stream, %lld bytes", entity->GetContentLength()); + NPT_LargeSize bytes_written = 0; + NPT_Result result = NPT_StreamToStreamCopy(*body_stream, *dest, 0, entity->GetContentLength(), &bytes_written); /* passing 0 if content length is unknown will read until nothing is left */ + if (NPT_FAILED(result)) { + NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)", + bytes_written, + result, + NPT_ResultText(result)); + } + + // flush to write out any buffered data left in chunked output if used + dest->Flush(); + + // cleanup (this will send zero size chunk followed by CRLF) + if (dest != &output_stream) delete dest; + + return result; +} + +/*---------------------------------------------------------------------- +| PLT_HttpServerSocketTask::Write ++---------------------------------------------------------------------*/ +NPT_Result +PLT_HttpServerSocketTask::Write(NPT_HttpResponse* response, + bool& keep_alive, + bool headers_only /* = false */) +{ + // get the socket output stream + NPT_OutputStreamReference output_stream; + NPT_CHECK_WARNING(m_Socket->GetOutputStream(output_stream)); + + // send headers + NPT_CHECK_WARNING(SendResponseHeaders(response, *output_stream, keep_alive)); + + // send the body + if (!headers_only) { + NPT_CHECK_WARNING(SendResponseBody(response, *output_stream)); + } + + // flush + output_stream->Flush(); + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_HttpListenTask::DoRun ++---------------------------------------------------------------------*/ +void +PLT_HttpListenTask::DoRun() +{ + while (!IsAborting(0)) { + NPT_Socket* client = NULL; + NPT_Result result = m_Socket->WaitForNewClient(client, 5000, NPT_SOCKET_FLAG_CANCELLABLE); + if (NPT_FAILED(result)) { + // cleanup just in case + if (client) delete client; + + // normal error + if (result == NPT_ERROR_TIMEOUT) continue; + + // exit on other errors ? + NPT_LOG_WARNING_2("PLT_HttpListenTask exiting with %d (%s)", result, NPT_ResultText(result)); + break; + } else { + PLT_ThreadTask* task = new PLT_HttpServerTask(m_Handler, client); + m_TaskManager->StartTask(task); + } + } +} |