input.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. const whiskers = require('whiskers');
  2. const fs = require('fs');
  3. const OriginModel = require('../models/origin.js').OriginModel;
  4. const ApiKeyModel = require('../models/apiKey.js').ApiKeyModel;
  5. const PasteContent = require('../models/pasteContent.js').PasteContent;
  6. const AccessModel = require('../models/access.js').AccessModel;
  7. const mCrypto = require('../src/crypto.js');
  8. const Security = require('../src/security.js');
  9. const CONFIG = require('../src/config.js');
  10. async function onAccessContent(app, req, entity) {
  11. const rel = "" + (req.body.rel || req.body.r || "");
  12. if (entity.privId !== req.params.id) {
  13. let accessEntry = new AccessModel(entity.privId, rel, Security.getRequestIp(req));
  14. await accessEntry.resolveIp();
  15. await app.databaseHelper.insertOne(accessEntry);
  16. }
  17. }
  18. async function renderRawPage(app, req, res, entity) {
  19. await onAccessContent(app, req, entity);
  20. if (entity.type === 'paste')
  21. return await app.routerUtils.staticServe(res, app.getData(entity.privId));
  22. else if (entity.type === 'file') {
  23. let data = JSON.parse(entity.data);
  24. return await app.routerUtils.staticDownload(res, app.getData(entity.privId), data.name, data.type);
  25. }
  26. else if (entity.type === 'short')
  27. return res.end(entity.data); // FIXME stats + ?rel
  28. app.routerUtils.onInternalError(res, "Unknown type: " +entity.type);
  29. }
  30. async function renderPublicPage(app, req, res, entity) {
  31. await onAccessContent(app, req, entity);
  32. if (entity.type === 'paste')
  33. return await app.routerUtils.staticServe(res, app.getData(entity.privId));
  34. else if (entity.type === 'file') {
  35. let data = JSON.parse(entity.data);
  36. // FIXME viewer
  37. return await app.routerUtils.staticDownload(res, app.getData(entity.privId), data.name, data.type);
  38. }
  39. else if (entity.type === 'short')
  40. return app.routerUtils.redirect(res, entity.data);
  41. app.routerUtils.onInternalError(res, "Unknown type: " +entity.type);
  42. }
  43. async function _readAccess(app, entityId) {
  44. return (await app.databaseHelper.fetch(AccessModel, { privId: entityId }))
  45. .map(x => x.describe())
  46. .map(x => { delete x.ipAddress; delete x.privId; x.ipRegion = JSON.parse(x.ipRegion); return x; });
  47. }
  48. async function renderPrivatePage(app, req, res, entity) {
  49. let stat;
  50. try { stat = fs.statSync(app.dataDir+entity.privId); } catch (e) { stat = { error: e }; }
  51. const access = await _readAccess(app, entity.privId);
  52. const origins = await app.databaseHelper.fetch(OriginModel, { privId: entity.privId });
  53. let context = app.routerUtils.commonRenderInfos();
  54. context.page_title += " - Pastit";
  55. entity.createdTime = entity.created.getTime();
  56. entity.expireTime = entity.expire?.getTime() || 0;
  57. res.end(whiskers.render(require('../templates/stats.js'), {
  58. ...context,
  59. ...{size: stat.size || 0},
  60. ...{ access: JSON.stringify(access)},
  61. ...{entity: entity},
  62. ...{origins: JSON.stringify(origins.map(i => i.name))}
  63. }));
  64. }
  65. module.exports = { register: app => {
  66. // Root
  67. app.router.get("/", (req, res) => {
  68. app.routerUtils.redirect(res, '/pastit');
  69. });
  70. // Access page
  71. app.router.get("/x/:id", async (req, res) => {
  72. let entity = await app.databaseHelper.findOne(PasteContent, { privId: req.params.id, publicId: req.params.id }, " or ");
  73. if (entity && entity.privId === req.params.id)
  74. return renderPrivatePage(app, req, res, entity);
  75. if (entity && !entity.expired)
  76. return renderPublicPage(app, req, res, entity);
  77. app.routerUtils.onPageNotFound(res);
  78. });
  79. app.router.get("/x/raw/:id", async (req, res) => {
  80. let entity = await app.databaseHelper.findOne(PasteContent, { privId: req.params.id, publicId: req.params.id }, " or ");
  81. if (entity && !entity.expired)
  82. return renderRawPage(app, req, res, entity);
  83. app.routerUtils.onPageNotFound(res);
  84. });
  85. // pastebin tool
  86. app.router.get("/pastit", (req, res) => {
  87. let context = app.routerUtils.commonRenderInfos();
  88. context.page_title += " - Pastit";
  89. res.end(whiskers.render(require('../templates/pastit.js'), context));
  90. });
  91. app.router.post("/pastit", async (req, res) => {
  92. const content = "" + (req.body.content || req.post);
  93. const privId = mCrypto.string(content);
  94. if (!CONFIG.reCaptchaSecret || req.body['g-recaptcha-response']) {
  95. const captchaOk = await Security.captchaCheck(req.body['g-recaptcha-response'], Security.getRequestIp(req));
  96. if (!captchaOk)
  97. return app.routerUtils.jsonResponse(res, { err: "Invalid captcha input", id: null });
  98. } else if (req.body['apiKey']) {
  99. if (!(await app.databaseHelper.findOne(ApiKeyModel, { apiKey: req.body['apiKey'] })))
  100. return app.routerUtils.jsonResponse(res, { err: "Unauthorized access", id: null });
  101. } else {
  102. return app.routerUtils.jsonResponse(res, { err: "Unauthorized access", id: null });
  103. }
  104. let entity = await app.databaseHelper.findOne(PasteContent, { privId: privId });
  105. if (!content || !content.length)
  106. return app.routerUtils.jsonResponse(res, { err: "Empty input", id: null });
  107. if (content.length > CONFIG.maxPastebinSize)
  108. return app.routerUtils.jsonResponse(res, { err: "Input size is too large", id: null });
  109. if (entity && !entity.expired) {
  110. entity.renew();
  111. await app.databaseHelper.update({privId: privId}, entity);
  112. } else {
  113. entity = entity || new PasteContent(privId, "paste", Security.getRequestIp(req));
  114. entity.expired = false;
  115. entity.apiKey = req.body['apiKey'] || null;
  116. entity.renew();
  117. fs.writeFileSync(app.getData(privId), content);
  118. await app.databaseHelper.upsertOne(entity);
  119. }
  120. if (req.body.apiKey)
  121. res.end(CONFIG.url+"/x/" +entity.publicId+"\r\n");
  122. else
  123. app.routerUtils.jsonResponse(res, { err: null, id: entity.publicId });
  124. });
  125. // URL shortener tool
  126. app.router.get("/short", (req, res) => {
  127. let context = app.routerUtils.commonRenderInfos();
  128. context.page_title += " - Shortener";
  129. res.end(whiskers.render(require('../templates/short.js'), context));
  130. });
  131. app.router.post("/short", async (req, res) => {
  132. const link = "" + req.body.content;
  133. const privId = mCrypto.string(await app.databaseHelper.count(PasteContent) + link);
  134. const captchaOk = await Security.captchaCheck(req.body['g-recaptcha-response'], Security.getRequestIp(req));
  135. if (!captchaOk)
  136. return app.routerUtils.jsonResponse(res, { err: "Invalid captcha input", id: null });
  137. if (!link || !link.length)
  138. return app.routerUtils.jsonResponse(res, { err: "Empty input", id: null });
  139. if (link.length > CONFIG.maxUrlSize)
  140. return app.routerUtils.jsonResponse(res, { err: "Input size is too large", id: null });
  141. entity = new PasteContent(privId, "short", Security.getRequestIp(req));
  142. entity.data = link;
  143. await app.databaseHelper.insertOne(entity);
  144. app.routerUtils.jsonResponse(res, { err: null, id: entity.privId });
  145. });
  146. // Files tool
  147. app.router.get("/files", (req, res) => {
  148. let context = app.routerUtils.commonRenderInfos();
  149. context.page_title += " - File host";
  150. res.end(whiskers.render(require('../templates/files.js'), context));
  151. });
  152. app.router.post("/files", async (req, res) => {
  153. const formData = (req.body["multipart-data"] || []).reduce((res, x) => { res[x.fieldName] = x; return res; }, {});
  154. const privId = mCrypto.string(await app.databaseHelper.count(PasteContent) + (req.content?.fileName || ""));
  155. const captchaOk = await Security.captchaCheck(formData["g-recaptcha-response"]?.fileData, req.headers['x-forwarded-for'] || req.socket.remoteAddress);
  156. if (!captchaOk)
  157. return app.routerUtils.jsonResponse(res, { err: "Invalid captcha input", id: null });
  158. if (!formData.content?.fileData || !formData.content.fileData.length)
  159. return app.routerUtils.jsonResponse(res, { err: "Empty input", id: null });
  160. if (formData.content.fileData.length > CONFIG.maxFileUploadSize)
  161. return app.routerUtils.jsonResponse(res, { err: "Input size is too large", id: null });
  162. const entity = new PasteContent(privId, "file", Security.getRequestIp(req));
  163. entity.data = JSON.stringify({ name: formData.content.fileName, type: formData.content.fileType });
  164. fs.writeFileSync(app.getData(privId), formData.content.fileData, {encoding: formData.content.fileType.indexOf('text') >= 0 ? 'utf8' : 'binary'});
  165. await app.databaseHelper.insertOne(entity);
  166. app.routerUtils.jsonResponse(res, { err: null, id: entity.privId });
  167. });
  168. // API page
  169. app.router.get("/api", (req, res) => {
  170. let context = app.routerUtils.commonRenderInfos();
  171. context.page_title += " - API Usage";
  172. res.end(whiskers.render(require('../templates/api.js'), context));
  173. });
  174. app.router.post("/api", async (req, res) => {
  175. const ipAddress = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
  176. const captchaOk = await Security.captchaCheck(req.body['g-recaptcha-response'], Security.getRequestIp(req));
  177. if (!captchaOk)
  178. return app.routerUtils.jsonResponse(res, { err: "Invalid captcha input", id: null });
  179. const privKey = mCrypto.string(Date.now() + "" + await app.databaseHelper.count(ApiKeyModel) + "SALT_INPUT_API_KEY" +ipAddress);
  180. const model = new ApiKeyModel(privKey, ipAddress);
  181. await app.databaseHelper.insertOne(model);
  182. return app.routerUtils.jsonResponse(res, { err: null, id: privKey });
  183. });
  184. }};