Migrating to new directory structure adopted from the RealVNC's source tree. More changes will follow.
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@589 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/common/rfb/HTTPServer.cxx b/common/rfb/HTTPServer.cxx
new file mode 100644
index 0000000..e40d480
--- /dev/null
+++ b/common/rfb/HTTPServer.cxx
@@ -0,0 +1,411 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <rfb/HTTPServer.h>
+#include <rfb/LogWriter.h>
+#include <rfb/util.h>
+#include <rdr/MemOutStream.h>
+
+#ifdef WIN32
+#define strcasecmp _stricmp
+#endif
+
+
+using namespace rfb;
+using namespace rdr;
+
+static LogWriter vlog("HTTPServer");
+
+const int clientWaitTimeMillis = 20000;
+const int idleTimeoutSecs = 5 * 60;
+
+
+//
+// -=- LineReader
+// Helper class which is repeatedly called until a line has been read
+// (lines end in \n or \r\n).
+// Returns true when line complete, and resets internal state so that
+// next read() call will start reading a new line.
+// Only one buffer is kept - process line before reading next line!
+//
+
+class LineReader : public CharArray {
+public:
+ LineReader(InStream& is_, int l)
+ : CharArray(l), is(is_), pos(0), len(l), bufferOverrun(false) {}
+
+ // Returns true if line complete, false otherwise
+ bool read() {
+ while (is.checkNoWait(1)) {
+ char c = is.readU8();
+
+ if (c == '\n') {
+ if (pos && (buf[pos-1] == '\r'))
+ pos--;
+ bufferOverrun = false;
+ buf[pos++] = 0;
+ pos = 0;
+ return true;
+ }
+
+ if (pos == (len-1)) {
+ bufferOverrun = true;
+ buf[pos] = 0;
+ return true;
+ }
+
+ buf[pos++] = c;
+ }
+
+ return false;
+ }
+ bool didBufferOverrun() const {return bufferOverrun;}
+protected:
+ InStream& is;
+ int pos, len;
+ bool bufferOverrun;
+};
+
+
+//
+// -=- HTTPServer::Session
+// Manages the internal state for an HTTP session.
+// processHTTP returns true when request has completed,
+// indicating that socket & session data can be deleted.
+//
+
+class rfb::HTTPServer::Session {
+public:
+ Session(network::Socket& s, rfb::HTTPServer& srv)
+ : contentType(0), contentLength(-1), lastModified(-1),
+ line(s.inStream(), 256), sock(s),
+ server(srv), state(ReadRequestLine), lastActive(time(0)) {
+ }
+ ~Session() {
+ }
+
+ void writeResponse(int result, const char* text);
+ bool writeResponse(int code);
+
+ bool processHTTP();
+
+ network::Socket* getSock() const {return &sock;}
+
+ int checkIdleTimeout();
+protected:
+ CharArray uri;
+ const char* contentType;
+ int contentLength;
+ time_t lastModified;
+ LineReader line;
+ network::Socket& sock;
+ rfb::HTTPServer& server;
+ enum {ReadRequestLine, ReadHeaders, WriteResponse} state;
+ enum {GetRequest, HeadRequest} request;
+ time_t lastActive;
+};
+
+
+// - Internal helper routines
+
+void
+copyStream(InStream& is, OutStream& os) {
+ try {
+ while (1) {
+ os.writeU8(is.readU8());
+ }
+ } catch (rdr::EndOfStream) {
+ }
+}
+
+void writeLine(OutStream& os, const char* text) {
+ os.writeBytes(text, strlen(text));
+ os.writeBytes("\r\n", 2);
+}
+
+
+// - Write an HTTP-compliant response to the client
+
+
+void
+HTTPServer::Session::writeResponse(int result, const char* text) {
+ char buffer[1024];
+ if (strlen(text) > 512)
+ throw new rdr::Exception("Internal error - HTTP response text too big");
+ sprintf(buffer, "%s %d %s", "HTTP/1.1", result, text);
+ OutStream& os=sock.outStream();
+ writeLine(os, buffer);
+ writeLine(os, "Server: TightVNC/4.0");
+ time_t now = time(0);
+ struct tm* tm = gmtime(&now);
+ strftime(buffer, 1024, "Date: %a, %d %b %Y %H:%M:%S GMT", tm);
+ writeLine(os, buffer);
+ if (lastModified == (time_t)-1 || lastModified == 0)
+ lastModified = now;
+ tm = gmtime(&lastModified);
+ strftime(buffer, 1024, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT", tm);
+ writeLine(os, buffer);
+ if (contentLength != -1) {
+ sprintf(buffer,"Content-Length: %d",contentLength);
+ writeLine(os, buffer);
+ }
+ writeLine(os, "Connection: close");
+ os.writeBytes("Content-Type: ", 14);
+ if (result == 200) {
+ if (!contentType)
+ contentType = guessContentType(uri.buf, "text/html");
+ os.writeBytes(contentType, strlen(contentType));
+ } else {
+ os.writeBytes("text/html", 9);
+ }
+ os.writeBytes("\r\n", 2);
+ writeLine(os, "");
+ if (result != 200) {
+ writeLine(os, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">");
+ writeLine(os, "<HTML><HEAD>");
+ sprintf(buffer, "<TITLE>%d %s</TITLE>", result, text);
+ writeLine(os, buffer);
+ writeLine(os, "</HEAD><BODY><H1>");
+ writeLine(os, text);
+ writeLine(os, "</H1></BODY></HTML>");
+ sock.outStream().flush();
+ }
+}
+
+bool
+HTTPServer::Session::writeResponse(int code) {
+ switch (code) {
+ case 200: writeResponse(code, "OK"); break;
+ case 400: writeResponse(code, "Bad Request"); break;
+ case 404: writeResponse(code, "Not Found"); break;
+ case 501: writeResponse(code, "Not Implemented"); break;
+ default: writeResponse(500, "Unknown Error"); break;
+ };
+
+ // This return code is passed straight out of processHTTP().
+ // true indicates that the request has been completely processed.
+ return true;
+}
+
+// - Main HTTP request processing routine
+
+bool
+HTTPServer::Session::processHTTP() {
+ lastActive = time(0);
+
+ while (sock.inStream().checkNoWait(1)) {
+
+ switch (state) {
+
+ // Reading the Request-Line
+ case ReadRequestLine:
+
+ // Either read a line, or run out of incoming data
+ if (!line.read())
+ return false;
+
+ // We have read a line! Skip it if it's blank
+ if (strlen(line.buf) == 0)
+ continue;
+
+ // The line contains a request to process.
+ {
+ char method[16], path[128], version[16];
+ int matched = sscanf(line.buf, "%15s%127s%15s",
+ method, path, version);
+ if (matched != 3)
+ return writeResponse(400);
+
+ // Store the required "method"
+ if (strcmp(method, "GET") == 0)
+ request = GetRequest;
+ else if (strcmp(method, "HEAD") == 0)
+ request = HeadRequest;
+ else
+ return writeResponse(501);
+
+ // Store the URI to the "document"
+ uri.buf = strDup(path);
+ }
+
+ // Move on to reading the request headers
+ state = ReadHeaders;
+ break;
+
+ // Reading the request headers
+ case ReadHeaders:
+
+ // Try to read a line
+ if (!line.read())
+ return false;
+
+ // Skip headers until we hit a blank line
+ if (strlen(line.buf) != 0)
+ continue;
+
+ // Headers ended - write the response!
+ {
+ CharArray address(sock.getPeerAddress());
+ vlog.info("getting %s for %s", uri.buf, address.buf);
+ contentLength = -1;
+ lastModified = -1;
+ InStream* data = server.getFile(uri.buf, &contentType, &contentLength,
+ &lastModified);
+ if (!data)
+ return writeResponse(404);
+
+ try {
+ writeResponse(200);
+ if (request == GetRequest)
+ copyStream(*data, sock.outStream());
+ sock.outStream().flush();
+ } catch (rdr::Exception& e) {
+ vlog.error("error writing HTTP document:%s", e.str());
+ }
+ delete data;
+ }
+
+ // The operation is complete!
+ return true;
+
+ default:
+ throw rdr::Exception("invalid HTTPSession state!");
+ };
+
+ }
+
+ // Indicate that we're still processing the HTTP request.
+ return false;
+}
+
+int HTTPServer::Session::checkIdleTimeout() {
+ time_t now = time(0);
+ int timeout = (lastActive + idleTimeoutSecs) - now;
+ if (timeout > 0)
+ return secsToMillis(timeout);
+ sock.shutdown();
+ return 0;
+}
+
+// -=- Constructor / destructor
+
+HTTPServer::HTTPServer() {
+}
+
+HTTPServer::~HTTPServer() {
+ std::list<Session*>::iterator i;
+ for (i=sessions.begin(); i!=sessions.end(); i++)
+ delete *i;
+}
+
+
+// -=- SocketServer interface implementation
+
+void
+HTTPServer::addSocket(network::Socket* sock, bool) {
+ Session* s = new Session(*sock, *this);
+ if (!s) {
+ sock->shutdown();
+ } else {
+ sock->inStream().setTimeout(clientWaitTimeMillis);
+ sock->outStream().setTimeout(clientWaitTimeMillis);
+ sessions.push_front(s);
+ }
+}
+
+void
+HTTPServer::removeSocket(network::Socket* sock) {
+ std::list<Session*>::iterator i;
+ for (i=sessions.begin(); i!=sessions.end(); i++) {
+ if ((*i)->getSock() == sock) {
+ delete *i;
+ sessions.erase(i);
+ return;
+ }
+ }
+}
+
+void
+HTTPServer::processSocketEvent(network::Socket* sock) {
+ std::list<Session*>::iterator i;
+ for (i=sessions.begin(); i!=sessions.end(); i++) {
+ if ((*i)->getSock() == sock) {
+ try {
+ if ((*i)->processHTTP()) {
+ vlog.info("completed HTTP request");
+ sock->shutdown();
+ }
+ } catch (rdr::Exception& e) {
+ vlog.error("untrapped: %s", e.str());
+ sock->shutdown();
+ }
+ return;
+ }
+ }
+ throw rdr::Exception("invalid Socket in HTTPServer");
+}
+
+void HTTPServer::getSockets(std::list<network::Socket*>* sockets)
+{
+ sockets->clear();
+ std::list<Session*>::iterator ci;
+ for (ci = sessions.begin(); ci != sessions.end(); ci++) {
+ sockets->push_back((*ci)->getSock());
+ }
+}
+
+int HTTPServer::checkTimeouts() {
+ std::list<Session*>::iterator ci;
+ int timeout = 0;
+ for (ci = sessions.begin(); ci != sessions.end(); ci++) {
+ soonestTimeout(&timeout, (*ci)->checkIdleTimeout());
+ }
+ return timeout;
+}
+
+
+// -=- Default getFile implementation
+
+InStream*
+HTTPServer::getFile(const char* name, const char** contentType,
+ int* contentLength, time_t* lastModified)
+{
+ return 0;
+}
+
+const char*
+HTTPServer::guessContentType(const char* name, const char* defType) {
+ CharArray file, ext;
+ if (!strSplit(name, '.', &file.buf, &ext.buf))
+ return defType;
+ if (strcasecmp(ext.buf, "html") == 0 ||
+ strcasecmp(ext.buf, "htm") == 0) {
+ return "text/html";
+ } else if (strcasecmp(ext.buf, "txt") == 0) {
+ return "text/plain";
+ } else if (strcasecmp(ext.buf, "gif") == 0) {
+ return "image/gif";
+ } else if (strcasecmp(ext.buf, "jpg") == 0) {
+ return "image/jpeg";
+ } else if (strcasecmp(ext.buf, "jar") == 0) {
+ return "application/java-archive";
+ } else if (strcasecmp(ext.buf, "exe") == 0) {
+ return "application/octet-stream";
+ }
+ return defType;
+}