Merge pull request #25959 from neo1973/TagLib_deprecation_warnings
[xbmc.git] / lib / libUPnP / Platinum / Source / Core / PltHttpServerTask.cpp
blobed23d47be88152e102b8512f485f498fac6857a4
1 /*****************************************************************
3 | Platinum - HTTP Server Tasks
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
6 | All rights reserved.
7 | http://www.plutinosoft.com
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.
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
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.
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
33 ****************************************************************/
35 /*----------------------------------------------------------------------
36 | includes
37 +---------------------------------------------------------------------*/
38 #include "PltHttpServerTask.h"
39 #include "PltHttp.h"
40 #include "PltVersion.h"
42 NPT_SET_LOCAL_LOGGER("platinum.core.http.servertask")
44 /*----------------------------------------------------------------------
45 | external references
46 +---------------------------------------------------------------------*/
47 extern NPT_String HttpServerHeader;
48 const char* const PLT_HTTP_DEFAULT_403_HTML = "<html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>Access to this URL is forbidden.</p></html>";
49 const char* const PLT_HTTP_DEFAULT_404_HTML = "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></html>";
50 const char* const PLT_HTTP_DEFAULT_500_HTML = "<html><head><title>500 Internal Error</title></head><body><h1>Internal Error</h1><p>The server encountered an unexpected condition which prevented it from fulfilling the request.</p></html>";
52 /*----------------------------------------------------------------------
53 | PLT_HttpServerSocketTask::PLT_HttpServerSocketTask
54 +---------------------------------------------------------------------*/
55 PLT_HttpServerSocketTask::PLT_HttpServerSocketTask(NPT_Socket* socket,
56 bool stay_alive_forever) :
57 m_Socket(socket),
58 m_StayAliveForever(stay_alive_forever)
60 // needed for PS3 that is some case will request data every 35 secs and
61 // won't like it if server disconnected too early
62 m_Socket->SetReadTimeout(60000);
63 m_Socket->SetWriteTimeout(600000);
66 /*----------------------------------------------------------------------
67 | PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask
68 +---------------------------------------------------------------------*/
69 PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask()
71 if (m_Socket) {
72 m_Socket->Cancel();
73 delete m_Socket;
77 /*----------------------------------------------------------------------
78 | PLT_HttpServerSocketTask::DoRun
79 +---------------------------------------------------------------------*/
80 void
81 PLT_HttpServerSocketTask::DoRun()
83 NPT_BufferedInputStreamReference buffered_input_stream;
84 NPT_HttpRequestContext context;
85 NPT_Result res = NPT_SUCCESS;
86 bool headers_only;
87 bool keep_alive = false;
89 // create a buffered input stream to parse HTTP request
90 NPT_InputStreamReference input_stream;
91 NPT_CHECK_LABEL_SEVERE(GetInputStream(input_stream), done);
92 NPT_CHECK_POINTER_LABEL_FATAL(input_stream.AsPointer(), done);
93 buffered_input_stream = new NPT_BufferedInputStream(input_stream);
95 while (!IsAborting(0)) {
96 NPT_HttpRequest* request = NULL;
97 NPT_HttpResponse* response = NULL;
99 // reset keep-alive to exit task on read failure
100 keep_alive = false;
102 // wait for a request
103 res = Read(buffered_input_stream, request, &context);
104 if (NPT_FAILED(res) || (request == NULL))
105 goto cleanup;
107 // process request and setup response
108 res = RespondToClient(*request, context, response);
109 if (NPT_FAILED(res) || (response == NULL))
110 goto cleanup;
112 // check if client requested keep-alive
113 keep_alive = PLT_HttpHelper::IsConnectionKeepAlive(*request);
114 headers_only = request->GetMethod() == NPT_HTTP_METHOD_HEAD;
116 // send response, pass keep-alive request from client
117 // (it can be overridden if response handler did not allow it)
118 res = Write(response, keep_alive, headers_only);
120 // on write error, reset keep_alive so we can close this connection
121 if (NPT_FAILED(res)) keep_alive = false;
123 cleanup:
124 // cleanup
125 delete request;
126 delete response;
128 if (!keep_alive && !m_StayAliveForever) {
129 return;
132 done:
133 return;
136 /*----------------------------------------------------------------------
137 | PLT_HttpServerSocketTask::GetInputStream
138 +---------------------------------------------------------------------*/
139 NPT_Result
140 PLT_HttpServerSocketTask::GetInputStream(NPT_InputStreamReference& stream)
142 return m_Socket->GetInputStream(stream);
145 /*----------------------------------------------------------------------
146 | PLT_HttpServerSocketTask::GetInfo
147 +---------------------------------------------------------------------*/
148 NPT_Result
149 PLT_HttpServerSocketTask::GetInfo(NPT_SocketInfo& info)
151 return m_Socket->GetInfo(info);
154 /*----------------------------------------------------------------------
155 | PLT_HttpServerSocketTask::Read
156 +---------------------------------------------------------------------*/
157 NPT_Result
158 PLT_HttpServerSocketTask::Read(NPT_BufferedInputStreamReference& buffered_input_stream,
159 NPT_HttpRequest*& request,
160 NPT_HttpRequestContext* context)
162 NPT_SocketInfo info;
163 GetInfo(info);
165 // update context with socket info if needed
166 if (context) {
167 context->SetLocalAddress(info.local_address);
168 context->SetRemoteAddress(info.remote_address);
171 // put back in buffered mode to be able to parse HTTP request properly
172 buffered_input_stream->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE);
174 // parse request
175 NPT_Result res = NPT_HttpRequest::Parse(*buffered_input_stream, &info.local_address, request);
176 if (NPT_FAILED(res) || !request) {
177 // only log if not timeout
178 res = NPT_FAILED(res)?res:NPT_FAILURE;
179 if (res != NPT_ERROR_TIMEOUT && res != NPT_ERROR_EOS) NPT_CHECK_WARNING(res);
180 return res;
183 // update context with socket info again
184 // to refresh the remote address in case it was a non connected udp socket
185 GetInfo(info);
186 if (context) {
187 context->SetLocalAddress(info.local_address);
188 context->SetRemoteAddress(info.remote_address);
191 // return right away if no body is expected
192 if (request->GetMethod() == NPT_HTTP_METHOD_GET ||
193 request->GetMethod() == NPT_HTTP_METHOD_HEAD) {
194 return NPT_SUCCESS;
197 // create an entity
198 NPT_HttpEntity* request_entity = new NPT_HttpEntity(request->GetHeaders());
199 request->SetEntity(request_entity);
201 NPT_MemoryStream* body_stream = new NPT_MemoryStream();
202 request_entity->SetInputStream((NPT_InputStreamReference)body_stream);
204 // unbuffer the stream to read body fast
205 buffered_input_stream->SetBufferSize(0);
207 // check for chunked Transfer-Encoding
208 if (request_entity->GetTransferEncoding() == "chunked") {
209 NPT_CHECK_SEVERE(NPT_StreamToStreamCopy(
210 *NPT_InputStreamReference(new NPT_HttpChunkedInputStream(buffered_input_stream)).AsPointer(),
211 *body_stream));
213 request_entity->SetTransferEncoding(NULL);
214 } else if (request_entity->GetContentLength()) {
215 // a request with a body must always have a content length if not chunked
216 NPT_CHECK_SEVERE(NPT_StreamToStreamCopy(
217 *buffered_input_stream.AsPointer(),
218 *body_stream,
220 request_entity->GetContentLength()));
221 } else {
222 request->SetEntity(NULL);
225 // rebuffer the stream
226 buffered_input_stream->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE);
228 return NPT_SUCCESS;
231 /*----------------------------------------------------------------------
232 | PLT_HttpServerSocketTask::RespondToClient
233 +---------------------------------------------------------------------*/
234 NPT_Result
235 PLT_HttpServerSocketTask::RespondToClient(NPT_HttpRequest& request,
236 const NPT_HttpRequestContext& context,
237 NPT_HttpResponse*& response)
239 NPT_Result result = NPT_ERROR_NO_SUCH_ITEM;
241 // reset output params first
242 response = NULL;
244 // prepare the response body
245 NPT_HttpEntity* body = new NPT_HttpEntity();
246 response = new NPT_HttpResponse(200, "OK", NPT_HTTP_PROTOCOL_1_1);
247 response->SetEntity(body);
249 // ask to setup the response
250 result = SetupResponse(request, context, *response);
252 // handle result
253 if (result == NPT_ERROR_NO_SUCH_ITEM) {
254 body->SetInputStream(PLT_HTTP_DEFAULT_404_HTML);
255 body->SetContentType("text/html");
256 response->SetStatus(404, "Not Found");
257 } else if (result == NPT_ERROR_PERMISSION_DENIED) {
258 body->SetInputStream(PLT_HTTP_DEFAULT_403_HTML);
259 body->SetContentType("text/html");
260 response->SetStatus(403, "Forbidden");
261 } else if (result == NPT_ERROR_TERMINATED) {
262 // mark that we want to exit
263 delete response;
264 response = NULL;
265 } else if (NPT_FAILED(result)) {
266 body->SetInputStream(PLT_HTTP_DEFAULT_500_HTML);
267 body->SetContentType("text/html");
268 response->SetStatus(500, "Internal Error");
271 return NPT_SUCCESS;
274 /*----------------------------------------------------------------------
275 | PLT_HttpServerSocketTask::SendResponseHeaders
276 +---------------------------------------------------------------------*/
277 NPT_Result
278 PLT_HttpServerSocketTask::SendResponseHeaders(NPT_HttpResponse* response,
279 NPT_OutputStream& output_stream,
280 bool& keep_alive)
282 // add any headers that may be missing
283 NPT_HttpHeaders& headers = response->GetHeaders();
285 // get the request entity to set additional headers
286 NPT_InputStreamReference body_stream;
287 NPT_HttpEntity* entity = response->GetEntity();
288 if (entity && NPT_SUCCEEDED(entity->GetInputStream(body_stream))) {
289 // set the content length if known
290 if (entity->ContentLengthIsKnown()) {
291 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH,
292 NPT_String::FromIntegerU(entity->GetContentLength()));
295 // content type
296 NPT_String content_type = entity->GetContentType();
297 if (!content_type.IsEmpty()) {
298 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, content_type);
301 // content encoding
302 NPT_String content_encoding = entity->GetContentEncoding();
303 if (!content_encoding.IsEmpty()) {
304 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING, content_encoding);
307 // transfer encoding
308 const NPT_String& transfer_encoding = entity->GetTransferEncoding();
309 if (!transfer_encoding.IsEmpty()) {
310 headers.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING, transfer_encoding);
313 } else if (!headers.GetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH)) {
314 // force content length to 0 if there is no message body
315 // (necessary for 1.1 or 1.0 with keep-alive connections)
316 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, "0");
319 const NPT_String* content_length = headers.GetHeaderValue(NPT_HTTP_HEADER_CONTENT_LENGTH);
320 const NPT_String* transfer_encoding = headers.GetHeaderValue(NPT_HTTP_HEADER_TRANSFER_ENCODING);
321 const NPT_String* connection_header = headers.GetHeaderValue(NPT_HTTP_HEADER_CONNECTION);
322 if (keep_alive) {
323 if (connection_header && connection_header->Compare("close") == 0) {
324 keep_alive = false;
325 } else {
326 // the request says client supports keep-alive
327 // but override if response has content-length header or
328 // transfer chunked encoding
329 keep_alive = content_length ||
330 (transfer_encoding && transfer_encoding->Compare(NPT_HTTP_TRANSFER_ENCODING_CHUNKED) == 0);
334 // only write keep-alive header for 1.1 if it's close
335 NPT_String protocol = response->GetProtocol();
336 if (protocol.Compare(NPT_HTTP_PROTOCOL_1_0, true) == 0 || !keep_alive) {
337 headers.SetHeader(NPT_HTTP_HEADER_CONNECTION, keep_alive?"keep-alive":"close", true);
339 headers.SetHeader(NPT_HTTP_HEADER_SERVER, PLT_HTTP_DEFAULT_SERVER, false); // set but don't replace
341 PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINE, "PLT_HttpServerSocketTask::Write", response);
343 // create a memory stream to buffer the headers
344 NPT_MemoryStream header_stream;
345 response->Emit(header_stream);
347 // send the headers
348 NPT_CHECK_WARNING(output_stream.WriteFully(header_stream.GetData(), header_stream.GetDataSize()));
350 return NPT_SUCCESS;
353 /*----------------------------------------------------------------------
354 | PLT_HttpServerSocketTask::SendResponseBody
355 +---------------------------------------------------------------------*/
356 NPT_Result
357 PLT_HttpServerSocketTask::SendResponseBody(NPT_HttpResponse* response,
358 NPT_OutputStream& output_stream)
360 NPT_HttpEntity* entity = response->GetEntity();
361 if (!entity) return NPT_SUCCESS;
363 NPT_InputStreamReference body_stream;
364 entity->GetInputStream(body_stream);
365 if (body_stream.IsNull()) return NPT_SUCCESS;
367 // check for chunked transfer encoding
368 NPT_OutputStream* dest = &output_stream;
369 if (entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
370 dest = new NPT_HttpChunkedOutputStream(output_stream);
373 // send body
374 NPT_LOG_FINE_1("sending body stream, %lld bytes", entity->GetContentLength());
375 NPT_LargeSize bytes_written = 0;
376 NPT_Result result = NPT_StreamToStreamCopy(*body_stream, *dest, 0, entity->GetContentLength(), &bytes_written); /* passing 0 if content length is unknown will read until nothing is left */
377 if (NPT_FAILED(result)) {
378 NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)",
379 bytes_written,
380 result,
381 NPT_ResultText(result));
384 // flush to write out any buffered data left in chunked output if used
385 dest->Flush();
387 // cleanup (this will send zero size chunk followed by CRLF)
388 if (dest != &output_stream) delete dest;
390 return result;
393 /*----------------------------------------------------------------------
394 | PLT_HttpServerSocketTask::Write
395 +---------------------------------------------------------------------*/
396 NPT_Result
397 PLT_HttpServerSocketTask::Write(NPT_HttpResponse* response,
398 bool& keep_alive,
399 bool headers_only /* = false */)
401 // get the socket output stream
402 NPT_OutputStreamReference output_stream;
403 NPT_CHECK_WARNING(m_Socket->GetOutputStream(output_stream));
405 // send headers
406 NPT_CHECK_WARNING(SendResponseHeaders(response, *output_stream, keep_alive));
408 // send the body
409 if (!headers_only) {
410 NPT_CHECK_WARNING(SendResponseBody(response, *output_stream));
413 // flush
414 output_stream->Flush();
416 return NPT_SUCCESS;
419 /*----------------------------------------------------------------------
420 | PLT_HttpListenTask::DoRun
421 +---------------------------------------------------------------------*/
422 void
423 PLT_HttpListenTask::DoRun()
425 while (!IsAborting(0)) {
426 NPT_Socket* client = NULL;
427 NPT_Result result = m_Socket->WaitForNewClient(client, 5000, NPT_SOCKET_FLAG_CANCELLABLE);
428 if (NPT_FAILED(result)) {
429 // cleanup just in case
430 if (client) delete client;
432 // normal error
433 if (result == NPT_ERROR_TIMEOUT) continue;
435 // exit on other errors ?
436 NPT_LOG_WARNING_2("PLT_HttpListenTask exiting with %d (%s)", result, NPT_ResultText(result));
437 break;
438 } else {
439 PLT_ThreadTask* task = new PLT_HttpServerTask(m_Handler, client);
440 m_TaskManager->StartTask(task);