changed: update version strings for beta4
[xbmc.git] / xbmc / utils / WebServer.cpp
blobdb3c5a0171d2fe933c0f20b626e1db0470a80d1b
1 /*
2 * Copyright (C) 2005-2010 Team XBMC
3 * http://www.xbmc.org
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.
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"
23 #ifdef HAS_WEB_SERVER
24 #include "../lib/libjsonrpc/JSONRPC.h"
25 #include "../lib/libhttpapi/HttpApi.h"
26 #include "../FileSystem/File.h"
27 #include "../FileSystem/Directory.h"
28 #include "../Util.h"
29 #include "log.h"
30 #include "SingleLock.h"
31 #include "DateTime.h"
32 #include "addons/AddonManager.h"
34 #ifdef _WIN32
35 #pragma comment(lib, "../../lib/libmicrohttpd_win32/lib/libmicrohttpd.dll.lib")
36 #endif
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;
46 using namespace std;
47 using namespace JSONRPC;
49 CWebServer::CWebServer()
51 m_running = false;
52 m_daemon = NULL;
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) );
61 return MHD_YES;
64 int CWebServer::AskForAuthentication(struct MHD_Connection *connection)
66 int ret;
67 struct MHD_Response *response;
69 response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
70 if (!response)
71 return MHD_NO;
73 ret = MHD_add_response_header (response, "WWW-Authenticate", "Basic realm=XBMC");
74 if (!ret)
76 MHD_destroy_response (response);
77 return MHD_NO;
80 ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response);
82 MHD_destroy_response (response);
84 return ret;
87 bool CWebServer::IsAuthenticated(CWebServer *server, struct MHD_Connection *connection)
89 CSingleLock lock (server->m_critSection);
90 if (!server->m_needcredentials)
91 return true;
93 const char *strbase = "Basic ";
94 const char *headervalue = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Authorization");
95 if (NULL == headervalue)
96 return false;
97 if (strncmp (headervalue, strbase, strlen(strbase)))
98 return false;
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)
108 #else
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)
113 #endif
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);
126 #ifdef HAS_JSONRPC
127 if (strURL.Equals("/jsonrpc"))
129 if (methodType == POST)
130 return JSONRPC(server, con_cls, connection, upload_data, upload_data_size);
131 else
132 return CreateMemoryDownloadResponse(connection, (void *)PAGE_JSONRPC_INFO, strlen(PAGE_JSONRPC_INFO));
134 #endif
136 #ifdef HAS_HTTPAPI
137 if ((methodType == GET || methodType == POST) && strURL.Left(18).Equals("/xbmcCmds/xbmcHttp"))
138 return HttpApi(connection);
139 #endif
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);
152 AddonPtr addon;
153 CAddonMgr::Get().GetDefault(ADDON_WEB_INTERFACE,addon);
154 if (addon)
155 strURL = CUtil::AddFileToFolder(addon->Path(),strURL);
156 if (CDirectory::Exists(strURL))
158 if (strURL.Right(1).Equals("/"))
159 strURL += DEFAULT_PAGE;
160 else
161 return CreateRedirect(connection, originalURL += "/");
163 return CreateFileDownloadResponse(connection, strURL);
165 #endif
167 return MHD_NO;
170 CWebServer::HTTPMethod CWebServer::GetMethod(const char *method)
172 if (strcmp(method, "GET") == 0)
173 return GET;
174 if (strcmp(method, "POST") == 0)
175 return POST;
176 if (strcmp(method, "HEAD") == 0)
177 return HEAD;
179 return UNKNOWN;
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)
184 #else
185 int CWebServer::JSONRPC(CWebServer *server, void **con_cls, struct MHD_Connection *connection, const char *upload_data, unsigned int *upload_data_size)
186 #endif
188 #ifdef HAS_JSONRPC
189 if ((*con_cls) == NULL)
191 *con_cls = new CStdString();
193 return MHD_YES;
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");
201 return MHD_NO;
203 else
205 post->append(upload_data, *upload_data_size);
206 *upload_data_size = 0;
207 return MHD_YES;
210 else
212 CStdString *jsoncall = (CStdString *)(*con_cls);
214 CHTTPClient client;
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);
222 delete jsoncall;
223 return ret;
225 #else
226 return MHD_NO;
227 #endif
230 int CWebServer::HttpApi(struct MHD_Connection *connection)
232 #ifdef HAS_HTTPAPI
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);
242 return ret;
244 #endif
245 return MHD_NO;
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);
254 return ret;
257 int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const CStdString &strURL)
259 int ret = MHD_NO;
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(),
266 2048,
267 &CWebServer::ContentReaderCallback, file,
268 &CWebServer::ContentReaderFreeCallback);
270 CStdString ext = CUtil::GetExtension(strURL);
271 ext = ext.ToLower();
272 const char *mime = CreateMimeTypeFromExtension(ext.c_str());
273 if (mime)
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);
284 else
286 delete file;
287 CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str());
288 return CreateErrorResponse(connection, MHD_HTTP_NOT_FOUND, GET); /* GET Assumed Temporarily */
290 return ret;
293 int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method)
295 int ret = MHD_NO;
296 size_t payloadSize = 0;
297 void *payload = NULL;
299 if (method != HEAD)
301 switch (responseType)
303 case MHD_HTTP_NOT_FOUND:
304 payloadSize = strlen(PAGE_FILE_NOT_FOUND);
305 payload = (void *)PAGE_FILE_NOT_FOUND;
306 break;
307 case MHD_HTTP_NOT_IMPLEMENTED:
308 payloadSize = strlen(NOT_SUPPORTED);
309 payload = (void *)NOT_SUPPORTED;
310 break;
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);
317 return ret;
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);
325 return ret;
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)
334 #endif
336 CFile *file = (CFile *)cls;
337 if((unsigned int)pos != file->GetPosition())
338 file->Seek(pos);
339 unsigned res = file->Read(buf, max);
340 if(res == 0)
341 return -1;
342 return res;
345 void CWebServer::ContentReaderFreeCallback(void *cls)
347 CFile *file = (CFile *)cls;
348 file->Close();
350 delete file;
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,
363 port,
364 NULL,
365 NULL,
366 &CWebServer::AnswerToConnection,
367 this,
368 #if (MHD_VERSION >= 0x00040002)
369 MHD_OPTION_THREAD_POOL_SIZE, 8,
370 #endif
371 MHD_OPTION_CONNECTION_LIMIT, 512,
372 MHD_OPTION_CONNECTION_TIMEOUT, timeout,
373 MHD_OPTION_END);
376 bool CWebServer::Start(int port, const CStdString &username, const CStdString &password)
378 SetCredentials(username, password);
379 if (!m_running)
381 m_daemon = StartMHD(MHD_USE_SELECT_INTERNALLY, port);
383 m_running = m_daemon != NULL;
384 if (m_running)
385 CLog::Log(LOGNOTICE, "WebServer: Started the webserver");
386 else
387 CLog::Log(LOGERROR, "WebServer: Failed to start the webserver");
389 return m_running;
392 bool CWebServer::Stop()
394 if (m_running)
396 MHD_stop_daemon(m_daemon);
397 m_running = false;
398 CLog::Log(LOGNOTICE, "WebServer: Stopped the webserver");
399 } else
400 CLog::Log(LOGNOTICE, "WebServer: Stopped failed because its not running");
402 return !m_running;
405 bool CWebServer::IsStarted()
407 return m_running;
410 void CWebServer::StringToBase64(const char *input, CStdString &output)
412 const char *lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
413 unsigned long l;
414 size_t length = strlen (input);
415 output = "";
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]);
427 if (i + 1 < length)
428 output.push_back(lookup[(l >> 6) & 0x3F]);
429 if (i + 2 < length)
430 output.push_back(lookup[l & 0x3F]);
433 int left = 3 - (length % 3);
435 if (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)
453 bool exists = false;
454 CFile *file = new CFile();
455 if (file->Open(path))
457 exists = true;
458 file->Close();
461 delete file;
463 if (exists)
465 string str = "vfs/";
466 str += path;
467 (*result)["path"] = str;
470 return exists;
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";
532 return NULL;
535 int CWebServer::CHTTPClient::GetPermissionFlags()
537 return OPERATION_PERMISSION_ALL;
540 int CWebServer::CHTTPClient::GetAnnouncementFlags()
542 // Does not support broadcast
543 return 0;
546 bool CWebServer::CHTTPClient::SetAnnouncementFlags(int flags)
548 return false;
550 #endif