1 /*****************************************************************
2 |
3 |   Platinum - HTTP Server Tasks
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 /*----------------------------------------------------------------------
36 |   includes
37 +---------------------------------------------------------------------*/
38 #include "PltHttpServerTask.h"
39 #include "PltHttp.h"
40 #include "PltVersion.h"
41 
42 NPT_SET_LOCAL_LOGGER("platinum.core.http.servertask")
43 
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>";
51 
52 /*----------------------------------------------------------------------
53 |   PLT_HttpServerSocketTask::PLT_HttpServerSocketTask
54 +---------------------------------------------------------------------*/
PLT_HttpServerSocketTask(NPT_Socket * socket,bool stay_alive_forever)55 PLT_HttpServerSocketTask::PLT_HttpServerSocketTask(NPT_Socket* socket,
56                                                    bool        stay_alive_forever) :
57     m_Socket(socket),
58     m_StayAliveForever(stay_alive_forever)
59 {
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);
64 }
65 
66 /*----------------------------------------------------------------------
67 |   PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask
68 +---------------------------------------------------------------------*/
~PLT_HttpServerSocketTask()69 PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask()
70 {
71     if (m_Socket) {
72         m_Socket->Cancel();
73         delete m_Socket;
74     }
75 }
76 
77 /*----------------------------------------------------------------------
78 |   PLT_HttpServerSocketTask::DoRun
79 +---------------------------------------------------------------------*/
80 void
DoRun()81 PLT_HttpServerSocketTask::DoRun()
82 {
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;
88 
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);
94 
95     while (!IsAborting(0)) {
96         NPT_HttpRequest*  request = NULL;
97         NPT_HttpResponse* response = NULL;
98 
99         // reset keep-alive to exit task on read failure
100         keep_alive = false;
101 
102         // wait for a request
103         res = Read(buffered_input_stream, request, &context);
104         if (NPT_FAILED(res) || (request == NULL))
105             goto cleanup;
106 
107         // process request and setup response
108         res = RespondToClient(*request, context, response);
109         if (NPT_FAILED(res) || (response == NULL))
110             goto cleanup;
111 
112         // check if client requested keep-alive
113         keep_alive = PLT_HttpHelper::IsConnectionKeepAlive(*request);
114         headers_only = request->GetMethod() == NPT_HTTP_METHOD_HEAD;
115 
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);
119 
120         // on write error, reset keep_alive so we can close this connection
121         if (NPT_FAILED(res)) keep_alive = false;
122 
123 cleanup:
124         // cleanup
125         delete request;
126         delete response;
127 
128         if (!keep_alive && !m_StayAliveForever) {
129             return;
130         }
131     }
132 done:
133     return;
134 }
135 
136 /*----------------------------------------------------------------------
137 |   PLT_HttpServerSocketTask::GetInputStream
138 +---------------------------------------------------------------------*/
139 NPT_Result
GetInputStream(NPT_InputStreamReference & stream)140 PLT_HttpServerSocketTask::GetInputStream(NPT_InputStreamReference& stream)
141 {
142     return m_Socket->GetInputStream(stream);
143 }
144 
145 /*----------------------------------------------------------------------
146 |   PLT_HttpServerSocketTask::GetInfo
147 +---------------------------------------------------------------------*/
148 NPT_Result
GetInfo(NPT_SocketInfo & info)149 PLT_HttpServerSocketTask::GetInfo(NPT_SocketInfo& info)
150 {
151     return m_Socket->GetInfo(info);
152 }
153 
154 /*----------------------------------------------------------------------
155 |   PLT_HttpServerSocketTask::Read
156 +---------------------------------------------------------------------*/
157 NPT_Result
Read(NPT_BufferedInputStreamReference & buffered_input_stream,NPT_HttpRequest * & request,NPT_HttpRequestContext * context)158 PLT_HttpServerSocketTask::Read(NPT_BufferedInputStreamReference& buffered_input_stream,
159                                NPT_HttpRequest*&                 request,
160                                NPT_HttpRequestContext*           context)
161 {
162     NPT_SocketInfo info;
163     GetInfo(info);
164 
165     // update context with socket info if needed
166     if (context) {
167         context->SetLocalAddress(info.local_address);
168         context->SetRemoteAddress(info.remote_address);
169     }
170 
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);
173 
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;
181     }
182 
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);
189     }
190 
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;
195     }
196 
197     // create an entity
198     NPT_HttpEntity* request_entity = new NPT_HttpEntity(request->GetHeaders());
199     request->SetEntity(request_entity);
200 
201     NPT_MemoryStream* body_stream = new NPT_MemoryStream();
202     request_entity->SetInputStream((NPT_InputStreamReference)body_stream);
203 
204     // unbuffer the stream to read body fast
205     buffered_input_stream->SetBufferSize(0);
206 
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));
212 
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,
219             0,
220             request_entity->GetContentLength()));
221     } else {
222         request->SetEntity(NULL);
223     }
224 
225     // rebuffer the stream
226     buffered_input_stream->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE);
227 
228     return NPT_SUCCESS;
229 }
230 
231 /*----------------------------------------------------------------------
232 |   PLT_HttpServerSocketTask::RespondToClient
233 +---------------------------------------------------------------------*/
234 NPT_Result
RespondToClient(NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse * & response)235 PLT_HttpServerSocketTask::RespondToClient(NPT_HttpRequest&              request,
236                                           const NPT_HttpRequestContext& context,
237                                           NPT_HttpResponse*&            response)
238 {
239     NPT_Result result   = NPT_ERROR_NO_SUCH_ITEM;
240 
241     // reset output params first
242     response = NULL;
243 
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);
248 
249     // ask to setup the response
250     result = SetupResponse(request, context, *response);
251 
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");
269     }
270 
271     return NPT_SUCCESS;
272 }
273 
274 /*----------------------------------------------------------------------
275 |   PLT_HttpServerSocketTask::SendResponseHeaders
276 +---------------------------------------------------------------------*/
277 NPT_Result
SendResponseHeaders(NPT_HttpResponse * response,NPT_OutputStream & output_stream,bool & keep_alive)278 PLT_HttpServerSocketTask::SendResponseHeaders(NPT_HttpResponse* response,
279                                               NPT_OutputStream& output_stream,
280                                               bool&             keep_alive)
281 {
282     // add any headers that may be missing
283     NPT_HttpHeaders& headers = response->GetHeaders();
284 
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()));
293         }
294 
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);
299         }
300 
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);
305         }
306 
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);
311         }
312 
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");
317     }
318 
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);
331         }
332     }
333 
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);
338     }
339     headers.SetHeader(NPT_HTTP_HEADER_SERVER, PLT_HTTP_DEFAULT_SERVER, false); // set but don't replace
340 
341     PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINE, "PLT_HttpServerSocketTask::Write", response);
342 
343     // create a memory stream to buffer the headers
344     NPT_MemoryStream header_stream;
345     response->Emit(header_stream);
346 
347     // send the headers
348     NPT_CHECK_WARNING(output_stream.WriteFully(header_stream.GetData(), header_stream.GetDataSize()));
349 
350     return NPT_SUCCESS;
351 }
352 
353 /*----------------------------------------------------------------------
354 |   PLT_HttpServerSocketTask::SendResponseBody
355 +---------------------------------------------------------------------*/
356 NPT_Result
SendResponseBody(NPT_HttpResponse * response,NPT_OutputStream & output_stream)357 PLT_HttpServerSocketTask::SendResponseBody(NPT_HttpResponse* response,
358                                            NPT_OutputStream& output_stream)
359 {
360     NPT_HttpEntity* entity = response->GetEntity();
361     if (!entity) return NPT_SUCCESS;
362 
363     NPT_InputStreamReference body_stream;
364     entity->GetInputStream(body_stream);
365     if (body_stream.IsNull()) return NPT_SUCCESS;
366 
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);
371     }
372 
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));
382     }
383 
384     // flush to write out any buffered data left in chunked output if used
385     dest->Flush();
386 
387     // cleanup (this will send zero size chunk followed by CRLF)
388     if (dest != &output_stream) delete dest;
389 
390     return result;
391 }
392 
393 /*----------------------------------------------------------------------
394 |   PLT_HttpServerSocketTask::Write
395 +---------------------------------------------------------------------*/
396 NPT_Result
Write(NPT_HttpResponse * response,bool & keep_alive,bool headers_only)397 PLT_HttpServerSocketTask::Write(NPT_HttpResponse* response,
398                                 bool&             keep_alive,
399                                 bool              headers_only /* = false */)
400 {
401     // get the socket output stream
402     NPT_OutputStreamReference output_stream;
403     NPT_CHECK_WARNING(m_Socket->GetOutputStream(output_stream));
404 
405     // send headers
406     NPT_CHECK_WARNING(SendResponseHeaders(response, *output_stream, keep_alive));
407 
408     // send the body
409     if (!headers_only) {
410         NPT_CHECK_WARNING(SendResponseBody(response, *output_stream));
411     }
412 
413     // flush
414     output_stream->Flush();
415 
416     return NPT_SUCCESS;
417 }
418 
419 /*----------------------------------------------------------------------
420 |   PLT_HttpListenTask::DoRun
421 +---------------------------------------------------------------------*/
422 void
DoRun()423 PLT_HttpListenTask::DoRun()
424 {
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;
431 
432             // normal error
433             if (result == NPT_ERROR_TIMEOUT) continue;
434 
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);
441         }
442     }
443 }
444