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 "interfaces/http-api/HttpApi.h"
25 #include "interfaces/json-rpc/JSONRPC.h"
26 #include "filesystem/File.h"
27 #include "filesystem/Directory.h"
29 #include "utils/log.h"
30 #include "utils/URIUtils.h"
31 #include "threads/SingleLock.h"
32 #include "XBDateTime.h"
33 #include "addons/AddonManager.h"
36 #pragma comment(lib, "../../lib/win32/libmicrohttpd_win32/lib/libmicrohttpd.dll.lib")
39 #define MAX_STRING_POST_SIZE 20000
40 #define PAGE_FILE_NOT_FOUND "<html><head><title>File not found</title></head><body>File not found</body></html>"
41 #define PAGE_JSONRPC_INFO "<html><head><title>JSONRPC</title></head><body>JSONRPC active and working</body></html>"
42 #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>"
43 #define DEFAULT_PAGE "index.html"
45 using namespace ADDON;
46 using namespace XFILE;
48 using namespace JSONRPC;
50 CWebServer::CWebServer()
54 m_needcredentials = true;
55 m_Credentials64Encoded = "eGJtYzp4Ym1j"; // xbmc:xbmc
58 int CWebServer::FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
60 map<CStdString, CStdString> *arguments = (map<CStdString, CStdString> *)cls;
61 arguments->insert( pair<CStdString,CStdString>(key,value) );
65 int CWebServer::AskForAuthentication(struct MHD_Connection *connection)
68 struct MHD_Response *response;
70 response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
74 ret = MHD_add_response_header (response, "WWW-Authenticate", "Basic realm=XBMC");
77 MHD_destroy_response (response);
81 ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response);
83 MHD_destroy_response (response);
88 bool CWebServer::IsAuthenticated(CWebServer *server, struct MHD_Connection *connection)
90 CSingleLock lock (server->m_critSection);
91 if (!server->m_needcredentials)
94 const char *strbase = "Basic ";
95 const char *headervalue = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Authorization");
96 if (NULL == headervalue)
98 if (strncmp (headervalue, strbase, strlen(strbase)))
101 return server->m_Credentials64Encoded.Equals(headervalue + strlen(strbase));
104 #if (MHD_VERSION >= 0x00040001)
105 int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
106 const char *url, const char *method,
107 const char *version, const char *upload_data,
108 size_t *upload_data_size, void **con_cls)
110 int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
111 const char *url, const char *method,
112 const char *version, const char *upload_data,
113 unsigned int *upload_data_size, void **con_cls)
116 CWebServer *server = (CWebServer *)cls;
117 CStdString strURL = url;
118 CStdString originalURL = url;
119 HTTPMethod methodType = GetMethod(method);
121 if (!IsAuthenticated(server, connection))
122 return AskForAuthentication(connection);
124 // if (methodType != GET && methodType != POST) /* Only GET and POST supported, catch other method types here to avoid continual checking later on */
125 // return CreateErrorResponse(connection, MHD_HTTP_NOT_IMPLEMENTED, methodType);
128 if (strURL.Equals("/jsonrpc"))
130 if (methodType == POST)
131 return JSONRPC(server, con_cls, connection, upload_data, upload_data_size);
133 return CreateMemoryDownloadResponse(connection, (void *)PAGE_JSONRPC_INFO, strlen(PAGE_JSONRPC_INFO));
138 if ((methodType == GET || methodType == POST) && strURL.Left(18).Equals("/xbmcCmds/xbmcHttp"))
139 return HttpApi(connection);
142 if (strURL.Left(4).Equals("/vfs"))
144 strURL = strURL.Right(strURL.length() - 5);
145 CURL::Decode(strURL);
146 return CreateFileDownloadResponse(connection, strURL, methodType);
149 #ifdef HAS_WEB_INTERFACE
151 CStdString addonPath;
152 bool useDefaultWebInterface = true;
153 if (strURL.Left(8).Equals("/addons/") || (strURL == "/addons"))
155 CStdStringArray components;
156 CUtil::Tokenize(strURL,components,"/");
157 if (components.size() > 1)
159 CAddonMgr::Get().GetAddon(components.at(1),addon);
163 pos = strURL.find('/', 8); // /addons/ = 8 characters +1 to start behind the last slash
164 if (pos != CStdString::npos)
165 strURL = strURL.substr(pos);
166 else // missing trailing slash
167 return CreateRedirect(connection, originalURL += "/");
169 useDefaultWebInterface = false;
170 addonPath = addon->Path();
171 if (addon->Type() != ADDON_WEB_INTERFACE) // No need to append /htdocs for web interfaces
172 addonPath = URIUtils::AddFileToFolder(addonPath, "/htdocs/");
177 if (strURL.length() < 8) // missing trailing slash
178 return CreateRedirect(connection, originalURL += "/");
180 return CreateAddonsListResponse(connection);
184 if (strURL.Equals("/"))
185 strURL.Format("/%s", DEFAULT_PAGE);
187 if (useDefaultWebInterface)
189 CAddonMgr::Get().GetDefault(ADDON_WEB_INTERFACE,addon);
191 addonPath = addon->Path();
195 strURL = URIUtils::AddFileToFolder(addon->Path(),strURL);
196 if (CDirectory::Exists(strURL))
198 if (strURL.Right(1).Equals("/"))
199 strURL += DEFAULT_PAGE;
201 return CreateRedirect(connection, originalURL += "/");
203 return CreateFileDownloadResponse(connection, strURL, methodType);
210 CWebServer::HTTPMethod CWebServer::GetMethod(const char *method)
212 if (strcmp(method, "GET") == 0)
214 if (strcmp(method, "POST") == 0)
216 if (strcmp(method, "HEAD") == 0)
222 #if (MHD_VERSION >= 0x00040001)
223 int CWebServer::JSONRPC(CWebServer *server, void **con_cls, struct MHD_Connection *connection, const char *upload_data, size_t *upload_data_size)
225 int CWebServer::JSONRPC(CWebServer *server, void **con_cls, struct MHD_Connection *connection, const char *upload_data, unsigned int *upload_data_size)
229 if ((*con_cls) == NULL)
231 *con_cls = new CStdString();
235 if (*upload_data_size)
237 CStdString *post = (CStdString *)(*con_cls);
238 if (*upload_data_size + post->size() > MAX_STRING_POST_SIZE)
240 CLog::Log(LOGERROR, "WebServer: Stopped uploading post since it exceeded size limitations");
245 post->append(upload_data, *upload_data_size);
246 *upload_data_size = 0;
252 CStdString *jsoncall = (CStdString *)(*con_cls);
255 CStdString jsonresponse = CJSONRPC::MethodCall(*jsoncall, server, &client);
257 struct MHD_Response *response = MHD_create_response_from_data(jsonresponse.length(), (void *) jsonresponse.c_str(), MHD_NO, MHD_YES);
258 int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
259 MHD_add_response_header(response, "Content-Type", "application/json");
260 MHD_destroy_response(response);
270 int CWebServer::HttpApi(struct MHD_Connection *connection)
273 map<CStdString, CStdString> arguments;
274 if (MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, FillArgumentMap, &arguments) > 0)
276 CStdString httpapiresponse = CHttpApi::WebMethodCall(arguments["command"], arguments["parameter"]);
278 struct MHD_Response *response = MHD_create_response_from_data(httpapiresponse.length(), (void *) httpapiresponse.c_str(), MHD_NO, MHD_YES);
279 int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
280 MHD_destroy_response(response);
288 int CWebServer::CreateRedirect(struct MHD_Connection *connection, const CStdString &strURL)
290 struct MHD_Response *response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
291 int ret = MHD_queue_response (connection, MHD_HTTP_FOUND, response);
292 MHD_add_response_header(response, "Location", strURL);
293 MHD_destroy_response (response);
297 int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const CStdString &strURL, HTTPMethod methodType)
300 CFile *file = new CFile();
302 if (file->Open(strURL, READ_NO_CACHE))
304 struct MHD_Response *response;
305 if (methodType != HEAD)
307 response = MHD_create_response_from_callback ( file->GetLength(),
309 &CWebServer::ContentReaderCallback, file,
310 &CWebServer::ContentReaderFreeCallback);
314 response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
317 CStdString ext = URIUtils::GetExtension(strURL);
319 const char *mime = CreateMimeTypeFromExtension(ext.c_str());
321 MHD_add_response_header(response, "Content-Type", mime);
323 CDateTime expiryTime = CDateTime::GetCurrentDateTime();
324 expiryTime += CDateTimeSpan(1, 0, 0, 0);
325 MHD_add_response_header(response, "Expires", expiryTime.GetAsRFC1123DateTime());
327 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
329 MHD_destroy_response(response);
334 CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str());
335 return CreateErrorResponse(connection, MHD_HTTP_NOT_FOUND, GET); /* GET Assumed Temporarily */
340 int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method)
343 size_t payloadSize = 0;
344 void *payload = NULL;
348 switch (responseType)
350 case MHD_HTTP_NOT_FOUND:
351 payloadSize = strlen(PAGE_FILE_NOT_FOUND);
352 payload = (void *)PAGE_FILE_NOT_FOUND;
354 case MHD_HTTP_NOT_IMPLEMENTED:
355 payloadSize = strlen(NOT_SUPPORTED);
356 payload = (void *)NOT_SUPPORTED;
361 struct MHD_Response *response = MHD_create_response_from_data (payloadSize, payload, MHD_NO, MHD_NO);
362 ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response);
363 MHD_destroy_response (response);
367 int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection, void *data, size_t size)
369 struct MHD_Response *response = MHD_create_response_from_data (size, data, MHD_NO, MHD_NO);
370 int ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
371 MHD_destroy_response (response);
375 int CWebServer::CreateAddonsListResponse(struct MHD_Connection *connection)
377 CStdString responseData = "<html><head><title>Add-on List</title></head><body>\n<h1>Available web interfaces:</h1>\n<ul>\n";
379 CAddonMgr::Get().GetAddons(ADDON_WEB_INTERFACE, addons);
380 IVECADDONS addons_it;
381 for (addons_it=addons.begin(); addons_it!=addons.end(); addons_it++)
382 responseData += "<li><a href=/addons/"+ (*addons_it)->ID() + "/>" + (*addons_it)->Name() + "</a></li>\n";
384 responseData += "</ul>\n</body></html>";
386 struct MHD_Response *response = MHD_create_response_from_data (responseData.length(), (void *)responseData.c_str(), MHD_NO, MHD_YES);
390 int ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
391 MHD_destroy_response (response);
395 #if (MHD_VERSION >= 0x00090200)
396 ssize_t CWebServer::ContentReaderCallback (void *cls, uint64_t pos, char *buf, size_t max)
397 #elif (MHD_VERSION >= 0x00040001)
398 int CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, int max)
399 #else //libmicrohttpd < 0.4.0
400 int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max)
403 CFile *file = (CFile *)cls;
404 if((unsigned int)pos != file->GetPosition())
406 unsigned res = file->Read(buf, max);
412 void CWebServer::ContentReaderFreeCallback(void *cls)
414 CFile *file = (CFile *)cls;
420 struct MHD_Daemon* CWebServer::StartMHD(unsigned int flags, int port)
422 // WARNING: when using MHD_USE_THREAD_PER_CONNECTION, set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
423 // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
425 unsigned int timeout = 60 * 60 * 24;
426 // MHD_USE_THREAD_PER_CONNECTION = one thread per connection
427 // 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]
429 return MHD_start_daemon(flags,
433 &CWebServer::AnswerToConnection,
435 #if (MHD_VERSION >= 0x00040002)
436 MHD_OPTION_THREAD_POOL_SIZE, 1,
438 MHD_OPTION_CONNECTION_LIMIT, 512,
439 MHD_OPTION_CONNECTION_TIMEOUT, timeout,
443 bool CWebServer::Start(int port, const CStdString &username, const CStdString &password)
445 SetCredentials(username, password);
448 m_daemon = StartMHD(MHD_USE_SELECT_INTERNALLY, port);
450 m_running = m_daemon != NULL;
452 CLog::Log(LOGNOTICE, "WebServer: Started the webserver");
454 CLog::Log(LOGERROR, "WebServer: Failed to start the webserver");
459 bool CWebServer::Stop()
463 MHD_stop_daemon(m_daemon);
465 CLog::Log(LOGNOTICE, "WebServer: Stopped the webserver");
467 CLog::Log(LOGNOTICE, "WebServer: Stopped failed because its not running");
472 bool CWebServer::IsStarted()
477 void CWebServer::StringToBase64(const char *input, CStdString &output)
479 const char *lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
481 size_t length = strlen (input);
484 for (unsigned int i = 0; i < length; i += 3)
486 l = (((unsigned long) input[i]) << 16)
487 | (((i + 1) < length) ? (((unsigned long) input[i + 1]) << 8) : 0)
488 | (((i + 2) < length) ? ((unsigned long) input[i + 2]) : 0);
491 output.push_back(lookup[(l >> 18) & 0x3F]);
492 output.push_back(lookup[(l >> 12) & 0x3F]);
495 output.push_back(lookup[(l >> 6) & 0x3F]);
497 output.push_back(lookup[l & 0x3F]);
500 int left = 3 - (length % 3);
504 for (int i = 0; i < left; i++)
505 output.push_back('=');
509 void CWebServer::SetCredentials(const CStdString &username, const CStdString &password)
511 CSingleLock lock (m_critSection);
512 CStdString str = username + ":" + password;
514 StringToBase64(str.c_str(), m_Credentials64Encoded);
515 m_needcredentials = !password.IsEmpty();
518 bool CWebServer::Download(const char *path, Json::Value *result)
521 CFile *file = new CFile();
522 if (file->Open(path))
534 (*result)["path"] = str;
540 int CWebServer::GetCapabilities()
542 return Response | FileDownload;
545 const char *CWebServer::CreateMimeTypeFromExtension(const char *ext)
547 if (strcmp(ext, ".aif") == 0) return "audio/aiff";
548 if (strcmp(ext, ".aiff") == 0) return "audio/aiff";
549 if (strcmp(ext, ".asf") == 0) return "video/x-ms-asf";
550 if (strcmp(ext, ".asx") == 0) return "video/x-ms-asf";
551 if (strcmp(ext, ".avi") == 0) return "video/avi";
552 if (strcmp(ext, ".avs") == 0) return "video/avs-video";
553 if (strcmp(ext, ".bin") == 0) return "application/octet-stream";
554 if (strcmp(ext, ".bmp") == 0) return "image/bmp";
555 if (strcmp(ext, ".dv") == 0) return "video/x-dv";
556 if (strcmp(ext, ".fli") == 0) return "video/fli";
557 if (strcmp(ext, ".gif") == 0) return "image/gif";
558 if (strcmp(ext, ".htm") == 0) return "text/html";
559 if (strcmp(ext, ".html") == 0) return "text/html";
560 if (strcmp(ext, ".htmls") == 0) return "text/html";
561 if (strcmp(ext, ".ico") == 0) return "image/x-icon";
562 if (strcmp(ext, ".it") == 0) return "audio/it";
563 if (strcmp(ext, ".jpeg") == 0) return "image/jpeg";
564 if (strcmp(ext, ".jpg") == 0) return "image/jpeg";
565 if (strcmp(ext, ".json") == 0) return "application/json";
566 if (strcmp(ext, ".kar") == 0) return "audio/midi";
567 if (strcmp(ext, ".list") == 0) return "text/plain";
568 if (strcmp(ext, ".log") == 0) return "text/plain";
569 if (strcmp(ext, ".lst") == 0) return "text/plain";
570 if (strcmp(ext, ".m2v") == 0) return "video/mpeg";
571 if (strcmp(ext, ".m3u") == 0) return "audio/x-mpequrl";
572 if (strcmp(ext, ".mid") == 0) return "audio/midi";
573 if (strcmp(ext, ".midi") == 0) return "audio/midi";
574 if (strcmp(ext, ".mod") == 0) return "audio/mod";
575 if (strcmp(ext, ".mov") == 0) return "video/quicktime";
576 if (strcmp(ext, ".mp2") == 0) return "audio/mpeg";
577 if (strcmp(ext, ".mp3") == 0) return "audio/mpeg3";
578 if (strcmp(ext, ".mpa") == 0) return "audio/mpeg";
579 if (strcmp(ext, ".mpeg") == 0) return "video/mpeg";
580 if (strcmp(ext, ".mpg") == 0) return "video/mpeg";
581 if (strcmp(ext, ".mpga") == 0) return "audio/mpeg";
582 if (strcmp(ext, ".pcx") == 0) return "image/x-pcx";
583 if (strcmp(ext, ".png") == 0) return "image/png";
584 if (strcmp(ext, ".rm") == 0) return "audio/x-pn-realaudio";
585 if (strcmp(ext, ".s3m") == 0) return "audio/s3m";
586 if (strcmp(ext, ".sid") == 0) return "audio/x-psid";
587 if (strcmp(ext, ".tif") == 0) return "image/tiff";
588 if (strcmp(ext, ".tiff") == 0) return "image/tiff";
589 if (strcmp(ext, ".txt") == 0) return "text/plain";
590 if (strcmp(ext, ".uni") == 0) return "text/uri-list";
591 if (strcmp(ext, ".viv") == 0) return "video/vivo";
592 if (strcmp(ext, ".wav") == 0) return "audio/wav";
593 if (strcmp(ext, ".xm") == 0) return "audio/xm";
594 if (strcmp(ext, ".xml") == 0) return "text/xml";
595 if (strcmp(ext, ".zip") == 0) return "application/zip";
596 if (strcmp(ext, ".tbn") == 0) return "image/jpeg";
597 if (strcmp(ext, ".js") == 0) return "application/javascript";
598 if (strcmp(ext, ".css") == 0) return "text/css";
602 int CWebServer::CHTTPClient::GetPermissionFlags()
604 return OPERATION_PERMISSION_ALL;
607 int CWebServer::CHTTPClient::GetAnnouncementFlags()
609 // Does not support broadcast
613 bool CWebServer::CHTTPClient::SetAnnouncementFlags(int flags)