summaryrefslogtreecommitdiffstats
path: root/xbmc/network/httprequesthandler
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/network/httprequesthandler
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/network/httprequesthandler')
-rw-r--r--xbmc/network/httprequesthandler/CMakeLists.txt30
-rw-r--r--xbmc/network/httprequesthandler/HTTPFileHandler.cpp103
-rw-r--r--xbmc/network/httprequesthandler/HTTPFileHandler.h48
-rw-r--r--xbmc/network/httprequesthandler/HTTPImageHandler.cpp51
-rw-r--r--xbmc/network/httprequesthandler/HTTPImageHandler.h29
-rw-r--r--xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp170
-rw-r--r--xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h46
-rw-r--r--xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp183
-rw-r--r--xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h71
-rw-r--r--xbmc/network/httprequesthandler/HTTPPythonHandler.cpp250
-rw-r--r--xbmc/network/httprequesthandler/HTTPPythonHandler.h51
-rw-r--r--xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp85
-rw-r--r--xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h30
-rw-r--r--xbmc/network/httprequesthandler/HTTPVfsHandler.cpp106
-rw-r--r--xbmc/network/httprequesthandler/HTTPVfsHandler.h28
-rw-r--r--xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp60
-rw-r--r--xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h38
-rw-r--r--xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp132
-rw-r--r--xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.h32
-rw-r--r--xbmc/network/httprequesthandler/IHTTPRequestHandler.cpp152
-rw-r--r--xbmc/network/httprequesthandler/IHTTPRequestHandler.h247
-rw-r--r--xbmc/network/httprequesthandler/python/CMakeLists.txt10
-rw-r--r--xbmc/network/httprequesthandler/python/HTTPPythonInvoker.cpp73
-rw-r--r--xbmc/network/httprequesthandler/python/HTTPPythonInvoker.h32
-rw-r--r--xbmc/network/httprequesthandler/python/HTTPPythonRequest.h42
-rw-r--r--xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp458
-rw-r--r--xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.h47
27 files changed, 2604 insertions, 0 deletions
diff --git a/xbmc/network/httprequesthandler/CMakeLists.txt b/xbmc/network/httprequesthandler/CMakeLists.txt
new file mode 100644
index 0000000..ea514c5
--- /dev/null
+++ b/xbmc/network/httprequesthandler/CMakeLists.txt
@@ -0,0 +1,30 @@
+if(MICROHTTPD_FOUND)
+ set(SOURCES HTTPFileHandler.cpp
+ HTTPImageHandler.cpp
+ HTTPImageTransformationHandler.cpp
+ HTTPJsonRpcHandler.cpp
+ HTTPRequestHandlerUtils.cpp
+ HTTPVfsHandler.cpp
+ HTTPWebinterfaceAddonsHandler.cpp
+ HTTPWebinterfaceHandler.cpp
+ IHTTPRequestHandler.cpp)
+
+ if(PYTHON_FOUND)
+ list(APPEND SOURCES HTTPPythonHandler.cpp)
+ endif()
+
+ set(HEADERS HTTPFileHandler.h
+ HTTPImageHandler.h
+ HTTPImageTransformationHandler.h
+ HTTPJsonRpcHandler.h
+ HTTPRequestHandlerUtils.h
+ HTTPVfsHandler.h
+ HTTPWebinterfaceAddonsHandler.h
+ HTTPWebinterfaceHandler.h
+ IHTTPRequestHandler.h)
+ if(PYTHON_FOUND)
+ list(APPEND HEADERS HTTPPythonHandler.h)
+ endif()
+
+ core_add_library(network_httprequesthandlers)
+endif()
diff --git a/xbmc/network/httprequesthandler/HTTPFileHandler.cpp b/xbmc/network/httprequesthandler/HTTPFileHandler.cpp
new file mode 100644
index 0000000..466ab9f
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPFileHandler.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPFileHandler.h"
+
+#include "filesystem/File.h"
+#include "utils/Mime.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+CHTTPFileHandler::CHTTPFileHandler()
+ : IHTTPRequestHandler(),
+ m_url(),
+ m_lastModified()
+{ }
+
+CHTTPFileHandler::CHTTPFileHandler(const HTTPRequest &request)
+ : IHTTPRequestHandler(request),
+ m_url(),
+ m_lastModified()
+{ }
+
+MHD_RESULT CHTTPFileHandler::HandleRequest()
+{
+ return !m_url.empty() ? MHD_YES : MHD_NO;
+}
+
+bool CHTTPFileHandler::GetLastModifiedDate(CDateTime &lastModified) const
+{
+ if (!m_lastModified.IsValid())
+ return false;
+
+ lastModified = m_lastModified;
+ return true;
+}
+
+void CHTTPFileHandler::SetFile(const std::string& file, int responseStatus)
+{
+ m_url = file;
+ m_response.status = responseStatus;
+ if (m_url.empty())
+ return;
+
+ // translate the response status into the response type
+ if (m_response.status == MHD_HTTP_OK)
+ m_response.type = HTTPFileDownload;
+ else if (m_response.status == MHD_HTTP_FOUND)
+ m_response.type = HTTPRedirect;
+ else
+ m_response.type = HTTPError;
+
+ // try to determine some additional information if the file can be downloaded
+ if (m_response.type == HTTPFileDownload)
+ {
+ // determine the content type
+ std::string ext = URIUtils::GetExtension(m_url);
+ StringUtils::ToLower(ext);
+ m_response.contentType = CMime::GetMimeType(ext);
+
+ // determine the last modified date
+ XFILE::CFile fileObj;
+ if (!fileObj.Open(m_url, XFILE::READ_NO_CACHE))
+ {
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else
+ {
+ struct __stat64 statBuffer;
+ if (fileObj.Stat(&statBuffer) == 0)
+ SetLastModifiedDate(&statBuffer);
+ }
+ }
+
+ // disable ranges and caching if the file can't be downloaded
+ if (m_response.type != HTTPFileDownload)
+ {
+ m_canHandleRanges = false;
+ m_canBeCached = false;
+ }
+
+ // disable caching if the last modified date couldn't be read
+ if (!m_lastModified.IsValid())
+ m_canBeCached = false;
+}
+
+void CHTTPFileHandler::SetLastModifiedDate(const struct __stat64 *statBuffer)
+{
+ struct tm *time;
+#ifdef HAVE_LOCALTIME_R
+ struct tm result = {};
+ time = localtime_r((const time_t*)&statBuffer->st_mtime, &result);
+#else
+ time = localtime((time_t *)&statBuffer->st_mtime);
+#endif
+ if (time != NULL)
+ m_lastModified = *time;
+}
diff --git a/xbmc/network/httprequesthandler/HTTPFileHandler.h b/xbmc/network/httprequesthandler/HTTPFileHandler.h
new file mode 100644
index 0000000..1562977
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPFileHandler.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "XBDateTime.h"
+#include "network/httprequesthandler/IHTTPRequestHandler.h"
+
+#include <string>
+
+class CHTTPFileHandler : public IHTTPRequestHandler
+{
+public:
+ ~CHTTPFileHandler() override = default;
+
+ MHD_RESULT HandleRequest() override;
+
+ bool CanHandleRanges() const override { return m_canHandleRanges; }
+ bool CanBeCached() const override { return m_canBeCached; }
+ bool GetLastModifiedDate(CDateTime &lastModified) const override;
+
+ std::string GetRedirectUrl() const override { return m_url; }
+ std::string GetResponseFile() const override { return m_url; }
+
+protected:
+ CHTTPFileHandler();
+ explicit CHTTPFileHandler(const HTTPRequest &request);
+
+ void SetFile(const std::string& file, int responseStatus);
+
+ void SetCanHandleRanges(bool canHandleRanges) { m_canHandleRanges = canHandleRanges; }
+ void SetCanBeCached(bool canBeCached) { m_canBeCached = canBeCached; }
+ void SetLastModifiedDate(const struct __stat64 *buffer);
+
+private:
+ std::string m_url;
+
+ bool m_canHandleRanges = true;
+ bool m_canBeCached = true;
+
+ CDateTime m_lastModified;
+
+};
diff --git a/xbmc/network/httprequesthandler/HTTPImageHandler.cpp b/xbmc/network/httprequesthandler/HTTPImageHandler.cpp
new file mode 100644
index 0000000..6cde13b
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPImageHandler.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPImageHandler.h"
+
+#include "URL.h"
+#include "filesystem/ImageFile.h"
+#include "network/WebServer.h"
+#include "utils/FileUtils.h"
+
+
+CHTTPImageHandler::CHTTPImageHandler(const HTTPRequest &request)
+ : CHTTPFileHandler(request)
+{
+ std::string file;
+ int responseStatus = MHD_HTTP_BAD_REQUEST;
+
+ // resolve the URL into a file path and a HTTP response status
+ if (m_request.pathUrl.size() > 7)
+ {
+ file = m_request.pathUrl.substr(7);
+
+ XFILE::CImageFile imageFile;
+ const CURL pathToUrl(file);
+ if (imageFile.Exists(pathToUrl) && CFileUtils::CheckFileAccessAllowed(file))
+ {
+ responseStatus = MHD_HTTP_OK;
+ struct __stat64 statBuffer;
+ if (imageFile.Stat(pathToUrl, &statBuffer) == 0)
+ {
+ SetLastModifiedDate(&statBuffer);
+ SetCanBeCached(true);
+ }
+ }
+ else
+ responseStatus = MHD_HTTP_NOT_FOUND;
+ }
+
+ // set the file and the HTTP response status
+ SetFile(file, responseStatus);
+}
+
+bool CHTTPImageHandler::CanHandleRequest(const HTTPRequest &request) const
+{
+ return request.pathUrl.find("/image/") == 0;
+}
diff --git a/xbmc/network/httprequesthandler/HTTPImageHandler.h b/xbmc/network/httprequesthandler/HTTPImageHandler.h
new file mode 100644
index 0000000..97df097
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPImageHandler.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "network/httprequesthandler/HTTPFileHandler.h"
+
+#include <string>
+
+class CHTTPImageHandler : public CHTTPFileHandler
+{
+public:
+ CHTTPImageHandler() = default;
+ ~CHTTPImageHandler() override = default;
+
+ IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPImageHandler(request); }
+ bool CanHandleRequest(const HTTPRequest &request) const override;
+
+ int GetPriority() const override { return 5; }
+ int GetMaximumAgeForCaching() const override { return 60 * 60 * 24 * 7; }
+
+protected:
+ explicit CHTTPImageHandler(const HTTPRequest &request);
+};
diff --git a/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp
new file mode 100644
index 0000000..3d2dccb
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPImageTransformationHandler.h"
+
+#include "TextureCacheJob.h"
+#include "URL.h"
+#include "filesystem/ImageFile.h"
+#include "network/WebServer.h"
+#include "network/httprequesthandler/HTTPRequestHandlerUtils.h"
+#include "utils/Mime.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <map>
+
+#define TRANSFORMATION_OPTION_WIDTH "width"
+#define TRANSFORMATION_OPTION_HEIGHT "height"
+#define TRANSFORMATION_OPTION_SCALING_ALGORITHM "scaling_algorithm"
+
+static const std::string ImageBasePath = "/image/";
+
+CHTTPImageTransformationHandler::CHTTPImageTransformationHandler()
+ : m_url(),
+ m_lastModified(),
+ m_buffer(NULL),
+ m_responseData()
+{ }
+
+CHTTPImageTransformationHandler::CHTTPImageTransformationHandler(const HTTPRequest &request)
+ : IHTTPRequestHandler(request),
+ m_url(),
+ m_lastModified(),
+ m_buffer(NULL),
+ m_responseData()
+{
+ m_url = m_request.pathUrl.substr(ImageBasePath.size());
+ if (m_url.empty())
+ {
+ m_response.status = MHD_HTTP_BAD_REQUEST;
+ m_response.type = HTTPError;
+ return;
+ }
+
+ XFILE::CImageFile imageFile;
+ const CURL pathToUrl(m_url);
+ if (!imageFile.Exists(pathToUrl))
+ {
+ m_response.status = MHD_HTTP_NOT_FOUND;
+ m_response.type = HTTPError;
+ return;
+ }
+
+ m_response.type = HTTPMemoryDownloadNoFreeCopy;
+ m_response.status = MHD_HTTP_OK;
+
+ // determine the content type
+ std::string ext = URIUtils::GetExtension(pathToUrl.GetHostName());
+ StringUtils::ToLower(ext);
+ m_response.contentType = CMime::GetMimeType(ext);
+
+ //! @todo determine the maximum age
+
+ // determine the last modified date
+ struct __stat64 statBuffer;
+ if (imageFile.Stat(pathToUrl, &statBuffer) != 0)
+ return;
+
+ struct tm *time;
+#ifdef HAVE_LOCALTIME_R
+ struct tm result = {};
+ time = localtime_r((time_t*)&statBuffer.st_mtime, &result);
+#else
+ time = localtime((time_t *)&statBuffer.st_mtime);
+#endif
+ if (time == NULL)
+ return;
+
+ m_lastModified = *time;
+}
+
+CHTTPImageTransformationHandler::~CHTTPImageTransformationHandler()
+{
+ m_responseData.clear();
+ delete m_buffer;
+ m_buffer = NULL;
+}
+
+bool CHTTPImageTransformationHandler::CanHandleRequest(const HTTPRequest &request) const
+{
+ if ((request.method != GET && request.method != HEAD) ||
+ request.pathUrl.find(ImageBasePath) != 0 || request.pathUrl.size() <= ImageBasePath.size())
+ return false;
+
+ // get the transformation options
+ std::map<std::string, std::string> options;
+ HTTPRequestHandlerUtils::GetRequestHeaderValues(request.connection, MHD_GET_ARGUMENT_KIND, options);
+
+ return (options.find(TRANSFORMATION_OPTION_WIDTH) != options.end() ||
+ options.find(TRANSFORMATION_OPTION_HEIGHT) != options.end());
+}
+
+MHD_RESULT CHTTPImageTransformationHandler::HandleRequest()
+{
+ if (m_response.type == HTTPError)
+ return MHD_YES;
+
+ // get the transformation options
+ std::map<std::string, std::string> options;
+ HTTPRequestHandlerUtils::GetRequestHeaderValues(m_request.connection, MHD_GET_ARGUMENT_KIND, options);
+
+ std::vector<std::string> urlOptions;
+ std::map<std::string, std::string>::const_iterator option = options.find(TRANSFORMATION_OPTION_WIDTH);
+ if (option != options.end())
+ urlOptions.push_back(TRANSFORMATION_OPTION_WIDTH "=" + option->second);
+
+ option = options.find(TRANSFORMATION_OPTION_HEIGHT);
+ if (option != options.end())
+ urlOptions.push_back(TRANSFORMATION_OPTION_HEIGHT "=" + option->second);
+
+ option = options.find(TRANSFORMATION_OPTION_SCALING_ALGORITHM);
+ if (option != options.end())
+ urlOptions.push_back(TRANSFORMATION_OPTION_SCALING_ALGORITHM "=" + option->second);
+
+ std::string imagePath = m_url;
+ if (!urlOptions.empty())
+ {
+ imagePath += "?";
+ imagePath += StringUtils::Join(urlOptions, "&");
+ }
+
+ // resize the image into the local buffer
+ size_t bufferSize;
+ if (!CTextureCacheJob::ResizeTexture(imagePath, m_buffer, bufferSize))
+ {
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ m_response.type = HTTPError;
+
+ return MHD_YES;
+ }
+
+ // store the size of the image
+ m_response.totalLength = bufferSize;
+
+ // nothing else to do if the request is not ranged
+ if (!GetRequestedRanges(m_response.totalLength))
+ {
+ m_responseData.push_back(CHttpResponseRange(m_buffer, 0, m_response.totalLength - 1));
+ return MHD_YES;
+ }
+
+ for (HttpRanges::const_iterator range = m_request.ranges.Begin(); range != m_request.ranges.End(); ++range)
+ m_responseData.push_back(CHttpResponseRange(m_buffer + range->GetFirstPosition(), range->GetFirstPosition(), range->GetLastPosition()));
+
+ return MHD_YES;
+}
+
+bool CHTTPImageTransformationHandler::GetLastModifiedDate(CDateTime &lastModified) const
+{
+ if (!m_lastModified.IsValid())
+ return false;
+
+ lastModified = m_lastModified;
+ return true;
+}
diff --git a/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h
new file mode 100644
index 0000000..6d8732d
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "XBDateTime.h"
+#include "network/httprequesthandler/IHTTPRequestHandler.h"
+
+#include <stdint.h>
+#include <string>
+
+class CHTTPImageTransformationHandler : public IHTTPRequestHandler
+{
+public:
+ CHTTPImageTransformationHandler();
+ ~CHTTPImageTransformationHandler() override;
+
+ IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPImageTransformationHandler(request); }
+ bool CanHandleRequest(const HTTPRequest &request)const override;
+
+ MHD_RESULT HandleRequest() override;
+
+ bool CanHandleRanges() const override { return true; }
+ bool CanBeCached() const override { return true; }
+ bool GetLastModifiedDate(CDateTime &lastModified) const override;
+
+ HttpResponseRanges GetResponseData() const override { return m_responseData; }
+
+ // priority must be higher than the one of CHTTPImageHandler
+ int GetPriority() const override { return 6; }
+
+protected:
+ explicit CHTTPImageTransformationHandler(const HTTPRequest &request);
+
+private:
+ std::string m_url;
+ CDateTime m_lastModified;
+
+ uint8_t* m_buffer;
+ HttpResponseRanges m_responseData;
+};
diff --git a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp
new file mode 100644
index 0000000..9cfdbc5
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPJsonRpcHandler.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "interfaces/json-rpc/JSONRPC.h"
+#include "interfaces/json-rpc/JSONServiceDescription.h"
+#include "network/httprequesthandler/HTTPRequestHandlerUtils.h"
+#include "utils/FileUtils.h"
+#include "utils/JSONVariantWriter.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#define MAX_HTTP_POST_SIZE 65536
+
+bool CHTTPJsonRpcHandler::CanHandleRequest(const HTTPRequest &request) const
+{
+ return (request.pathUrl.compare("/jsonrpc") == 0);
+}
+
+MHD_RESULT CHTTPJsonRpcHandler::HandleRequest()
+{
+ CHTTPClient client(m_request.method);
+ bool isRequest = false;
+ std::string jsonpCallback;
+
+ // get all query arguments
+ std::map<std::string, std::string> arguments;
+ HTTPRequestHandlerUtils::GetRequestHeaderValues(m_request.connection, MHD_GET_ARGUMENT_KIND, arguments);
+
+ if (m_request.method == POST)
+ {
+ std::string contentType = HTTPRequestHandlerUtils::GetRequestHeaderValue(m_request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
+ // If the content-type of the m_request was specified, it must be application/json-rpc, application/json, or application/jsonrequest
+ // http://www.jsonrpc.org/historical/json-rpc-over-http.html
+ if (!contentType.empty() && contentType.compare("application/json-rpc") != 0 &&
+ contentType.compare("application/json") != 0 && contentType.compare("application/jsonrequest") != 0)
+ {
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_UNSUPPORTED_MEDIA_TYPE;
+ return MHD_YES;
+ }
+
+ isRequest = true;
+ }
+ else if (m_request.method == GET || m_request.method == HEAD)
+ {
+ std::map<std::string, std::string>::const_iterator argument = arguments.find("request");
+ if (argument != arguments.end() && !argument->second.empty())
+ {
+ m_requestData = argument->second;
+ isRequest = true;
+ }
+ }
+
+ std::map<std::string, std::string>::const_iterator argument = arguments.find("jsonp");
+ if (argument != arguments.end() && !argument->second.empty())
+ jsonpCallback = argument->second;
+ else
+ {
+ argument = arguments.find("callback");
+ if (argument != arguments.end() && !argument->second.empty())
+ jsonpCallback = argument->second;
+ }
+
+ if (isRequest)
+ {
+ m_responseData = JSONRPC::CJSONRPC::MethodCall(m_requestData, &m_transportLayer, &client);
+
+ if (!jsonpCallback.empty())
+ m_responseData = jsonpCallback + "(" + m_responseData + ");";
+ }
+ else if (jsonpCallback.empty())
+ {
+ // get the whole output of JSONRPC.Introspect
+ CVariant result;
+ JSONRPC::CJSONServiceDescription::Print(result, &m_transportLayer, &client);
+ if (!CJSONVariantWriter::Write(result, m_responseData, false))
+ {
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+
+ return MHD_YES;
+ }
+ }
+ else
+ {
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_BAD_REQUEST;
+
+ return MHD_YES;
+ }
+
+ m_requestData.clear();
+
+ m_responseRange.SetData(m_responseData.c_str(), m_responseData.size());
+
+ m_response.type = HTTPMemoryDownloadNoFreeCopy;
+ m_response.status = MHD_HTTP_OK;
+ m_response.contentType = "application/json";
+ m_response.totalLength = m_responseData.size();
+
+ return MHD_YES;
+}
+
+HttpResponseRanges CHTTPJsonRpcHandler::GetResponseData() const
+{
+ HttpResponseRanges ranges;
+ ranges.push_back(m_responseRange);
+
+ return ranges;
+}
+
+bool CHTTPJsonRpcHandler::appendPostData(const char *data, size_t size)
+{
+ if (m_requestData.size() + size > MAX_HTTP_POST_SIZE)
+ {
+ CServiceBroker::GetLogging()
+ .GetLogger("CHTTPJsonRpcHandler")
+ ->error("Stopped uploading POST data since it exceeded size limitations ({})",
+ MAX_HTTP_POST_SIZE);
+ return false;
+ }
+
+ m_requestData.append(data, size);
+
+ return true;
+}
+
+bool CHTTPJsonRpcHandler::CHTTPTransportLayer::PrepareDownload(const char *path, CVariant &details, std::string &protocol)
+{
+ if (!CFileUtils::Exists(path))
+ return false;
+
+ protocol = "http";
+ std::string url;
+ std::string strPath = path;
+ if (StringUtils::StartsWith(strPath, "image://") ||
+ (StringUtils::StartsWith(strPath, "special://") && StringUtils::EndsWith(strPath, ".tbn")))
+ url = "image/";
+ else
+ url = "vfs/";
+ url += CURL::Encode(strPath);
+ details["path"] = url;
+
+ return true;
+}
+
+bool CHTTPJsonRpcHandler::CHTTPTransportLayer::Download(const char *path, CVariant &result)
+{
+ return false;
+}
+
+int CHTTPJsonRpcHandler::CHTTPTransportLayer::GetCapabilities()
+{
+ return JSONRPC::Response | JSONRPC::FileDownloadRedirect;
+}
+
+CHTTPJsonRpcHandler::CHTTPClient::CHTTPClient(HTTPMethod method)
+ : m_permissionFlags(JSONRPC::ReadData)
+{
+ // with a HTTP POST request everything is allowed
+ if (method == POST)
+ m_permissionFlags = JSONRPC::OPERATION_PERMISSION_ALL;
+}
+
+int CHTTPJsonRpcHandler::CHTTPClient::GetAnnouncementFlags()
+{
+ // Does not support broadcast
+ return 0;
+}
+
+bool CHTTPJsonRpcHandler::CHTTPClient::SetAnnouncementFlags(int flags)
+{
+ return false;
+}
diff --git a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h
new file mode 100644
index 0000000..88d4496
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/json-rpc/IClient.h"
+#include "interfaces/json-rpc/ITransportLayer.h"
+#include "network/httprequesthandler/IHTTPRequestHandler.h"
+
+#include <string>
+
+class CHTTPJsonRpcHandler : public IHTTPRequestHandler
+{
+public:
+ CHTTPJsonRpcHandler() = default;
+ ~CHTTPJsonRpcHandler() override = default;
+
+ // implementations of IHTTPRequestHandler
+ IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPJsonRpcHandler(request); }
+ bool CanHandleRequest(const HTTPRequest &request) const override;
+
+ MHD_RESULT HandleRequest() override;
+
+ HttpResponseRanges GetResponseData() const override;
+
+ int GetPriority() const override { return 5; }
+
+protected:
+ explicit CHTTPJsonRpcHandler(const HTTPRequest &request)
+ : IHTTPRequestHandler(request)
+ { }
+
+ bool appendPostData(const char *data, size_t size) override;
+
+private:
+ std::string m_requestData;
+ std::string m_responseData;
+ CHttpResponseRange m_responseRange;
+
+ class CHTTPTransportLayer : public JSONRPC::ITransportLayer
+ {
+ public:
+ CHTTPTransportLayer() = default;
+ ~CHTTPTransportLayer() override = default;
+
+ // implementations of JSONRPC::ITransportLayer
+ bool PrepareDownload(const char *path, CVariant &details, std::string &protocol) override;
+ bool Download(const char *path, CVariant &result) override;
+ int GetCapabilities() override;
+ };
+ CHTTPTransportLayer m_transportLayer;
+
+ class CHTTPClient : public JSONRPC::IClient
+ {
+ public:
+ explicit CHTTPClient(HTTPMethod method);
+ ~CHTTPClient() override = default;
+
+ int GetPermissionFlags() override { return m_permissionFlags; }
+ int GetAnnouncementFlags() override;
+ bool SetAnnouncementFlags(int flags) override;
+
+ private:
+ int m_permissionFlags;
+ };
+};
diff --git a/xbmc/network/httprequesthandler/HTTPPythonHandler.cpp b/xbmc/network/httprequesthandler/HTTPPythonHandler.cpp
new file mode 100644
index 0000000..8633744
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPPythonHandler.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPPythonHandler.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "addons/Webinterface.h"
+#include "addons/addoninfo/AddonType.h"
+#include "filesystem/File.h"
+#include "interfaces/generic/ScriptInvocationManager.h"
+#include "interfaces/python/XBPython.h"
+#include "network/WebServer.h"
+#include "network/httprequesthandler/HTTPRequestHandlerUtils.h"
+#include "network/httprequesthandler/HTTPWebinterfaceHandler.h"
+#include "network/httprequesthandler/python/HTTPPythonInvoker.h"
+#include "network/httprequesthandler/python/HTTPPythonWsgiInvoker.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#define MAX_STRING_POST_SIZE 20000
+
+CHTTPPythonHandler::CHTTPPythonHandler()
+ : IHTTPRequestHandler(),
+ m_scriptPath(),
+ m_addon(),
+ m_lastModified(),
+ m_requestData(),
+ m_responseData(),
+ m_responseRanges(),
+ m_redirectUrl()
+{ }
+
+CHTTPPythonHandler::CHTTPPythonHandler(const HTTPRequest &request)
+ : IHTTPRequestHandler(request),
+ m_scriptPath(),
+ m_addon(),
+ m_lastModified(),
+ m_requestData(),
+ m_responseData(),
+ m_responseRanges(),
+ m_redirectUrl()
+{
+ m_response.type = HTTPMemoryDownloadNoFreeCopy;
+
+ // get the real path of the script and check if it actually exists
+ m_response.status = CHTTPWebinterfaceHandler::ResolveUrl(m_request.pathUrl, m_scriptPath, m_addon);
+ // only allow requests to a non-static webinterface addon
+ if (m_addon == NULL || m_addon->Type() != ADDON::AddonType::WEB_INTERFACE ||
+ std::dynamic_pointer_cast<ADDON::CWebinterface>(m_addon)->GetType() ==
+ ADDON::WebinterfaceTypeStatic)
+ {
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+
+ return;
+ }
+
+ std::shared_ptr<ADDON::CWebinterface> webinterface = std::dynamic_pointer_cast<ADDON::CWebinterface>(m_addon);
+
+ // forward every request to the default entry point
+ m_scriptPath = webinterface->LibPath();
+
+ // we need to map any requests to a specific WSGI webinterface to the root path
+ std::string baseLocation = webinterface->GetBaseLocation();
+ if (!URIUtils::PathHasParent(m_request.pathUrl, baseLocation))
+ {
+ m_response.type = HTTPRedirect;
+ m_response.status = MHD_HTTP_MOVED_PERMANENTLY;
+ m_redirectUrl = baseLocation + m_request.pathUrl;
+ }
+
+ // no need to try to read the last modified date from a non-existing file
+ if (m_response.status != MHD_HTTP_OK)
+ return;
+
+ // determine the last modified date
+ const CURL pathToUrl(m_scriptPath);
+ struct __stat64 statBuffer;
+ if (XFILE::CFile::Stat(pathToUrl, &statBuffer) != 0)
+ return;
+
+ struct tm* time;
+#ifdef HAVE_LOCALTIME_R
+ struct tm result = {};
+ time = localtime_r((time_t*)&statBuffer.st_mtime, &result);
+#else
+ time = localtime((time_t *)&statBuffer.st_mtime);
+#endif
+ if (time == NULL)
+ return;
+
+ m_lastModified = *time;
+}
+
+bool CHTTPPythonHandler::CanHandleRequest(const HTTPRequest &request) const
+{
+ ADDON::AddonPtr addon;
+ std::string path;
+ // try to resolve the addon as any python script must be part of a webinterface
+ if (!CHTTPWebinterfaceHandler::ResolveAddon(request.pathUrl, addon, path) || addon == NULL ||
+ addon->Type() != ADDON::AddonType::WEB_INTERFACE)
+ return false;
+
+ // static webinterfaces aren't allowed to run python scripts
+ ADDON::CWebinterface* webinterface = static_cast<ADDON::CWebinterface*>(addon.get());
+ if (webinterface->GetType() != ADDON::WebinterfaceTypeWsgi)
+ return false;
+
+ return true;
+}
+
+MHD_RESULT CHTTPPythonHandler::HandleRequest()
+{
+ if (m_response.type == HTTPError || m_response.type == HTTPRedirect)
+ return MHD_YES;
+
+ std::vector<std::string> args;
+ args.push_back(m_scriptPath);
+
+ try
+ {
+ HTTPPythonRequest* pythonRequest = new HTTPPythonRequest();
+ pythonRequest->connection = m_request.connection;
+ pythonRequest->file = URIUtils::GetFileName(m_request.pathUrl);
+ HTTPRequestHandlerUtils::GetRequestHeaderValues(m_request.connection, MHD_GET_ARGUMENT_KIND, pythonRequest->getValues);
+ HTTPRequestHandlerUtils::GetRequestHeaderValues(m_request.connection, MHD_HEADER_KIND, pythonRequest->headerValues);
+ pythonRequest->method = m_request.method;
+ pythonRequest->postValues = m_postFields;
+ pythonRequest->requestContent = m_requestData;
+ pythonRequest->responseType = HTTPNone;
+ pythonRequest->responseLength = 0;
+ pythonRequest->responseStatus = MHD_HTTP_OK;
+ pythonRequest->url = m_request.pathUrlFull;
+ pythonRequest->path = m_request.pathUrl;
+ pythonRequest->version = m_request.version;
+ pythonRequest->requestTime = CDateTime::GetCurrentDateTime();
+ pythonRequest->lastModifiedTime = m_lastModified;
+
+ std::string hostname;
+ uint16_t port;
+ if (GetHostnameAndPort(hostname, port))
+ {
+ pythonRequest->hostname = hostname;
+ pythonRequest->port = port;
+ }
+
+ CHTTPPythonInvoker* pythonInvoker =
+ new CHTTPPythonWsgiInvoker(&CServiceBroker::GetXBPython(), pythonRequest);
+ LanguageInvokerPtr languageInvokerPtr(pythonInvoker);
+ int result = CScriptInvocationManager::GetInstance().ExecuteSync(m_scriptPath, languageInvokerPtr, m_addon, args, 30000, false);
+
+ // check if the script couldn't be started
+ if (result < 0)
+ {
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ m_response.type = HTTPError;
+
+ return MHD_YES;
+ }
+ // check if the script exited with an error
+ if (result > 0)
+ {
+ // check if the script didn't finish in time
+ if (result == ETIMEDOUT)
+ m_response.status = MHD_HTTP_REQUEST_TIMEOUT;
+ else
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ m_response.type = HTTPError;
+
+ return MHD_YES;
+ }
+
+ HTTPPythonRequest* pythonFinalizedRequest = pythonInvoker->GetRequest();
+ if (pythonFinalizedRequest == NULL)
+ {
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return MHD_YES;
+ }
+
+ m_response.type = pythonFinalizedRequest->responseType;
+ m_response.status = pythonFinalizedRequest->responseStatus;
+ if (m_response.status < MHD_HTTP_BAD_REQUEST)
+ {
+ if (m_response.type == HTTPNone)
+ m_response.type = HTTPMemoryDownloadNoFreeCopy;
+ m_response.headers = pythonFinalizedRequest->responseHeaders;
+
+ if (pythonFinalizedRequest->lastModifiedTime.IsValid())
+ m_lastModified = pythonFinalizedRequest->lastModifiedTime;
+ }
+ else
+ {
+ if (m_response.type == HTTPNone)
+ m_response.type = HTTPError;
+ m_response.headers = pythonFinalizedRequest->responseHeadersError;
+ }
+
+ m_responseData = pythonFinalizedRequest->responseData;
+ if (pythonFinalizedRequest->responseLength > 0 && pythonFinalizedRequest->responseLength <= m_responseData.size())
+ m_response.totalLength = pythonFinalizedRequest->responseLength;
+ else
+ m_response.totalLength = m_responseData.size();
+
+ CHttpResponseRange responseRange(m_responseData.c_str(), m_responseData.size());
+ m_responseRanges.push_back(responseRange);
+
+ if (!pythonFinalizedRequest->responseContentType.empty())
+ m_response.contentType = pythonFinalizedRequest->responseContentType;
+ }
+ catch (...)
+ {
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return MHD_YES;
+}
+
+bool CHTTPPythonHandler::GetLastModifiedDate(CDateTime &lastModified) const
+{
+ if (!m_lastModified.IsValid())
+ return false;
+
+ lastModified = m_lastModified;
+ return true;
+}
+
+bool CHTTPPythonHandler::appendPostData(const char *data, size_t size)
+{
+ if (m_requestData.size() + size > MAX_STRING_POST_SIZE)
+ {
+ CServiceBroker::GetLogging()
+ .GetLogger("CHTTPPythonHandler")
+ ->error("Stopped uploading post since it exceeded size limitations ({})",
+ MAX_STRING_POST_SIZE);
+ return false;
+ }
+
+ m_requestData.append(data, size);
+
+ return true;
+}
diff --git a/xbmc/network/httprequesthandler/HTTPPythonHandler.h b/xbmc/network/httprequesthandler/HTTPPythonHandler.h
new file mode 100644
index 0000000..166430e
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPPythonHandler.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "XBDateTime.h"
+#include "addons/IAddon.h"
+#include "addons/Webinterface.h"
+#include "network/httprequesthandler/IHTTPRequestHandler.h"
+
+class CHTTPPythonHandler : public IHTTPRequestHandler
+{
+public:
+ CHTTPPythonHandler();
+ ~CHTTPPythonHandler() override = default;
+
+ IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPPythonHandler(request); }
+ bool CanHandleRequest(const HTTPRequest &request) const override;
+ bool CanHandleRanges() const override { return false; }
+ bool CanBeCached() const override { return false; }
+ bool GetLastModifiedDate(CDateTime &lastModified) const override;
+
+ MHD_RESULT HandleRequest() override;
+
+ HttpResponseRanges GetResponseData() const override { return m_responseRanges; }
+
+ std::string GetRedirectUrl() const override { return m_redirectUrl; }
+
+ int GetPriority() const override { return 3; }
+
+protected:
+ explicit CHTTPPythonHandler(const HTTPRequest &request);
+
+ bool appendPostData(const char *data, size_t size) override;
+
+private:
+ std::string m_scriptPath;
+ ADDON::AddonPtr m_addon;
+ CDateTime m_lastModified;
+
+ std::string m_requestData;
+ std::string m_responseData;
+ HttpResponseRanges m_responseRanges;
+
+ std::string m_redirectUrl;
+};
diff --git a/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp b/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp
new file mode 100644
index 0000000..240449a
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPRequestHandlerUtils.h"
+
+#include "utils/StringUtils.h"
+
+#include <map>
+
+std::string HTTPRequestHandlerUtils::GetRequestHeaderValue(struct MHD_Connection *connection, enum MHD_ValueKind kind, const std::string &key)
+{
+ if (connection == nullptr)
+ return "";
+
+ const char* value = MHD_lookup_connection_value(connection, kind, key.c_str());
+ if (value == nullptr)
+ return "";
+
+ if (StringUtils::EqualsNoCase(key, MHD_HTTP_HEADER_CONTENT_TYPE))
+ {
+ // Work around a bug in firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=416178)
+ // by cutting of anything that follows a ";" in a "Content-Type" header field
+ std::string strValue(value);
+ size_t pos = strValue.find(';');
+ if (pos != std::string::npos)
+ strValue = strValue.substr(0, pos);
+
+ return strValue;
+ }
+
+ return value;
+}
+
+int HTTPRequestHandlerUtils::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::map<std::string, std::string> &headerValues)
+{
+ if (connection == nullptr)
+ return -1;
+
+ return MHD_get_connection_values(connection, kind, FillArgumentMap, &headerValues);
+}
+
+int HTTPRequestHandlerUtils::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::multimap<std::string, std::string> &headerValues)
+{
+ if (connection == nullptr)
+ return -1;
+
+ return MHD_get_connection_values(connection, kind, FillArgumentMultiMap, &headerValues);
+}
+
+bool HTTPRequestHandlerUtils::GetRequestedRanges(struct MHD_Connection *connection, uint64_t totalLength, CHttpRanges &ranges)
+{
+ ranges.Clear();
+
+ if (connection == nullptr)
+ return false;
+
+ return ranges.Parse(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE), totalLength);
+}
+
+MHD_RESULT HTTPRequestHandlerUtils::FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
+{
+ if (cls == nullptr || key == nullptr)
+ return MHD_NO;
+
+ std::map<std::string, std::string> *arguments = reinterpret_cast<std::map<std::string, std::string>*>(cls);
+ arguments->insert(std::make_pair(key, value != nullptr ? value : ""));
+
+ return MHD_YES;
+}
+
+MHD_RESULT HTTPRequestHandlerUtils::FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
+{
+ if (cls == nullptr || key == nullptr)
+ return MHD_NO;
+
+ std::multimap<std::string, std::string> *arguments = reinterpret_cast<std::multimap<std::string, std::string>*>(cls);
+ arguments->insert(std::make_pair(key, value != nullptr ? value : ""));
+
+ return MHD_YES;
+}
diff --git a/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h b/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h
new file mode 100644
index 0000000..d02b5c1
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "network/httprequesthandler/IHTTPRequestHandler.h"
+
+#include <stdint.h>
+#include <string>
+
+class HTTPRequestHandlerUtils
+{
+public:
+ static std::string GetRequestHeaderValue(struct MHD_Connection *connection, enum MHD_ValueKind kind, const std::string &key);
+ static int GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::map<std::string, std::string> &headerValues);
+ static int GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::multimap<std::string, std::string> &headerValues);
+
+ static bool GetRequestedRanges(struct MHD_Connection *connection, uint64_t totalLength, CHttpRanges &ranges);
+
+private:
+ HTTPRequestHandlerUtils() = delete;
+
+ static MHD_RESULT FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value);
+ static MHD_RESULT FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value);
+};
diff --git a/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp b/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp
new file mode 100644
index 0000000..ef2f7a6
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPVfsHandler.h"
+
+#include "MediaSource.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "media/MediaLockState.h"
+#include "settings/MediaSourceSettings.h"
+#include "storage/MediaManager.h"
+#include "utils/FileUtils.h"
+#include "utils/URIUtils.h"
+
+CHTTPVfsHandler::CHTTPVfsHandler(const HTTPRequest &request)
+ : CHTTPFileHandler(request)
+{
+ std::string file;
+ int responseStatus = MHD_HTTP_BAD_REQUEST;
+
+ if (m_request.pathUrl.size() > 5)
+ {
+ file = m_request.pathUrl.substr(5);
+
+ if (CFileUtils::Exists(file))
+ {
+ bool accessible = false;
+ if (file.substr(0, 8) == "image://")
+ accessible = true;
+ else
+ {
+ std::string sourceTypes[] = { "video", "music", "pictures" };
+ unsigned int size = sizeof(sourceTypes) / sizeof(std::string);
+
+ std::string realPath = URIUtils::GetRealPath(file);
+ // for rar:// and zip:// paths we need to extract the path to the archive instead of using the VFS path
+ while (URIUtils::IsInArchive(realPath))
+ realPath = CURL(realPath).GetHostName();
+
+ // Check manually configured sources
+ VECSOURCES *sources = NULL;
+ for (unsigned int index = 0; index < size && !accessible; index++)
+ {
+ sources = CMediaSourceSettings::GetInstance().GetSources(sourceTypes[index]);
+ if (sources == NULL)
+ continue;
+
+ for (const auto& source : *sources)
+ {
+ if (accessible)
+ break;
+
+ // don't allow access to locked / disabled sharing sources
+ if (source.m_iHasLock == LOCK_STATE_LOCKED || !source.m_allowSharing)
+ continue;
+
+ for (const auto& path : source.vecPaths)
+ {
+ std::string realSourcePath = URIUtils::GetRealPath(path);
+ if (URIUtils::PathHasParent(realPath, realSourcePath, true))
+ {
+ accessible = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Check auto-mounted sources
+ if (!accessible)
+ {
+ bool isSource;
+ VECSOURCES removableSources;
+ CServiceBroker::GetMediaManager().GetRemovableDrives(removableSources);
+ int sourceIndex = CUtil::GetMatchingSource(realPath, removableSources, isSource);
+ if (sourceIndex >= 0 && sourceIndex < static_cast<int>(removableSources.size()) &&
+ removableSources.at(sourceIndex).m_iHasLock != LOCK_STATE_LOCKED &&
+ removableSources.at(sourceIndex).m_allowSharing)
+ accessible = true;
+ }
+ }
+
+ if (accessible)
+ responseStatus = MHD_HTTP_OK;
+ // the file exists but not in one of the defined sources so we deny access to it
+ else
+ responseStatus = MHD_HTTP_UNAUTHORIZED;
+ }
+ else
+ responseStatus = MHD_HTTP_NOT_FOUND;
+ }
+
+ // set the file and the HTTP response status
+ SetFile(file, responseStatus);
+}
+
+bool CHTTPVfsHandler::CanHandleRequest(const HTTPRequest &request) const
+{
+ return request.pathUrl.find("/vfs") == 0;
+}
diff --git a/xbmc/network/httprequesthandler/HTTPVfsHandler.h b/xbmc/network/httprequesthandler/HTTPVfsHandler.h
new file mode 100644
index 0000000..af66bad
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPVfsHandler.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "network/httprequesthandler/HTTPFileHandler.h"
+
+#include <string>
+
+class CHTTPVfsHandler : public CHTTPFileHandler
+{
+public:
+ CHTTPVfsHandler() = default;
+ ~CHTTPVfsHandler() override = default;
+
+ IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPVfsHandler(request); }
+ bool CanHandleRequest(const HTTPRequest &request) const override;
+
+ int GetPriority() const override { return 5; }
+
+protected:
+ explicit CHTTPVfsHandler(const HTTPRequest &request);
+};
diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp
new file mode 100644
index 0000000..fa47144
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPWebinterfaceAddonsHandler.h"
+
+#include "ServiceBroker.h"
+#include "addons/Addon.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonType.h"
+#include "network/WebServer.h"
+
+#define ADDON_HEADER "<html><head><title>Add-on List</title></head><body>\n<h1>Available web interfaces:</h1>\n<ul>\n"
+
+bool CHTTPWebinterfaceAddonsHandler::CanHandleRequest(const HTTPRequest &request) const
+{
+ return (request.pathUrl.compare("/addons") == 0 || request.pathUrl.compare("/addons/") == 0);
+}
+
+MHD_RESULT CHTTPWebinterfaceAddonsHandler::HandleRequest()
+{
+ m_responseData = ADDON_HEADER;
+ ADDON::VECADDONS addons;
+ if (!CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON::AddonType::WEB_INTERFACE) ||
+ addons.empty())
+ {
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+
+ return MHD_YES;
+ }
+
+ for (const auto& addon : addons)
+ m_responseData += "<li><a href=/addons/" + addon->ID() + "/>" + addon->Name() + "</a></li>\n";
+
+ m_responseData += "</ul>\n</body></html>";
+
+ m_responseRange.SetData(m_responseData.c_str(), m_responseData.size());
+
+ m_response.type = HTTPMemoryDownloadNoFreeCopy;
+ m_response.status = MHD_HTTP_OK;
+ m_response.contentType = "text/html";
+ m_response.totalLength = m_responseData.size();
+
+ return MHD_YES;
+}
+
+HttpResponseRanges CHTTPWebinterfaceAddonsHandler::GetResponseData() const
+{
+ HttpResponseRanges ranges;
+ ranges.push_back(m_responseRange);
+
+ return ranges;
+}
+
+
diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h
new file mode 100644
index 0000000..20a44ec
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "network/httprequesthandler/IHTTPRequestHandler.h"
+
+#include <string>
+
+class CHTTPWebinterfaceAddonsHandler : public IHTTPRequestHandler
+{
+public:
+ CHTTPWebinterfaceAddonsHandler() = default;
+ ~CHTTPWebinterfaceAddonsHandler() override = default;
+
+ IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPWebinterfaceAddonsHandler(request); }
+ bool CanHandleRequest(const HTTPRequest &request) const override;
+
+ MHD_RESULT HandleRequest() override;
+
+ HttpResponseRanges GetResponseData() const override;
+
+ int GetPriority() const override { return 4; }
+
+protected:
+ explicit CHTTPWebinterfaceAddonsHandler(const HTTPRequest &request)
+ : IHTTPRequestHandler(request)
+ { }
+
+private:
+ std::string m_responseData;
+ CHttpResponseRange m_responseRange;
+};
diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp
new file mode 100644
index 0000000..fe6e760
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPWebinterfaceHandler.h"
+
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/AddonSystemSettings.h"
+#include "addons/Webinterface.h"
+#include "addons/addoninfo/AddonType.h"
+#include "filesystem/Directory.h"
+#include "utils/FileUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#define WEBSERVER_DIRECTORY_SEPARATOR "/"
+
+CHTTPWebinterfaceHandler::CHTTPWebinterfaceHandler(const HTTPRequest &request)
+ : CHTTPFileHandler(request)
+{
+ // resolve the URL into a file path and a HTTP response status
+ std::string file;
+ int responseStatus = ResolveUrl(request.pathUrl, file);
+
+ // set the file and the HTTP response status
+ SetFile(file, responseStatus);
+}
+
+bool CHTTPWebinterfaceHandler::CanHandleRequest(const HTTPRequest &request) const
+{
+ return true;
+}
+
+int CHTTPWebinterfaceHandler::ResolveUrl(const std::string &url, std::string &path)
+{
+ ADDON::AddonPtr dummyAddon;
+ return ResolveUrl(url, path, dummyAddon);
+}
+
+int CHTTPWebinterfaceHandler::ResolveUrl(const std::string &url, std::string &path, ADDON::AddonPtr &addon)
+{
+ // determine the addon and addon's path
+ if (!ResolveAddon(url, addon, path))
+ return MHD_HTTP_NOT_FOUND;
+
+ if (XFILE::CDirectory::Exists(path))
+ {
+ if (URIUtils::GetFileName(path).empty())
+ {
+ // determine the actual file path using the default entry point
+ if (addon != NULL && addon->Type() == ADDON::AddonType::WEB_INTERFACE)
+ path = std::dynamic_pointer_cast<ADDON::CWebinterface>(addon)->GetEntryPoint(path);
+ }
+ else
+ {
+ URIUtils::AddSlashAtEnd(path);
+ return MHD_HTTP_FOUND;
+ }
+ }
+
+ if (!CFileUtils::CheckFileAccessAllowed(path))
+ return MHD_HTTP_NOT_FOUND;
+
+ if (!CFileUtils::Exists(path))
+ return MHD_HTTP_NOT_FOUND;
+
+ return MHD_HTTP_OK;
+}
+
+bool CHTTPWebinterfaceHandler::ResolveAddon(const std::string &url, ADDON::AddonPtr &addon)
+{
+ std::string addonPath;
+ return ResolveAddon(url, addon, addonPath);
+}
+
+bool CHTTPWebinterfaceHandler::ResolveAddon(const std::string &url, ADDON::AddonPtr &addon, std::string &addonPath)
+{
+ std::string path = url;
+
+ // check if the URL references a specific addon
+ if (url.find("/addons/") == 0 && url.size() > 8)
+ {
+ std::vector<std::string> components;
+ StringUtils::Tokenize(path, components, WEBSERVER_DIRECTORY_SEPARATOR);
+ if (components.size() <= 1)
+ return false;
+
+ if (!CServiceBroker::GetAddonMgr().GetAddon(components.at(1), addon,
+ ADDON::OnlyEnabled::CHOICE_YES) ||
+ addon == NULL)
+ return false;
+
+ addonPath = addon->Path();
+ if (addon->Type() !=
+ ADDON::AddonType::WEB_INTERFACE) // No need to append /htdocs for web interfaces
+ addonPath = URIUtils::AddFileToFolder(addonPath, "/htdocs/");
+
+ // remove /addons/<addon-id> from the path
+ components.erase(components.begin(), components.begin() + 2);
+
+ // determine the path within the addon
+ path = StringUtils::Join(components, WEBSERVER_DIRECTORY_SEPARATOR);
+ }
+ else if (!ADDON::CAddonSystemSettings::GetInstance().GetActive(ADDON::AddonType::WEB_INTERFACE,
+ addon) ||
+ addon == NULL)
+ return false;
+
+ // get the path of the addon
+ addonPath = addon->Path();
+
+ // add /htdocs/ to the addon's path if it's not a webinterface
+ if (addon->Type() != ADDON::AddonType::WEB_INTERFACE)
+ addonPath = URIUtils::AddFileToFolder(addonPath, "/htdocs/");
+
+ // append the path within the addon to the path of the addon
+ addonPath = URIUtils::AddFileToFolder(addonPath, path);
+
+ // ensure that we don't have a directory traversal hack here
+ // by checking if the resolved absolute path is inside the addon path
+ std::string realPath = URIUtils::GetRealPath(addonPath);
+ std::string realAddonPath = URIUtils::GetRealPath(addon->Path());
+ if (!URIUtils::PathHasParent(realPath, realAddonPath, true))
+ return false;
+
+ return true;
+}
diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.h b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.h
new file mode 100644
index 0000000..5618c75
--- /dev/null
+++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "addons/IAddon.h"
+#include "network/httprequesthandler/HTTPFileHandler.h"
+
+#include <string>
+
+class CHTTPWebinterfaceHandler : public CHTTPFileHandler
+{
+public:
+ CHTTPWebinterfaceHandler() = default;
+ ~CHTTPWebinterfaceHandler() override = default;
+
+ IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPWebinterfaceHandler(request); }
+ bool CanHandleRequest(const HTTPRequest &request) const override;
+
+ static int ResolveUrl(const std::string &url, std::string &path);
+ static int ResolveUrl(const std::string &url, std::string &path, ADDON::AddonPtr &addon);
+ static bool ResolveAddon(const std::string &url, ADDON::AddonPtr &addon);
+ static bool ResolveAddon(const std::string &url, ADDON::AddonPtr &addon, std::string &addonPath);
+
+protected:
+ explicit CHTTPWebinterfaceHandler(const HTTPRequest &request);
+};
diff --git a/xbmc/network/httprequesthandler/IHTTPRequestHandler.cpp b/xbmc/network/httprequesthandler/IHTTPRequestHandler.cpp
new file mode 100644
index 0000000..3a06604
--- /dev/null
+++ b/xbmc/network/httprequesthandler/IHTTPRequestHandler.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "IHTTPRequestHandler.h"
+
+#include "network/WebServer.h"
+#include "network/httprequesthandler/HTTPRequestHandlerUtils.h"
+#include "utils/StringUtils.h"
+
+#include <limits>
+#include <utility>
+
+static const std::string HTTPMethodHead = "HEAD";
+static const std::string HTTPMethodGet = "GET";
+static const std::string HTTPMethodPost = "POST";
+
+HTTPMethod GetHTTPMethod(const char *method)
+{
+ if (HTTPMethodGet.compare(method) == 0)
+ return GET;
+ if (HTTPMethodPost.compare(method) == 0)
+ return POST;
+ if (HTTPMethodHead.compare(method) == 0)
+ return HEAD;
+
+ return UNKNOWN;
+}
+
+std::string GetHTTPMethod(HTTPMethod method)
+{
+ switch (method)
+ {
+ case HEAD:
+ return HTTPMethodHead;
+
+ case GET:
+ return HTTPMethodGet;
+
+ case POST:
+ return HTTPMethodPost;
+
+ case UNKNOWN:
+ break;
+ }
+
+ return "";
+}
+
+IHTTPRequestHandler::IHTTPRequestHandler()
+ : m_request(),
+ m_response(),
+ m_postFields()
+{ }
+
+IHTTPRequestHandler::IHTTPRequestHandler(const HTTPRequest &request)
+ : m_request(request),
+ m_response(),
+ m_postFields()
+{
+ m_response.type = HTTPError;
+ m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ m_response.totalLength = 0;
+}
+
+bool IHTTPRequestHandler::HasResponseHeader(const std::string &field) const
+{
+ if (field.empty())
+ return false;
+
+ return m_response.headers.find(field) != m_response.headers.end();
+}
+
+bool IHTTPRequestHandler::AddResponseHeader(const std::string &field, const std::string &value, bool allowMultiple /* = false */)
+{
+ if (field.empty() || value.empty())
+ return false;
+
+ if (!allowMultiple && HasResponseHeader(field))
+ return false;
+
+ m_response.headers.insert(std::make_pair(field, value));
+ return true;
+}
+
+void IHTTPRequestHandler::AddPostField(const std::string &key, const std::string &value)
+{
+ if (key.empty())
+ return;
+
+ std::map<std::string, std::string>::iterator field = m_postFields.find(key);
+ if (field == m_postFields.end())
+ m_postFields[key] = value;
+ else
+ m_postFields[key].append(value);
+}
+
+bool IHTTPRequestHandler::AddPostData(const char *data, size_t size)
+{
+ if (size > 0)
+ return appendPostData(data, size);
+
+ return true;
+}
+
+bool IHTTPRequestHandler::GetRequestedRanges(uint64_t totalLength)
+{
+ if (!m_ranged || m_request.webserver == NULL || m_request.connection == NULL)
+ return false;
+
+ m_request.ranges.Clear();
+ if (totalLength == 0)
+ return true;
+
+ return HTTPRequestHandlerUtils::GetRequestedRanges(m_request.connection, totalLength, m_request.ranges);
+}
+
+bool IHTTPRequestHandler::GetHostnameAndPort(std::string& hostname, uint16_t &port)
+{
+ if (m_request.webserver == NULL || m_request.connection == NULL)
+ return false;
+
+ std::string hostnameAndPort = HTTPRequestHandlerUtils::GetRequestHeaderValue(m_request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_HOST);
+ if (hostnameAndPort.empty())
+ return false;
+
+ size_t pos = hostnameAndPort.find(':');
+ hostname = hostnameAndPort.substr(0, pos);
+ if (hostname.empty())
+ return false;
+
+ if (pos != std::string::npos)
+ {
+ std::string strPort = hostnameAndPort.substr(pos + 1);
+ if (!StringUtils::IsNaturalNumber(strPort))
+ return false;
+
+ unsigned long portL = strtoul(strPort.c_str(), NULL, 0);
+ if (portL > std::numeric_limits<uint16_t>::max())
+ return false;
+
+ port = static_cast<uint16_t>(portL);
+ }
+ else
+ port = 80;
+
+ return true;
+}
diff --git a/xbmc/network/httprequesthandler/IHTTPRequestHandler.h b/xbmc/network/httprequesthandler/IHTTPRequestHandler.h
new file mode 100644
index 0000000..13c170f
--- /dev/null
+++ b/xbmc/network/httprequesthandler/IHTTPRequestHandler.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/HttpRangeUtils.h"
+
+#include <map>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+#include <microhttpd.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#if MHD_VERSION >= 0x00097002
+using MHD_RESULT = MHD_Result;
+#else
+using MHD_RESULT = int;
+#endif
+
+class CDateTime;
+class CWebServer;
+
+enum HTTPMethod
+{
+ UNKNOWN,
+ POST,
+ GET,
+ HEAD
+};
+
+HTTPMethod GetHTTPMethod(const char *method);
+std::string GetHTTPMethod(HTTPMethod method);
+
+typedef enum HTTPResponseType
+{
+ HTTPNone,
+ // creates and returns a HTTP error
+ HTTPError,
+ // creates and returns a HTTP redirect response
+ HTTPRedirect,
+ // creates a HTTP response with the content from a file
+ HTTPFileDownload,
+ // creates a HTTP response from a buffer without copying or freeing the buffer
+ HTTPMemoryDownloadNoFreeNoCopy,
+ // creates a HTTP response from a buffer by copying but not freeing the buffer
+ HTTPMemoryDownloadNoFreeCopy,
+ // creates a HTTP response from a buffer without copying followed by freeing the buffer
+ // the buffer must have been malloc'ed and not new'ed
+ HTTPMemoryDownloadFreeNoCopy,
+ // creates a HTTP response from a buffer by copying followed by freeing the buffer
+ // the buffer must have been malloc'ed and not new'ed
+ HTTPMemoryDownloadFreeCopy
+} HTTPResponseType;
+
+typedef struct HTTPRequest
+{
+ CWebServer *webserver;
+ struct MHD_Connection *connection;
+ std::string pathUrlFull;
+ std::string pathUrl;
+ HTTPMethod method;
+ std::string version;
+ CHttpRanges ranges;
+} HTTPRequest;
+
+typedef struct HTTPResponseDetails {
+ HTTPResponseType type;
+ int status;
+ std::multimap<std::string, std::string> headers;
+ std::string contentType;
+ uint64_t totalLength;
+} HTTPResponseDetails;
+
+class IHTTPRequestHandler
+{
+public:
+ virtual ~IHTTPRequestHandler() = default;
+
+ /*!
+ * \brief Creates a new HTTP request handler for the given request.
+ *
+ * \details This call is responsible for doing some preparation work like -
+ * depending on the supported features - determining whether the requested
+ * entity supports ranges, whether it can be cached and what it's last
+ * modified date is.
+ *
+ * \param request HTTP request to be handled
+ */
+ virtual IHTTPRequestHandler* Create(const HTTPRequest &request) const = 0;
+
+ /*!
+ * \brief Returns the priority of the HTTP request handler.
+ *
+ * \details The higher the priority the more important is the HTTP request
+ * handler.
+ */
+ virtual int GetPriority() const { return 0; }
+
+ /*!
+ * \brief Checks if the HTTP request handler can handle the given request.
+ *
+ * \param request HTTP request to be handled
+ * \return True if the given HTTP request can be handled otherwise false.
+ */
+ virtual bool CanHandleRequest(const HTTPRequest &request) const = 0;
+
+ /*!
+ * \brief Handles the HTTP request.
+ *
+ * \return MHD_NO if a severe error has occurred otherwise MHD_YES.
+ */
+ virtual MHD_RESULT HandleRequest() = 0;
+
+ /*!
+ * \brief Whether the HTTP response could also be provided in ranges.
+ */
+ virtual bool CanHandleRanges() const { return false; }
+
+ /*!
+ * \brief Whether the HTTP response can be cached.
+ */
+ virtual bool CanBeCached() const { return false; }
+
+ /*!
+ * \brief Returns the maximum age (in seconds) for which the response can be cached.
+ *
+ * \details This is only used if the response can be cached.
+ */
+ virtual int GetMaximumAgeForCaching() const { return 0; }
+
+ /*!
+ * \brief Returns the last modification date of the response data.
+ *
+ * \details This is only used if the response can be cached.
+ */
+ virtual bool GetLastModifiedDate(CDateTime &lastModified) const { return false; }
+
+ /*!
+ * \brief Returns the ranges with raw data belonging to the response.
+ *
+ * \details This is only used if the response type is one of the HTTPMemoryDownload types.
+ */
+ virtual HttpResponseRanges GetResponseData() const { return HttpResponseRanges(); }
+
+ /*!
+ * \brief Returns the URL to which the request should be redirected.
+ *
+ * \details This is only used if the response type is HTTPRedirect.
+ */
+ virtual std::string GetRedirectUrl() const { return ""; }
+
+ /*!
+ * \brief Returns the path to the file that should be returned as the response.
+ *
+ * \details This is only used if the response type is HTTPFileDownload.
+ */
+ virtual std::string GetResponseFile() const { return ""; }
+
+ /*!
+ * \brief Returns the HTTP request handled by the HTTP request handler.
+ */
+ const HTTPRequest& GetRequest() const { return m_request; }
+
+ /*!
+ * \brief Returns true if the HTTP request is ranged, otherwise false.
+ */
+ bool IsRequestRanged() const { return m_ranged; }
+
+ /*!
+ * \brief Sets whether the HTTP request contains ranges or not
+ */
+ void SetRequestRanged(bool ranged) { m_ranged = ranged; }
+
+ /*!
+ * \brief Sets the response status of the HTTP response.
+ *
+ * \param status HTTP status of the response
+ */
+ void SetResponseStatus(int status) { m_response.status = status; }
+
+ /*!
+ * \brief Checks if the given HTTP header field is part of the response details.
+ *
+ * \param field HTTP header field name
+ * \return True if the header field is set, otherwise false.
+ */
+ bool HasResponseHeader(const std::string &field) const;
+
+ /*!
+ * \brief Adds the given HTTP header field and value to the response details.
+ *
+ * \param field HTTP header field name
+ * \param value HTTP header field value
+ * \param allowMultiple Whether the same header is allowed multiple times
+ * \return True if the header field was added, otherwise false.
+ */
+ bool AddResponseHeader(const std::string &field, const std::string &value, bool allowMultiple = false);
+
+ /*!
+ * \brief Returns the HTTP response header details.
+ */
+ const HTTPResponseDetails& GetResponseDetails() const { return m_response; }
+
+ /*!
+ * \brief Adds the given key-value pair extracted from the HTTP POST data.
+ *
+ * \param key Key of the HTTP POST field
+ * \param value Value of the HTTP POST field
+ */
+ void AddPostField(const std::string &key, const std::string &value);
+ /*!
+ * \brief Adds the given raw HTTP POST data.
+ *
+ * \param data Raw HTTP POST data
+ * \param size Size of the raw HTTP POST data
+ */
+ bool AddPostData(const char *data, size_t size);
+
+protected:
+ IHTTPRequestHandler();
+ explicit IHTTPRequestHandler(const HTTPRequest &request);
+
+ virtual bool appendPostData(const char *data, size_t size)
+ { return true; }
+
+ bool GetRequestedRanges(uint64_t totalLength);
+ bool GetHostnameAndPort(std::string& hostname, uint16_t &port);
+
+ HTTPRequest m_request;
+ HTTPResponseDetails m_response;
+
+ std::map<std::string, std::string> m_postFields;
+
+private:
+ bool m_ranged = false;
+};
diff --git a/xbmc/network/httprequesthandler/python/CMakeLists.txt b/xbmc/network/httprequesthandler/python/CMakeLists.txt
new file mode 100644
index 0000000..7bbbad9
--- /dev/null
+++ b/xbmc/network/httprequesthandler/python/CMakeLists.txt
@@ -0,0 +1,10 @@
+if(MICROHTTPD_FOUND AND PYTHON_FOUND)
+ set(SOURCES HTTPPythonInvoker.cpp
+ HTTPPythonWsgiInvoker.cpp)
+
+ set(HEADERS HTTPPythonInvoker.h
+ HTTPPythonRequest.h
+ HTTPPythonWsgiInvoker.h)
+
+ core_add_library(network_httprequesthandlers_python)
+endif()
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonInvoker.cpp b/xbmc/network/httprequesthandler/python/HTTPPythonInvoker.cpp
new file mode 100644
index 0000000..696c039
--- /dev/null
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonInvoker.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPPythonInvoker.h"
+
+#include "CompileInfo.h"
+#include "utils/StringUtils.h"
+
+CHTTPPythonInvoker::CHTTPPythonInvoker(ILanguageInvocationHandler* invocationHandler, HTTPPythonRequest* request)
+ : CPythonInvoker(invocationHandler),
+ m_request(request),
+ m_internalError(false)
+{ }
+
+CHTTPPythonInvoker::~CHTTPPythonInvoker()
+{
+ delete m_request;
+ m_request = NULL;
+}
+
+void CHTTPPythonInvoker::onAbort()
+{
+ if (m_request == NULL)
+ return;
+
+ m_internalError = true;
+ m_request->responseType = HTTPError;
+ m_request->responseStatus = MHD_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+void CHTTPPythonInvoker::onError(const std::string& exceptionType /* = "" */, const std::string& exceptionValue /* = "" */, const std::string& exceptionTraceback /* = "" */)
+{
+ if (m_request == NULL)
+ return;
+
+ m_internalError = true;
+ m_request->responseType = HTTPMemoryDownloadNoFreeCopy;
+ m_request->responseStatus = MHD_HTTP_INTERNAL_SERVER_ERROR;
+
+ std::string output;
+ if (!exceptionType.empty())
+ {
+ output += exceptionType;
+
+ if (!exceptionValue.empty())
+ output += ": " + exceptionValue;
+ output += "\n";
+ }
+
+ if (!exceptionTraceback .empty())
+ output += exceptionTraceback;
+
+ // replace all special characters
+
+ StringUtils::Replace(output, "<", "&lt;");
+ StringUtils::Replace(output, ">", "&gt;");
+ StringUtils::Replace(output, " ", "&nbsp;");
+ StringUtils::Replace(output, "\n", "\n<br />");
+
+ if (!exceptionType.empty())
+ {
+ // now make the type and value bold (needs to be done here because otherwise the < and > would have been replaced
+ output = "<b>" + output;
+ output.insert(output.find('\n'), "</b>");
+ }
+
+ m_request->responseData = "<html><head><title>" + std::string(CCompileInfo::GetAppName()) + ": python error</title></head><body>" + output + "</body></html>";
+}
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonInvoker.h b/xbmc/network/httprequesthandler/python/HTTPPythonInvoker.h
new file mode 100644
index 0000000..83f1f34
--- /dev/null
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonInvoker.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/python/PythonInvoker.h"
+#include "network/httprequesthandler/python/HTTPPythonRequest.h"
+
+#include <string>
+
+class CHTTPPythonInvoker : public CPythonInvoker
+{
+public:
+ ~CHTTPPythonInvoker() override;
+
+ virtual HTTPPythonRequest* GetRequest() = 0;
+
+protected:
+ CHTTPPythonInvoker(ILanguageInvocationHandler* invocationHandler, HTTPPythonRequest* request);
+
+ // overrides of CPythonInvoker
+ void onAbort() override;
+ void onError(const std::string& exceptionType = "", const std::string& exceptionValue = "", const std::string& exceptionTraceback = "") override;
+
+ HTTPPythonRequest* m_request;
+ bool m_internalError;
+};
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonRequest.h b/xbmc/network/httprequesthandler/python/HTTPPythonRequest.h
new file mode 100644
index 0000000..7874203
--- /dev/null
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonRequest.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "XBDateTime.h"
+#include "network/httprequesthandler/IHTTPRequestHandler.h"
+
+#include <map>
+#include <stdint.h>
+#include <string>
+
+typedef struct HTTPPythonRequest
+{
+ struct MHD_Connection *connection;
+ std::string hostname;
+ uint16_t port;
+ std::string url;
+ std::string path;
+ std::string file;
+ HTTPMethod method;
+ std::string version;
+ std::multimap<std::string, std::string> headerValues;
+ std::map<std::string, std::string> getValues;
+ std::map<std::string, std::string> postValues;
+ std::string requestContent;
+ CDateTime requestTime;
+ CDateTime lastModifiedTime;
+
+ HTTPResponseType responseType;
+ int responseStatus;
+ std::string responseContentType;
+ std::string responseData;
+ size_t responseLength;
+ std::multimap<std::string, std::string> responseHeaders;
+ std::multimap<std::string, std::string> responseHeadersError;
+} HTTPPythonRequest;
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
new file mode 100644
index 0000000..ac047a7
--- /dev/null
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPPythonWsgiInvoker.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "addons/Webinterface.h"
+#include "addons/addoninfo/AddonType.h"
+#include "interfaces/legacy/wsgi/WsgiErrorStream.h"
+#include "interfaces/legacy/wsgi/WsgiInputStream.h"
+#include "interfaces/legacy/wsgi/WsgiResponse.h"
+#include "interfaces/python/swig.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <utility>
+
+#include <Python.h>
+
+#define MODULE "xbmc"
+
+#define RUNSCRIPT_PREAMBLE \
+ "" \
+ "import " MODULE "\n" \
+ "class xbmcout:\n" \
+ " def __init__(self, loglevel=" MODULE ".LOGINFO):\n" \
+ " self.ll=loglevel\n" \
+ " def write(self, data):\n" \
+ " " MODULE ".log(data,self.ll)\n" \
+ " def close(self):\n" \
+ " " MODULE ".log('.')\n" \
+ " def flush(self):\n" \
+ " " MODULE ".log('.')\n" \
+ "import sys\n" \
+ "sys.stdout = xbmcout()\n" \
+ "sys.stderr = xbmcout(" MODULE ".LOGERROR)\n" \
+ ""
+
+#define RUNSCRIPT_SETUPTOOLS_HACK \
+ "" \
+ "import types,sys\n" \
+ "pkg_resources_code = \\\n" \
+ "\"\"\"\n" \
+ "def resource_filename(__name__,__path__):\n" \
+ " return __path__\n" \
+ "\"\"\"\n" \
+ "pkg_resources = types.ModuleType('pkg_resources')\n" \
+ "exec(pkg_resources_code, pkg_resources.__dict__)\n" \
+ "sys.modules['pkg_resources'] = pkg_resources\n" \
+ ""
+
+#define RUNSCRIPT_POSTSCRIPT \
+ MODULE ".log('-->HTTP Python WSGI Interpreter Initialized<--', " MODULE ".LOGINFO)\n" \
+ ""
+
+#if defined(TARGET_ANDROID)
+#define RUNSCRIPT \
+ RUNSCRIPT_PREAMBLE RUNSCRIPT_SETUPTOOLS_HACK RUNSCRIPT_POSTSCRIPT
+#else
+#define RUNSCRIPT \
+ RUNSCRIPT_PREAMBLE RUNSCRIPT_POSTSCRIPT
+#endif
+
+namespace PythonBindings {
+PyObject* PyInit_Module_xbmc(void);
+PyObject* PyInit_Module_xbmcaddon(void);
+PyObject* PyInit_Module_xbmcwsgi(void);
+}
+
+using namespace PythonBindings;
+
+typedef struct
+{
+ const char *name;
+ CPythonInvoker::PythonModuleInitialization initialization;
+} PythonModule;
+
+static PythonModule PythonModules[] =
+{
+ { "xbmc", PyInit_Module_xbmc },
+ { "xbmcaddon", PyInit_Module_xbmcaddon },
+ { "xbmcwsgi", PyInit_Module_xbmcwsgi }
+};
+
+CHTTPPythonWsgiInvoker::CHTTPPythonWsgiInvoker(ILanguageInvocationHandler* invocationHandler, HTTPPythonRequest* request)
+ : CHTTPPythonInvoker(invocationHandler, request),
+ m_wsgiResponse(NULL)
+{
+ PyImport_AppendInittab("xbmc", PyInit_Module_xbmc);
+ PyImport_AppendInittab("xbmcaddon", PyInit_Module_xbmcaddon);
+ PyImport_AppendInittab("xbmcwsgi", PyInit_Module_xbmcwsgi);
+}
+
+CHTTPPythonWsgiInvoker::~CHTTPPythonWsgiInvoker()
+{
+ delete m_wsgiResponse;
+ m_wsgiResponse = NULL;
+}
+
+HTTPPythonRequest* CHTTPPythonWsgiInvoker::GetRequest()
+{
+ if (m_request == NULL || m_wsgiResponse == NULL)
+ return NULL;
+
+ if (m_internalError)
+ return m_request;
+
+ m_wsgiResponse->Finalize(m_request);
+ return m_request;
+}
+
+void CHTTPPythonWsgiInvoker::executeScript(FILE* fp, const std::string& script, PyObject* moduleDict)
+{
+ if (m_request == NULL || m_addon == NULL || m_addon->Type() != ADDON::AddonType::WEB_INTERFACE ||
+ fp == NULL || script.empty() || moduleDict == NULL)
+ return;
+
+ auto logger = CServiceBroker::GetLogging().GetLogger(
+ StringUtils::Format("CHTTPPythonWsgiInvoker[{}]", script));
+
+ ADDON::CWebinterface* webinterface = static_cast<ADDON::CWebinterface*>(m_addon.get());
+ if (webinterface->GetType() != ADDON::WebinterfaceTypeWsgi)
+ {
+ logger->error("trying to execute a non-WSGI script");
+ return;
+ }
+
+ PyObject* pyScript = NULL;
+ PyObject* pyModule = NULL;
+ PyObject* pyEntryPoint = NULL;
+ std::map<std::string, std::string> cgiEnvironment;
+ PyObject* pyEnviron = NULL;
+ PyObject* pyStart_response = NULL;
+ PyObject* pyArgs = NULL;
+ PyObject* pyResult = NULL;
+ PyObject* pyResultIterator = NULL;
+ PyObject* pyIterResult = NULL;
+
+ // get the script
+ std::string scriptName = URIUtils::GetFileName(script);
+ URIUtils::RemoveExtension(scriptName);
+ pyScript = PyUnicode_FromStringAndSize(scriptName.c_str(), scriptName.size());
+ if (pyScript == NULL)
+ {
+ logger->error("failed to convert script to python string");
+ return;
+ }
+
+ // load the script
+ logger->debug("loading script");
+ pyModule = PyImport_Import(pyScript);
+ Py_DECREF(pyScript);
+ if (pyModule == NULL)
+ {
+ logger->error("failed to load WSGI script");
+ return;
+ }
+
+ // get the entry point
+ const std::string& entryPoint = webinterface->EntryPoint();
+ logger->debug(R"(loading entry point "{}")", entryPoint);
+ pyEntryPoint = PyObject_GetAttrString(pyModule, entryPoint.c_str());
+ if (pyEntryPoint == NULL)
+ {
+ logger->error(R"(failed to load entry point "{}")", entryPoint);
+ goto cleanup;
+ }
+
+ // check if the loaded entry point is a callable function
+ if (!PyCallable_Check(pyEntryPoint))
+ {
+ logger->error(R"(defined entry point "{}" is not callable)", entryPoint);
+ goto cleanup;
+ }
+
+ // prepare the WsgiResponse object
+ m_wsgiResponse = new XBMCAddon::xbmcwsgi::WsgiResponse();
+ if (m_wsgiResponse == NULL)
+ {
+ logger->error("failed to create WsgiResponse object");
+ goto cleanup;
+ }
+
+ try
+ {
+ // prepare the start_response callable
+ pyStart_response = PythonBindings::makePythonInstance(m_wsgiResponse, true);
+
+ // create the (CGI) environment dictionary
+ cgiEnvironment = createCgiEnvironment(m_request, m_addon);
+ // and turn it into a python dictionary
+ pyEnviron = PyDict_New();
+ for (const auto& cgiEnv : cgiEnvironment)
+ {
+ PyObject* pyEnvEntry = PyUnicode_FromStringAndSize(cgiEnv.second.c_str(), cgiEnv.second.size());
+ PyDict_SetItemString(pyEnviron, cgiEnv.first.c_str(), pyEnvEntry);
+ Py_DECREF(pyEnvEntry);
+ }
+
+ // add the WSGI-specific environment variables
+ addWsgiEnvironment(m_request, pyEnviron);
+ }
+ catch (const XBMCAddon::WrongTypeException& e)
+ {
+ logger->error("failed to prepare WsgiResponse object with wrong type exception: {}",
+ e.GetExMessage());
+ goto cleanup;
+ }
+ catch (const XbmcCommons::Exception& e)
+ {
+ logger->error("failed to prepare WsgiResponse object with exception: {}", e.GetExMessage());
+ goto cleanup;
+ }
+ catch (...)
+ {
+ logger->error("failed to prepare WsgiResponse object with unknown exception");
+ goto cleanup;
+ }
+
+ // put together the arguments
+ pyArgs = PyTuple_Pack(2, pyEnviron, pyStart_response);
+ Py_DECREF(pyEnviron);
+ Py_DECREF(pyStart_response);
+
+ // call the given handler with the prepared arguments
+ pyResult = PyObject_CallObject(pyEntryPoint, pyArgs);
+ Py_DECREF(pyArgs);
+ if (pyResult == NULL)
+ {
+ logger->error("no result");
+ goto cleanup;
+ }
+
+ // try to get an iterator from the result object
+ pyResultIterator = PyObject_GetIter(pyResult);
+ if (pyResultIterator == NULL || !PyIter_Check(pyResultIterator))
+ {
+ logger->error("result is not iterable");
+ goto cleanup;
+ }
+
+ // go through all the iterables in the result and turn them into strings
+ while ((pyIterResult = PyIter_Next(pyResultIterator)) != NULL)
+ {
+ std::string result;
+ try
+ {
+ PythonBindings::PyXBMCGetUnicodeString(result, pyIterResult, false, "result", "handle_request");
+ }
+ catch (const XBMCAddon::WrongTypeException& e)
+ {
+ logger->error("failed to parse result iterable object with wrong type exception: {}",
+ e.GetExMessage());
+ goto cleanup;
+ }
+ catch (const XbmcCommons::Exception& e)
+ {
+ logger->error("failed to parse result iterable object with exception: {}", e.GetExMessage());
+ goto cleanup;
+ }
+ catch (...)
+ {
+ logger->error("failed to parse result iterable object with unknown exception");
+ goto cleanup;
+ }
+
+ // append the result string to the response
+ m_wsgiResponse->Append(result);
+ }
+
+cleanup:
+ if (pyIterResult != NULL)
+ {
+ Py_DECREF(pyIterResult);
+ }
+ if (pyResultIterator != NULL)
+ {
+ // Call optional close method on iterator
+ if (PyObject_HasAttrString(pyResultIterator, "close") == 1)
+ {
+ if (PyObject_CallMethod(pyResultIterator, "close", NULL) == NULL)
+ logger->error("failed to close iterator object");
+ }
+ Py_DECREF(pyResultIterator);
+ }
+ if (pyResult != NULL)
+ {
+ Py_DECREF(pyResult);
+ }
+ if (pyEntryPoint != NULL)
+ {
+ Py_DECREF(pyEntryPoint);
+ }
+ if (pyModule != NULL)
+ {
+ Py_DECREF(pyModule);
+ }
+}
+
+std::map<std::string, CPythonInvoker::PythonModuleInitialization> CHTTPPythonWsgiInvoker::getModules() const
+{
+ static std::map<std::string, PythonModuleInitialization> modules;
+ if (modules.empty())
+ {
+ for (const PythonModule& pythonModule : PythonModules)
+ modules.insert(std::make_pair(pythonModule.name, pythonModule.initialization));
+ }
+
+ return modules;
+}
+
+const char* CHTTPPythonWsgiInvoker::getInitializationScript() const
+{
+ return RUNSCRIPT;
+}
+
+std::map<std::string, std::string> CHTTPPythonWsgiInvoker::createCgiEnvironment(
+ const HTTPPythonRequest* httpRequest, const ADDON::AddonPtr& addon)
+{
+ std::map<std::string, std::string> environment;
+
+ // REQUEST_METHOD
+ std::string requestMethod;
+ switch (httpRequest->method)
+ {
+ case HEAD:
+ requestMethod = "HEAD";
+ break;
+
+ case POST:
+ requestMethod = "POST";
+ break;
+
+ case GET:
+ default:
+ requestMethod = "GET";
+ break;
+ }
+ environment.insert(std::make_pair("REQUEST_METHOD", requestMethod));
+
+ // SCRIPT_NAME
+ std::string scriptName = std::dynamic_pointer_cast<ADDON::CWebinterface>(addon)->GetBaseLocation();
+ environment.insert(std::make_pair("SCRIPT_NAME", scriptName));
+
+ // PATH_INFO
+ std::string pathInfo = httpRequest->path.substr(scriptName.size());
+ environment.insert(std::make_pair("PATH_INFO", pathInfo));
+
+ // QUERY_STRING
+ size_t iOptions = httpRequest->url.find_first_of('?');
+ if (iOptions != std::string::npos)
+ environment.insert(std::make_pair("QUERY_STRING", httpRequest->url.substr(iOptions+1)));
+ else
+ environment.insert(std::make_pair("QUERY_STRING", ""));
+
+ // CONTENT_TYPE
+ std::string headerValue;
+ std::multimap<std::string, std::string>::const_iterator headerIt = httpRequest->headerValues.find(MHD_HTTP_HEADER_CONTENT_TYPE);
+ if (headerIt != httpRequest->headerValues.end())
+ headerValue = headerIt->second;
+ environment.insert(std::make_pair("CONTENT_TYPE", headerValue));
+
+ // CONTENT_LENGTH
+ headerValue.clear();
+ headerIt = httpRequest->headerValues.find(MHD_HTTP_HEADER_CONTENT_LENGTH);
+ if (headerIt != httpRequest->headerValues.end())
+ headerValue = headerIt->second;
+ environment.insert(std::make_pair("CONTENT_LENGTH", headerValue));
+
+ // SERVER_NAME
+ environment.insert(std::make_pair("SERVER_NAME", httpRequest->hostname));
+
+ // SERVER_PORT
+ environment.insert(std::make_pair("SERVER_PORT", std::to_string(httpRequest->port)));
+
+ // SERVER_PROTOCOL
+ environment.insert(std::make_pair("SERVER_PROTOCOL", httpRequest->version));
+
+ // HTTP_<HEADER_NAME>
+ for (headerIt = httpRequest->headerValues.begin(); headerIt != httpRequest->headerValues.end(); ++headerIt)
+ {
+ std::string headerName = headerIt->first;
+ StringUtils::ToUpper(headerName);
+ environment.insert(std::make_pair("HTTP_" + headerName, headerIt->second));
+ }
+
+ return environment;
+}
+
+void CHTTPPythonWsgiInvoker::addWsgiEnvironment(HTTPPythonRequest* request, void* environment)
+{
+ if (environment == nullptr)
+ return;
+
+ PyObject* pyEnviron = reinterpret_cast<PyObject*>(environment);
+ if (pyEnviron == nullptr)
+ return;
+
+ // WSGI-defined variables
+ {
+ // wsgi.version
+ PyObject* pyValue = Py_BuildValue("(ii)", 1, 0);
+ PyDict_SetItemString(pyEnviron, "wsgi.version", pyValue);
+ Py_DECREF(pyValue);
+ }
+ {
+ // wsgi.url_scheme
+ PyObject* pyValue = PyUnicode_FromStringAndSize("http", 4);
+ PyDict_SetItemString(pyEnviron, "wsgi.url_scheme", pyValue);
+ Py_DECREF(pyValue);
+ }
+ {
+ // wsgi.input
+ XBMCAddon::xbmcwsgi::WsgiInputStream* wsgiInputStream = new XBMCAddon::xbmcwsgi::WsgiInputStream();
+ if (request != NULL)
+ wsgiInputStream->SetRequest(request);
+
+ PythonBindings::prepareForReturn(wsgiInputStream);
+ PyObject* pyWsgiInputStream = PythonBindings::makePythonInstance(wsgiInputStream, false);
+ PyDict_SetItemString(pyEnviron, "wsgi.input", pyWsgiInputStream);
+ Py_DECREF(pyWsgiInputStream);
+ }
+ {
+ // wsgi.errors
+ XBMCAddon::xbmcwsgi::WsgiErrorStream* wsgiErrorStream = new XBMCAddon::xbmcwsgi::WsgiErrorStream();
+ if (request != NULL)
+ wsgiErrorStream->SetRequest(request);
+
+ PythonBindings::prepareForReturn(wsgiErrorStream);
+ PyObject* pyWsgiErrorStream = PythonBindings::makePythonInstance(wsgiErrorStream, false);
+ PyDict_SetItemString(pyEnviron, "wsgi.errors", pyWsgiErrorStream);
+ Py_DECREF(pyWsgiErrorStream);
+ }
+ {
+ // wsgi.multithread
+ PyObject* pyValue = Py_BuildValue("b", false);
+ PyDict_SetItemString(pyEnviron, "wsgi.multithread", pyValue);
+ Py_DECREF(pyValue);
+ }
+ {
+ // wsgi.multiprocess
+ PyObject* pyValue = Py_BuildValue("b", false);
+ PyDict_SetItemString(pyEnviron, "wsgi.multiprocess", pyValue);
+ Py_DECREF(pyValue);
+ }
+ {
+ // wsgi.run_once
+ PyObject* pyValue = Py_BuildValue("b", true);
+ PyDict_SetItemString(pyEnviron, "wsgi.run_once", pyValue);
+ Py_DECREF(pyValue);
+ }
+}
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.h b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.h
new file mode 100644
index 0000000..3ec34c0
--- /dev/null
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/python/PythonInvoker.h"
+#include "network/httprequesthandler/python/HTTPPythonInvoker.h"
+#include "network/httprequesthandler/python/HTTPPythonRequest.h"
+
+#include <map>
+#include <string>
+
+namespace XBMCAddon
+{
+ namespace xbmcwsgi
+ {
+ class WsgiResponse;
+ }
+}
+
+class CHTTPPythonWsgiInvoker : public CHTTPPythonInvoker
+{
+public:
+ CHTTPPythonWsgiInvoker(ILanguageInvocationHandler* invocationHandler, HTTPPythonRequest* request);
+ ~CHTTPPythonWsgiInvoker() override;
+
+ // implementations of CHTTPPythonInvoker
+ HTTPPythonRequest* GetRequest() override;
+
+protected:
+ // overrides of CPythonInvoker
+ void executeScript(FILE* fp, const std::string& script, PyObject* moduleDict) override;
+ std::map<std::string, PythonModuleInitialization> getModules() const override;
+ const char* getInitializationScript() const override;
+
+private:
+ static std::map<std::string, std::string> createCgiEnvironment(
+ const HTTPPythonRequest* httpRequest, const ADDON::AddonPtr& addon);
+ static void addWsgiEnvironment(HTTPPythonRequest* request, void* environment);
+
+ XBMCAddon::xbmcwsgi::WsgiResponse* m_wsgiResponse;
+};