server.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. var crypto = require('crypto');
  2. var fs = require('fs');
  3. var path = require('path');
  4. var Busboy = require('busboy');
  5. var express = require('express');
  6. var http = require('http');
  7. var https = require('https');
  8. var request = require('request');
  9. var tmp = require('tmp');
  10. // Different headers can be pushed depending on data format
  11. // to allow for changes with backwards compatibility
  12. var UP1_HEADERS = {
  13. v1: new Buffer("UP1\0", 'binary')
  14. }
  15. function handle_upload(req, res) {
  16. var config = req.app.locals.config
  17. var busboy = new Busboy({
  18. headers: req.headers,
  19. limits: {
  20. fileSize: config.maximum_file_size,
  21. files: 1,
  22. parts: 3
  23. }
  24. });
  25. var fields = {};
  26. var tmpfname = null;
  27. busboy.on('field', function(fieldname, value) {
  28. fields[fieldname] = value;
  29. });
  30. busboy.on('file', function(fieldname, file, filename) {
  31. try {
  32. var ftmp = tmp.fileSync({ postfix: '.tmp', dir: req.app.locals.config.path.i, keep: true });
  33. tmpfname = ftmp.name;
  34. var fstream = fs.createWriteStream('', {fd: ftmp.fd, defaultEncoding: 'binary'});
  35. fstream.write(UP1_HEADERS.v1);
  36. file.pipe(fstream);
  37. } catch (err) {
  38. console.error("Error on file:", err);
  39. res.send("Internal Server Error");
  40. req.unpipe(busboy);
  41. res.close();
  42. }
  43. });
  44. busboy.on('finish', function() {
  45. try {
  46. if (!tmpfname) {
  47. res.send("Internal Server Error");
  48. } else if (fields.api_key !== config['api_key']) {
  49. res.send('{"error": "API key doesn\'t match", "code": 2}');
  50. } else if (!fields.ident) {
  51. res.send('{"error": "Ident not provided", "code": 11}');
  52. } else if (fields.ident.length !== 22) {
  53. res.send('{"error": "Ident length is incorrect", "code": 3}');
  54. } else if (ident_exists(fields.ident)) {
  55. res.send('{"error": "Ident is already taken.", "code": 4}');
  56. } else {
  57. var delhmac = crypto.createHmac('sha256', config.delete_key)
  58. .update(fields.ident)
  59. .digest('hex');
  60. fs.rename(tmpfname, ident_path(fields.ident), function() {
  61. res.json({delkey: delhmac});
  62. });
  63. }
  64. } catch (err) {
  65. console.error("Error on finish:", err);
  66. res.send("Internal Server Error");
  67. }
  68. });
  69. return req.pipe(busboy);
  70. };
  71. function handle_delete(req, res) {
  72. var config = req.app.locals.config
  73. if (!req.query.ident) {
  74. res.send('{"error": "Ident not provided", "code": 11}');
  75. return;
  76. }
  77. if (!req.query.delkey) {
  78. res.send('{"error": "Delete key not provided", "code": 12}');
  79. return;
  80. }
  81. var delhmac = crypto.createHmac('sha256', config.delete_key)
  82. .update(req.query.ident)
  83. .digest('hex');
  84. if (req.query.ident.length !== 22) {
  85. res.send('{"error": "Ident length is incorrect", "code": 3}');
  86. } else if (delhmac !== req.query.delkey) {
  87. res.send('{"error": "Incorrect delete key", "code": 10}');
  88. } else if (!ident_exists(req.query.ident)) {
  89. res.send('{"error": "Ident does not exist", "code": 9}');
  90. } else {
  91. fs.unlink(ident_path(req.query.ident), function() {
  92. cf_invalidate(req.query.ident, config);
  93. res.redirect('/');
  94. });
  95. }
  96. };
  97. function ident_path(ident) {
  98. return '../i/' + path.basename(ident);
  99. }
  100. function ident_exists(ident) {
  101. try {
  102. fs.lstatSync(ident_path(ident));
  103. return true;
  104. } catch (err) {
  105. return false;
  106. }
  107. }
  108. function cf_do_invalidate(ident, mode, cfconfig) {
  109. var inv_url = mode + '://' + cfconfig.url + '/i/' + ident;
  110. request.post({
  111. url: 'https://www.cloudflare.com/api_json.html',
  112. form: {
  113. a: 'zone_file_purge',
  114. tkn: cfconfig.token,
  115. email: cfconfig.email,
  116. z: cfconfig.domain,
  117. url: inv_url
  118. }
  119. }, function(err, response, body) {
  120. if (err) {
  121. console.error("Cache invalidate failed for", ident);
  122. console.error("Body:", body);
  123. return;
  124. }
  125. try {
  126. var result = JSON.parse(body)
  127. if (result.result === 'error') {
  128. console.error("Cache invalidate failed for", ident);
  129. console.error("Message:", msg);
  130. }
  131. } catch(err) {}
  132. });
  133. }
  134. function cf_invalidate(ident, config) {
  135. var cfconfig = config['cloudflare-cache-invalidate']
  136. if (!cfconfig.enabled) {
  137. return;
  138. }
  139. if (config.http.enabled)
  140. cf_do_invalidate(ident, 'http', cfconfig);
  141. if (config.https.enabled)
  142. cf_do_invalidate(ident, 'https', cfconfig);
  143. }
  144. function create_app(config) {
  145. var app = express();
  146. app.locals.config = config
  147. app.use('', express.static(config.path.client));
  148. app.use('/i', express.static(config.path.i));
  149. app.post('/up', handle_upload);
  150. app.get('/del', handle_delete);
  151. return app
  152. }
  153. /* Convert an IP:port string to a split IP and port */
  154. function get_addr_port(s) {
  155. var spl = s.split(":");
  156. if (spl.length === 1)
  157. return { host: spl[0], port: 80 };
  158. else if (spl[0] === '')
  159. return { port: parseInt(spl[1]) };
  160. else
  161. return { host: spl[0], port: parseInt(spl[1]) };
  162. }
  163. function serv(server, serverconfig, callback) {
  164. var ap = get_addr_port(serverconfig.listen);
  165. return server.listen(ap.port, ap.host, callback);
  166. }
  167. function init_defaults(config) {
  168. config.path = config.path ? config.path : {};
  169. config.path.i = config.path.i ? config.path.i : "../i";
  170. config.path.client = config.path.client ? config.path.client : "../client";
  171. }
  172. function init(config) {
  173. init_defaults(config)
  174. var app = create_app(config);
  175. if (config.http.enabled) {
  176. serv(http.createServer(app), config.http, function() {
  177. console.info('Started server at http://%s:%s', this.address().address, this.address().port);
  178. });
  179. }
  180. if (config.https.enabled) {
  181. var sec_creds = {
  182. key: fs.readFileSync(config.https.key),
  183. cert: fs.readFileSync(config.https.cert)
  184. };
  185. serv(https.createServer(sec_creds, app), config.https, function() {
  186. console.info('Started server at https://%s:%s', this.address().address, this.address().port);
  187. });
  188. }
  189. }
  190. function main(configpath) {
  191. init(JSON.parse(fs.readFileSync(configpath)));
  192. }
  193. main('./server.conf')