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 11KB

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