diff options
Diffstat (limited to '')
-rw-r--r-- | xbmc/network/httprequesthandler/HTTPPythonHandler.cpp | 250 |
1 files changed, 250 insertions, 0 deletions
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; +} |