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 if (cls == NULL || key == NULL)
81 map<string, string> *arguments = (map<string, string> *)cls;
82 arguments->insert(pair<string, string>(key, value != NULL ? value : StringUtils::Empty));
86 int CWebServer::FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
88 if (cls == NULL || key == NULL)
91 multimap<string, string> *arguments = (multimap<string, string> *)cls;
92 arguments->insert(pair<string, string>(key, value != NULL ? value : StringUtils::Empty));
96 int CWebServer::AskForAuthentication(struct MHD_Connection *connection)
99 struct MHD_Response *response;
101 response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
105 ret = AddHeader(response, MHD_HTTP_HEADER_WWW_AUTHENTICATE, "Basic realm=XBMC");
106 ret |= AddHeader(response, MHD_HTTP_HEADER_CONNECTION, "close");
109 MHD_destroy_response (response);
113 ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response);
115 MHD_destroy_response (response);
120 bool CWebServer::IsAuthenticated(CWebServer *server, struct MHD_Connection *connection)
122 CSingleLock lock (server->m_critSection);
123 if (!server->m_needcredentials)
126 const char *strbase = "Basic ";
127 const char *headervalue = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Authorization");
128 if (NULL == headervalue)
130 if (strncmp (headervalue, strbase, strlen(strbase)))
133 return (server->m_Credentials64Encoded.compare(headervalue + strlen(strbase)) == 0);
136 #if (MHD_VERSION >= 0x00040001)
137 int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
138 const char *url, const char *method,
139 const char *version, const char *upload_data,
140 size_t *upload_data_size, void **con_cls)
142 int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
143 const char *url, const char *method,
144 const char *version, const char *upload_data,
145 unsigned int *upload_data_size, void **con_cls)
148 CWebServer *server = (CWebServer *)cls;
149 HTTPMethod methodType = GetMethod(method);
150 HTTPRequest request = { connection, url, methodType, version, server };
152 if (!IsAuthenticated(server, connection))
153 return AskForAuthentication(connection);
155 // Check if this is the first call to
156 // AnswerToConnection for this request
157 if (*con_cls == NULL)
159 // Look for a IHTTPRequestHandler which can
160 // take care of the current request
161 for (vector<IHTTPRequestHandler *>::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
163 IHTTPRequestHandler *requestHandler = *it;
164 if (requestHandler->CheckHTTPRequest(request))
166 // We found a matching IHTTPRequestHandler
167 // so let's get a new instance for this request
168 IHTTPRequestHandler *handler = requestHandler->GetInstance();
170 // If we got a POST request we need to take
171 // care of the POST data
172 if (methodType == POST)
174 ConnectionHandler *conHandler = new ConnectionHandler();
175 conHandler->requestHandler = handler;
177 // Get the content-type of the POST data
178 string contentType = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
179 if (!contentType.empty())
181 // If the content-type is application/x-ww-form-urlencoded or multipart/form-data
182 // we can use MHD's POST processor
183 if (stricmp(contentType.c_str(), MHD_HTTP_POST_ENCODING_FORM_URLENCODED) == 0 ||
184 stricmp(contentType.c_str(), MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA) == 0)
186 // Get a new MHD_PostProcessor
187 conHandler->postprocessor = MHD_create_post_processor(connection, MAX_POST_BUFFER_SIZE, &CWebServer::HandlePostField, (void*)conHandler);
189 // MHD doesn't seem to be able to handle
191 if (conHandler->postprocessor == NULL)
193 delete conHandler->requestHandler;
196 return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType);
200 // otherwise we need to handle the POST data ourselves
201 // which is done in the next call to AnswerToConnection
203 *con_cls = (void*)conHandler;
206 // No POST request so nothing special to handle
208 return HandleRequest(handler, request);
212 // This is a subsequent call to
213 // AnswerToConnection for this request
216 // Again we need to take special care
218 if (methodType == POST)
220 ConnectionHandler *conHandler = (ConnectionHandler *)*con_cls;
221 if (conHandler->requestHandler == NULL)
222 return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType);
224 // We only need to handle POST data
225 // if there actually is data left to handle
226 if (*upload_data_size > 0)
228 // Either use MHD's POST processor
229 if (conHandler->postprocessor != NULL)
230 MHD_post_process(conHandler->postprocessor, upload_data, *upload_data_size);
231 // or simply copy the data to the handler
233 conHandler->requestHandler->AddPostData(upload_data, *upload_data_size);
235 // Signal that we have handled the data
236 *upload_data_size = 0;
240 // We have handled all POST data
241 // so it's time to invoke the IHTTPRequestHandler
244 if (conHandler->postprocessor != NULL)
245 MHD_destroy_post_processor(conHandler->postprocessor);
248 int ret = HandleRequest(conHandler->requestHandler, request);
253 // It's unusual to get more than one call
254 // to AnswerToConnection for none-POST
255 // requests, but let's handle it anyway
258 for (vector<IHTTPRequestHandler *>::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
260 IHTTPRequestHandler *requestHandler = *it;
261 if (requestHandler->CheckHTTPRequest(request))
262 return HandleRequest(requestHandler->GetInstance(), request);
267 return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
270 #if (MHD_VERSION >= 0x00040001)
271 int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key,
272 const char *filename, const char *content_type,
273 const char *transfer_encoding, const char *data, uint64_t off,
276 int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key,
277 const char *filename, const char *content_type,
278 const char *transfer_encoding, const char *data, uint64_t off,
282 ConnectionHandler *conHandler = (ConnectionHandler *)cls;
284 if (conHandler == NULL || conHandler->requestHandler == NULL || size == 0)
287 conHandler->requestHandler->AddPostField(key, string(data, size));
291 int CWebServer::HandleRequest(IHTTPRequestHandler *handler, const HTTPRequest &request)
294 return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
296 int ret = handler->HandleHTTPRequest(request);
300 return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
303 struct MHD_Response *response = NULL;
304 int responseCode = handler->GetHTTPResonseCode();
305 switch (handler->GetHTTPResponseType())
312 ret = CreateRedirect(request.connection, handler->GetHTTPRedirectUrl(), response);
315 case HTTPFileDownload:
316 ret = CreateFileDownloadResponse(request.connection, handler->GetHTTPResponseFile(), request.method, response, responseCode);
319 case HTTPMemoryDownloadNoFreeNoCopy:
320 ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), false, false, response);
323 case HTTPMemoryDownloadNoFreeCopy:
324 ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), false, true, response);
327 case HTTPMemoryDownloadFreeNoCopy:
328 ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), true, false, response);
331 case HTTPMemoryDownloadFreeCopy:
332 ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), true, true, response);
336 ret = CreateErrorResponse(request.connection, handler->GetHTTPResonseCode(), request.method, response);
341 return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
347 return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
350 multimap<string, string> header = handler->GetHTTPResponseHeaderFields();
351 for (multimap<string, string>::const_iterator it = header.begin(); it != header.end(); it++)
352 AddHeader(response, it->first.c_str(), it->second.c_str());
354 MHD_queue_response(request.connection, responseCode, response);
355 MHD_destroy_response(response);
361 HTTPMethod CWebServer::GetMethod(const char *method)
363 if (strcmp(method, "GET") == 0)
365 if (strcmp(method, "POST") == 0)
367 if (strcmp(method, "HEAD") == 0)
373 int CWebServer::CreateRedirect(struct MHD_Connection *connection, const string &strURL, struct MHD_Response *&response)
375 response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
378 AddHeader(response, "Location", strURL.c_str());
384 int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode)
386 CFile *file = new CFile();
388 #ifdef WEBSERVER_DEBUG
389 CLog::Log(LOGDEBUG, "webserver [IN] %s", strURL.c_str());
390 multimap<string, string> headers;
391 if (GetRequestHeaderValues(connection, MHD_HEADER_KIND, headers) > 0)
393 for (multimap<string, string>::const_iterator header = headers.begin(); header != headers.end(); header++)
394 CLog::Log(LOGDEBUG, "webserver [IN] %s: %s", header->first.c_str(), header->second.c_str());
398 if (file->Open(strURL, READ_NO_CACHE))
402 int64_t fileLength = file->GetLength();
404 // try to get the file's last modified date
405 CDateTime lastModified;
406 if (!GetLastModifiedDateTime(file, lastModified))
407 lastModified.Reset();
409 // get the MIME type for the Content-Type header
410 CStdString ext = URIUtils::GetExtension(strURL);
411 StringUtils::ToLower(ext);
412 string mimeType = CreateMimeTypeFromExtension(ext.c_str());
414 if (methodType != HEAD)
416 int64_t firstPosition = 0;
417 int64_t lastPosition = fileLength - 1;
418 uint64_t totalLength = 0;
419 HttpFileDownloadContext *context = new HttpFileDownloadContext();
420 context->file = file;
421 context->rangesLength = fileLength;
422 context->contentType = mimeType;
423 context->boundaryWritten = false;
424 context->writePosition = 0;
426 if (methodType == GET)
428 // handle If-Modified-Since
429 string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Modified-Since");
430 if (!ifModifiedSince.empty() && lastModified.IsValid())
432 CDateTime ifModifiedSinceDate;
433 ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince);
435 if (lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
438 response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
439 responseCode = MHD_HTTP_NOT_MODIFIED;
445 // handle Range header
446 context->rangesLength = ParseRangeHeader(GetRequestHeaderValue(connection, MHD_HEADER_KIND, "Range"), fileLength, context->ranges, firstPosition, lastPosition);
448 // handle If-Range header but only if the Range header is present
449 if (!context->ranges.empty())
451 string ifRange = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Range");
452 if (!ifRange.empty() && lastModified.IsValid())
454 CDateTime ifRangeDate;
455 ifRangeDate.SetFromRFC1123DateTime(ifRange);
457 // check if the last modification is newer than the If-Range date
458 // if so we have to server the whole file instead
459 if (lastModified.GetAsUTCDateTime() > ifRangeDate)
460 context->ranges.clear();
468 // if there are no ranges, add the whole range
469 if (context->ranges.empty() || context->rangesLength == fileLength)
471 if (context->rangesLength == fileLength)
472 context->ranges.clear();
474 context->ranges.push_back(HttpRange(0, fileLength - 1));
475 context->rangesLength = fileLength;
477 lastPosition = fileLength - 1;
480 responseCode = MHD_HTTP_PARTIAL_CONTENT;
482 // remember the total number of ranges
483 context->rangeCount = context->ranges.size();
484 // remember the total length
485 totalLength = context->rangesLength;
487 // we need to remember whether we are ranged because the range length
488 // might change and won't be reliable anymore for length comparisons
489 ranged = context->rangeCount > 1 || context->rangesLength < fileLength;
491 // adjust the MIME type and range length in case of multiple ranges
492 // which requires multipart boundaries
493 if (context->rangeCount > 1)
495 context->boundary = GenerateMultipartBoundary();
496 mimeType = "multipart/byteranges; boundary=" + context->boundary;
498 // build part of the boundary with the optional Content-Type header
499 // "--<boundary>\r\nContent-Type: <content-type>\r\n
500 context->boundaryWithHeader = "\r\n--" + context->boundary + "\r\n";
501 if (!context->contentType.empty())
502 context->boundaryWithHeader += "Content-Type: " + context->contentType + "\r\n";
504 // for every range, we need to add a boundary with header
505 for (HttpRanges::const_iterator range = context->ranges.begin(); range != context->ranges.end(); range++)
507 // we need to temporarily add the Content-Range header to the
508 // boundary to be able to determine the length
509 string completeBoundaryWithHeader = context->boundaryWithHeader;
510 completeBoundaryWithHeader += StringUtils::Format("Content-Range: " CONTENT_RANGE_FORMAT,
511 range->first, range->second, range->second - range->first + 1);
512 completeBoundaryWithHeader += "\r\n\r\n";
514 totalLength += completeBoundaryWithHeader.size();
516 // and at the very end a special end-boundary "\r\n--<boundary>--"
517 totalLength += 4 + context->boundary.size() + 2;
520 // set the initial write position
521 context->writePosition = context->ranges.begin()->first;
523 // create the response object
524 response = MHD_create_response_from_callback(totalLength,
526 &CWebServer::ContentReaderCallback, context,
527 &CWebServer::ContentReaderFreeCallback);
530 if (response == NULL)
538 // add Content-Range header
540 AddHeader(response, "Content-Range", StringUtils::Format(CONTENT_RANGE_FORMAT, firstPosition, lastPosition, fileLength).c_str());
546 CStdString contentLength = StringUtils::Format("%" PRId64, fileLength);
548 response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
549 if (response == NULL)
555 AddHeader(response, "Content-Length", contentLength);
558 // add "Accept-Ranges: bytes" header
559 AddHeader(response, "Accept-Ranges", "bytes");
561 // set the Content-Type header
562 if (!mimeType.empty())
563 AddHeader(response, "Content-Type", mimeType.c_str());
565 // set the Last-Modified header
566 if (lastModified.IsValid())
567 AddHeader(response, "Last-Modified", lastModified.GetAsRFC1123DateTime());
569 // set the Expires header
570 CDateTime expiryTime = CDateTime::GetCurrentDateTime();
571 if (StringUtils::EqualsNoCase(mimeType, "text/html") ||
572 StringUtils::EqualsNoCase(mimeType, "text/css") ||
573 StringUtils::EqualsNoCase(mimeType, "application/javascript"))
574 expiryTime += CDateTimeSpan(1, 0, 0, 0);
576 expiryTime += CDateTimeSpan(365, 0, 0, 0);
577 AddHeader(response, "Expires", expiryTime.GetAsRFC1123DateTime());
579 // only close the CFile instance if libmicrohttpd doesn't have to grab the data of the file
589 CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str());
590 return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
596 int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response)
598 size_t payloadSize = 0;
599 void *payload = NULL;
603 switch (responseType)
605 case MHD_HTTP_NOT_FOUND:
606 payloadSize = strlen(PAGE_FILE_NOT_FOUND);
607 payload = (void *)PAGE_FILE_NOT_FOUND;
609 case MHD_HTTP_NOT_IMPLEMENTED:
610 payloadSize = strlen(NOT_SUPPORTED);
611 payload = (void *)NOT_SUPPORTED;
616 response = MHD_create_response_from_data (payloadSize, payload, MHD_NO, MHD_NO);
622 int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection, void *data, size_t size, bool free, bool copy, struct MHD_Response *&response)
624 response = MHD_create_response_from_data (size, data, free ? MHD_YES : MHD_NO, copy ? MHD_YES : MHD_NO);
630 int CWebServer::SendErrorResponse(struct MHD_Connection *connection, int errorType, HTTPMethod method)
632 struct MHD_Response *response = NULL;
633 int ret = CreateErrorResponse(connection, errorType, method, response);
636 ret = MHD_queue_response (connection, errorType, response);
637 MHD_destroy_response (response);
643 void* CWebServer::UriRequestLogger(void *cls, const char *uri)
645 CLog::Log(LOGDEBUG, "webserver: request received for %s", uri);
649 #if (MHD_VERSION >= 0x00090200)
650 ssize_t CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, size_t max)
651 #elif (MHD_VERSION >= 0x00040001)
652 int CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, int max)
653 #else //libmicrohttpd < 0.4.0
654 int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max)
657 HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls;
658 if (context == NULL || context->file == NULL)
661 #ifdef WEBSERVER_DEBUG
662 CLog::Log(LOGDEBUG, "webserver [OUT] write maximum %d bytes from %" PRIu64 " (%" PRIu64 ")", max, context->writePosition, pos);
665 // check if we need to add the end-boundary
666 if (context->rangeCount > 1 && context->ranges.empty())
668 // put together the end-boundary
669 string endBoundary = "\r\n--" + context->boundary + "--";
670 if ((unsigned int)max != endBoundary.size())
673 // copy the boundary into the buffer
674 memcpy(buf, endBoundary.c_str(), endBoundary.size());
675 return endBoundary.size();
678 if (context->ranges.empty())
681 int64_t start = context->ranges.at(0).first;
682 int64_t end = context->ranges.at(0).second;
683 int64_t maximum = (int64_t)max;
686 if (context->rangeCount > 1 && !context->boundaryWritten)
688 // put together the boundary for the current range
689 string boundary = context->boundaryWithHeader;
690 boundary += StringUtils::Format("Content-Range: " CONTENT_RANGE_FORMAT, start, end, end - start + 1) + "\r\n\r\n";
692 // copy the boundary into the buffer
693 memcpy(buf, boundary.c_str(), boundary.size());
694 // advance the buffer position
695 buf += boundary.size();
696 // update the number of written byte
697 written += boundary.size();
698 // update the maximum number of bytes
699 maximum -= boundary.size();
700 context->boundaryWritten = true;
703 // check if the current position is within this range
704 // if not, set it to the start position
705 if (context->writePosition < start || context->writePosition > end)
706 context->writePosition = start;
707 // adjust the maximum number of read bytes
708 maximum = std::min(maximum, end - context->writePosition + 1);
710 // seek to the position if necessary
711 if(context->writePosition != context->file->GetPosition())
712 context->file->Seek(context->writePosition);
714 // read data from the file
715 unsigned int res = context->file->Read(buf, maximum);
719 // add the number of read bytes to the number of written bytes
721 #ifdef WEBSERVER_DEBUG
722 CLog::Log(LOGDEBUG, "webserver [OUT] wrote %d bytes from %" PRId64 " in range (%" PRId64 " - %" PRId64 ")", written, context->writePosition, start, end);
724 // update the current write position
725 context->writePosition += res;
727 // if we have read all the data from the current range
728 // remove it from the list
729 if (context->writePosition >= end + 1)
731 context->ranges.erase(context->ranges.begin());
732 context->boundaryWritten = false;
738 void CWebServer::ContentReaderFreeCallback(void *cls)
740 HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls;
744 if (context->file != NULL)
746 context->file->Close();
747 delete context->file;
748 context->file = NULL;
751 #ifdef WEBSERVER_DEBUG
752 CLog::Log(LOGDEBUG, "webserver [OUT] done");
757 struct MHD_Daemon* CWebServer::StartMHD(unsigned int flags, int port)
759 unsigned int timeout = 60 * 60 * 24;
761 return MHD_start_daemon(flags |
762 #if (MHD_VERSION >= 0x00040002) && (MHD_VERSION < 0x00090B01)
763 // use main thread for each connection, can only handle one request at a
764 // time [unless you set the thread pool size]
765 MHD_USE_SELECT_INTERNALLY
767 // one thread per connection
768 // WARNING: set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
769 // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
770 MHD_USE_THREAD_PER_CONNECTION
776 &CWebServer::AnswerToConnection,
779 #if (MHD_VERSION >= 0x00040002) && (MHD_VERSION < 0x00090B01)
780 MHD_OPTION_THREAD_POOL_SIZE, 4,
782 MHD_OPTION_CONNECTION_LIMIT, 512,
783 MHD_OPTION_CONNECTION_TIMEOUT, timeout,
784 MHD_OPTION_URI_LOG_CALLBACK, &CWebServer::UriRequestLogger, this,
788 bool CWebServer::Start(int port, const string &username, const string &password)
790 SetCredentials(username, password);
794 if ((v6testSock = socket(AF_INET6, SOCK_STREAM, 0)) >= 0)
796 closesocket(v6testSock);
797 m_daemon_ip6 = StartMHD(MHD_USE_IPv6, port);
800 m_daemon_ip4 = StartMHD(0 , port);
802 m_running = (m_daemon_ip6 != NULL) || (m_daemon_ip4 != NULL);
804 CLog::Log(LOGNOTICE, "WebServer: Started the webserver");
806 CLog::Log(LOGERROR, "WebServer: Failed to start the webserver");
811 bool CWebServer::Stop()
815 if (m_daemon_ip6 != NULL)
816 MHD_stop_daemon(m_daemon_ip6);
818 if (m_daemon_ip4 != NULL)
819 MHD_stop_daemon(m_daemon_ip4);
822 CLog::Log(LOGNOTICE, "WebServer: Stopped the webserver");
825 CLog::Log(LOGNOTICE, "WebServer: Stopped failed because its not running");
830 bool CWebServer::IsStarted()
835 void CWebServer::SetCredentials(const string &username, const string &password)
837 CSingleLock lock (m_critSection);
838 CStdString str = username + ":" + password;
840 Base64::Encode(str.c_str(), m_Credentials64Encoded);
841 m_needcredentials = !password.empty();
844 bool CWebServer::PrepareDownload(const char *path, CVariant &details, std::string &protocol)
846 if (CFile::Exists(path))
850 CStdString strPath = path;
851 if (StringUtils::StartsWith(strPath, "image://") ||
852 (StringUtils::StartsWith(strPath, "special://") && StringUtils::EndsWith(strPath, ".tbn")))
856 url += CURL::Encode(strPath);
857 details["path"] = url;
864 bool CWebServer::Download(const char *path, CVariant &result)
869 int CWebServer::GetCapabilities()
871 return Response | FileDownloadRedirect;
874 void CWebServer::RegisterRequestHandler(IHTTPRequestHandler *handler)
879 for (vector<IHTTPRequestHandler *>::iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
884 if ((*it)->GetPriority() < handler->GetPriority())
886 m_requestHandlers.insert(it, handler);
891 m_requestHandlers.push_back(handler);
894 void CWebServer::UnregisterRequestHandler(IHTTPRequestHandler *handler)
899 for (vector<IHTTPRequestHandler *>::iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
903 m_requestHandlers.erase(it);
909 std::string CWebServer::GetRequestHeaderValue(struct MHD_Connection *connection, enum MHD_ValueKind kind, const std::string &key)
911 if (connection == NULL)
914 const char* value = MHD_lookup_connection_value(connection, kind, key.c_str());
918 if (stricmp(key.c_str(), MHD_HTTP_HEADER_CONTENT_TYPE) == 0)
920 // Work around a bug in firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=416178)
921 // by cutting of anything that follows a ";" in a "Content-Type" header field
922 string strValue(value);
923 size_t pos = strValue.find(';');
924 if (pos != string::npos)
925 strValue = strValue.substr(0, pos);
933 int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::map<std::string, std::string> &headerValues)
935 if (connection == NULL)
938 return MHD_get_connection_values(connection, kind, FillArgumentMap, &headerValues);
941 int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::multimap<std::string, std::string> &headerValues)
943 if (connection == NULL)
946 return MHD_get_connection_values(connection, kind, FillArgumentMultiMap, &headerValues);
949 std::string CWebServer::CreateMimeTypeFromExtension(const char *ext)
951 if (strcmp(ext, ".kar") == 0) return "audio/midi";
952 if (strcmp(ext, ".tbn") == 0) return "image/jpeg";
953 return CMime::GetMimeType(ext);
956 int CWebServer::AddHeader(struct MHD_Response *response, const std::string &name, const std::string &value)
958 if (response == NULL || name.empty())
961 #ifdef WEBSERVER_DEBUG
962 CLog::Log(LOGDEBUG, "webserver [OUT] %s: %s", name.c_str(), value.c_str());
964 return MHD_add_response_header(response, name.c_str(), value.c_str());
967 int64_t CWebServer::ParseRangeHeader(const std::string &rangeHeaderValue, int64_t totalLength, HttpRanges &ranges, int64_t &firstPosition, int64_t &lastPosition)
970 lastPosition = totalLength - 1;
972 if (rangeHeaderValue.empty() || !StringUtils::StartsWithNoCase(rangeHeaderValue, "bytes="))
975 int64_t rangesLength = 0;
977 // remove "bytes=" from the beginning
978 string rangesValue = rangeHeaderValue.substr(6);
979 // split the value of the "Range" header by ","
980 vector<string> rangeValues = StringUtils::Split(rangesValue, ",");
981 for (vector<string>::const_iterator range = rangeValues.begin(); range != rangeValues.end(); range++)
983 // there must be a "-" in the range definition
984 if (range->find("-") == string::npos)
990 vector<string> positions = StringUtils::Split(*range, "-");
991 if (positions.size() > 2)
997 int64_t positionStart = -1;
998 int64_t positionEnd = -1;
999 if (!positions.at(0).empty())
1000 positionStart = str2int64(positions.at(0), -1);
1001 if (!positions.at(1).empty())
1002 positionEnd = str2int64(positions.at(1), -1);
1004 if (positionStart < 0 && positionEnd < 0)
1010 // if there's no end position, use the file's length
1011 if (positionEnd < 0)
1012 positionEnd = totalLength - 1;
1013 else if (positionStart < 0)
1015 positionStart = totalLength - positionEnd;
1016 positionEnd = totalLength - 1;
1019 if (positionEnd < positionStart)
1027 firstPosition = positionStart;
1028 lastPosition = positionEnd;
1032 if (positionStart < firstPosition)
1033 firstPosition = positionStart;
1034 if (positionEnd > lastPosition)
1035 lastPosition = positionEnd;
1038 ranges.push_back(HttpRange(positionStart, positionEnd));
1039 rangesLength += positionEnd - positionStart + 1;
1042 if (!ranges.empty() || rangesLength > 0)
1043 return rangesLength;
1048 std::string CWebServer::GenerateMultipartBoundary()
1050 static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1052 // create a string of length 30 to 40 and pre-fill it with "-"
1053 size_t count = (size_t)CUtil::GetRandomNumber() % 11 + 30;
1054 string boundary(count, '-');
1056 for (size_t i = (size_t)CUtil::GetRandomNumber() % 5 + 8; i < count; i++)
1057 boundary.replace(i, 1, 1, chars[(size_t)CUtil::GetRandomNumber() % 64]);
1062 bool CWebServer::GetLastModifiedDateTime(XFILE::CFile *file, CDateTime &lastModified)
1067 struct __stat64 statBuffer;
1068 if (file->Stat(&statBuffer) != 0)
1071 struct tm *time = localtime((time_t *)&statBuffer.st_mtime);
1075 lastModified = *time;