You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HTTPServer.cxx 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. /* 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. #include <rfb/HTTPServer.h>
  19. #include <rfb/LogWriter.h>
  20. #include <rfb/util.h>
  21. #include <rdr/MemOutStream.h>
  22. #include <time.h>
  23. // *** Shouldn't really link against this - only for ClientWaitTimeMillis
  24. // and IdleTimeout
  25. #include <rfb/ServerCore.h>
  26. #ifdef WIN32
  27. #define strcasecmp _stricmp
  28. #endif
  29. using namespace rfb;
  30. using namespace rdr;
  31. static LogWriter vlog("HTTPServer");
  32. //
  33. // -=- LineReader
  34. // Helper class which is repeatedly called until a line has been read
  35. // (lines end in \n or \r\n).
  36. // Returns true when line complete, and resets internal state so that
  37. // next read() call will start reading a new line.
  38. // Only one buffer is kept - process line before reading next line!
  39. //
  40. class LineReader : public CharArray {
  41. public:
  42. LineReader(InStream& is_, int l)
  43. : CharArray(l), is(is_), pos(0), len(l), bufferOverrun(false) {}
  44. // Returns true if line complete, false otherwise
  45. bool read() {
  46. while (is.checkNoWait(1)) {
  47. char c = is.readU8();
  48. if (c == '\n') {
  49. if (pos && (buf[pos-1] == '\r'))
  50. pos--;
  51. bufferOverrun = false;
  52. buf[pos++] = 0;
  53. pos = 0;
  54. return true;
  55. }
  56. if (pos == (len-1)) {
  57. bufferOverrun = true;
  58. buf[pos] = 0;
  59. return true;
  60. }
  61. buf[pos++] = c;
  62. }
  63. return false;
  64. }
  65. bool didBufferOverrun() const {return bufferOverrun;}
  66. protected:
  67. InStream& is;
  68. int pos, len;
  69. bool bufferOverrun;
  70. };
  71. //
  72. // -=- HTTPServer::Session
  73. // Manages the internal state for an HTTP session.
  74. // processHTTP returns true when request has completed,
  75. // indicating that socket & session data can be deleted.
  76. //
  77. class rfb::HTTPServer::Session {
  78. public:
  79. Session(network::Socket& s, rfb::HTTPServer& srv)
  80. : contentType(0), line(s.inStream(), 256), sock(s),
  81. server(srv), state(ReadRequestLine), lastActive(time(0)) {
  82. }
  83. ~Session() {
  84. }
  85. void writeResponse(int result, const char* text);
  86. bool writeResponse(int code);
  87. bool processHTTP();
  88. network::Socket* getSock() const {return &sock;}
  89. int checkIdleTimeout();
  90. protected:
  91. CharArray uri;
  92. const char* contentType;
  93. LineReader line;
  94. network::Socket& sock;
  95. rfb::HTTPServer& server;
  96. enum {ReadRequestLine, ReadHeaders, WriteResponse} state;
  97. enum {GetRequest, HeadRequest} request;
  98. time_t lastActive;
  99. };
  100. // - Internal helper routines
  101. void
  102. copyStream(InStream& is, OutStream& os) {
  103. try {
  104. while (1) {
  105. os.writeU8(is.readU8());
  106. }
  107. } catch (rdr::EndOfStream) {
  108. }
  109. }
  110. void writeLine(OutStream& os, const char* text) {
  111. os.writeBytes(text, strlen(text));
  112. os.writeBytes("\r\n", 2);
  113. }
  114. // - Write an HTTP-compliant response to the client
  115. void
  116. HTTPServer::Session::writeResponse(int result, const char* text) {
  117. char buffer[1024];
  118. if (strlen(text) > 512)
  119. throw new rdr::Exception("Internal error - HTTP response text too big");
  120. sprintf(buffer, "%s %d %s", "HTTP/1.1", result, text);
  121. OutStream& os=sock.outStream();
  122. writeLine(os, buffer);
  123. writeLine(os, "Server: RealVNC/4.0");
  124. writeLine(os, "Connection: close");
  125. os.writeBytes("Content-Type: ", 14);
  126. if (result == 200) {
  127. if (!contentType)
  128. contentType = guessContentType(uri.buf, "text/html");
  129. os.writeBytes(contentType, strlen(contentType));
  130. } else {
  131. os.writeBytes("text/html", 9);
  132. }
  133. os.writeBytes("\r\n", 2);
  134. writeLine(os, "");
  135. if (result != 200) {
  136. writeLine(os, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">");
  137. writeLine(os, "<HTML><HEAD>");
  138. sprintf(buffer, "<TITLE>%d %s</TITLE>", result, text);
  139. writeLine(os, buffer);
  140. writeLine(os, "</HEAD><BODY><H1>");
  141. writeLine(os, text);
  142. writeLine(os, "</H1></BODY></HTML>");
  143. sock.outStream().flush();
  144. }
  145. }
  146. bool
  147. HTTPServer::Session::writeResponse(int code) {
  148. switch (code) {
  149. case 200: writeResponse(code, "OK"); break;
  150. case 400: writeResponse(code, "Bad Request"); break;
  151. case 404: writeResponse(code, "Not Found"); break;
  152. case 501: writeResponse(code, "Not Implemented"); break;
  153. default: writeResponse(500, "Unknown Error"); break;
  154. };
  155. // This return code is passed straight out of processHTTP().
  156. // true indicates that the request has been completely processed.
  157. return true;
  158. }
  159. // - Main HTTP request processing routine
  160. bool
  161. HTTPServer::Session::processHTTP() {
  162. lastActive = time(0);
  163. while (sock.inStream().checkNoWait(1)) {
  164. switch (state) {
  165. // Reading the Request-Line
  166. case ReadRequestLine:
  167. // Either read a line, or run out of incoming data
  168. if (!line.read())
  169. return false;
  170. // We have read a line! Skip it if it's blank
  171. if (strlen(line.buf) == 0)
  172. continue;
  173. // The line contains a request to process.
  174. {
  175. char method[16], path[128], version[16];
  176. int matched = sscanf(line.buf, "%15s%127s%15s",
  177. method, path, version);
  178. if (matched != 3)
  179. return writeResponse(400);
  180. // Store the required "method"
  181. if (strcmp(method, "GET") == 0)
  182. request = GetRequest;
  183. else if (strcmp(method, "HEAD") == 0)
  184. request = HeadRequest;
  185. else
  186. return writeResponse(501);
  187. // Store the URI to the "document"
  188. uri.buf = strDup(path);
  189. }
  190. // Move on to reading the request headers
  191. state = ReadHeaders;
  192. break;
  193. // Reading the request headers
  194. case ReadHeaders:
  195. // Try to read a line
  196. if (!line.read())
  197. return false;
  198. // Skip headers until we hit a blank line
  199. if (strlen(line.buf) != 0)
  200. continue;
  201. // Headers ended - write the response!
  202. {
  203. CharArray address(sock.getPeerAddress());
  204. vlog.info("getting %s for %s", uri.buf, address.buf);
  205. InStream* data = server.getFile(uri.buf, &contentType);
  206. if (!data)
  207. return writeResponse(404);
  208. try {
  209. writeResponse(200);
  210. if (request == GetRequest)
  211. copyStream(*data, sock.outStream());
  212. sock.outStream().flush();
  213. } catch (rdr::Exception& e) {
  214. vlog.error("error writing HTTP document:%s", e.str());
  215. }
  216. delete data;
  217. }
  218. // The operation is complete!
  219. return true;
  220. default:
  221. throw rdr::Exception("invalid HTTPSession state!");
  222. };
  223. }
  224. // Indicate that we're still processing the HTTP request.
  225. return false;
  226. }
  227. int HTTPServer::Session::checkIdleTimeout() {
  228. time_t now = time(0);
  229. int timeout = (lastActive + rfb::Server::idleTimeout) - now;
  230. if (timeout > 0)
  231. return timeout * 1000;
  232. sock.shutdown();
  233. return 0;
  234. }
  235. // -=- Constructor / destructor
  236. HTTPServer::HTTPServer() {
  237. }
  238. HTTPServer::~HTTPServer() {
  239. std::list<Session*>::iterator i;
  240. for (i=sessions.begin(); i!=sessions.end(); i++) {
  241. delete (*i)->getSock();
  242. delete *i;
  243. }
  244. }
  245. // -=- SocketServer interface implementation
  246. void
  247. HTTPServer::addClient(network::Socket* sock) {
  248. Session* s = new Session(*sock, *this);
  249. if (!s) {
  250. sock->shutdown();
  251. } else {
  252. sock->inStream().setTimeout(rfb::Server::clientWaitTimeMillis);
  253. sock->outStream().setTimeout(rfb::Server::clientWaitTimeMillis);
  254. sessions.push_front(s);
  255. }
  256. }
  257. bool
  258. HTTPServer::processSocketEvent(network::Socket* sock) {
  259. std::list<Session*>::iterator i;
  260. for (i=sessions.begin(); i!=sessions.end(); i++) {
  261. if ((*i)->getSock() == sock) {
  262. try {
  263. if ((*i)->processHTTP()) {
  264. vlog.info("completed HTTP request");
  265. delete *i;
  266. sessions.erase(i);
  267. break;
  268. }
  269. return true;
  270. } catch (rdr::Exception& e) {
  271. vlog.error("untrapped: %s", e.str());
  272. delete *i;
  273. sessions.erase(i);
  274. break;
  275. }
  276. }
  277. }
  278. delete sock;
  279. return false;
  280. }
  281. void HTTPServer::getSockets(std::list<network::Socket*>* sockets)
  282. {
  283. sockets->clear();
  284. std::list<Session*>::iterator ci;
  285. for (ci = sessions.begin(); ci != sessions.end(); ci++) {
  286. sockets->push_back((*ci)->getSock());
  287. }
  288. }
  289. int HTTPServer::checkTimeouts() {
  290. std::list<Session*>::iterator ci;
  291. int timeout = 0;
  292. for (ci = sessions.begin(); ci != sessions.end(); ci++) {
  293. soonestTimeout(&timeout, (*ci)->checkIdleTimeout());
  294. }
  295. return timeout;
  296. }
  297. // -=- Default getFile implementation
  298. InStream*
  299. HTTPServer::getFile(const char* name, const char** contentType) {
  300. return 0;
  301. }
  302. const char*
  303. HTTPServer::guessContentType(const char* name, const char* defType) {
  304. CharArray file, ext;
  305. if (!strSplit(name, '.', &file.buf, &ext.buf))
  306. return defType;
  307. if (strcasecmp(ext.buf, "html") == 0 ||
  308. strcasecmp(ext.buf, "htm") == 0) {
  309. return "text/html";
  310. } else if (strcasecmp(ext.buf, "txt") == 0) {
  311. return "text/plain";
  312. } else if (strcasecmp(ext.buf, "gif") == 0) {
  313. return "image/gif";
  314. } else if (strcasecmp(ext.buf, "jpg") == 0) {
  315. return "image/jpeg";
  316. } else if (strcasecmp(ext.buf, "jar") == 0) {
  317. return "application/java-archive";
  318. } else if (strcasecmp(ext.buf, "exe") == 0) {
  319. return "application/octet-stream";
  320. }
  321. return defType;
  322. }