123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- /* Copyright (C) 2002-2004 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>
- #include <time.h>
-
- // *** Shouldn't really link against this - only for ClientWaitTimeMillis
- // and IdleTimeout
- #include <rfb/ServerCore.h>
-
- #ifdef WIN32
- #define strcasecmp _stricmp
- #endif
-
-
- using namespace rfb;
- using namespace rdr;
-
- static LogWriter vlog("HTTPServer");
-
-
- //
- // -=- 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), 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;
- 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: RealVNC/4.0");
- 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);
- InStream* data = server.getFile(uri.buf, &contentType);
- 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 + rfb::Server::idleTimeout) - now;
- if (timeout > 0)
- return timeout * 1000;
- 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)->getSock();
- delete *i;
- }
- }
-
-
- // -=- SocketServer interface implementation
-
- void
- HTTPServer::addClient(network::Socket* sock) {
- Session* s = new Session(*sock, *this);
- if (!s) {
- sock->shutdown();
- } else {
- sock->inStream().setTimeout(rfb::Server::clientWaitTimeMillis);
- sock->outStream().setTimeout(rfb::Server::clientWaitTimeMillis);
- sessions.push_front(s);
- }
- }
-
- bool
- 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");
- delete *i;
- sessions.erase(i);
- break;
- }
- return true;
- } catch (rdr::Exception& e) {
- vlog.error("untrapped: %s", e.str());
- delete *i;
- sessions.erase(i);
- break;
- }
- }
- }
- delete sock;
- return false;
- }
-
- 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) {
- 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;
- }
|