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;
+}