/* * Copyright (C) 2005-2013 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, see * . * */ #if (defined HAVE_CONFIG_H) && (!defined TARGET_WINDOWS) #include "config.h" #endif #include "WebServer.h" #ifdef HAS_WEB_SERVER #include "URL.h" #include "Util.h" #include "XBDateTime.h" #include "filesystem/File.h" #include "settings/Settings.h" #include "threads/SingleLock.h" #include "utils/Base64.h" #include "utils/log.h" #include "utils/Mime.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/Variant.h" #include //#define WEBSERVER_DEBUG #ifdef TARGET_WINDOWS #pragma comment(lib, "libmicrohttpd.dll.lib") #endif #define MAX_POST_BUFFER_SIZE 2048 #define PAGE_FILE_NOT_FOUND "File not foundFile not found" #define NOT_SUPPORTED "Not SupportedThe method you are trying to use is not supported by this server" #define CONTENT_RANGE_FORMAT "bytes %" PRId64 "-%" PRId64 "/%" PRId64 using namespace XFILE; using namespace std; using namespace JSONRPC; typedef struct { boost::shared_ptr file; HttpRanges ranges; size_t rangeCount; int64_t rangesLength; string boundary; string boundaryWithHeader; bool boundaryWritten; string contentType; int64_t writePosition; } HttpFileDownloadContext; vector CWebServer::m_requestHandlers; CWebServer::CWebServer() { m_running = false; m_daemon_ip6 = NULL; m_daemon_ip4 = NULL; m_needcredentials = true; m_Credentials64Encoded = "eGJtYzp4Ym1j"; // xbmc:xbmc } int CWebServer::FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { if (cls == NULL || key == NULL) return MHD_NO; map *arguments = (map *)cls; arguments->insert(pair(key, value != NULL ? value : StringUtils::Empty)); return MHD_YES; } int CWebServer::FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { if (cls == NULL || key == NULL) return MHD_NO; multimap *arguments = (multimap *)cls; arguments->insert(pair(key, value != NULL ? value : StringUtils::Empty)); return MHD_YES; } int CWebServer::AskForAuthentication(struct MHD_Connection *connection) { int ret; struct MHD_Response *response; response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO); if (!response) return MHD_NO; ret = AddHeader(response, MHD_HTTP_HEADER_WWW_AUTHENTICATE, "Basic realm=XBMC"); ret |= AddHeader(response, MHD_HTTP_HEADER_CONNECTION, "close"); if (!ret) { MHD_destroy_response (response); return MHD_NO; } ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response); MHD_destroy_response (response); return ret; } bool CWebServer::IsAuthenticated(CWebServer *server, struct MHD_Connection *connection) { CSingleLock lock (server->m_critSection); if (!server->m_needcredentials) return true; const char *strbase = "Basic "; const char *headervalue = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Authorization"); if (NULL == headervalue) return false; if (strncmp (headervalue, strbase, strlen(strbase))) return false; return (server->m_Credentials64Encoded.compare(headervalue + strlen(strbase)) == 0); } #if (MHD_VERSION >= 0x00040001) int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) #else int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, unsigned int *upload_data_size, void **con_cls) #endif { CWebServer *server = (CWebServer *)cls; HTTPMethod methodType = GetMethod(method); HTTPRequest request = { connection, url, methodType, version, server }; if (!IsAuthenticated(server, connection)) return AskForAuthentication(connection); // Check if this is the first call to // AnswerToConnection for this request if (*con_cls == NULL) { // Look for a IHTTPRequestHandler which can // take care of the current request for (vector::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++) { IHTTPRequestHandler *requestHandler = *it; if (requestHandler->CheckHTTPRequest(request)) { // We found a matching IHTTPRequestHandler // so let's get a new instance for this request IHTTPRequestHandler *handler = requestHandler->GetInstance(); // If we got a POST request we need to take // care of the POST data if (methodType == POST) { ConnectionHandler *conHandler = new ConnectionHandler(); conHandler->requestHandler = handler; // Get the content-type of the POST data string contentType = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE); if (!contentType.empty()) { // If the content-type is application/x-ww-form-urlencoded or multipart/form-data // we can use MHD's POST processor if (stricmp(contentType.c_str(), MHD_HTTP_POST_ENCODING_FORM_URLENCODED) == 0 || stricmp(contentType.c_str(), MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA) == 0) { // Get a new MHD_PostProcessor conHandler->postprocessor = MHD_create_post_processor(connection, MAX_POST_BUFFER_SIZE, &CWebServer::HandlePostField, (void*)conHandler); // MHD doesn't seem to be able to handle // this post request if (conHandler->postprocessor == NULL) { delete conHandler->requestHandler; delete conHandler; return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType); } } } // otherwise we need to handle the POST data ourselves // which is done in the next call to AnswerToConnection *con_cls = (void*)conHandler; return MHD_YES; } // No POST request so nothing special to handle else return HandleRequest(handler, request); } } } // This is a subsequent call to // AnswerToConnection for this request else { // Again we need to take special care // of the POST data if (methodType == POST) { ConnectionHandler *conHandler = (ConnectionHandler *)*con_cls; if (conHandler->requestHandler == NULL) return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType); // We only need to handle POST data // if there actually is data left to handle if (*upload_data_size > 0) { // Either use MHD's POST processor if (conHandler->postprocessor != NULL) MHD_post_process(conHandler->postprocessor, upload_data, *upload_data_size); // or simply copy the data to the handler else conHandler->requestHandler->AddPostData(upload_data, *upload_data_size); // Signal that we have handled the data *upload_data_size = 0; return MHD_YES; } // We have handled all POST data // so it's time to invoke the IHTTPRequestHandler else { if (conHandler->postprocessor != NULL) MHD_destroy_post_processor(conHandler->postprocessor); *con_cls = NULL; int ret = HandleRequest(conHandler->requestHandler, request); delete conHandler; return ret; } } // It's unusual to get more than one call // to AnswerToConnection for none-POST // requests, but let's handle it anyway else { for (vector::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++) { IHTTPRequestHandler *requestHandler = *it; if (requestHandler->CheckHTTPRequest(request)) return HandleRequest(requestHandler->GetInstance(), request); } } } return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType); } #if (MHD_VERSION >= 0x00040001) int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size) #else int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, unsigned int size) #endif { ConnectionHandler *conHandler = (ConnectionHandler *)cls; if (conHandler == NULL || conHandler->requestHandler == NULL || size == 0) return MHD_NO; conHandler->requestHandler->AddPostField(key, string(data, size)); return MHD_YES; } int CWebServer::HandleRequest(IHTTPRequestHandler *handler, const HTTPRequest &request) { if (handler == NULL) return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); int ret = handler->HandleHTTPRequest(request); if (ret == MHD_NO) { delete handler; return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); } struct MHD_Response *response = NULL; int responseCode = handler->GetHTTPResonseCode(); switch (handler->GetHTTPResponseType()) { case HTTPNone: delete handler; return MHD_NO; case HTTPRedirect: ret = CreateRedirect(request.connection, handler->GetHTTPRedirectUrl(), response); break; case HTTPFileDownload: ret = CreateFileDownloadResponse(request.connection, handler->GetHTTPResponseFile(), request.method, response, responseCode); break; case HTTPMemoryDownloadNoFreeNoCopy: ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), false, false, response); break; case HTTPMemoryDownloadNoFreeCopy: ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), false, true, response); break; case HTTPMemoryDownloadFreeNoCopy: ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), true, false, response); break; case HTTPMemoryDownloadFreeCopy: ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), true, true, response); break; case HTTPError: ret = CreateErrorResponse(request.connection, handler->GetHTTPResonseCode(), request.method, response); break; default: delete handler; return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); } if (ret == MHD_NO) { delete handler; return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); } multimap header = handler->GetHTTPResponseHeaderFields(); for (multimap::const_iterator it = header.begin(); it != header.end(); it++) AddHeader(response, it->first.c_str(), it->second.c_str()); MHD_queue_response(request.connection, responseCode, response); MHD_destroy_response(response); delete handler; return MHD_YES; } HTTPMethod CWebServer::GetMethod(const char *method) { if (strcmp(method, "GET") == 0) return GET; if (strcmp(method, "POST") == 0) return POST; if (strcmp(method, "HEAD") == 0) return HEAD; return UNKNOWN; } int CWebServer::CreateRedirect(struct MHD_Connection *connection, const string &strURL, struct MHD_Response *&response) { response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO); if (response) { AddHeader(response, "Location", strURL.c_str()); return MHD_YES; } return MHD_NO; } int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode) { boost::shared_ptr file = boost::make_shared(); #ifdef WEBSERVER_DEBUG CLog::Log(LOGDEBUG, "webserver [IN] %s", strURL.c_str()); multimap headers; if (GetRequestHeaderValues(connection, MHD_HEADER_KIND, headers) > 0) { for (multimap::const_iterator header = headers.begin(); header != headers.end(); header++) CLog::Log(LOGDEBUG, "webserver [IN] %s: %s", header->first.c_str(), header->second.c_str()); } #endif if (file->Open(strURL, READ_NO_CACHE)) { bool getData = true; bool ranged = false; int64_t fileLength = file->GetLength(); // try to get the file's last modified date CDateTime lastModified; if (!GetLastModifiedDateTime(file.get(), lastModified)) lastModified.Reset(); // get the MIME type for the Content-Type header std::string ext = URIUtils::GetExtension(strURL); StringUtils::ToLower(ext); string mimeType = CreateMimeTypeFromExtension(ext.c_str()); if (methodType != HEAD) { int64_t firstPosition = 0; int64_t lastPosition = fileLength - 1; uint64_t totalLength = 0; std::auto_ptr context(new HttpFileDownloadContext()); context->file = file; context->rangesLength = fileLength; context->contentType = mimeType; context->boundaryWritten = false; context->writePosition = 0; if (methodType == GET) { // handle If-Modified-Since string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Modified-Since"); if (!ifModifiedSince.empty() && lastModified.IsValid()) { CDateTime ifModifiedSinceDate; ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince); if (lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate) { getData = false; response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO); if (response == NULL) return MHD_NO; responseCode = MHD_HTTP_NOT_MODIFIED; } } if (getData) { // handle Range header context->rangesLength = ParseRangeHeader(GetRequestHeaderValue(connection, MHD_HEADER_KIND, "Range"), fileLength, context->ranges, firstPosition, lastPosition); // handle If-Range header but only if the Range header is present if (!context->ranges.empty()) { string ifRange = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Range"); if (!ifRange.empty() && lastModified.IsValid()) { CDateTime ifRangeDate; ifRangeDate.SetFromRFC1123DateTime(ifRange); // check if the last modification is newer than the If-Range date // if so we have to server the whole file instead if (lastModified.GetAsUTCDateTime() > ifRangeDate) context->ranges.clear(); } } } } if (getData) { // if there are no ranges, add the whole range if (context->ranges.empty() || context->rangesLength == fileLength) { if (context->rangesLength == fileLength) context->ranges.clear(); context->ranges.push_back(HttpRange(0, fileLength - 1)); context->rangesLength = fileLength; firstPosition = 0; lastPosition = fileLength - 1; } else responseCode = MHD_HTTP_PARTIAL_CONTENT; // remember the total number of ranges context->rangeCount = context->ranges.size(); // remember the total length totalLength = context->rangesLength; // we need to remember whether we are ranged because the range length // might change and won't be reliable anymore for length comparisons ranged = context->rangeCount > 1 || context->rangesLength < fileLength; // adjust the MIME type and range length in case of multiple ranges // which requires multipart boundaries if (context->rangeCount > 1) { context->boundary = GenerateMultipartBoundary(); mimeType = "multipart/byteranges; boundary=" + context->boundary; // build part of the boundary with the optional Content-Type header // "--\r\nContent-Type: \r\n context->boundaryWithHeader = "\r\n--" + context->boundary + "\r\n"; if (!context->contentType.empty()) context->boundaryWithHeader += "Content-Type: " + context->contentType + "\r\n"; // for every range, we need to add a boundary with header for (HttpRanges::const_iterator range = context->ranges.begin(); range != context->ranges.end(); range++) { // we need to temporarily add the Content-Range header to the // boundary to be able to determine the length string completeBoundaryWithHeader = context->boundaryWithHeader; completeBoundaryWithHeader += StringUtils::Format("Content-Range: " CONTENT_RANGE_FORMAT, range->first, range->second, range->second - range->first + 1); completeBoundaryWithHeader += "\r\n\r\n"; totalLength += completeBoundaryWithHeader.size(); } // and at the very end a special end-boundary "\r\n----" totalLength += 4 + context->boundary.size() + 2; } // set the initial write position context->writePosition = context->ranges.begin()->first; // create the response object response = MHD_create_response_from_callback(totalLength, 2048, &CWebServer::ContentReaderCallback, context.get(), &CWebServer::ContentReaderFreeCallback); if (response == NULL) return MHD_NO; context.release(); // ownership was passed to mhd } // add Content-Range header if (ranged) AddHeader(response, "Content-Range", StringUtils::Format(CONTENT_RANGE_FORMAT, firstPosition, lastPosition, fileLength).c_str()); } else { getData = false; std::string contentLength = StringUtils::Format("%" PRId64, fileLength); response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO); if (response == NULL) return MHD_NO; AddHeader(response, "Content-Length", contentLength); } // add "Accept-Ranges: bytes" header AddHeader(response, "Accept-Ranges", "bytes"); // set the Content-Type header if (!mimeType.empty()) AddHeader(response, "Content-Type", mimeType.c_str()); // set the Last-Modified header if (lastModified.IsValid()) AddHeader(response, "Last-Modified", lastModified.GetAsRFC1123DateTime()); // set the Expires header CDateTime expiryTime = CDateTime::GetCurrentDateTime(); if (StringUtils::EqualsNoCase(mimeType, "text/html") || StringUtils::EqualsNoCase(mimeType, "text/css") || StringUtils::EqualsNoCase(mimeType, "application/javascript")) expiryTime += CDateTimeSpan(1, 0, 0, 0); else expiryTime += CDateTimeSpan(365, 0, 0, 0); AddHeader(response, "Expires", expiryTime.GetAsRFC1123DateTime()); } else { CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str()); return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType); } return MHD_YES; } int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response) { size_t payloadSize = 0; void *payload = NULL; if (method != HEAD) { switch (responseType) { case MHD_HTTP_NOT_FOUND: payloadSize = strlen(PAGE_FILE_NOT_FOUND); payload = (void *)PAGE_FILE_NOT_FOUND; break; case MHD_HTTP_NOT_IMPLEMENTED: payloadSize = strlen(NOT_SUPPORTED); payload = (void *)NOT_SUPPORTED; break; } } response = MHD_create_response_from_data (payloadSize, payload, MHD_NO, MHD_NO); if (response) return MHD_YES; return MHD_NO; } int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection, void *data, size_t size, bool free, bool copy, struct MHD_Response *&response) { response = MHD_create_response_from_data (size, data, free ? MHD_YES : MHD_NO, copy ? MHD_YES : MHD_NO); if (response) return MHD_YES; return MHD_NO; } int CWebServer::SendErrorResponse(struct MHD_Connection *connection, int errorType, HTTPMethod method) { struct MHD_Response *response = NULL; int ret = CreateErrorResponse(connection, errorType, method, response); if (ret == MHD_YES) { ret = MHD_queue_response (connection, errorType, response); MHD_destroy_response (response); } return ret; } void* CWebServer::UriRequestLogger(void *cls, const char *uri) { CLog::Log(LOGDEBUG, "webserver: request received for %s", uri); return NULL; } #if (MHD_VERSION >= 0x00090200) ssize_t CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, size_t max) #elif (MHD_VERSION >= 0x00040001) int CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, int max) #else //libmicrohttpd < 0.4.0 int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max) #endif { HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls; if (context == NULL || context->file == NULL) return -1; #ifdef WEBSERVER_DEBUG CLog::Log(LOGDEBUG, "webserver [OUT] write maximum %d bytes from %" PRIu64 " (%" PRIu64 ")", max, context->writePosition, pos); #endif // check if we need to add the end-boundary if (context->rangeCount > 1 && context->ranges.empty()) { // put together the end-boundary string endBoundary = "\r\n--" + context->boundary + "--"; if ((unsigned int)max != endBoundary.size()) return -1; // copy the boundary into the buffer memcpy(buf, endBoundary.c_str(), endBoundary.size()); return endBoundary.size(); } if (context->ranges.empty()) return -1; int64_t start = context->ranges.at(0).first; int64_t end = context->ranges.at(0).second; int64_t maximum = (int64_t)max; int written = 0; if (context->rangeCount > 1 && !context->boundaryWritten) { // put together the boundary for the current range string boundary = context->boundaryWithHeader; boundary += StringUtils::Format("Content-Range: " CONTENT_RANGE_FORMAT, start, end, end - start + 1) + "\r\n\r\n"; // copy the boundary into the buffer memcpy(buf, boundary.c_str(), boundary.size()); // advance the buffer position buf += boundary.size(); // update the number of written byte written += boundary.size(); // update the maximum number of bytes maximum -= boundary.size(); context->boundaryWritten = true; } // check if the current position is within this range // if not, set it to the start position if (context->writePosition < start || context->writePosition > end) context->writePosition = start; // adjust the maximum number of read bytes maximum = std::min(maximum, end - context->writePosition + 1); // seek to the position if necessary if(context->writePosition != context->file->GetPosition()) context->file->Seek(context->writePosition); // read data from the file ssize_t res = context->file->Read(buf, maximum); if (res <= 0) return -1; // add the number of read bytes to the number of written bytes written += res; #ifdef WEBSERVER_DEBUG CLog::Log(LOGDEBUG, "webserver [OUT] wrote %d bytes from %" PRId64 " in range (%" PRId64 " - %" PRId64 ")", written, context->writePosition, start, end); #endif // update the current write position context->writePosition += res; // if we have read all the data from the current range // remove it from the list if (context->writePosition >= end + 1) { context->ranges.erase(context->ranges.begin()); context->boundaryWritten = false; } return written; } void CWebServer::ContentReaderFreeCallback(void *cls) { HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls; delete context; #ifdef WEBSERVER_DEBUG CLog::Log(LOGDEBUG, "webserver [OUT] done"); #endif } struct MHD_Daemon* CWebServer::StartMHD(unsigned int flags, int port) { unsigned int timeout = 60 * 60 * 24; return MHD_start_daemon(flags | #if (MHD_VERSION >= 0x00040002) && (MHD_VERSION < 0x00090B01) // use main thread for each connection, can only handle one request at a // time [unless you set the thread pool size] MHD_USE_SELECT_INTERNALLY #else // one thread per connection // WARNING: set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1 // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop MHD_USE_THREAD_PER_CONNECTION #endif , port, NULL, NULL, &CWebServer::AnswerToConnection, this, #if (MHD_VERSION >= 0x00040002) && (MHD_VERSION < 0x00090B01) MHD_OPTION_THREAD_POOL_SIZE, 4, #endif MHD_OPTION_CONNECTION_LIMIT, 512, MHD_OPTION_CONNECTION_TIMEOUT, timeout, MHD_OPTION_URI_LOG_CALLBACK, &CWebServer::UriRequestLogger, this, MHD_OPTION_END); } bool CWebServer::Start(int port, const string &username, const string &password) { SetCredentials(username, password); if (!m_running) { int v6testSock; if ((v6testSock = socket(AF_INET6, SOCK_STREAM, 0)) >= 0) { closesocket(v6testSock); m_daemon_ip6 = StartMHD(MHD_USE_IPv6, port); } m_daemon_ip4 = StartMHD(0 , port); m_running = (m_daemon_ip6 != NULL) || (m_daemon_ip4 != NULL); if (m_running) CLog::Log(LOGNOTICE, "WebServer: Started the webserver"); else CLog::Log(LOGERROR, "WebServer: Failed to start the webserver"); } return m_running; } bool CWebServer::Stop() { if (m_running) { if (m_daemon_ip6 != NULL) MHD_stop_daemon(m_daemon_ip6); if (m_daemon_ip4 != NULL) MHD_stop_daemon(m_daemon_ip4); m_running = false; CLog::Log(LOGNOTICE, "WebServer: Stopped the webserver"); } else CLog::Log(LOGNOTICE, "WebServer: Stopped failed because its not running"); return !m_running; } bool CWebServer::IsStarted() { return m_running; } void CWebServer::SetCredentials(const string &username, const string &password) { CSingleLock lock (m_critSection); std::string str = username + ':' + password; Base64::Encode(str.c_str(), m_Credentials64Encoded); m_needcredentials = !password.empty(); } bool CWebServer::PrepareDownload(const char *path, CVariant &details, std::string &protocol) { if (CFile::Exists(path)) { protocol = "http"; 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; } return false; } bool CWebServer::Download(const char *path, CVariant &result) { return false; } int CWebServer::GetCapabilities() { return Response | FileDownloadRedirect; } void CWebServer::RegisterRequestHandler(IHTTPRequestHandler *handler) { if (handler == NULL) return; for (vector::iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++) { if (*it == handler) return; if ((*it)->GetPriority() < handler->GetPriority()) { m_requestHandlers.insert(it, handler); return; } } m_requestHandlers.push_back(handler); } void CWebServer::UnregisterRequestHandler(IHTTPRequestHandler *handler) { if (handler == NULL) return; for (vector::iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++) { if (*it == handler) { m_requestHandlers.erase(it); return; } } } std::string CWebServer::GetRequestHeaderValue(struct MHD_Connection *connection, enum MHD_ValueKind kind, const std::string &key) { if (connection == NULL) return ""; const char* value = MHD_lookup_connection_value(connection, kind, key.c_str()); if (value == NULL) return ""; if (stricmp(key.c_str(), MHD_HTTP_HEADER_CONTENT_TYPE) == 0) { // 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 string strValue(value); size_t pos = strValue.find(';'); if (pos != string::npos) strValue = strValue.substr(0, pos); return strValue; } return value; } int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::map &headerValues) { if (connection == NULL) return -1; return MHD_get_connection_values(connection, kind, FillArgumentMap, &headerValues); } int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::multimap &headerValues) { if (connection == NULL) return -1; return MHD_get_connection_values(connection, kind, FillArgumentMultiMap, &headerValues); } std::string CWebServer::CreateMimeTypeFromExtension(const char *ext) { if (strcmp(ext, ".kar") == 0) return "audio/midi"; if (strcmp(ext, ".tbn") == 0) return "image/jpeg"; return CMime::GetMimeType(ext); } int CWebServer::AddHeader(struct MHD_Response *response, const std::string &name, const std::string &value) { if (response == NULL || name.empty()) return 0; #ifdef WEBSERVER_DEBUG CLog::Log(LOGDEBUG, "webserver [OUT] %s: %s", name.c_str(), value.c_str()); #endif return MHD_add_response_header(response, name.c_str(), value.c_str()); } int64_t CWebServer::ParseRangeHeader(const std::string &rangeHeaderValue, int64_t totalLength, HttpRanges &ranges, int64_t &firstPosition, int64_t &lastPosition) { firstPosition = 0; lastPosition = totalLength - 1; if (rangeHeaderValue.empty() || !StringUtils::StartsWithNoCase(rangeHeaderValue, "bytes=")) return totalLength; int64_t rangesLength = 0; // remove "bytes=" from the beginning string rangesValue = rangeHeaderValue.substr(6); // split the value of the "Range" header by "," vector rangeValues = StringUtils::Split(rangesValue, ","); for (vector::const_iterator range = rangeValues.begin(); range != rangeValues.end(); range++) { // there must be a "-" in the range definition if (range->find("-") == string::npos) { ranges.clear(); return totalLength; } vector positions = StringUtils::Split(*range, "-"); if (positions.size() > 2) { ranges.clear(); return totalLength; } int64_t positionStart = -1; int64_t positionEnd = -1; if (!positions.at(0).empty()) positionStart = str2int64(positions.at(0), -1); if (!positions.at(1).empty()) positionEnd = str2int64(positions.at(1), -1); if (positionStart < 0 && positionEnd < 0) { ranges.clear(); return totalLength; } // if there's no end position, use the file's length if (positionEnd < 0) positionEnd = totalLength - 1; else if (positionStart < 0) { positionStart = totalLength - positionEnd; positionEnd = totalLength - 1; } if (positionEnd < positionStart) { ranges.clear(); return totalLength; } if (ranges.empty()) { firstPosition = positionStart; lastPosition = positionEnd; } else { if (positionStart < firstPosition) firstPosition = positionStart; if (positionEnd > lastPosition) lastPosition = positionEnd; } ranges.push_back(HttpRange(positionStart, positionEnd)); rangesLength += positionEnd - positionStart + 1; } if (!ranges.empty() || rangesLength > 0) return rangesLength; return totalLength; } std::string CWebServer::GenerateMultipartBoundary() { static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; // create a string of length 30 to 40 and pre-fill it with "-" size_t count = (size_t)CUtil::GetRandomNumber() % 11 + 30; string boundary(count, '-'); for (size_t i = (size_t)CUtil::GetRandomNumber() % 5 + 8; i < count; i++) boundary.replace(i, 1, 1, chars[(size_t)CUtil::GetRandomNumber() % 64]); return boundary; } bool CWebServer::GetLastModifiedDateTime(XFILE::CFile *file, CDateTime &lastModified) { if (file == NULL) return false; struct __stat64 statBuffer; if (file->Stat(&statBuffer) != 0) return false; 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 false; lastModified = *time; return true; } #endif