2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "WebServer.h"
25 #include "XBDateTime.h"
26 #include "filesystem/File.h"
27 #include "settings/Settings.h"
28 #include "threads/SingleLock.h"
29 #include "utils/Base64.h"
30 #include "utils/log.h"
31 #include "utils/Mime.h"
32 #include "utils/StringUtils.h"
33 #include "utils/URIUtils.h"
34 #include "utils/Variant.h"
36 //#define WEBSERVER_DEBUG
39 #pragma comment(lib, "libmicrohttpd.dll.lib")
42 #define MAX_POST_BUFFER_SIZE 2048
44 #define PAGE_FILE_NOT_FOUND "<html><head><title>File not found</title></head><body>File not found</body></html>"
45 #define NOT_SUPPORTED "<html><head><title>Not Supported</title></head><body>The method you are trying to use is not supported by this server</body></html>"
47 #define CONTENT_RANGE_FORMAT "bytes %" PRId64 "-%" PRId64 "/%" PRId64
49 using namespace XFILE;
51 using namespace JSONRPC;
59 string boundaryWithHeader;
62 int64_t writePosition;
63 } HttpFileDownloadContext;
65 vector<IHTTPRequestHandler *> CWebServer::m_requestHandlers;
67 CWebServer::CWebServer()
72 m_needcredentials = true;
73 m_Credentials64Encoded = "eGJtYzp4Ym1j"; // xbmc:xbmc
76 int CWebServer::FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
78 map<string, string> *arguments = (map<string, string> *)cls;
79 arguments->insert(pair<string,string>(key,value));
83 int CWebServer::FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
85 multimap<string, string> *arguments = (multimap<string, string> *)cls;
86 arguments->insert(pair<string,string>(key,value));
90 int CWebServer::AskForAuthentication(struct MHD_Connection *connection)
93 struct MHD_Response *response;
95 response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
99 ret = AddHeader(response, MHD_HTTP_HEADER_WWW_AUTHENTICATE, "Basic realm=XBMC");
100 ret |= AddHeader(response, MHD_HTTP_HEADER_CONNECTION, "close");
103 MHD_destroy_response (response);
107 ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response);
109 MHD_destroy_response (response);
114 bool CWebServer::IsAuthenticated(CWebServer *server, struct MHD_Connection *connection)
116 CSingleLock lock (server->m_critSection);
117 if (!server->m_needcredentials)
120 const char *strbase = "Basic ";
121 const char *headervalue = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Authorization");
122 if (NULL == headervalue)
124 if (strncmp (headervalue, strbase, strlen(strbase)))
127 return (server->m_Credentials64Encoded.compare(headervalue + strlen(strbase)) == 0);
130 #if (MHD_VERSION >= 0x00040001)
131 int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
132 const char *url, const char *method,
133 const char *version, const char *upload_data,
134 size_t *upload_data_size, void **con_cls)
136 int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
137 const char *url, const char *method,
138 const char *version, const char *upload_data,
139 unsigned int *upload_data_size, void **con_cls)
142 CWebServer *server = (CWebServer *)cls;
143 HTTPMethod methodType = GetMethod(method);
144 HTTPRequest request = { connection, url, methodType, version, server };
146 if (!IsAuthenticated(server, connection))
147 return AskForAuthentication(connection);
149 // Check if this is the first call to
150 // AnswerToConnection for this request
151 if (*con_cls == NULL)
153 // Look for a IHTTPRequestHandler which can
154 // take care of the current request
155 for (vector<IHTTPRequestHandler *>::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
157 IHTTPRequestHandler *requestHandler = *it;
158 if (requestHandler->CheckHTTPRequest(request))
160 // We found a matching IHTTPRequestHandler
161 // so let's get a new instance for this request
162 IHTTPRequestHandler *handler = requestHandler->GetInstance();
164 // If we got a POST request we need to take
165 // care of the POST data
166 if (methodType == POST)
168 ConnectionHandler *conHandler = new ConnectionHandler();
169 conHandler->requestHandler = handler;
171 // Get the content-type of the POST data
172 string contentType = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
173 if (!contentType.empty())
175 // If the content-type is application/x-ww-form-urlencoded or multipart/form-data
176 // we can use MHD's POST processor
177 if (stricmp(contentType.c_str(), MHD_HTTP_POST_ENCODING_FORM_URLENCODED) == 0 ||
178 stricmp(contentType.c_str(), MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA) == 0)
180 // Get a new MHD_PostProcessor
181 conHandler->postprocessor = MHD_create_post_processor(connection, MAX_POST_BUFFER_SIZE, &CWebServer::HandlePostField, (void*)conHandler);
183 // MHD doesn't seem to be able to handle
185 if (conHandler->postprocessor == NULL)
187 delete conHandler->requestHandler;
190 return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType);
194 // otherwise we need to handle the POST data ourselves
195 // which is done in the next call to AnswerToConnection
197 *con_cls = (void*)conHandler;
200 // No POST request so nothing special to handle
202 return HandleRequest(handler, request);
206 // This is a subsequent call to
207 // AnswerToConnection for this request
210 // Again we need to take special care
212 if (methodType == POST)
214 ConnectionHandler *conHandler = (ConnectionHandler *)*con_cls;
215 if (conHandler->requestHandler == NULL)
216 return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType);
218 // We only need to handle POST data
219 // if there actually is data left to handle
220 if (*upload_data_size > 0)
222 // Either use MHD's POST processor
223 if (conHandler->postprocessor != NULL)
224 MHD_post_process(conHandler->postprocessor, upload_data, *upload_data_size);
225 // or simply copy the data to the handler
227 conHandler->requestHandler->AddPostData(upload_data, *upload_data_size);
229 // Signal that we have handled the data
230 *upload_data_size = 0;
234 // We have handled all POST data
235 // so it's time to invoke the IHTTPRequestHandler
238 if (conHandler->postprocessor != NULL)
239 MHD_destroy_post_processor(conHandler->postprocessor);
242 int ret = HandleRequest(conHandler->requestHandler, request);
247 // It's unusual to get more than one call
248 // to AnswerToConnection for none-POST
249 // requests, but let's handle it anyway
252 for (vector<IHTTPRequestHandler *>::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
254 IHTTPRequestHandler *requestHandler = *it;
255 if (requestHandler->CheckHTTPRequest(request))
256 return HandleRequest(requestHandler->GetInstance(), request);
261 return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
264 #if (MHD_VERSION >= 0x00040001)
265 int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key,
266 const char *filename, const char *content_type,
267 const char *transfer_encoding, const char *data, uint64_t off,
270 int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key,
271 const char *filename, const char *content_type,
272 const char *transfer_encoding, const char *data, uint64_t off,
276 ConnectionHandler *conHandler = (ConnectionHandler *)cls;
278 if (conHandler == NULL || conHandler->requestHandler == NULL || size == 0)
281 conHandler->requestHandler->AddPostField(key, string(data, size));
285 int CWebServer::HandleRequest(IHTTPRequestHandler *handler, const HTTPRequest &request)
288 return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
290 int ret = handler->HandleHTTPRequest(request);
294 return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
297 struct MHD_Response *response = NULL;
298 int responseCode = handler->GetHTTPResonseCode();
299 switch (handler->GetHTTPResponseType())
306 ret = CreateRedirect(request.connection, handler->GetHTTPRedirectUrl(), response);
309 case HTTPFileDownload:
310 ret = CreateFileDownloadResponse(request.connection, handler->GetHTTPResponseFile(), request.method, response, responseCode);
313 case HTTPMemoryDownloadNoFreeNoCopy:
314 ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), false, false, response);
317 case HTTPMemoryDownloadNoFreeCopy:
318 ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), false, true, response);
321 case HTTPMemoryDownloadFreeNoCopy:
322 ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), true, false, response);
325 case HTTPMemoryDownloadFreeCopy:
326 ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), true, true, response);
330 ret = CreateErrorResponse(request.connection, handler->GetHTTPResonseCode(), request.method, response);
335 return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
341 return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
344 multimap<string, string> header = handler->GetHTTPResponseHeaderFields();
345 for (multimap<string, string>::const_iterator it = header.begin(); it != header.end(); it++)
346 AddHeader(response, it->first.c_str(), it->second.c_str());
348 MHD_queue_response(request.connection, responseCode, response);
349 MHD_destroy_response(response);
355 HTTPMethod CWebServer::GetMethod(const char *method)
357 if (strcmp(method, "GET") == 0)
359 if (strcmp(method, "POST") == 0)
361 if (strcmp(method, "HEAD") == 0)
367 int CWebServer::CreateRedirect(struct MHD_Connection *connection, const string &strURL, struct MHD_Response *&response)
369 response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
372 AddHeader(response, "Location", strURL.c_str());
378 int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode)
380 CFile *file = new CFile();
382 #ifdef WEBSERVER_DEBUG
383 CLog::Log(LOGDEBUG, "webserver [IN] %s", strURL.c_str());
384 multimap<string, string> headers;
385 if (GetRequestHeaderValues(connection, MHD_HEADER_KIND, headers) > 0)
387 for (multimap<string, string>::const_iterator header = headers.begin(); header != headers.end(); header++)
388 CLog::Log(LOGDEBUG, "webserver [IN] %s: %s", header->first.c_str(), header->second.c_str());
392 if (file->Open(strURL, READ_NO_CACHE))
396 int64_t fileLength = file->GetLength();
398 // try to get the file's last modified date
399 CDateTime lastModified;
400 if (!GetLastModifiedDateTime(file, lastModified))
401 lastModified.Reset();
403 // get the MIME type for the Content-Type header
404 CStdString ext = URIUtils::GetExtension(strURL);
406 string mimeType = CreateMimeTypeFromExtension(ext.c_str());
408 if (methodType != HEAD)
410 int64_t firstPosition = 0;
411 int64_t lastPosition = fileLength - 1;
412 uint64_t totalLength = 0;
413 HttpFileDownloadContext *context = new HttpFileDownloadContext();
414 context->file = file;
415 context->rangesLength = fileLength;
416 context->contentType = mimeType;
417 context->boundaryWritten = false;
418 context->writePosition = 0;
420 if (methodType == GET)
422 // handle If-Modified-Since
423 string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Modified-Since");
424 if (!ifModifiedSince.empty() && lastModified.IsValid())
426 CDateTime ifModifiedSinceDate;
427 ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince);
429 if (lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
432 response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
433 responseCode = MHD_HTTP_NOT_MODIFIED;
439 // handle Range header
440 context->rangesLength = ParseRangeHeader(GetRequestHeaderValue(connection, MHD_HEADER_KIND, "Range"), fileLength, context->ranges, firstPosition, lastPosition);
442 // handle If-Range header but only if the Range header is present
443 if (!context->ranges.empty())
445 string ifRange = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Range");
446 if (!ifRange.empty() && lastModified.IsValid())
448 CDateTime ifRangeDate;
449 ifRangeDate.SetFromRFC1123DateTime(ifRange);
451 // check if the last modification is newer than the If-Range date
452 // if so we have to server the whole file instead
453 if (lastModified.GetAsUTCDateTime() > ifRangeDate)
454 context->ranges.clear();
462 // if there are no ranges, add the whole range
463 if (context->ranges.empty() || context->rangesLength == fileLength)
465 if (context->rangesLength == fileLength)
466 context->ranges.clear();
468 context->ranges.push_back(HttpRange(0, fileLength - 1));
469 context->rangesLength = fileLength;
471 lastPosition = fileLength - 1;
474 responseCode = MHD_HTTP_PARTIAL_CONTENT;
476 // remember the total number of ranges
477 context->rangeCount = context->ranges.size();
478 // remember the total length
479 totalLength = context->rangesLength;
481 // we need to remember whether we are ranged because the range length
482 // might change and won't be reliable anymore for length comparisons
483 ranged = context->rangeCount > 1 || context->rangesLength < fileLength;
485 // adjust the MIME type and range length in case of multiple ranges
486 // which requires multipart boundaries
487 if (context->rangeCount > 1)
489 context->boundary = GenerateMultipartBoundary();
490 mimeType = "multipart/byteranges; boundary=" + context->boundary;
492 // build part of the boundary with the optional Content-Type header
493 // "--<boundary>\r\nContent-Type: <content-type>\r\n
494 context->boundaryWithHeader = "\r\n--" + context->boundary + "\r\n";
495 if (!context->contentType.empty())
496 context->boundaryWithHeader += "Content-Type: " + context->contentType + "\r\n";
498 // for every range, we need to add a boundary with header
499 for (HttpRanges::const_iterator range = context->ranges.begin(); range != context->ranges.end(); range++)
501 // we need to temporarily add the Content-Range header to the
502 // boundary to be able to determine the length
503 string completeBoundaryWithHeader = context->boundaryWithHeader;
504 completeBoundaryWithHeader += StringUtils::Format("Content-Range: " CONTENT_RANGE_FORMAT,
505 range->first, range->second, range->second - range->first + 1);
506 completeBoundaryWithHeader += "\r\n\r\n";
508 totalLength += completeBoundaryWithHeader.size();
510 // and at the very end a special end-boundary "\r\n--<boundary>--"
511 totalLength += 4 + context->boundary.size() + 2;
514 // set the initial write position
515 context->writePosition = context->ranges.begin()->first;
517 // create the response object
518 response = MHD_create_response_from_callback(totalLength,
520 &CWebServer::ContentReaderCallback, context,
521 &CWebServer::ContentReaderFreeCallback);
524 if (response == NULL)
532 // add Content-Range header
534 AddHeader(response, "Content-Range", StringUtils::Format(CONTENT_RANGE_FORMAT, firstPosition, lastPosition, fileLength).c_str());
540 CStdString contentLength;
541 contentLength.Format("%" PRId64, fileLength);
543 response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
544 if (response == NULL)
550 AddHeader(response, "Content-Length", contentLength);
553 // add "Accept-Ranges: bytes" header
554 AddHeader(response, "Accept-Ranges", "bytes");
556 // set the Content-Type header
557 if (!mimeType.empty())
558 AddHeader(response, "Content-Type", mimeType.c_str());
560 // set the Last-Modified header
561 if (lastModified.IsValid())
562 AddHeader(response, "Last-Modified", lastModified.GetAsRFC1123DateTime());
564 // set the Expires header
565 CDateTime expiryTime = CDateTime::GetCurrentDateTime();
566 if (StringUtils::EqualsNoCase(mimeType, "text/html") ||
567 StringUtils::EqualsNoCase(mimeType, "text/css") ||
568 StringUtils::EqualsNoCase(mimeType, "application/javascript"))
569 expiryTime += CDateTimeSpan(1, 0, 0, 0);
571 expiryTime += CDateTimeSpan(365, 0, 0, 0);
572 AddHeader(response, "Expires", expiryTime.GetAsRFC1123DateTime());
574 // only close the CFile instance if libmicrohttpd doesn't have to grab the data of the file
584 CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str());
585 return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
591 int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response)
593 size_t payloadSize = 0;
594 void *payload = NULL;
598 switch (responseType)
600 case MHD_HTTP_NOT_FOUND:
601 payloadSize = strlen(PAGE_FILE_NOT_FOUND);
602 payload = (void *)PAGE_FILE_NOT_FOUND;
604 case MHD_HTTP_NOT_IMPLEMENTED:
605 payloadSize = strlen(NOT_SUPPORTED);
606 payload = (void *)NOT_SUPPORTED;
611 response = MHD_create_response_from_data (payloadSize, payload, MHD_NO, MHD_NO);
617 int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection, void *data, size_t size, bool free, bool copy, struct MHD_Response *&response)
619 response = MHD_create_response_from_data (size, data, free ? MHD_YES : MHD_NO, copy ? MHD_YES : MHD_NO);
625 int CWebServer::SendErrorResponse(struct MHD_Connection *connection, int errorType, HTTPMethod method)
627 struct MHD_Response *response = NULL;
628 int ret = CreateErrorResponse(connection, errorType, method, response);
631 ret = MHD_queue_response (connection, errorType, response);
632 MHD_destroy_response (response);
638 void* CWebServer::UriRequestLogger(void *cls, const char *uri)
640 CLog::Log(LOGDEBUG, "webserver: request received for %s", uri);
644 #if (MHD_VERSION >= 0x00090200)
645 ssize_t CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, size_t max)
646 #elif (MHD_VERSION >= 0x00040001)
647 int CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, int max)
648 #else //libmicrohttpd < 0.4.0
649 int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max)
652 HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls;
653 if (context == NULL || context->file == NULL)
656 #ifdef WEBSERVER_DEBUG
657 CLog::Log(LOGDEBUG, "webserver [OUT] write maximum %d bytes from %" PRIu64 " (%" PRIu64 ")", max, context->writePosition, pos);
660 // check if we need to add the end-boundary
661 if (context->rangeCount > 1 && context->ranges.empty())
663 // put together the end-boundary
664 string endBoundary = "\r\n--" + context->boundary + "--";
665 if ((unsigned int)max != endBoundary.size())
668 // copy the boundary into the buffer
669 memcpy(buf, endBoundary.c_str(), endBoundary.size());
670 return endBoundary.size();
673 if (context->ranges.empty())
676 int64_t start = context->ranges.at(0).first;
677 int64_t end = context->ranges.at(0).second;
678 int64_t maximum = (int64_t)max;
681 if (context->rangeCount > 1 && !context->boundaryWritten)
683 // put together the boundary for the current range
684 string boundary = context->boundaryWithHeader;
685 boundary += StringUtils::Format("Content-Range: " CONTENT_RANGE_FORMAT, start, end, end - start + 1) + "\r\n\r\n";
687 // copy the boundary into the buffer
688 memcpy(buf, boundary.c_str(), boundary.size());
689 // advance the buffer position
690 buf += boundary.size();
691 // update the number of written byte
692 written += boundary.size();
693 // update the maximum number of bytes
694 maximum -= boundary.size();
695 context->boundaryWritten = true;
698 // check if the current position is within this range
699 // if not, set it to the start position
700 if (context->writePosition < start || context->writePosition > end)
701 context->writePosition = start;
702 // adjust the maximum number of read bytes
703 maximum = std::min(maximum, end - context->writePosition + 1);
705 // seek to the position if necessary
706 if(context->writePosition != context->file->GetPosition())
707 context->file->Seek(context->writePosition);
709 // read data from the file
710 unsigned int res = context->file->Read(buf, maximum);
714 // add the number of read bytes to the number of written bytes
716 #ifdef WEBSERVER_DEBUG
717 CLog::Log(LOGDEBUG, "webserver [OUT] wrote %d bytes from %" PRId64 " in range (%" PRId64 " - %" PRId64 ")", written, context->writePosition, start, end);
719 // update the current write position
720 context->writePosition += res;
722 // if we have read all the data from the current range
723 // remove it from the list
724 if (context->writePosition >= end + 1)
726 context->ranges.erase(context->ranges.begin());
727 context->boundaryWritten = false;
733 void CWebServer::ContentReaderFreeCallback(void *cls)
735 HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls;
739 if (context->file != NULL)
741 context->file->Close();
742 delete context->file;
743 context->file = NULL;
746 #ifdef WEBSERVER_DEBUG
747 CLog::Log(LOGDEBUG, "webserver [OUT] done");
752 struct MHD_Daemon* CWebServer::StartMHD(unsigned int flags, int port)
754 unsigned int timeout = 60 * 60 * 24;
756 return MHD_start_daemon(flags |
757 #if (MHD_VERSION >= 0x00040002) && (MHD_VERSION < 0x00090B01)
758 // use main thread for each connection, can only handle one request at a
759 // time [unless you set the thread pool size]
760 MHD_USE_SELECT_INTERNALLY
762 // one thread per connection
763 // WARNING: set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
764 // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
765 MHD_USE_THREAD_PER_CONNECTION
771 &CWebServer::AnswerToConnection,
774 #if (MHD_VERSION >= 0x00040002) && (MHD_VERSION < 0x00090B01)
775 MHD_OPTION_THREAD_POOL_SIZE, 4,
777 MHD_OPTION_CONNECTION_LIMIT, 512,
778 MHD_OPTION_CONNECTION_TIMEOUT, timeout,
779 MHD_OPTION_URI_LOG_CALLBACK, &CWebServer::UriRequestLogger, this,
783 bool CWebServer::Start(int port, const string &username, const string &password)
785 SetCredentials(username, password);
789 if ((v6testSock = socket(AF_INET6, SOCK_STREAM, 0)) > 0)
791 closesocket(v6testSock);
792 m_daemon_ip6 = StartMHD(MHD_USE_IPv6, port);
795 m_daemon_ip4 = StartMHD(0 , port);
797 m_running = (m_daemon_ip6 != NULL) || (m_daemon_ip4 != NULL);
799 CLog::Log(LOGNOTICE, "WebServer: Started the webserver");
801 CLog::Log(LOGERROR, "WebServer: Failed to start the webserver");
806 bool CWebServer::Stop()
810 if (m_daemon_ip6 != NULL)
811 MHD_stop_daemon(m_daemon_ip6);
813 if (m_daemon_ip4 != NULL)
814 MHD_stop_daemon(m_daemon_ip4);
817 CLog::Log(LOGNOTICE, "WebServer: Stopped the webserver");
820 CLog::Log(LOGNOTICE, "WebServer: Stopped failed because its not running");
825 bool CWebServer::IsStarted()
830 void CWebServer::SetCredentials(const string &username, const string &password)
832 CSingleLock lock (m_critSection);
833 CStdString str = username + ":" + password;
835 Base64::Encode(str.c_str(), m_Credentials64Encoded);
836 m_needcredentials = !password.empty();
839 bool CWebServer::PrepareDownload(const char *path, CVariant &details, std::string &protocol)
842 CFile *file = new CFile();
843 if (file->Open(path))
855 CStdString strPath = path;
856 if (strPath.Left(8) == "image://" ||
857 (strPath.Left(10) == "special://" && strPath.Right(4) == ".tbn"))
861 CURL::Encode(strPath);
863 details["path"] = url;
869 bool CWebServer::Download(const char *path, CVariant &result)
874 int CWebServer::GetCapabilities()
876 return Response | FileDownloadRedirect;
879 void CWebServer::RegisterRequestHandler(IHTTPRequestHandler *handler)
884 for (vector<IHTTPRequestHandler *>::iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
889 if ((*it)->GetPriority() < handler->GetPriority())
891 m_requestHandlers.insert(it, handler);
896 m_requestHandlers.push_back(handler);
899 void CWebServer::UnregisterRequestHandler(IHTTPRequestHandler *handler)
904 for (vector<IHTTPRequestHandler *>::iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
908 m_requestHandlers.erase(it);
914 std::string CWebServer::GetRequestHeaderValue(struct MHD_Connection *connection, enum MHD_ValueKind kind, const std::string &key)
916 if (connection == NULL)
919 const char* value = MHD_lookup_connection_value(connection, kind, key.c_str());
923 if (stricmp(key.c_str(), MHD_HTTP_HEADER_CONTENT_TYPE) == 0)
925 // Work around a bug in firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=416178)
926 // by cutting of anything that follows a ";" in a "Content-Type" header field
927 string strValue(value);
928 size_t pos = strValue.find(';');
929 if (pos != string::npos)
930 strValue = strValue.substr(0, pos);
938 int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::map<std::string, std::string> &headerValues)
940 if (connection == NULL)
943 return MHD_get_connection_values(connection, kind, FillArgumentMap, &headerValues);
946 int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::multimap<std::string, std::string> &headerValues)
948 if (connection == NULL)
951 return MHD_get_connection_values(connection, kind, FillArgumentMultiMap, &headerValues);
954 std::string CWebServer::CreateMimeTypeFromExtension(const char *ext)
956 if (strcmp(ext, ".kar") == 0) return "audio/midi";
957 if (strcmp(ext, ".tbn") == 0) return "image/jpeg";
958 return CMime::GetMimeType(ext);
961 int CWebServer::AddHeader(struct MHD_Response *response, const std::string &name, const std::string &value)
963 if (response == NULL || name.empty())
966 #ifdef WEBSERVER_DEBUG
967 CLog::Log(LOGDEBUG, "webserver [OUT] %s: %s", name.c_str(), value.c_str());
969 return MHD_add_response_header(response, name.c_str(), value.c_str());
972 int64_t CWebServer::ParseRangeHeader(const std::string &rangeHeaderValue, int64_t totalLength, HttpRanges &ranges, int64_t &firstPosition, int64_t &lastPosition)
975 lastPosition = totalLength - 1;
977 if (rangeHeaderValue.empty() || !StringUtils::StartsWith(rangeHeaderValue, "bytes="))
980 int64_t rangesLength = 0;
982 // remove "bytes=" from the beginning
983 string rangesValue = rangeHeaderValue.substr(6);
984 // split the value of the "Range" header by ","
985 vector<string> rangeValues = StringUtils::Split(rangesValue, ",");
986 for (vector<string>::const_iterator range = rangeValues.begin(); range != rangeValues.end(); range++)
988 // there must be a "-" in the range definition
989 if (range->find("-") == string::npos)
995 vector<string> positions = StringUtils::Split(*range, "-");
996 if (positions.size() > 2)
1002 int64_t positionStart = -1;
1003 int64_t positionEnd = -1;
1004 if (!positions.at(0).empty())
1005 positionStart = str2int64(positions.at(0), -1);
1006 if (!positions.at(1).empty())
1007 positionEnd = str2int64(positions.at(1), -1);
1009 if (positionStart < 0 && positionEnd < 0)
1015 // if there's no end position, use the file's length
1016 if (positionEnd < 0)
1017 positionEnd = totalLength - 1;
1018 else if (positionStart < 0)
1020 positionStart = totalLength - positionEnd;
1021 positionEnd = totalLength - 1;
1024 if (positionEnd < positionStart)
1032 firstPosition = positionStart;
1033 lastPosition = positionEnd;
1037 if (positionStart < firstPosition)
1038 firstPosition = positionStart;
1039 if (positionEnd > lastPosition)
1040 lastPosition = positionEnd;
1043 ranges.push_back(HttpRange(positionStart, positionEnd));
1044 rangesLength += positionEnd - positionStart + 1;
1047 if (!ranges.empty() || rangesLength > 0)
1048 return rangesLength;
1053 std::string CWebServer::GenerateMultipartBoundary()
1055 static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1057 // create a string of length 30 to 40 and pre-fill it with "-"
1058 size_t count = (size_t)CUtil::GetRandomNumber() % 11 + 30;
1059 string boundary(count, '-');
1061 for (size_t i = (size_t)CUtil::GetRandomNumber() % 5 + 8; i < count; i++)
1062 boundary.replace(i, 1, 1, chars[(size_t)CUtil::GetRandomNumber() % 64]);
1067 bool CWebServer::GetLastModifiedDateTime(XFILE::CFile *file, CDateTime &lastModified)
1072 struct __stat64 statBuffer;
1073 if (file->Stat(&statBuffer) != 0)
1076 struct tm *time = localtime((time_t *)&statBuffer.st_mtime);
1080 lastModified = *time;