summaryrefslogtreecommitdiffstats
path: root/lib/libUPnP/Platinum/Source/Core/PltHttpServerTask.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libUPnP/Platinum/Source/Core/PltHttpServerTask.cpp')
-rw-r--r--lib/libUPnP/Platinum/Source/Core/PltHttpServerTask.cpp443
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);
+ }
+ }
+}