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