input.js 9.3 KB

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