2 * Copyright (C) 2005-2010 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 #include "WebServer.h"
24 #include "../lib/libjsonrpc/JSONRPC.h"
25 #include "../lib/libhttpapi/HttpApi.h"
26 #include "../FileSystem/File.h"
27 #include "../FileSystem/Directory.h"
30 #include "SingleLock.h"
32 #include "addons/AddonManager.h"
35 #pragma comment(lib, "../../lib/libmicrohttpd_win32/lib/libmicrohttpd.dll.lib")
38 #define MAX_STRING_POST_SIZE 20000
39 #define PAGE_FILE_NOT_FOUND "<html><head><title>File not found</title></head><body>File not found</body></html>"
40 #define PAGE_JSONRPC_INFO "<html><head><title>JSONRPC</title></head><body>JSONRPC active and working</body></html>"
41 #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>"
42 #define DEFAULT_PAGE "index.html"
44 using namespace ADDON
;
45 using namespace XFILE
;
47 using namespace JSONRPC
;
49 CWebServer::CWebServer()
53 m_needcredentials
= true;
54 m_Credentials64Encoded
= "eGJtYzp4Ym1j"; // xbmc:xbmc
57 int CWebServer::FillArgumentMap(void *cls
, enum MHD_ValueKind kind
, const char *key
, const char *value
)
59 map
<CStdString
, CStdString
> *arguments
= (map
<CStdString
, CStdString
> *)cls
;
60 arguments
->insert( pair
<CStdString
,CStdString
>(key
,value
) );
64 int CWebServer::AskForAuthentication(struct MHD_Connection
*connection
)
67 struct MHD_Response
*response
;
69 response
= MHD_create_response_from_data (0, NULL
, MHD_NO
, MHD_NO
);
73 ret
= MHD_add_response_header (response
, "WWW-Authenticate", "Basic realm=XBMC");
76 MHD_destroy_response (response
);
80 ret
= MHD_queue_response (connection
, MHD_HTTP_UNAUTHORIZED
, response
);
82 MHD_destroy_response (response
);
87 bool CWebServer::IsAuthenticated(CWebServer
*server
, struct MHD_Connection
*connection
)
89 CSingleLock
lock (server
->m_critSection
);
90 if (!server
->m_needcredentials
)
93 const char *strbase
= "Basic ";
94 const char *headervalue
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Authorization");
95 if (NULL
== headervalue
)
97 if (strncmp (headervalue
, strbase
, strlen(strbase
)))
100 return server
->m_Credentials64Encoded
.Equals(headervalue
+ strlen(strbase
));
103 #if (MHD_VERSION >= 0x00040001)
104 int CWebServer::AnswerToConnection(void *cls
, struct MHD_Connection
*connection
,
105 const char *url
, const char *method
,
106 const char *version
, const char *upload_data
,
107 size_t *upload_data_size
, void **con_cls
)
109 int CWebServer::AnswerToConnection(void *cls
, struct MHD_Connection
*connection
,
110 const char *url
, const char *method
,
111 const char *version
, const char *upload_data
,
112 unsigned int *upload_data_size
, void **con_cls
)
115 CWebServer
*server
= (CWebServer
*)cls
;
116 CStdString strURL
= url
;
117 CStdString originalURL
= url
;
118 HTTPMethod methodType
= GetMethod(method
);
120 if (!IsAuthenticated(server
, connection
))
121 return AskForAuthentication(connection
);
123 if (methodType
!= GET
&& methodType
!= POST
) /* Only GET and POST supported, catch other method types here to avoid continual checking later on */
124 return CreateErrorResponse(connection
, MHD_HTTP_NOT_IMPLEMENTED
, methodType
);
127 if (strURL
.Equals("/jsonrpc"))
129 if (methodType
== POST
)
130 return JSONRPC(server
, con_cls
, connection
, upload_data
, upload_data_size
);
132 return CreateMemoryDownloadResponse(connection
, (void *)PAGE_JSONRPC_INFO
, strlen(PAGE_JSONRPC_INFO
));
137 if ((methodType
== GET
|| methodType
== POST
) && strURL
.Left(18).Equals("/xbmcCmds/xbmcHttp"))
138 return HttpApi(connection
);
141 if (strURL
.Left(4).Equals("/vfs"))
143 strURL
= strURL
.Right(strURL
.length() - 5);
144 CUtil::URLDecode(strURL
);
145 return CreateFileDownloadResponse(connection
, strURL
);
148 #ifdef HAS_WEB_INTERFACE
149 if (strURL
.Equals("/"))
150 strURL
.Format("/%s", DEFAULT_PAGE
);
153 CAddonMgr::Get().GetDefault(ADDON_WEB_INTERFACE
,addon
);
155 strURL
= CUtil::AddFileToFolder(addon
->Path(),strURL
);
156 if (CDirectory::Exists(strURL
))
158 if (strURL
.Right(1).Equals("/"))
159 strURL
+= DEFAULT_PAGE
;
161 return CreateRedirect(connection
, originalURL
+= "/");
163 return CreateFileDownloadResponse(connection
, strURL
);
170 CWebServer::HTTPMethod
CWebServer::GetMethod(const char *method
)
172 if (strcmp(method
, "GET") == 0)
174 if (strcmp(method
, "POST") == 0)
176 if (strcmp(method
, "HEAD") == 0)
182 #if (MHD_VERSION >= 0x00040001)
183 int CWebServer::JSONRPC(CWebServer
*server
, void **con_cls
, struct MHD_Connection
*connection
, const char *upload_data
, size_t *upload_data_size
)
185 int CWebServer::JSONRPC(CWebServer
*server
, void **con_cls
, struct MHD_Connection
*connection
, const char *upload_data
, unsigned int *upload_data_size
)
189 if ((*con_cls
) == NULL
)
191 *con_cls
= new CStdString();
195 if (*upload_data_size
)
197 CStdString
*post
= (CStdString
*)(*con_cls
);
198 if (*upload_data_size
+ post
->size() > MAX_STRING_POST_SIZE
)
200 CLog::Log(LOGERROR
, "WebServer: Stopped uploading post since it exceeded size limitations");
205 post
->append(upload_data
, *upload_data_size
);
206 *upload_data_size
= 0;
212 CStdString
*jsoncall
= (CStdString
*)(*con_cls
);
215 CStdString jsonresponse
= CJSONRPC::MethodCall(*jsoncall
, server
, &client
);
217 struct MHD_Response
*response
= MHD_create_response_from_data(jsonresponse
.length(), (void *) jsonresponse
.c_str(), MHD_NO
, MHD_YES
);
218 int ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
219 MHD_add_response_header(response
, "Content-Type", "application/json");
220 MHD_destroy_response(response
);
230 int CWebServer::HttpApi(struct MHD_Connection
*connection
)
233 map
<CStdString
, CStdString
> arguments
;
234 if (MHD_get_connection_values(connection
, MHD_GET_ARGUMENT_KIND
, FillArgumentMap
, &arguments
) > 0)
236 CStdString httpapiresponse
= CHttpApi::WebMethodCall(arguments
["command"], arguments
["parameter"]);
238 struct MHD_Response
*response
= MHD_create_response_from_data(httpapiresponse
.length(), (void *) httpapiresponse
.c_str(), MHD_NO
, MHD_YES
);
239 int ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
240 MHD_destroy_response(response
);
248 int CWebServer::CreateRedirect(struct MHD_Connection
*connection
, const CStdString
&strURL
)
250 struct MHD_Response
*response
= MHD_create_response_from_data (0, NULL
, MHD_NO
, MHD_NO
);
251 int ret
= MHD_queue_response (connection
, MHD_HTTP_FOUND
, response
);
252 MHD_add_response_header(response
, "Location", strURL
);
253 MHD_destroy_response (response
);
257 int CWebServer::CreateFileDownloadResponse(struct MHD_Connection
*connection
, const CStdString
&strURL
)
260 CFile
*file
= new CFile();
262 if (file
->Open(strURL
, READ_NO_CACHE
))
264 struct MHD_Response
*response
;
265 response
= MHD_create_response_from_callback ( file
->GetLength(),
267 &CWebServer::ContentReaderCallback
, file
,
268 &CWebServer::ContentReaderFreeCallback
);
270 CStdString ext
= CUtil::GetExtension(strURL
);
272 const char *mime
= CreateMimeTypeFromExtension(ext
.c_str());
274 MHD_add_response_header(response
, "Content-Type", mime
);
276 CDateTime expiryTime
= CDateTime::GetCurrentDateTime();
277 expiryTime
+= CDateTimeSpan(1, 0, 0, 0);
278 MHD_add_response_header(response
, "Expires", expiryTime
.GetAsRFC1123DateTime());
280 ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
282 MHD_destroy_response(response
);
287 CLog::Log(LOGERROR
, "WebServer: Failed to open %s", strURL
.c_str());
288 return CreateErrorResponse(connection
, MHD_HTTP_NOT_FOUND
, GET
); /* GET Assumed Temporarily */
293 int CWebServer::CreateErrorResponse(struct MHD_Connection
*connection
, int responseType
, HTTPMethod method
)
296 size_t payloadSize
= 0;
297 void *payload
= NULL
;
301 switch (responseType
)
303 case MHD_HTTP_NOT_FOUND
:
304 payloadSize
= strlen(PAGE_FILE_NOT_FOUND
);
305 payload
= (void *)PAGE_FILE_NOT_FOUND
;
307 case MHD_HTTP_NOT_IMPLEMENTED
:
308 payloadSize
= strlen(NOT_SUPPORTED
);
309 payload
= (void *)NOT_SUPPORTED
;
314 struct MHD_Response
*response
= MHD_create_response_from_data (payloadSize
, payload
, MHD_NO
, MHD_NO
);
315 ret
= MHD_queue_response (connection
, MHD_HTTP_NOT_FOUND
, response
);
316 MHD_destroy_response (response
);
320 int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection
*connection
, void *data
, size_t size
)
322 struct MHD_Response
*response
= MHD_create_response_from_data (size
, data
, MHD_NO
, MHD_NO
);
323 int ret
= MHD_queue_response (connection
, MHD_HTTP_OK
, response
);
324 MHD_destroy_response (response
);
328 #if (MHD_VERSION >= 0x00090200)
329 ssize_t
CWebServer::ContentReaderCallback (void *cls
, uint64_t pos
, char *buf
, size_t max
)
330 #elif (MHD_VERSION >= 0x00040001)
331 int CWebServer::ContentReaderCallback(void *cls
, uint64_t pos
, char *buf
, int max
)
332 #else //libmicrohttpd < 0.4.0
333 int CWebServer::ContentReaderCallback(void *cls
, size_t pos
, char *buf
, int max
)
336 CFile
*file
= (CFile
*)cls
;
337 if((unsigned int)pos
!= file
->GetPosition())
339 unsigned res
= file
->Read(buf
, max
);
345 void CWebServer::ContentReaderFreeCallback(void *cls
)
347 CFile
*file
= (CFile
*)cls
;
353 struct MHD_Daemon
* CWebServer::StartMHD(unsigned int flags
, int port
)
355 // WARNING: when using MHD_USE_THREAD_PER_CONNECTION, set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
356 // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
358 unsigned int timeout
= 60 * 60 * 24;
359 // MHD_USE_THREAD_PER_CONNECTION = one thread per connection
360 // MHD_USE_SELECT_INTERNALLY = use main thread for each connection, can only handle one request at a time [unless you set the thread pool size]
362 return MHD_start_daemon(flags
,
366 &CWebServer::AnswerToConnection
,
368 #if (MHD_VERSION >= 0x00040002)
369 MHD_OPTION_THREAD_POOL_SIZE
, 8,
371 MHD_OPTION_CONNECTION_LIMIT
, 512,
372 MHD_OPTION_CONNECTION_TIMEOUT
, timeout
,
376 bool CWebServer::Start(int port
, const CStdString
&username
, const CStdString
&password
)
378 SetCredentials(username
, password
);
381 m_daemon
= StartMHD(MHD_USE_SELECT_INTERNALLY
, port
);
383 m_running
= m_daemon
!= NULL
;
385 CLog::Log(LOGNOTICE
, "WebServer: Started the webserver");
387 CLog::Log(LOGERROR
, "WebServer: Failed to start the webserver");
392 bool CWebServer::Stop()
396 MHD_stop_daemon(m_daemon
);
398 CLog::Log(LOGNOTICE
, "WebServer: Stopped the webserver");
400 CLog::Log(LOGNOTICE
, "WebServer: Stopped failed because its not running");
405 bool CWebServer::IsStarted()
410 void CWebServer::StringToBase64(const char *input
, CStdString
&output
)
412 const char *lookup
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
414 size_t length
= strlen (input
);
417 for (unsigned int i
= 0; i
< length
; i
+= 3)
419 l
= (((unsigned long) input
[i
]) << 16)
420 | (((i
+ 1) < length
) ? (((unsigned long) input
[i
+ 1]) << 8) : 0)
421 | (((i
+ 2) < length
) ? ((unsigned long) input
[i
+ 2]) : 0);
424 output
.push_back(lookup
[(l
>> 18) & 0x3F]);
425 output
.push_back(lookup
[(l
>> 12) & 0x3F]);
428 output
.push_back(lookup
[(l
>> 6) & 0x3F]);
430 output
.push_back(lookup
[l
& 0x3F]);
433 int left
= 3 - (length
% 3);
437 for (int i
= 0; i
< left
; i
++)
438 output
.push_back('=');
442 void CWebServer::SetCredentials(const CStdString
&username
, const CStdString
&password
)
444 CSingleLock
lock (m_critSection
);
445 CStdString str
= username
+ ":" + password
;
447 StringToBase64(str
.c_str(), m_Credentials64Encoded
);
448 m_needcredentials
= !password
.IsEmpty();
451 bool CWebServer::Download(const char *path
, Json::Value
*result
)
454 CFile
*file
= new CFile();
455 if (file
->Open(path
))
467 (*result
)["path"] = str
;
473 int CWebServer::GetCapabilities()
475 return Response
| FileDownload
;
478 const char *CWebServer::CreateMimeTypeFromExtension(const char *ext
)
480 if (strcmp(ext
, ".aif") == 0) return "audio/aiff";
481 if (strcmp(ext
, ".aiff") == 0) return "audio/aiff";
482 if (strcmp(ext
, ".asf") == 0) return "video/x-ms-asf";
483 if (strcmp(ext
, ".asx") == 0) return "video/x-ms-asf";
484 if (strcmp(ext
, ".avi") == 0) return "video/avi";
485 if (strcmp(ext
, ".avs") == 0) return "video/avs-video";
486 if (strcmp(ext
, ".bin") == 0) return "application/octet-stream";
487 if (strcmp(ext
, ".bmp") == 0) return "image/bmp";
488 if (strcmp(ext
, ".dv") == 0) return "video/x-dv";
489 if (strcmp(ext
, ".fli") == 0) return "video/fli";
490 if (strcmp(ext
, ".gif") == 0) return "image/gif";
491 if (strcmp(ext
, ".htm") == 0) return "text/html";
492 if (strcmp(ext
, ".html") == 0) return "text/html";
493 if (strcmp(ext
, ".htmls") == 0) return "text/html";
494 if (strcmp(ext
, ".ico") == 0) return "image/x-icon";
495 if (strcmp(ext
, ".it") == 0) return "audio/it";
496 if (strcmp(ext
, ".jpeg") == 0) return "image/jpeg";
497 if (strcmp(ext
, ".jpg") == 0) return "image/jpeg";
498 if (strcmp(ext
, ".json") == 0) return "application/json";
499 if (strcmp(ext
, ".kar") == 0) return "audio/midi";
500 if (strcmp(ext
, ".list") == 0) return "text/plain";
501 if (strcmp(ext
, ".log") == 0) return "text/plain";
502 if (strcmp(ext
, ".lst") == 0) return "text/plain";
503 if (strcmp(ext
, ".m2v") == 0) return "video/mpeg";
504 if (strcmp(ext
, ".m3u") == 0) return "audio/x-mpequrl";
505 if (strcmp(ext
, ".mid") == 0) return "audio/midi";
506 if (strcmp(ext
, ".midi") == 0) return "audio/midi";
507 if (strcmp(ext
, ".mod") == 0) return "audio/mod";
508 if (strcmp(ext
, ".mov") == 0) return "video/quicktime";
509 if (strcmp(ext
, ".mp2") == 0) return "audio/mpeg";
510 if (strcmp(ext
, ".mp3") == 0) return "audio/mpeg3";
511 if (strcmp(ext
, ".mpa") == 0) return "audio/mpeg";
512 if (strcmp(ext
, ".mpeg") == 0) return "video/mpeg";
513 if (strcmp(ext
, ".mpg") == 0) return "video/mpeg";
514 if (strcmp(ext
, ".mpga") == 0) return "audio/mpeg";
515 if (strcmp(ext
, ".pcx") == 0) return "image/x-pcx";
516 if (strcmp(ext
, ".png") == 0) return "image/png";
517 if (strcmp(ext
, ".rm") == 0) return "audio/x-pn-realaudio";
518 if (strcmp(ext
, ".s3m") == 0) return "audio/s3m";
519 if (strcmp(ext
, ".sid") == 0) return "audio/x-psid";
520 if (strcmp(ext
, ".tif") == 0) return "image/tiff";
521 if (strcmp(ext
, ".tiff") == 0) return "image/tiff";
522 if (strcmp(ext
, ".txt") == 0) return "text/plain";
523 if (strcmp(ext
, ".uni") == 0) return "text/uri-list";
524 if (strcmp(ext
, ".viv") == 0) return "video/vivo";
525 if (strcmp(ext
, ".wav") == 0) return "audio/wav";
526 if (strcmp(ext
, ".xm") == 0) return "audio/xm";
527 if (strcmp(ext
, ".xml") == 0) return "text/xml";
528 if (strcmp(ext
, ".zip") == 0) return "application/zip";
529 if (strcmp(ext
, ".tbn") == 0) return "image/jpeg";
530 if (strcmp(ext
, ".js") == 0) return "application/javascript";
531 if (strcmp(ext
, ".css") == 0) return "text/css";
535 int CWebServer::CHTTPClient::GetPermissionFlags()
537 return OPERATION_PERMISSION_ALL
;
540 int CWebServer::CHTTPClient::GetAnnouncementFlags()
542 // Does not support broadcast
546 bool CWebServer::CHTTPClient::SetAnnouncementFlags(int flags
)