Merged from linuxport rev21200:21590. Updated.
[xbmc:xbmc-antiquated.git] / XBMC / xbmc / lib / libUPnP / Platinum / Source / Core / PltHttpServer.cpp
1 /*****************************************************************
2 |
3 |   Platinum - HTTP Server
4 |
5 | Copyright (c) 2004-2008, 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
21 | This program is distributed in the hope that it will be useful,
22 | but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 | GNU General Public License for more details.
25 |
26 | You should have received a copy of the GNU General Public License
27 | along with this program; see the file LICENSE.txt. If not, write to
28 | the Free Software Foundation, Inc., 
29 | 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
30 | http://www.gnu.org/licenses/gpl-2.0.html
31 |
32 ****************************************************************/
33
34 /*----------------------------------------------------------------------
35 |   includes
36 +---------------------------------------------------------------------*/
37 #include "PltTaskManager.h"
38 #include "PltHttpServer.h"
39 #include "PltHttp.h"
40 #include "PltVersion.h"
41
42 NPT_SET_LOCAL_LOGGER("platinum.core.http.server")
43
44 /*----------------------------------------------------------------------
45 |   PLT_HttpServer::PLT_HttpServer
46 +---------------------------------------------------------------------*/
47 PLT_HttpServer::PLT_HttpServer(unsigned int port,
48                                bool         port_rebind   /* = false */,
49                                NPT_Cardinal max_clients   /* = 0 */,
50                                bool         reuse_address /* = false */) :
51     m_TaskManager(new PLT_TaskManager(max_clients)),
52     m_Port(port),
53     m_PortRebind(port_rebind),
54     m_ReuseAddress(reuse_address),
55     m_HttpListenTask(NULL)
56 {
57 }
58
59 /*----------------------------------------------------------------------
60 |   PLT_HttpServer::~PLT_HttpServer
61 +---------------------------------------------------------------------*/
62 PLT_HttpServer::~PLT_HttpServer()
63
64     delete m_TaskManager;
65 }
66
67 /*----------------------------------------------------------------------
68 |   PLT_HttpServer::Start
69 +---------------------------------------------------------------------*/
70 NPT_Result
71 PLT_HttpServer::Start()
72 {
73     NPT_Result res = NPT_FAILURE;
74     
75     // if we're given a port for our http server, try it
76     if (m_Port) {
77         res = SetListenPort(m_Port, m_ReuseAddress);
78         // return right away on failure if told not to try to rebind randomly
79         if (NPT_FAILED(res) && !m_PortRebind) {
80             NPT_CHECK_SEVERE(res);
81         }
82     }
83     
84     // try random port now
85     if (!m_Port || NPT_FAILED(res)) {
86         // randomly try a port for our http server
87         int retries = 100;
88         do {    
89             int random = NPT_System::GetRandomInteger();
90             int port = (unsigned short)(50000 + (random % 15000));
91             if (NPT_SUCCEEDED(SetListenPort(port, m_ReuseAddress))) {
92                 break;
93             }
94         } while (--retries > 0);
95
96         if (retries == 0) NPT_CHECK_SEVERE(NPT_FAILURE);
97     }
98
99     m_Port = m_BoundPort;
100     
101     // start a task to listen
102     m_HttpListenTask = new PLT_HttpServerListenTask(this, &m_Socket, false);
103     m_TaskManager->StartTask(m_HttpListenTask, NULL, false);
104
105     NPT_SocketInfo info;
106     m_Socket.GetInfo(info);
107     NPT_LOG_INFO_2("HttpServer listening on %s:%d", 
108         (const char*)info.local_address.GetIpAddress().ToString(), 
109         m_Port);
110     return NPT_SUCCESS;
111 }
112
113 /*----------------------------------------------------------------------
114 |   PLT_HttpServer::Stop
115 +---------------------------------------------------------------------*/
116 NPT_Result
117 PLT_HttpServer::Stop()
118 {
119     if (m_HttpListenTask) {
120         m_HttpListenTask->Kill();
121         m_HttpListenTask = NULL;
122     }
123
124     // stop all other pending tasks 
125     m_TaskManager->StopAllTasks();
126
127     return NPT_SUCCESS;
128 }
129
130 /*----------------------------------------------------------------------
131 |   PLT_HttpServer::ProcessHttpRequest
132 +---------------------------------------------------------------------*/
133 NPT_Result 
134 PLT_HttpServer::ProcessHttpRequest(NPT_HttpRequest&              request, 
135                                    const NPT_HttpRequestContext& context,
136                                    NPT_HttpResponse*&            response,
137                                    bool&                         headers_only) 
138 {
139     NPT_LOG_FINE_3("Received %s Request from %s for %s", 
140         (const char*) request.GetMethod(),
141         (const char*) context.GetRemoteAddress().GetIpAddress().ToString(),
142         (const char*) request.GetUrl().GetPath());
143         
144     PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request);
145
146     NPT_HttpRequestHandler* handler = FindRequestHandler(request);
147     if (handler == NULL) {
148         response = new NPT_HttpResponse(404, "Not Found", NPT_HTTP_PROTOCOL_1_1);
149     } else {
150         // create a repsones object
151         response = new NPT_HttpResponse(200, "OK", NPT_HTTP_PROTOCOL_1_1);
152         response->GetHeaders().SetHeader("Server", "Platinum/" PLT_PLATINUM_VERSION_STRING);
153
154         // prepare the response
155         response->SetEntity(new NPT_HttpEntity());
156
157         // ask the handler to setup the response
158         handler->SetupResponse(request, context, *response);
159
160         // set headers_only flag
161         headers_only = (request.GetMethod()==NPT_HTTP_METHOD_HEAD);
162     }
163
164     return NPT_SUCCESS;
165 }
166
167 /*----------------------------------------------------------------------
168 |   PLT_FileServer::GetMimeType
169 +---------------------------------------------------------------------*/
170 const char* 
171 PLT_FileServer::GetMimeType(const NPT_String& filename)
172 {
173     int last_dot = filename.ReverseFind('.');
174     if (last_dot > 0) {
175         NPT_String extension = filename.GetChars()+last_dot+1;
176         extension.MakeLowercase();
177
178         for (unsigned int i=0; i<NPT_ARRAY_SIZE(NPT_HttpFileRequestHandler_DefaultFileTypeMap); i++) {
179             if (extension == NPT_HttpFileRequestHandler_DefaultFileTypeMap[i].extension) {
180                 return NPT_HttpFileRequestHandler_DefaultFileTypeMap[i].mime_type;
181             }
182         }
183     }
184
185     return "application/octet-stream";
186 }
187
188 /*----------------------------------------------------------------------
189 |   PLT_FileServer::ServeFile
190 +---------------------------------------------------------------------*/
191 NPT_Result 
192 PLT_FileServer::ServeFile(NPT_HttpResponse& response,
193                           NPT_String        file_path, 
194                           NPT_Position      start,
195                           NPT_Position      end,
196                           bool              request_is_head) 
197 {
198     NPT_LargeSize            total_len;
199     NPT_InputStreamReference stream;
200     NPT_File                 file(file_path);
201
202     // prevent hackers from accessing files outside of our root
203     if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0)) {
204         response.SetStatus(404, "File Not Found");
205         return NPT_SUCCESS;
206     }
207
208     if (NPT_FAILED(file.Open(NPT_FILE_OPEN_MODE_READ)) || 
209         NPT_FAILED(file.GetInputStream(stream))        ||
210         stream.IsNull()                                                            ||
211         NPT_FAILED(stream->GetSize(total_len))) {
212         // file didn't open
213         response.SetStatus(404, "File Not Found");
214         return NPT_SUCCESS;
215     } else {
216         NPT_HttpEntity* entity = new NPT_HttpEntity();
217         entity->SetContentLength(total_len);
218         response.SetEntity(entity);
219
220         // set the content type if we can
221         entity->SetContentType(GetMimeType(file_path));
222
223         // request is HEAD, returns without setting a body
224         if (request_is_head) return NPT_SUCCESS;
225
226         // see if it was a byte range request
227         if (start != (NPT_Position)-1 || end != (NPT_Position)-1) {
228             NPT_Position start_offset = (NPT_Position)-1, end_offset = total_len-1, len;
229             if (start == (NPT_Position)-1 && end != (NPT_Position)-1) {
230                 // we are asked for the last N=end bytes
231                 // adjust start according to total length
232                 if (end >= total_len) {
233                     start_offset = 0;
234                 } else {
235                     start_offset = total_len-end;
236                 }
237             } else if (start != (NPT_Position)-1) {
238                 start_offset = start;
239                 if (end != (NPT_Position)-1) {
240                     if (end < start) {
241                         // if the end is specified but incorrect
242                         // set the end_offset in order to generate a bad response
243                         end_offset = (NPT_Position)-1;
244                     } else if (end - start < total_len) {
245                         end_offset = end;
246                     }
247                 }
248             }
249
250             // in case the range request was invalid or we can't seek then respond appropriately
251             if (start_offset == (NPT_Position)-1 || end_offset == (NPT_Position)-1 || 
252                 start_offset > end_offset || NPT_FAILED(stream->Seek(start_offset))) {
253                 response.SetStatus(416, "Requested range not satisfiable");
254             } else {
255                 len = end_offset - start_offset + 1;
256                 response.SetStatus(206, "Partial Content");
257                 PLT_HttpHelper::SetContentRange(response, start_offset, end_offset, total_len);
258
259                 entity->SetInputStream(stream);
260                 entity->SetContentLength(len);
261             }
262         } else {
263             entity->SetInputStream(stream);
264         }
265         return NPT_SUCCESS;
266     }
267 }