9fa969c6985448c5de7df034112345c583016672
[vuplus_xbmc] / xbmc / network / WebServer.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "WebServer.h"
22 #ifdef HAS_WEB_SERVER
23 #include "URL.h"
24 #include "Util.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"
35
36 //#define WEBSERVER_DEBUG
37
38 #ifdef TARGET_WINDOWS
39 #pragma comment(lib, "libmicrohttpd.dll.lib")
40 #endif
41
42 #define MAX_POST_BUFFER_SIZE 2048
43
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>"
46
47 #define CONTENT_RANGE_FORMAT  "bytes %" PRId64 "-%" PRId64 "/%" PRId64
48
49 using namespace XFILE;
50 using namespace std;
51 using namespace JSONRPC;
52
53 typedef struct {
54   CFile *file;
55   HttpRanges ranges;
56   size_t rangeCount;
57   int64_t rangesLength;
58   string boundary;
59   string boundaryWithHeader;
60   bool boundaryWritten;
61   string contentType;
62   int64_t writePosition;
63 } HttpFileDownloadContext;
64
65 vector<IHTTPRequestHandler *> CWebServer::m_requestHandlers;
66
67 CWebServer::CWebServer()
68 {
69   m_running = false;
70   m_daemon_ip6 = NULL;
71   m_daemon_ip4 = NULL;
72   m_needcredentials = true;
73   m_Credentials64Encoded = "eGJtYzp4Ym1j"; // xbmc:xbmc
74 }
75
76 int CWebServer::FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) 
77 {
78   if (cls == NULL || key == NULL)
79     return MHD_NO;
80
81   map<string, string> *arguments = (map<string, string> *)cls;
82   arguments->insert(pair<string, string>(key, value != NULL ? value : StringUtils::Empty));
83   return MHD_YES; 
84 }
85
86 int CWebServer::FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) 
87 {
88   if (cls == NULL || key == NULL)
89     return MHD_NO;
90
91   multimap<string, string> *arguments = (multimap<string, string> *)cls;
92   arguments->insert(pair<string, string>(key, value != NULL ? value : StringUtils::Empty));
93   return MHD_YES; 
94 }
95
96 int CWebServer::AskForAuthentication(struct MHD_Connection *connection)
97 {
98   int ret;
99   struct MHD_Response *response;
100
101   response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
102   if (!response)
103     return MHD_NO;
104
105   ret = AddHeader(response, MHD_HTTP_HEADER_WWW_AUTHENTICATE, "Basic realm=XBMC");
106   ret |= AddHeader(response, MHD_HTTP_HEADER_CONNECTION, "close");
107   if (!ret)
108   {
109     MHD_destroy_response (response);
110     return MHD_NO;
111   }
112
113   ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response);
114
115   MHD_destroy_response (response);
116
117   return ret;
118 }
119
120 bool CWebServer::IsAuthenticated(CWebServer *server, struct MHD_Connection *connection)
121 {
122   CSingleLock lock (server->m_critSection);
123   if (!server->m_needcredentials)
124     return true;
125
126   const char *strbase = "Basic ";
127   const char *headervalue = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Authorization");
128   if (NULL == headervalue)
129     return false;
130   if (strncmp (headervalue, strbase, strlen(strbase)))
131     return false;
132
133   return (server->m_Credentials64Encoded.compare(headervalue + strlen(strbase)) == 0);
134 }
135
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)
141 #else
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)
146 #endif
147 {
148   CWebServer *server = (CWebServer *)cls;
149   HTTPMethod methodType = GetMethod(method);
150   HTTPRequest request = { connection, url, methodType, version, server };
151
152   if (!IsAuthenticated(server, connection)) 
153     return AskForAuthentication(connection);
154
155   // Check if this is the first call to
156   // AnswerToConnection for this request
157   if (*con_cls == NULL)
158   {
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++)
162     {
163       IHTTPRequestHandler *requestHandler = *it;
164       if (requestHandler->CheckHTTPRequest(request))
165       {
166         // We found a matching IHTTPRequestHandler
167         // so let's get a new instance for this request
168         IHTTPRequestHandler *handler = requestHandler->GetInstance();
169
170         // If we got a POST request we need to take
171         // care of the POST data
172         if (methodType == POST)
173         {
174           ConnectionHandler *conHandler = new ConnectionHandler();
175           conHandler->requestHandler = handler;
176
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())
180           {
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)
185             {
186               // Get a new MHD_PostProcessor
187               conHandler->postprocessor = MHD_create_post_processor(connection, MAX_POST_BUFFER_SIZE, &CWebServer::HandlePostField, (void*)conHandler);
188
189               // MHD doesn't seem to be able to handle
190               // this post request
191               if (conHandler->postprocessor == NULL)
192               {
193                 delete conHandler->requestHandler;
194                 delete conHandler;
195
196                 return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType);
197               }
198             }
199           }
200           // otherwise we need to handle the POST data ourselves
201           // which is done in the next call to AnswerToConnection
202
203           *con_cls = (void*)conHandler;
204           return MHD_YES;
205         }
206         // No POST request so nothing special to handle
207         else
208           return HandleRequest(handler, request);
209       }
210     }
211   }
212   // This is a subsequent call to
213   // AnswerToConnection for this request
214   else
215   {
216     // Again we need to take special care
217     // of the POST data
218     if (methodType == POST)
219     {
220       ConnectionHandler *conHandler = (ConnectionHandler *)*con_cls;
221       if (conHandler->requestHandler == NULL)
222         return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType);
223
224       // We only need to handle POST data
225       // if there actually is data left to handle
226       if (*upload_data_size > 0)
227       {
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
232         else
233           conHandler->requestHandler->AddPostData(upload_data, *upload_data_size);
234
235         // Signal that we have handled the data
236         *upload_data_size = 0;
237
238         return MHD_YES;
239       }
240       // We have handled all POST data
241       // so it's time to invoke the IHTTPRequestHandler
242       else
243       {
244         if (conHandler->postprocessor != NULL)
245           MHD_destroy_post_processor(conHandler->postprocessor);
246         *con_cls = NULL;
247
248         int ret = HandleRequest(conHandler->requestHandler, request);
249         delete conHandler;
250         return ret;
251       }
252     }
253     // It's unusual to get more than one call
254     // to AnswerToConnection for none-POST
255     // requests, but let's handle it anyway
256     else
257     {
258       for (vector<IHTTPRequestHandler *>::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
259       {
260         IHTTPRequestHandler *requestHandler = *it;
261         if (requestHandler->CheckHTTPRequest(request))
262           return HandleRequest(requestHandler->GetInstance(), request);
263       }
264     }
265   }
266
267   return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
268 }
269
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,
274                                 size_t size)
275 #else
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,
279                                 unsigned int size)
280 #endif
281 {
282   ConnectionHandler *conHandler = (ConnectionHandler *)cls;
283
284   if (conHandler == NULL || conHandler->requestHandler == NULL || size == 0)
285     return MHD_NO;
286
287   conHandler->requestHandler->AddPostField(key, string(data, size));
288   return MHD_YES;
289 }
290
291 int CWebServer::HandleRequest(IHTTPRequestHandler *handler, const HTTPRequest &request)
292 {
293   if (handler == NULL)
294     return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
295
296   int ret = handler->HandleHTTPRequest(request);
297   if (ret == MHD_NO)
298   {
299     delete handler;
300     return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
301   }
302
303   struct MHD_Response *response = NULL;
304   int responseCode = handler->GetHTTPResonseCode();
305   switch (handler->GetHTTPResponseType())
306   {
307     case HTTPNone:
308       delete handler;
309       return MHD_NO;
310
311     case HTTPRedirect:
312       ret = CreateRedirect(request.connection, handler->GetHTTPRedirectUrl(), response);
313       break;
314
315     case HTTPFileDownload:
316       ret = CreateFileDownloadResponse(request.connection, handler->GetHTTPResponseFile(), request.method, response, responseCode);
317       break;
318
319     case HTTPMemoryDownloadNoFreeNoCopy:
320       ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), false, false, response);
321       break;
322
323     case HTTPMemoryDownloadNoFreeCopy:
324       ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), false, true, response);
325       break;
326
327     case HTTPMemoryDownloadFreeNoCopy:
328       ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), true, false, response);
329       break;
330
331     case HTTPMemoryDownloadFreeCopy:
332       ret = CreateMemoryDownloadResponse(request.connection, handler->GetHTTPResponseData(), handler->GetHTTPResonseDataLength(), true, true, response);
333       break;
334
335     case HTTPError:
336       ret = CreateErrorResponse(request.connection, handler->GetHTTPResonseCode(), request.method, response);
337       break;
338
339     default:
340       delete handler;
341       return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
342   }
343
344   if (ret == MHD_NO)
345   {
346     delete handler;
347     return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
348   }
349
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());
353
354   MHD_queue_response(request.connection, responseCode, response);
355   MHD_destroy_response(response);
356   delete handler;
357
358   return MHD_YES;
359 }
360
361 HTTPMethod CWebServer::GetMethod(const char *method)
362 {
363   if (strcmp(method, "GET") == 0)
364     return GET;
365   if (strcmp(method, "POST") == 0)
366     return POST;
367   if (strcmp(method, "HEAD") == 0)
368     return HEAD;
369
370   return UNKNOWN;
371 }
372
373 int CWebServer::CreateRedirect(struct MHD_Connection *connection, const string &strURL, struct MHD_Response *&response)
374 {
375   response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
376   if (response)
377   {
378     AddHeader(response, "Location", strURL.c_str());
379     return MHD_YES;
380   }
381   return MHD_NO;
382 }
383
384 int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode)
385 {
386   CFile *file = new CFile();
387
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)
392   {
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());
395   }
396 #endif
397
398   if (file->Open(strURL, READ_NO_CACHE))
399   {
400     bool getData = true;
401     bool ranged = false;
402     int64_t fileLength = file->GetLength();
403
404     // try to get the file's last modified date
405     CDateTime lastModified;
406     if (!GetLastModifiedDateTime(file, lastModified))
407       lastModified.Reset();
408
409     // get the MIME type for the Content-Type header
410     CStdString ext = URIUtils::GetExtension(strURL);
411     ext = ext.ToLower();
412     string mimeType = CreateMimeTypeFromExtension(ext.c_str());
413
414     if (methodType != HEAD)
415     {
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;
425
426       if (methodType == GET)
427       {
428         // handle If-Modified-Since
429         string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Modified-Since");
430         if (!ifModifiedSince.empty() && lastModified.IsValid())
431         {
432           CDateTime ifModifiedSinceDate;
433           ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince);
434
435           if (lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
436           {
437             getData = false;
438             response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
439             responseCode = MHD_HTTP_NOT_MODIFIED;
440           }
441         }
442
443         if (getData)
444         {
445           // handle Range header
446           context->rangesLength = ParseRangeHeader(GetRequestHeaderValue(connection, MHD_HEADER_KIND, "Range"), fileLength, context->ranges, firstPosition, lastPosition);
447
448           // handle If-Range header but only if the Range header is present
449           if (!context->ranges.empty())
450           {
451             string ifRange = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Range");
452             if (!ifRange.empty() && lastModified.IsValid())
453             {
454               CDateTime ifRangeDate;
455               ifRangeDate.SetFromRFC1123DateTime(ifRange);
456
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();
461             }
462           }
463         }
464       }
465
466       if (getData)
467       {
468         // if there are no ranges, add the whole range
469         if (context->ranges.empty() || context->rangesLength == fileLength)
470         {
471           if (context->rangesLength == fileLength)
472             context->ranges.clear();
473
474           context->ranges.push_back(HttpRange(0, fileLength - 1));
475           context->rangesLength = fileLength;
476           firstPosition = 0;
477           lastPosition = fileLength - 1;
478         }
479         else
480           responseCode = MHD_HTTP_PARTIAL_CONTENT;
481
482         // remember the total number of ranges
483         context->rangeCount = context->ranges.size();
484         // remember the total length
485         totalLength = context->rangesLength;
486
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;
490
491         // adjust the MIME type and range length in case of multiple ranges
492         // which requires multipart boundaries
493         if (context->rangeCount > 1)
494         {
495           context->boundary = GenerateMultipartBoundary();
496           mimeType = "multipart/byteranges; boundary=" + context->boundary;
497
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";
503
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++)
506           {
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";
513
514             totalLength += completeBoundaryWithHeader.size();
515           }
516           // and at the very end a special end-boundary "\r\n--<boundary>--"
517           totalLength += 4 + context->boundary.size() + 2;
518         }
519
520         // set the initial write position
521         context->writePosition = context->ranges.begin()->first;
522
523         // create the response object
524         response = MHD_create_response_from_callback(totalLength,
525                                                      2048,
526                                                      &CWebServer::ContentReaderCallback, context,
527                                                      &CWebServer::ContentReaderFreeCallback);
528       }
529
530       if (response == NULL)
531       {
532         file->Close();
533         delete file;
534         delete context;
535         return MHD_NO;
536       }
537
538       // add Content-Range header
539       if (ranged)
540         AddHeader(response, "Content-Range", StringUtils::Format(CONTENT_RANGE_FORMAT, firstPosition, lastPosition, fileLength).c_str());
541     }
542     else
543     {
544       getData = false;
545
546       CStdString contentLength;
547       contentLength.Format("%" PRId64, fileLength);
548
549       response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
550       if (response == NULL)
551       {
552         file->Close();
553         delete file;
554         return MHD_NO;
555       }
556       AddHeader(response, "Content-Length", contentLength);
557     }
558
559     // add "Accept-Ranges: bytes" header
560     AddHeader(response, "Accept-Ranges", "bytes");
561
562     // set the Content-Type header
563     if (!mimeType.empty())
564       AddHeader(response, "Content-Type", mimeType.c_str());
565
566     // set the Last-Modified header
567     if (lastModified.IsValid())
568       AddHeader(response, "Last-Modified", lastModified.GetAsRFC1123DateTime());
569
570     // set the Expires header
571     CDateTime expiryTime = CDateTime::GetCurrentDateTime();
572     if (StringUtils::EqualsNoCase(mimeType, "text/html") ||
573         StringUtils::EqualsNoCase(mimeType, "text/css") ||
574         StringUtils::EqualsNoCase(mimeType, "application/javascript"))
575       expiryTime += CDateTimeSpan(1, 0, 0, 0);
576     else
577       expiryTime += CDateTimeSpan(365, 0, 0, 0);
578     AddHeader(response, "Expires", expiryTime.GetAsRFC1123DateTime());
579
580     // only close the CFile instance if libmicrohttpd doesn't have to grab the data of the file
581     if (!getData)
582     {
583       file->Close();
584       delete file;
585     }
586   }
587   else
588   {
589     delete file;
590     CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str());
591     return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
592   }
593
594   return MHD_YES;
595 }
596
597 int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response)
598 {
599   size_t payloadSize = 0;
600   void *payload = NULL;
601
602   if (method != HEAD)
603   {
604     switch (responseType)
605     {
606       case MHD_HTTP_NOT_FOUND:
607         payloadSize = strlen(PAGE_FILE_NOT_FOUND);
608         payload = (void *)PAGE_FILE_NOT_FOUND;
609         break;
610       case MHD_HTTP_NOT_IMPLEMENTED:
611         payloadSize = strlen(NOT_SUPPORTED);
612         payload = (void *)NOT_SUPPORTED;
613         break;
614     }
615   }
616
617   response = MHD_create_response_from_data (payloadSize, payload, MHD_NO, MHD_NO);
618   if (response)
619     return MHD_YES;
620   return MHD_NO;
621 }
622
623 int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection, void *data, size_t size, bool free, bool copy, struct MHD_Response *&response)
624 {
625   response = MHD_create_response_from_data (size, data, free ? MHD_YES : MHD_NO, copy ? MHD_YES : MHD_NO);
626   if (response)
627     return MHD_YES;
628   return MHD_NO;
629 }
630
631 int CWebServer::SendErrorResponse(struct MHD_Connection *connection, int errorType, HTTPMethod method)
632 {
633   struct MHD_Response *response = NULL;
634   int ret = CreateErrorResponse(connection, errorType, method, response);
635   if (ret == MHD_YES)
636   {
637     ret = MHD_queue_response (connection, errorType, response);
638     MHD_destroy_response (response);
639   }
640
641   return ret;
642 }
643
644 void* CWebServer::UriRequestLogger(void *cls, const char *uri)
645 {
646   CLog::Log(LOGDEBUG, "webserver: request received for %s", uri);
647   return NULL;
648 }
649
650 #if (MHD_VERSION >= 0x00090200)
651 ssize_t CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, size_t max)
652 #elif (MHD_VERSION >= 0x00040001)
653 int CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, int max)
654 #else   //libmicrohttpd < 0.4.0
655 int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max)
656 #endif
657 {
658   HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls;
659   if (context == NULL || context->file == NULL)
660     return -1;
661
662 #ifdef WEBSERVER_DEBUG
663   CLog::Log(LOGDEBUG, "webserver [OUT] write maximum %d bytes from %" PRIu64 " (%" PRIu64 ")", max, context->writePosition, pos);
664 #endif
665
666   // check if we need to add the end-boundary
667   if (context->rangeCount > 1 && context->ranges.empty())
668   {
669     // put together the end-boundary
670     string endBoundary = "\r\n--" + context->boundary + "--";
671     if ((unsigned int)max != endBoundary.size())
672       return -1;
673
674     // copy the boundary into the buffer
675     memcpy(buf, endBoundary.c_str(), endBoundary.size());
676     return endBoundary.size();
677   }
678
679   if (context->ranges.empty())
680     return -1;
681
682   int64_t start = context->ranges.at(0).first;
683   int64_t end = context->ranges.at(0).second;
684   int64_t maximum = (int64_t)max;
685   int written = 0;
686
687   if (context->rangeCount > 1 && !context->boundaryWritten)
688   {
689     // put together the boundary for the current range
690     string boundary = context->boundaryWithHeader;
691     boundary += StringUtils::Format("Content-Range: " CONTENT_RANGE_FORMAT, start, end, end - start + 1) + "\r\n\r\n";
692
693     // copy the boundary into the buffer
694     memcpy(buf, boundary.c_str(), boundary.size());
695     // advance the buffer position
696     buf += boundary.size();
697     // update the number of written byte
698     written += boundary.size();
699     // update the maximum number of bytes
700     maximum -= boundary.size();
701     context->boundaryWritten = true;
702   }
703
704   // check if the current position is within this range
705   // if not, set it to the start position
706   if (context->writePosition < start || context->writePosition > end)
707     context->writePosition = start;
708   // adjust the maximum number of read bytes
709   maximum = std::min(maximum, end - context->writePosition + 1);
710
711   // seek to the position if necessary
712   if(context->writePosition != context->file->GetPosition())
713     context->file->Seek(context->writePosition);
714
715   // read data from the file
716   unsigned int res = context->file->Read(buf, maximum);
717   if (res == 0)
718     return -1;
719
720   // add the number of read bytes to the number of written bytes
721   written += res;
722 #ifdef WEBSERVER_DEBUG
723   CLog::Log(LOGDEBUG, "webserver [OUT] wrote %d bytes from %" PRId64 " in range (%" PRId64 " - %" PRId64 ")", written, context->writePosition, start, end);
724 #endif
725   // update the current write position
726   context->writePosition += res;
727
728   // if we have read all the data from the current range
729   // remove it from the list
730   if (context->writePosition >= end + 1)
731   {
732     context->ranges.erase(context->ranges.begin());
733     context->boundaryWritten = false;
734   }
735
736   return written;
737 }
738
739 void CWebServer::ContentReaderFreeCallback(void *cls)
740 {
741   HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls;
742   if (context == NULL)
743     return;
744
745   if (context->file != NULL)
746   {
747     context->file->Close();
748     delete context->file;
749     context->file = NULL;
750   }
751
752 #ifdef WEBSERVER_DEBUG
753   CLog::Log(LOGDEBUG, "webserver [OUT] done");
754 #endif
755   delete context;
756 }
757
758 struct MHD_Daemon* CWebServer::StartMHD(unsigned int flags, int port)
759 {
760   unsigned int timeout = 60 * 60 * 24;
761
762   return MHD_start_daemon(flags |
763 #if (MHD_VERSION >= 0x00040002) && (MHD_VERSION < 0x00090B01)
764                           // use main thread for each connection, can only handle one request at a
765                           // time [unless you set the thread pool size]
766                           MHD_USE_SELECT_INTERNALLY
767 #else
768                           // one thread per connection
769                           // WARNING: set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
770                           // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
771                           MHD_USE_THREAD_PER_CONNECTION
772 #endif
773                           ,
774                           port,
775                           NULL,
776                           NULL,
777                           &CWebServer::AnswerToConnection,
778                           this,
779
780 #if (MHD_VERSION >= 0x00040002) && (MHD_VERSION < 0x00090B01)
781                           MHD_OPTION_THREAD_POOL_SIZE, 4,
782 #endif
783                           MHD_OPTION_CONNECTION_LIMIT, 512,
784                           MHD_OPTION_CONNECTION_TIMEOUT, timeout,
785                           MHD_OPTION_URI_LOG_CALLBACK, &CWebServer::UriRequestLogger, this,
786                           MHD_OPTION_END);
787 }
788
789 bool CWebServer::Start(int port, const string &username, const string &password)
790 {
791   SetCredentials(username, password);
792   if (!m_running)
793   {
794     int v6testSock;
795     if ((v6testSock = socket(AF_INET6, SOCK_STREAM, 0)) >= 0)
796     {
797       closesocket(v6testSock);
798       m_daemon_ip6 = StartMHD(MHD_USE_IPv6, port);
799     }
800     
801     m_daemon_ip4 = StartMHD(0 , port);
802     
803     m_running = (m_daemon_ip6 != NULL) || (m_daemon_ip4 != NULL);
804     if (m_running)
805       CLog::Log(LOGNOTICE, "WebServer: Started the webserver");
806     else
807       CLog::Log(LOGERROR, "WebServer: Failed to start the webserver");
808   }
809   return m_running;
810 }
811
812 bool CWebServer::Stop()
813 {
814   if (m_running)
815   {
816     if (m_daemon_ip6 != NULL)
817       MHD_stop_daemon(m_daemon_ip6);
818
819     if (m_daemon_ip4 != NULL)
820       MHD_stop_daemon(m_daemon_ip4);
821     
822     m_running = false;
823     CLog::Log(LOGNOTICE, "WebServer: Stopped the webserver");
824   }
825   else 
826     CLog::Log(LOGNOTICE, "WebServer: Stopped failed because its not running");
827
828   return !m_running;
829 }
830
831 bool CWebServer::IsStarted()
832 {
833   return m_running;
834 }
835
836 void CWebServer::SetCredentials(const string &username, const string &password)
837 {
838   CSingleLock lock (m_critSection);
839   CStdString str = username + ":" + password;
840
841   Base64::Encode(str.c_str(), m_Credentials64Encoded);
842   m_needcredentials = !password.empty();
843 }
844
845 bool CWebServer::PrepareDownload(const char *path, CVariant &details, std::string &protocol)
846 {
847   if (CFile::Exists(path))
848   {
849     protocol = "http";
850     string url;
851     CStdString strPath = path;
852     if (strPath.Left(8) == "image://" ||
853        (strPath.Left(10) == "special://" && strPath.Right(4) == ".tbn"))
854       url = "image/";
855     else
856       url = "vfs/";
857     CURL::Encode(strPath);
858     url += strPath;
859     details["path"] = url;
860     return true;
861   }
862
863   return false;
864 }
865
866 bool CWebServer::Download(const char *path, CVariant &result)
867 {
868   return false;
869 }
870
871 int CWebServer::GetCapabilities()
872 {
873   return Response | FileDownloadRedirect;
874 }
875
876 void CWebServer::RegisterRequestHandler(IHTTPRequestHandler *handler)
877 {
878   if (handler == NULL)
879     return;
880
881   for (vector<IHTTPRequestHandler *>::iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
882   {
883     if (*it == handler)
884       return;
885
886     if ((*it)->GetPriority() < handler->GetPriority())
887     {
888       m_requestHandlers.insert(it, handler);
889       return;
890     }
891   }
892
893   m_requestHandlers.push_back(handler);
894 }
895
896 void CWebServer::UnregisterRequestHandler(IHTTPRequestHandler *handler)
897 {
898   if (handler == NULL)
899     return;
900
901   for (vector<IHTTPRequestHandler *>::iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); it++)
902   {
903     if (*it == handler)
904     {
905       m_requestHandlers.erase(it);
906       return;
907     }
908   }
909 }
910
911 std::string CWebServer::GetRequestHeaderValue(struct MHD_Connection *connection, enum MHD_ValueKind kind, const std::string &key)
912 {
913   if (connection == NULL)
914     return "";
915
916   const char* value = MHD_lookup_connection_value(connection, kind, key.c_str());
917   if (value == NULL)
918     return "";
919
920   if (stricmp(key.c_str(), MHD_HTTP_HEADER_CONTENT_TYPE) == 0)
921   {
922     // Work around a bug in firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=416178)
923     // by cutting of anything that follows a ";" in a "Content-Type" header field
924     string strValue(value);
925     size_t pos = strValue.find(';');
926     if (pos != string::npos)
927       strValue = strValue.substr(0, pos);
928
929     return strValue;
930   }
931
932   return value;
933 }
934
935 int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::map<std::string, std::string> &headerValues)
936 {
937   if (connection == NULL)
938     return -1;
939
940   return MHD_get_connection_values(connection, kind, FillArgumentMap, &headerValues);
941 }
942
943 int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::multimap<std::string, std::string> &headerValues)
944 {
945   if (connection == NULL)
946     return -1;
947
948   return MHD_get_connection_values(connection, kind, FillArgumentMultiMap, &headerValues);
949 }
950
951 std::string CWebServer::CreateMimeTypeFromExtension(const char *ext)
952 {
953   if (strcmp(ext, ".kar") == 0)   return "audio/midi";
954   if (strcmp(ext, ".tbn") == 0)   return "image/jpeg";
955   return CMime::GetMimeType(ext);
956 }
957
958 int CWebServer::AddHeader(struct MHD_Response *response, const std::string &name, const std::string &value)
959 {
960   if (response == NULL || name.empty())
961     return 0;
962
963 #ifdef WEBSERVER_DEBUG
964   CLog::Log(LOGDEBUG, "webserver [OUT] %s: %s", name.c_str(), value.c_str());
965 #endif
966   return MHD_add_response_header(response, name.c_str(), value.c_str());
967 }
968
969 int64_t CWebServer::ParseRangeHeader(const std::string &rangeHeaderValue, int64_t totalLength, HttpRanges &ranges, int64_t &firstPosition, int64_t &lastPosition)
970 {
971   firstPosition = 0;
972   lastPosition = totalLength - 1;
973
974   if (rangeHeaderValue.empty() || !StringUtils::StartsWithNoCase(rangeHeaderValue, "bytes="))
975     return totalLength;
976
977   int64_t rangesLength = 0;
978
979   // remove "bytes=" from the beginning
980   string rangesValue = rangeHeaderValue.substr(6);
981   // split the value of the "Range" header by ","
982   vector<string> rangeValues = StringUtils::Split(rangesValue, ",");
983   for (vector<string>::const_iterator range = rangeValues.begin(); range != rangeValues.end(); range++)
984   {
985     // there must be a "-" in the range definition
986     if (range->find("-") == string::npos)
987     {
988       ranges.clear();
989       return totalLength;
990     }
991
992     vector<string> positions = StringUtils::Split(*range, "-");
993     if (positions.size() > 2)
994     {
995       ranges.clear();
996       return totalLength;
997     }
998
999     int64_t positionStart = -1;
1000     int64_t positionEnd = -1;
1001     if (!positions.at(0).empty())
1002       positionStart = str2int64(positions.at(0), -1);
1003     if (!positions.at(1).empty())
1004       positionEnd = str2int64(positions.at(1), -1);
1005
1006     if (positionStart < 0 && positionEnd < 0)
1007     {
1008       ranges.clear();
1009       return totalLength;
1010     }
1011
1012     // if there's no end position, use the file's length
1013     if (positionEnd < 0)
1014       positionEnd = totalLength - 1;
1015     else if (positionStart < 0)
1016     {
1017       positionStart = totalLength - positionEnd;
1018       positionEnd = totalLength - 1;
1019     }
1020
1021     if (positionEnd < positionStart)
1022     {
1023       ranges.clear();
1024       return totalLength;
1025     }
1026
1027     if (ranges.empty())
1028     {
1029       firstPosition = positionStart;
1030       lastPosition = positionEnd;
1031     }
1032     else
1033     {
1034       if (positionStart < firstPosition)
1035         firstPosition = positionStart;
1036       if (positionEnd > lastPosition)
1037         lastPosition = positionEnd;
1038     }
1039
1040     ranges.push_back(HttpRange(positionStart, positionEnd));
1041     rangesLength += positionEnd - positionStart + 1;
1042   }
1043
1044   if (!ranges.empty() || rangesLength > 0)
1045     return rangesLength;
1046
1047   return totalLength;
1048 }
1049
1050 std::string CWebServer::GenerateMultipartBoundary()
1051 {
1052   static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1053
1054   // create a string of length 30 to 40 and pre-fill it with "-"
1055   size_t count = (size_t)CUtil::GetRandomNumber() % 11 + 30;
1056   string boundary(count, '-');
1057
1058   for (size_t i = (size_t)CUtil::GetRandomNumber() % 5 + 8; i < count; i++)
1059     boundary.replace(i, 1, 1, chars[(size_t)CUtil::GetRandomNumber() % 64]);
1060
1061   return boundary;
1062 }
1063
1064 bool CWebServer::GetLastModifiedDateTime(XFILE::CFile *file, CDateTime &lastModified)
1065 {
1066   if (file == NULL)
1067     return false;
1068
1069   struct __stat64 statBuffer;
1070   if (file->Stat(&statBuffer) != 0)
1071     return false;
1072
1073   struct tm *time = localtime((time_t *)&statBuffer.st_mtime);
1074   if (time == NULL)
1075     return false;
1076
1077   lastModified = *time;
1078   return true;
1079 }
1080 #endif