summaryrefslogtreecommitdiffstats
path: root/lib/libUPnP/Platinum/Source/Core/PltHttpServer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libUPnP/Platinum/Source/Core/PltHttpServer.cpp')
-rw-r--r--lib/libUPnP/Platinum/Source/Core/PltHttpServer.cpp310
1 files changed, 310 insertions, 0 deletions
diff --git a/lib/libUPnP/Platinum/Source/Core/PltHttpServer.cpp b/lib/libUPnP/Platinum/Source/Core/PltHttpServer.cpp
new file mode 100644
index 0000000..2557f62
--- /dev/null
+++ b/lib/libUPnP/Platinum/Source/Core/PltHttpServer.cpp
@@ -0,0 +1,310 @@
+/*****************************************************************
+|
+| Platinum - HTTP Server
+|
+| 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 "PltTaskManager.h"
+#include "PltHttpServer.h"
+#include "PltHttp.h"
+#include "PltVersion.h"
+#include "PltUtilities.h"
+#include "PltProtocolInfo.h"
+#include "PltMimeType.h"
+
+NPT_SET_LOCAL_LOGGER("platinum.core.http.server")
+
+/*----------------------------------------------------------------------
+| PLT_HttpServer::PLT_HttpServer
++---------------------------------------------------------------------*/
+PLT_HttpServer::PLT_HttpServer(NPT_IpAddress address,
+ NPT_IpPort port,
+ bool allow_random_port_on_bind_failure, /* = false */
+ NPT_Cardinal max_clients, /* = 50 */
+ bool reuse_address) : /* = false */
+ NPT_HttpServer(address, port, true),
+ m_TaskManager(new PLT_TaskManager(max_clients)),
+ m_Address(address),
+ m_Port(port),
+ m_AllowRandomPortOnBindFailure(allow_random_port_on_bind_failure),
+ m_ReuseAddress(reuse_address),
+ m_Running(false),
+ m_Aborted(false)
+{
+}
+
+/*----------------------------------------------------------------------
+| PLT_HttpServer::~PLT_HttpServer
++---------------------------------------------------------------------*/
+PLT_HttpServer::~PLT_HttpServer()
+{
+ Stop();
+}
+
+/*----------------------------------------------------------------------
+| PLT_HttpServer::Start
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_HttpServer::Start()
+{
+ NPT_Result res = NPT_FAILURE;
+
+ // we can't start an already running server or restart an aborted server
+ // because the socket is shared create a new instance
+ if (m_Running || m_Aborted) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
+
+ // if we're given a port for our http server, try it
+ if (m_Port) {
+ res = SetListenPort(m_Port, m_ReuseAddress);
+ // return right away if failed and not allowed to try again randomly
+ if (NPT_FAILED(res) && !m_AllowRandomPortOnBindFailure) {
+ NPT_CHECK_SEVERE(res);
+ }
+ }
+
+ // try random port now
+ if (!m_Port || NPT_FAILED(res)) {
+ int retries = 100;
+ do {
+ int random = NPT_System::GetRandomInteger();
+ int port = (unsigned short)(1024 + (random % 1024));
+ if (NPT_SUCCEEDED(SetListenPort(port, m_ReuseAddress))) {
+ break;
+ }
+ } while (--retries > 0);
+
+ if (retries == 0) NPT_CHECK_SEVERE(NPT_FAILURE);
+ }
+
+ // keep track of port server has successfully bound
+ m_Port = m_BoundPort;
+
+ // Tell server to try to listen to more incoming sockets
+ // (this could fail silently)
+ if (m_TaskManager->GetMaxTasks() > 20) {
+ m_Socket.Listen(m_TaskManager->GetMaxTasks());
+ }
+
+ // start a task to listen for incoming connections
+ PLT_HttpListenTask *task = new PLT_HttpListenTask(this, &m_Socket, false);
+ NPT_CHECK_SEVERE(m_TaskManager->StartTask(task));
+
+ NPT_SocketInfo info;
+ m_Socket.GetInfo(info);
+ NPT_LOG_INFO_2("HttpServer listening on %s:%d",
+ (const char*)info.local_address.GetIpAddress().ToString(),
+ m_Port);
+
+ m_Running = true;
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| PLT_HttpServer::Stop
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_HttpServer::Stop()
+{
+ // we can't restart an aborted server
+ if (m_Aborted || !m_Running) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
+
+ // stop all other pending tasks
+ m_TaskManager->Abort();
+
+ m_Running = false;
+ m_Aborted = true;
+
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| PLT_HttpServer::SetupResponse
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_HttpServer::SetupResponse(NPT_HttpRequest& request,
+ const NPT_HttpRequestContext& context,
+ NPT_HttpResponse& response)
+{
+ NPT_String prefix = NPT_String::Format("PLT_HttpServer::SetupResponse %s request from %s for \"%s\"",
+ (const char*) request.GetMethod(),
+ (const char*) context.GetRemoteAddress().ToString(),
+ (const char*) request.GetUrl().ToString());
+ PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINE, prefix, &request);
+
+ NPT_List<NPT_HttpRequestHandler*> handlers = FindRequestHandlers(request);
+ if (handlers.GetItemCount() == 0) return NPT_ERROR_NO_SUCH_ITEM;
+
+ // ask the handler to setup the response
+ NPT_Result result = (*handlers.GetFirstItem())->SetupResponse(request, context, response);
+
+ // DLNA compliance
+ PLT_UPnPMessageHelper::SetDate(response);
+ if (request.GetHeaders().GetHeader("Accept-Language")) {
+ response.GetHeaders().SetHeader("Content-Language", "en");
+ }
+ return result;
+}
+
+/*----------------------------------------------------------------------
+| PLT_HttpServer::ServeFile
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_HttpServer::ServeFile(const NPT_HttpRequest& request,
+ const NPT_HttpRequestContext& context,
+ NPT_HttpResponse& response,
+ NPT_String file_path)
+{
+ NPT_InputStreamReference stream;
+ NPT_File file(file_path);
+ NPT_FileInfo file_info;
+
+ // prevent hackers from accessing files outside of our root
+ if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0)) {
+ return NPT_ERROR_NO_SUCH_ITEM;
+ }
+
+ // check for range requests
+ const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);
+
+ // handle potential 304 only if range header not set
+ NPT_DateTime date;
+ NPT_TimeStamp timestamp;
+ if (NPT_SUCCEEDED(NPT_File::GetInfo(file_path, &file_info)) &&
+ NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIfModifiedSince((NPT_HttpMessage&)request, date)) &&
+ !range_spec) {
+ date.ToTimeStamp(timestamp);
+
+ NPT_LOG_INFO_5("File %s timestamps: request=%d (%s) vs file=%d (%s)",
+ (const char*)request.GetUrl().GetPath(),
+ (NPT_UInt32)timestamp.ToSeconds(),
+ (const char*)date.ToString(),
+ (NPT_UInt32)file_info.m_ModificationTime,
+ (const char*)NPT_DateTime(file_info.m_ModificationTime).ToString());
+
+ if (timestamp >= file_info.m_ModificationTime) {
+ // it's a match
+ NPT_LOG_FINE_1("Returning 304 for %s", request.GetUrl().GetPath().GetChars());
+ response.SetStatus(304, "Not Modified", NPT_HTTP_PROTOCOL_1_1);
+ return NPT_SUCCESS;
+ }
+ }
+
+ // open file
+ if (NPT_FAILED(file.Open(NPT_FILE_OPEN_MODE_READ)) ||
+ NPT_FAILED(file.GetInputStream(stream)) ||
+ stream.IsNull()) {
+ return NPT_ERROR_NO_SUCH_ITEM;
+ }
+
+ // set Last-Modified and Cache-Control headers
+ if (file_info.m_ModificationTime) {
+ NPT_DateTime last_modified = NPT_DateTime(file_info.m_ModificationTime);
+ response.GetHeaders().SetHeader("Last-Modified", last_modified.ToString(NPT_DateTime::FORMAT_RFC_1123), true);
+ response.GetHeaders().SetHeader("Cache-Control", "max-age=0,must-revalidate", true);
+ //response.GetHeaders().SetHeader("Cache-Control", "max-age=1800", true);
+ }
+
+ PLT_HttpRequestContext tmp_context(request, context);
+ return ServeStream(request, context, response, stream, PLT_MimeType::GetMimeType(file_path, &tmp_context));
+}
+
+/*----------------------------------------------------------------------
+| PLT_HttpServer::ServeStream
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_HttpServer::ServeStream(const NPT_HttpRequest& request,
+ const NPT_HttpRequestContext& context,
+ NPT_HttpResponse& response,
+ NPT_InputStreamReference& body,
+ const char* content_type)
+{
+ if (body.IsNull()) return NPT_FAILURE;
+
+ // set date
+ NPT_TimeStamp now;
+ NPT_System::GetCurrentTimeStamp(now);
+ response.GetHeaders().SetHeader("Date", NPT_DateTime(now).ToString(NPT_DateTime::FORMAT_RFC_1123), true);
+
+ // get entity
+ NPT_HttpEntity* entity = response.GetEntity();
+ NPT_CHECK_POINTER_FATAL(entity);
+
+ // set the content type
+ entity->SetContentType(content_type);
+
+ // check for range requests
+ const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);
+
+ // setup entity body
+ NPT_CHECK(NPT_HttpFileRequestHandler::SetupResponseBody(response, body, range_spec));
+
+ // set some default headers
+ if (response.GetEntity()->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
+ // set but don't replace Accept-Range header only if body is seekable
+ NPT_Position offset;
+ if (NPT_SUCCEEDED(body->Tell(offset)) && NPT_SUCCEEDED(body->Seek(offset))) {
+ response.GetHeaders().SetHeader(NPT_HTTP_HEADER_ACCEPT_RANGES, "bytes", false);
+ }
+ }
+
+ // set getcontentFeatures.dlna.org
+ const NPT_String* value = request.GetHeaders().GetHeaderValue("getcontentFeatures.dlna.org");
+ if (value) {
+ PLT_HttpRequestContext tmp_context(request, context);
+ const char* dlna = PLT_ProtocolInfo::GetDlnaExtension(entity->GetContentType(),
+ &tmp_context);
+ if (dlna) response.GetHeaders().SetHeader("ContentFeatures.DLNA.ORG", dlna, false);
+ }
+
+ // transferMode.dlna.org
+ value = request.GetHeaders().GetHeaderValue("transferMode.dlna.org");
+ if (value) {
+ // Interactive mode not supported?
+ /*if (value->Compare("Interactive", true) == 0) {
+ response.SetStatus(406, "Not Acceptable");
+ return NPT_SUCCESS;
+ }*/
+
+ response.GetHeaders().SetHeader("TransferMode.DLNA.ORG", value->GetChars(), false);
+ } else {
+ response.GetHeaders().SetHeader("TransferMode.DLNA.ORG", "Streaming", false);
+ }
+
+ if (request.GetHeaders().GetHeaderValue("TimeSeekRange.dlna.org")) {
+ response.SetStatus(406, "Not Acceptable");
+ return NPT_SUCCESS;
+ }
+
+ return NPT_SUCCESS;
+}