Simple image host.
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.

context.js 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. var formidable = require("formidable");
  2. var crypto = require("crypto");
  3. var zlib = require("zlib");
  4. var fs = require("fs");
  5. var log = require("mlogger");
  6. var preprocess = require("./preprocess.js");
  7. var sessions = {};
  8. var sessionTimeouts = {};
  9. if (fs.existsSync(".sessions")) {
  10. sessions = JSON.parse(fs.readFileSync(".sessions", "utf8"));
  11. //Set appropriate timeouts.
  12. Object.keys(sessions).forEach(function(i) {
  13. sessionTimeouts[i] = setTimeout(function() {
  14. delete sessions[i];
  15. }, 1000*60*10);
  16. });
  17. }
  18. function saveSessions() {
  19. fs.writeFileSync(".sessions", JSON.stringify(sessions));
  20. process.exit();
  21. }
  22. process.on("SIGINT", saveSessions);
  23. process.on("SIGTERM", saveSessions);
  24. function templatify(str, args, ctx, env) {
  25. env.url = ctx.req.url;
  26. var env = {
  27. session: function(key) {
  28. ctx.cachable = false;
  29. return ctx.session[key];
  30. },
  31. arg: function(key) {
  32. return ctx.htmlEntities(args[key]);
  33. },
  34. noescape: args,
  35. env: env,
  36. template: function(key) {
  37. return ctx.template(key, args);
  38. }
  39. }
  40. str = preprocess(str, env);
  41. return str;
  42. }
  43. var cache = {};
  44. module.exports = function(options) {
  45. this.req = options.req;
  46. this.res = options.res;
  47. this.templates = options.templates;
  48. this.views = options.views;
  49. this.db = options.db;
  50. this.conf = options.conf;
  51. this.query = this.req.url.split("?")[1] || "";
  52. this.shouldGzip = /gzip/.test(this.req.headers["accept-encoding"]);
  53. this.cachable = true;
  54. this.statusCode = 200;
  55. this.headers = {};
  56. if (this.conf.debug)
  57. this.startTime = new Date();
  58. //Handle cookies
  59. this.cookies = {};
  60. this.req.headers.cookie = this.req.headers.cookie || "";
  61. this.req.headers.cookie.split(/;\s*/).forEach(function(elem) {
  62. var pair = elem.split("=");
  63. this.cookies[pair[0]] = decodeURIComponent(pair[1]);
  64. }.bind(this));
  65. //Handle sessions
  66. var key;
  67. if (sessions[this.cookies.session]) {
  68. key = this.cookies.session;
  69. } else {
  70. do {
  71. key = crypto.randomBytes(64).toString("hex");
  72. } while (sessions[key]);
  73. sessions[key] = {};
  74. this.res.setHeader("Set-Cookie", "session="+key);
  75. }
  76. this.session = sessions[key];
  77. //Reset session delete timer
  78. if (sessionTimeouts[key])
  79. clearTimeout(sessionTimeouts[key]);
  80. sessionTimeouts[key] = setTimeout(function() {
  81. delete sessions[key];
  82. }, this.conf.session_timeout);
  83. }
  84. module.exports.prototype = {
  85. _end: function(str) {
  86. if (this.conf.debug) {
  87. var ms = (new Date().getTime() - this.startTime.getTime());
  88. log.info(
  89. "Request:\t"+
  90. ms+" millisecond(s)\t"+
  91. (this.statusCode || 200)+"\t"+
  92. this.req.url
  93. );
  94. } else {
  95. log.info("Request: "+this.req.url);
  96. }
  97. this.res.writeHead(this.statusCode, this.headers);
  98. this.res.end(str);
  99. },
  100. end: function(str, alreadyGzipped) {
  101. if (!this.shouldGzip)
  102. return this._end(str);
  103. this.setHeader("Content-Encoding", "gzip");
  104. if (alreadyGzipped)
  105. return this._end(str);
  106. zlib.gzip(str, function(err, res) {
  107. if (err)
  108. throw err;
  109. this._end(res);
  110. }.bind(this));
  111. },
  112. succeed: function(obj) {
  113. obj = obj || {};
  114. obj.success = true;
  115. this.setHeader("Content-Type", "application/json");
  116. this.end(JSON.stringify(obj));
  117. },
  118. fail: function(err) {
  119. log.info("Sending error to client:");
  120. log.info(err);
  121. this.setHeader("Content-Type", "application/json");
  122. obj = {};
  123. obj.success = false;
  124. obj.error = err.toString();
  125. this.end(JSON.stringify(obj));
  126. },
  127. err404: function() {
  128. this.setStatus(404);
  129. this.end(this.view("404"));
  130. },
  131. template: function(name, args) {
  132. var str = this.templates[name];
  133. if (!str)
  134. throw new Error("No such template: "+name);
  135. return templatify(str, args, this, {template: name});
  136. },
  137. view: function(name, args) {
  138. var str = this.views[name];
  139. if (!str)
  140. throw new Error("No such view: "+name);
  141. return templatify(str, args, this, {view: name});
  142. },
  143. getPostData: function(cb) {
  144. if (this.postData)
  145. return cb(null, this.postData.data, this.postData.files);
  146. if (this.req.method.toUpperCase() != "POST")
  147. return cb(new Error("Expected POST request, got "+this.req.method));
  148. var form = new formidable.IncomingForm();
  149. form.parse(this.req, function(err, data, files) {
  150. if (err) return cb(err);
  151. this.postData = {
  152. data: data,
  153. files: files
  154. }
  155. cb(null, data, files);
  156. }.bind(this));
  157. },
  158. setStatus: function(code) {
  159. this.statusCode = code;
  160. },
  161. setHeader: function(key, val) {
  162. this.headers[key] = val;
  163. },
  164. login: function(username, id) {
  165. this.session.loggedIn = true;
  166. this.session.username = username;
  167. this.session.userId = id;
  168. },
  169. logout: function() {
  170. this.session.loggedIn = false;
  171. delete this.session.username;
  172. delete this.session.userId;
  173. },
  174. async: function(n, cb) {
  175. if (typeof n !== "number")
  176. throw new Error("Expected number, got "+typeof n);
  177. if (n < 1)
  178. return cb();
  179. var res = {};
  180. var errs = {};
  181. var errnum = 0;
  182. return function(key, val, err) {
  183. if (key)
  184. res[key] = val;
  185. if (err)
  186. errs[key] = err;
  187. if (n === 1)
  188. cb((errnum ? errs : null), res);
  189. else
  190. n -= 1;
  191. }
  192. },
  193. htmlEntities: function(arg) {
  194. if (typeof arg === "string") {
  195. return arg.replace(/&/g, "&amp;")
  196. .replace(/</g, "&lt;")
  197. .replace(/>/g, "&gt;")
  198. .replace(/"/g, "&quot;");
  199. } else {
  200. return arg;
  201. }
  202. }
  203. }