PageRenderTime 64ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/libUPnP/Platinum/Source/Core/PltHttpServer.cpp

https://gitlab.com/cronmod-dev/xbmc
C++ | 310 lines | 176 code | 43 blank | 91 comment | 34 complexity | 835781735d1698cd7b1efa4c8d373655 MD5 | raw file
  1. /*****************************************************************
  2. |
  3. | Platinum - HTTP Server
  4. |
  5. | Copyright (c) 2004-2010, Plutinosoft, LLC.
  6. | All rights reserved.
  7. | http://www.plutinosoft.com
  8. |
  9. | This program is free software; you can redistribute it and/or
  10. | modify it under the terms of the GNU General Public License
  11. | as published by the Free Software Foundation; either version 2
  12. | of the License, or (at your option) any later version.
  13. |
  14. | OEMs, ISVs, VARs and other distributors that combine and
  15. | distribute commercially licensed software with Platinum software
  16. | and do not wish to distribute the source code for the commercially
  17. | licensed software under version 2, or (at your option) any later
  18. | version, of the GNU General Public License (the "GPL") must enter
  19. | into a commercial license agreement with Plutinosoft, LLC.
  20. | licensing@plutinosoft.com
  21. |
  22. | This program is distributed in the hope that it will be useful,
  23. | but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. | GNU General Public License for more details.
  26. |
  27. | You should have received a copy of the GNU General Public License
  28. | along with this program; see the file LICENSE.txt. If not, write to
  29. | the Free Software Foundation, Inc.,
  30. | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  31. | http://www.gnu.org/licenses/gpl-2.0.html
  32. |
  33. ****************************************************************/
  34. /*----------------------------------------------------------------------
  35. | includes
  36. +---------------------------------------------------------------------*/
  37. #include "PltTaskManager.h"
  38. #include "PltHttpServer.h"
  39. #include "PltHttp.h"
  40. #include "PltVersion.h"
  41. #include "PltUtilities.h"
  42. #include "PltProtocolInfo.h"
  43. #include "PltMimeType.h"
  44. NPT_SET_LOCAL_LOGGER("platinum.core.http.server")
  45. /*----------------------------------------------------------------------
  46. | PLT_HttpServer::PLT_HttpServer
  47. +---------------------------------------------------------------------*/
  48. PLT_HttpServer::PLT_HttpServer(NPT_IpAddress address,
  49. NPT_IpPort port,
  50. bool allow_random_port_on_bind_failure, /* = false */
  51. NPT_Cardinal max_clients, /* = 50 */
  52. bool reuse_address) : /* = false */
  53. NPT_HttpServer(address, port, true),
  54. m_TaskManager(new PLT_TaskManager(max_clients)),
  55. m_Address(address),
  56. m_Port(port),
  57. m_AllowRandomPortOnBindFailure(allow_random_port_on_bind_failure),
  58. m_ReuseAddress(reuse_address),
  59. m_Running(false),
  60. m_Aborted(false)
  61. {
  62. }
  63. /*----------------------------------------------------------------------
  64. | PLT_HttpServer::~PLT_HttpServer
  65. +---------------------------------------------------------------------*/
  66. PLT_HttpServer::~PLT_HttpServer()
  67. {
  68. Stop();
  69. }
  70. /*----------------------------------------------------------------------
  71. | PLT_HttpServer::Start
  72. +---------------------------------------------------------------------*/
  73. NPT_Result
  74. PLT_HttpServer::Start()
  75. {
  76. NPT_Result res = NPT_FAILURE;
  77. // we can't start an already running server or restart an aborted server
  78. // because the socket is shared create a new instance
  79. if (m_Running || m_Aborted) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
  80. // if we're given a port for our http server, try it
  81. if (m_Port) {
  82. res = SetListenPort(m_Port, m_ReuseAddress);
  83. // return right away if failed and not allowed to try again randomly
  84. if (NPT_FAILED(res) && !m_AllowRandomPortOnBindFailure) {
  85. NPT_CHECK_SEVERE(res);
  86. }
  87. }
  88. // try random port now
  89. if (!m_Port || NPT_FAILED(res)) {
  90. int retries = 100;
  91. do {
  92. int random = NPT_System::GetRandomInteger();
  93. int port = (unsigned short)(1024 + (random % 1024));
  94. if (NPT_SUCCEEDED(SetListenPort(port, m_ReuseAddress))) {
  95. break;
  96. }
  97. } while (--retries > 0);
  98. if (retries == 0) NPT_CHECK_SEVERE(NPT_FAILURE);
  99. }
  100. // keep track of port server has successfully bound
  101. m_Port = m_BoundPort;
  102. // Tell server to try to listen to more incoming sockets
  103. // (this could fail silently)
  104. if (m_TaskManager->GetMaxTasks() > 20) {
  105. m_Socket.Listen(m_TaskManager->GetMaxTasks());
  106. }
  107. // start a task to listen for incoming connections
  108. PLT_HttpListenTask *task = new PLT_HttpListenTask(this, &m_Socket, false);
  109. NPT_CHECK_SEVERE(m_TaskManager->StartTask(task));
  110. NPT_SocketInfo info;
  111. m_Socket.GetInfo(info);
  112. NPT_LOG_INFO_2("HttpServer listening on %s:%d",
  113. (const char*)info.local_address.GetIpAddress().ToString(),
  114. m_Port);
  115. m_Running = true;
  116. return NPT_SUCCESS;
  117. }
  118. /*----------------------------------------------------------------------
  119. | PLT_HttpServer::Stop
  120. +---------------------------------------------------------------------*/
  121. NPT_Result
  122. PLT_HttpServer::Stop()
  123. {
  124. // we can't restart an aborted server
  125. if (m_Aborted || !m_Running) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
  126. // stop all other pending tasks
  127. m_TaskManager->Abort();
  128. m_Running = false;
  129. m_Aborted = true;
  130. return NPT_SUCCESS;
  131. }
  132. /*----------------------------------------------------------------------
  133. | PLT_HttpServer::SetupResponse
  134. +---------------------------------------------------------------------*/
  135. NPT_Result
  136. PLT_HttpServer::SetupResponse(NPT_HttpRequest& request,
  137. const NPT_HttpRequestContext& context,
  138. NPT_HttpResponse& response)
  139. {
  140. NPT_String prefix = NPT_String::Format("PLT_HttpServer::SetupResponse %s request from %s for \"%s\"",
  141. (const char*) request.GetMethod(),
  142. (const char*) context.GetRemoteAddress().ToString(),
  143. (const char*) request.GetUrl().ToString());
  144. PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINE, prefix, &request);
  145. NPT_List<NPT_HttpRequestHandler*> handlers = FindRequestHandlers(request);
  146. if (handlers.GetItemCount() == 0) return NPT_ERROR_NO_SUCH_ITEM;
  147. // ask the handler to setup the response
  148. NPT_Result result = (*handlers.GetFirstItem())->SetupResponse(request, context, response);
  149. // DLNA compliance
  150. PLT_UPnPMessageHelper::SetDate(response);
  151. if (request.GetHeaders().GetHeader("Accept-Language")) {
  152. response.GetHeaders().SetHeader("Content-Language", "en");
  153. }
  154. return result;
  155. }
  156. /*----------------------------------------------------------------------
  157. | PLT_HttpServer::ServeFile
  158. +---------------------------------------------------------------------*/
  159. NPT_Result
  160. PLT_HttpServer::ServeFile(const NPT_HttpRequest& request,
  161. const NPT_HttpRequestContext& context,
  162. NPT_HttpResponse& response,
  163. NPT_String file_path)
  164. {
  165. NPT_InputStreamReference stream;
  166. NPT_File file(file_path);
  167. NPT_FileInfo file_info;
  168. // prevent hackers from accessing files outside of our root
  169. if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0) ||
  170. NPT_FAILED(NPT_File::GetInfo(file_path, &file_info))) {
  171. return NPT_ERROR_NO_SUCH_ITEM;
  172. }
  173. // check for range requests
  174. const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);
  175. // handle potential 304 only if range header not set
  176. NPT_DateTime date;
  177. NPT_TimeStamp timestamp;
  178. if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIfModifiedSince((NPT_HttpMessage&)request, date)) &&
  179. !range_spec) {
  180. date.ToTimeStamp(timestamp);
  181. NPT_LOG_INFO_5("File %s timestamps: request=%d (%s) vs file=%d (%s)",
  182. (const char*)request.GetUrl().GetPath(),
  183. (NPT_UInt32)timestamp.ToSeconds(),
  184. (const char*)date.ToString(),
  185. (NPT_UInt32)file_info.m_ModificationTime,
  186. (const char*)NPT_DateTime(file_info.m_ModificationTime).ToString());
  187. if (timestamp >= file_info.m_ModificationTime) {
  188. // it's a match
  189. NPT_LOG_FINE_1("Returning 304 for %s", request.GetUrl().GetPath().GetChars());
  190. response.SetStatus(304, "Not Modified", NPT_HTTP_PROTOCOL_1_1);
  191. return NPT_SUCCESS;
  192. }
  193. }
  194. // open file
  195. if (NPT_FAILED(file.Open(NPT_FILE_OPEN_MODE_READ)) ||
  196. NPT_FAILED(file.GetInputStream(stream)) ||
  197. stream.IsNull()) {
  198. return NPT_ERROR_NO_SUCH_ITEM;
  199. }
  200. // set Last-Modified and Cache-Control headers
  201. if (file_info.m_ModificationTime) {
  202. NPT_DateTime last_modified = NPT_DateTime(file_info.m_ModificationTime);
  203. response.GetHeaders().SetHeader("Last-Modified", last_modified.ToString(NPT_DateTime::FORMAT_RFC_1123), true);
  204. response.GetHeaders().SetHeader("Cache-Control", "max-age=0,must-revalidate", true);
  205. //response.GetHeaders().SetHeader("Cache-Control", "max-age=1800", true);
  206. }
  207. PLT_HttpRequestContext tmp_context(request, context);
  208. return ServeStream(request, context, response, stream, PLT_MimeType::GetMimeType(file_path, &tmp_context));
  209. }
  210. /*----------------------------------------------------------------------
  211. | PLT_HttpServer::ServeStream
  212. +---------------------------------------------------------------------*/
  213. NPT_Result
  214. PLT_HttpServer::ServeStream(const NPT_HttpRequest& request,
  215. const NPT_HttpRequestContext& context,
  216. NPT_HttpResponse& response,
  217. NPT_InputStreamReference& body,
  218. const char* content_type)
  219. {
  220. if (body.IsNull()) return NPT_FAILURE;
  221. // set date
  222. NPT_TimeStamp now;
  223. NPT_System::GetCurrentTimeStamp(now);
  224. response.GetHeaders().SetHeader("Date", NPT_DateTime(now).ToString(NPT_DateTime::FORMAT_RFC_1123), true);
  225. // get entity
  226. NPT_HttpEntity* entity = response.GetEntity();
  227. NPT_CHECK_POINTER_FATAL(entity);
  228. // set the content type
  229. entity->SetContentType(content_type);
  230. // check for range requests
  231. const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);
  232. // setup entity body
  233. NPT_CHECK(NPT_HttpFileRequestHandler::SetupResponseBody(response, body, range_spec));
  234. // set some default headers
  235. if (response.GetEntity()->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
  236. // set but don't replace Accept-Range header only if body is seekable
  237. NPT_Position offset;
  238. if (NPT_SUCCEEDED(body->Tell(offset)) && NPT_SUCCEEDED(body->Seek(offset))) {
  239. response.GetHeaders().SetHeader(NPT_HTTP_HEADER_ACCEPT_RANGES, "bytes", false);
  240. }
  241. }
  242. // set getcontentFeatures.dlna.org
  243. const NPT_String* value = request.GetHeaders().GetHeaderValue("getcontentFeatures.dlna.org");
  244. if (value) {
  245. PLT_HttpRequestContext tmp_context(request, context);
  246. const char* dlna = PLT_ProtocolInfo::GetDlnaExtension(entity->GetContentType(),
  247. &tmp_context);
  248. if (dlna) response.GetHeaders().SetHeader("ContentFeatures.DLNA.ORG", dlna, false);
  249. }
  250. // transferMode.dlna.org
  251. value = request.GetHeaders().GetHeaderValue("transferMode.dlna.org");
  252. if (value) {
  253. // Interactive mode not supported?
  254. /*if (value->Compare("Interactive", true) == 0) {
  255. response.SetStatus(406, "Not Acceptable");
  256. return NPT_SUCCESS;
  257. }*/
  258. response.GetHeaders().SetHeader("TransferMode.DLNA.ORG", value->GetChars(), false);
  259. } else {
  260. response.GetHeaders().SetHeader("TransferMode.DLNA.ORG", "Streaming", false);
  261. }
  262. if (request.GetHeaders().GetHeaderValue("TimeSeekRange.dlna.org")) {
  263. response.SetStatus(406, "Not Acceptable");
  264. return NPT_SUCCESS;
  265. }
  266. return NPT_SUCCESS;
  267. }