blob: 3bac4f89367a9397aac6c478bdba2514bf07be55 [file] [log] [blame]
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +00001/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19#include <rfb/HTTPServer.h>
20#include <rfb/LogWriter.h>
21#include <rfb/util.h>
22#include <rdr/MemOutStream.h>
23#include <time.h>
24
25// *** Shouldn't really link against this - only for ClientWaitTimeMillis
26// and IdleTimeout
27#include <rfb/ServerCore.h>
28
29#ifdef WIN32
30#define strcasecmp _stricmp
31#endif
32
33
34using namespace rfb;
35using namespace rdr;
36
37static LogWriter vlog("HTTPServer");
38
39
40//
41// -=- LineReader
42// Helper class which is repeatedly called until a line has been read
43// (lines end in \n or \r\n).
44// Returns true when line complete, and resets internal state so that
45// next read() call will start reading a new line.
46// Only one buffer is kept - process line before reading next line!
47//
48
49class LineReader : public CharArray {
50public:
51 LineReader(InStream& is_, int l)
52 : CharArray(l), is(is_), pos(0), len(l), bufferOverrun(false) {}
53
54 // Returns true if line complete, false otherwise
55 bool read() {
56 while (is.checkNoWait(1)) {
57 char c = is.readU8();
58
59 if (c == '\n') {
60 if (pos && (buf[pos-1] == '\r'))
61 pos--;
62 bufferOverrun = false;
63 buf[pos++] = 0;
64 pos = 0;
65 return true;
66 }
67
68 if (pos == (len-1)) {
69 bufferOverrun = true;
70 buf[pos] = 0;
71 return true;
72 }
73
74 buf[pos++] = c;
75 }
76
77 return false;
78 }
79 bool didBufferOverrun() const {return bufferOverrun;}
80protected:
81 InStream& is;
82 int pos, len;
83 bool bufferOverrun;
84};
85
86
87//
88// -=- HTTPServer::Session
89// Manages the internal state for an HTTP session.
90// processHTTP returns true when request has completed,
91// indicating that socket & session data can be deleted.
92//
93
94class rfb::HTTPServer::Session {
95public:
96 Session(network::Socket& s, rfb::HTTPServer& srv)
97 : contentType(0), line(s.inStream(), 256), sock(s),
98 server(srv), state(ReadRequestLine), lastActive(time(0)) {
99 }
100 ~Session() {
101 }
102
103 void writeResponse(int result, const char* text);
104 bool writeResponse(int code);
105
106 bool processHTTP();
107
108 network::Socket* getSock() const {return &sock;}
109
110 int checkIdleTimeout();
111protected:
112 CharArray uri;
113 const char* contentType;
114 LineReader line;
115 network::Socket& sock;
116 rfb::HTTPServer& server;
117 enum {ReadRequestLine, ReadHeaders, WriteResponse} state;
118 enum {GetRequest, HeadRequest} request;
119 time_t lastActive;
120};
121
122
123// - Internal helper routines
124
125void
126copyStream(InStream& is, OutStream& os) {
127 try {
128 while (1) {
129 os.writeU8(is.readU8());
130 }
131 } catch (rdr::EndOfStream) {
132 }
133}
134
135void writeLine(OutStream& os, const char* text) {
136 os.writeBytes(text, strlen(text));
137 os.writeBytes("\r\n", 2);
138}
139
140
141// - Write an HTTP-compliant response to the client
142
143void
144HTTPServer::Session::writeResponse(int result, const char* text) {
145 char buffer[1024];
146 if (strlen(text) > 512)
147 throw new rdr::Exception("Internal error - HTTP response text too big");
148 sprintf(buffer, "%s %d %s", "HTTP/1.1", result, text);
149 OutStream& os=sock.outStream();
150 writeLine(os, buffer);
Peter Åstrand9fb4e0e2004-12-30 10:03:00 +0000151 writeLine(os, "Server: TightVNC/4.0");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000152 writeLine(os, "Connection: close");
153 os.writeBytes("Content-Type: ", 14);
154 if (result == 200) {
155 if (!contentType)
156 contentType = guessContentType(uri.buf, "text/html");
157 os.writeBytes(contentType, strlen(contentType));
158 } else {
159 os.writeBytes("text/html", 9);
160 }
161 os.writeBytes("\r\n", 2);
162 writeLine(os, "");
163 if (result != 200) {
164 writeLine(os, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">");
165 writeLine(os, "<HTML><HEAD>");
166 sprintf(buffer, "<TITLE>%d %s</TITLE>", result, text);
167 writeLine(os, buffer);
168 writeLine(os, "</HEAD><BODY><H1>");
169 writeLine(os, text);
170 writeLine(os, "</H1></BODY></HTML>");
171 sock.outStream().flush();
172 }
173}
174
175bool
176HTTPServer::Session::writeResponse(int code) {
177 switch (code) {
178 case 200: writeResponse(code, "OK"); break;
179 case 400: writeResponse(code, "Bad Request"); break;
180 case 404: writeResponse(code, "Not Found"); break;
181 case 501: writeResponse(code, "Not Implemented"); break;
182 default: writeResponse(500, "Unknown Error"); break;
183 };
184
185 // This return code is passed straight out of processHTTP().
186 // true indicates that the request has been completely processed.
187 return true;
188}
189
190// - Main HTTP request processing routine
191
192bool
193HTTPServer::Session::processHTTP() {
194 lastActive = time(0);
195
196 while (sock.inStream().checkNoWait(1)) {
197
198 switch (state) {
199
200 // Reading the Request-Line
201 case ReadRequestLine:
202
203 // Either read a line, or run out of incoming data
204 if (!line.read())
205 return false;
206
207 // We have read a line! Skip it if it's blank
208 if (strlen(line.buf) == 0)
209 continue;
210
211 // The line contains a request to process.
212 {
213 char method[16], path[128], version[16];
214 int matched = sscanf(line.buf, "%15s%127s%15s",
215 method, path, version);
216 if (matched != 3)
217 return writeResponse(400);
218
219 // Store the required "method"
220 if (strcmp(method, "GET") == 0)
221 request = GetRequest;
222 else if (strcmp(method, "HEAD") == 0)
223 request = HeadRequest;
224 else
225 return writeResponse(501);
226
227 // Store the URI to the "document"
228 uri.buf = strDup(path);
229 }
230
231 // Move on to reading the request headers
232 state = ReadHeaders;
233 break;
234
235 // Reading the request headers
236 case ReadHeaders:
237
238 // Try to read a line
239 if (!line.read())
240 return false;
241
242 // Skip headers until we hit a blank line
243 if (strlen(line.buf) != 0)
244 continue;
245
246 // Headers ended - write the response!
247 {
248 CharArray address(sock.getPeerAddress());
249 vlog.info("getting %s for %s", uri.buf, address.buf);
250 InStream* data = server.getFile(uri.buf, &contentType);
251 if (!data)
252 return writeResponse(404);
253
254 try {
255 writeResponse(200);
256 if (request == GetRequest)
257 copyStream(*data, sock.outStream());
258 sock.outStream().flush();
259 } catch (rdr::Exception& e) {
260 vlog.error("error writing HTTP document:%s", e.str());
261 }
262 delete data;
263 }
264
265 // The operation is complete!
266 return true;
267
268 default:
269 throw rdr::Exception("invalid HTTPSession state!");
270 };
271
272 }
273
274 // Indicate that we're still processing the HTTP request.
275 return false;
276}
277
278int HTTPServer::Session::checkIdleTimeout() {
279 time_t now = time(0);
280 int timeout = (lastActive + rfb::Server::idleTimeout) - now;
281 if (timeout > 0)
282 return timeout * 1000;
283 sock.shutdown();
284 return 0;
285}
286
287// -=- Constructor / destructor
288
289HTTPServer::HTTPServer() {
290}
291
292HTTPServer::~HTTPServer() {
293 std::list<Session*>::iterator i;
294 for (i=sessions.begin(); i!=sessions.end(); i++) {
295 delete (*i)->getSock();
296 delete *i;
297 }
298}
299
300
301// -=- SocketServer interface implementation
302
303void
304HTTPServer::addClient(network::Socket* sock) {
305 Session* s = new Session(*sock, *this);
306 if (!s) {
307 sock->shutdown();
308 } else {
309 sock->inStream().setTimeout(rfb::Server::clientWaitTimeMillis);
310 sock->outStream().setTimeout(rfb::Server::clientWaitTimeMillis);
311 sessions.push_front(s);
312 }
313}
314
315bool
316HTTPServer::processSocketEvent(network::Socket* sock) {
317 std::list<Session*>::iterator i;
318 for (i=sessions.begin(); i!=sessions.end(); i++) {
319 if ((*i)->getSock() == sock) {
320 try {
321 if ((*i)->processHTTP()) {
322 vlog.info("completed HTTP request");
323 delete *i;
324 sessions.erase(i);
325 break;
326 }
327 return true;
328 } catch (rdr::Exception& e) {
329 vlog.error("untrapped: %s", e.str());
330 delete *i;
331 sessions.erase(i);
332 break;
333 }
334 }
335 }
336 delete sock;
337 return false;
338}
339
340void HTTPServer::getSockets(std::list<network::Socket*>* sockets)
341{
342 sockets->clear();
343 std::list<Session*>::iterator ci;
344 for (ci = sessions.begin(); ci != sessions.end(); ci++) {
345 sockets->push_back((*ci)->getSock());
346 }
347}
348
349int HTTPServer::checkTimeouts() {
350 std::list<Session*>::iterator ci;
351 int timeout = 0;
352 for (ci = sessions.begin(); ci != sessions.end(); ci++) {
353 soonestTimeout(&timeout, (*ci)->checkIdleTimeout());
354 }
355 return timeout;
356}
357
358
359// -=- Default getFile implementation
360
361InStream*
362HTTPServer::getFile(const char* name, const char** contentType) {
363 return 0;
364}
365
366const char*
367HTTPServer::guessContentType(const char* name, const char* defType) {
368 CharArray file, ext;
369 if (!strSplit(name, '.', &file.buf, &ext.buf))
370 return defType;
371 if (strcasecmp(ext.buf, "html") == 0 ||
372 strcasecmp(ext.buf, "htm") == 0) {
373 return "text/html";
374 } else if (strcasecmp(ext.buf, "txt") == 0) {
375 return "text/plain";
376 } else if (strcasecmp(ext.buf, "gif") == 0) {
377 return "image/gif";
378 } else if (strcasecmp(ext.buf, "jpg") == 0) {
379 return "image/jpeg";
380 } else if (strcasecmp(ext.buf, "jar") == 0) {
381 return "application/java-archive";
382 } else if (strcasecmp(ext.buf, "exe") == 0) {
383 return "application/octet-stream";
384 }
385 return defType;
386}