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