summaryrefslogtreecommitdiffstats
path: root/xbmc/network/httprequesthandler/python
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/network/httprequesthandler/python')
-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
6 files changed, 662 insertions, 0 deletions
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;
+};