123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- const jsl = require("svjsl");
- const fs = require("fs-extra");
- const http = require("http");
- var httpServer = require("./httpServer"); // module loading order is a bit fucked, so this module has to be loaded multiple times
- const parseJokes = require("./parseJokes");
- const logRequest = require("./logRequest");
- const convertFileFormat = require("./fileFormatConverter");
- const analytics = require("./analytics");
- const parseURL = require("./parseURL");
- const meter = require("./meter");
- const tr = require("./translate");
- const settings = require("../settings");
- const fileFormatConverter = require("./fileFormatConverter");
- jsl.unused(http, analytics, tr);
- /** @typedef {parseJokes.SingleJoke|parseJokes.TwopartJoke} JokeSubmission */
- /** @typedef {import("./types/jokes").Joke} Joke */
- /**
- * To be called when a joke is submitted
- * @param {http.ServerResponse} res
- * @param {String} data
- * @param {String} fileFormat
- * @param {String} ip
- * @param {(analytics.AnalyticsDocsRequest|analytics.AnalyticsSuccessfulRequest|analytics.AnalyticsRateLimited|analytics.AnalyticsError|analytics.AnalyticsSubmission)} analyticsObject
- * @param {Boolean} dryRun Set to true to not add the joke to the joke file after validating it
- */
- function jokeSubmission(res, data, fileFormat, ip, analyticsObject, dryRun)
- {
- try
- {
- if(typeof dryRun != "boolean")
- dryRun = false;
- if(typeof httpServer == "object" && Object.keys(httpServer).length <= 0)
- httpServer = require("./httpServer");
-
- let submittedJoke = JSON.parse(data);
- let langCode = (submittedJoke.lang || settings.languages.defaultLanguage).toString().toLowerCase();
- if(jsl.isEmpty(submittedJoke))
- return httpServer.respondWithError(res, 105, 400, fileFormat, tr(langCode, "requestBodyIsInvalid"), langCode);
-
- let invalidChars = data.match(settings.jokes.submissions.invalidCharRegex);
- let invalidCharsStr = invalidChars ? invalidChars.map(ch => `0x${ch.charCodeAt(0).toString(16)}`).join(", ") : null;
- if(invalidCharsStr && invalidChars.length > 0)
- return httpServer.respondWithError(res, 109, 400, fileFormat, tr(langCode, "invalidChars", invalidCharsStr), langCode);
-
- if(submittedJoke.formatVersion == parseJokes.jokeFormatVersion && submittedJoke.formatVersion == settings.jokes.jokesFormatVersion)
- {
- // format version is correct, validate joke now
- let validationResult = parseJokes.validateSingle(submittedJoke, langCode);
- if(Array.isArray(validationResult))
- return httpServer.respondWithError(res, 105, 400, fileFormat, tr(langCode, "submittedJokeFormatInvalid", validationResult.join("\n")), langCode);
- else if(validationResult === true)
- {
- // joke is valid, find file name and then write to file
- let sanitizedIP = ip.replace(settings.httpServer.ipSanitization.regex, settings.httpServer.ipSanitization.replaceChar).substring(0, 8);
- let curUnix = new Date().getTime();
- let fileName = `${settings.jokes.jokeSubmissionPath}${langCode}/submission_${sanitizedIP}_0_${curUnix}.json`;
- let iter = 0;
- let findNextNum = currentNum => {
- iter++;
- if(iter >= settings.httpServer.rateLimiting)
- {
- logRequest("ratelimited", `IP: ${ip}`, analyticsObject);
- return httpServer.respondWithError(res, 101, 429, fileFormat, tr(langCode, "rateLimited", settings.httpServer.rateLimiting, settings.httpServer.timeFrame));
- }
- if(fs.existsSync(`${settings.jokes.jokeSubmissionPath}submission_${sanitizedIP}_${currentNum}_${curUnix}.json`))
- return findNextNum(currentNum + 1);
- else return currentNum;
- };
- fs.ensureDirSync(`${settings.jokes.jokeSubmissionPath}${langCode}`);
- if(fs.existsSync(`${settings.jokes.jokeSubmissionPath}${fileName}`))
- fileName = `${settings.jokes.jokeSubmissionPath}${langCode}/submission_${sanitizedIP}_${findNextNum()}_${curUnix}.json`;
- try
- {
- // file name was found, write to file now:
- if(dryRun)
- {
- let respObj = {
- error: false,
- message: tr(langCode, "dryRunSuccessful", parseJokes.jokeFormatVersion, submittedJoke.formatVersion),
- timestamp: new Date().getTime()
- };
- return httpServer.pipeString(res, fileFormatConverter.auto(fileFormat, respObj, langCode), parseURL.getMimeTypeFromFileFormatString(fileFormat), 201);
- }
- return writeJokeToFile(res, fileName, submittedJoke, fileFormat, ip, analyticsObject, langCode);
- }
- catch(err)
- {
- return httpServer.respondWithError(res, 100, 500, fileFormat, tr(langCode, "errWhileSavingSubmission", err), langCode);
- }
- }
- }
- else
- {
- return httpServer.respondWithError(res, 105, 400, fileFormat, tr(langCode, "wrongFormatVersion", parseJokes.jokeFormatVersion, submittedJoke.formatVersion), langCode);
- }
- }
- catch(err)
- {
- return httpServer.respondWithError(res, 105, 400, fileFormat, tr(settings.languages.defaultLanguage, "invalidJSON", err), settings.languages.defaultLanguage);
- }
- }
- /**
- * Writes a joke to a json file
- * @param {http.ServerResponse} res
- * @param {String} filePath
- * @param {JokeSubmission} submittedJoke
- * @param {String} fileFormat
- * @param {String} ip
- * @param {(analytics.AnalyticsDocsRequest|analytics.AnalyticsSuccessfulRequest|analytics.AnalyticsRateLimited|analytics.AnalyticsError|analytics.AnalyticsSubmission)} analyticsObject
- * @param {String} [langCode]
- */
- function writeJokeToFile(res, filePath, submittedJoke, fileFormat, ip, analyticsObject, langCode)
- {
- if(typeof httpServer == "object" && Object.keys(httpServer).length <= 0)
- httpServer = require("./httpServer");
- let reformattedJoke = reformatJoke(submittedJoke);
- fs.writeFile(filePath, JSON.stringify(reformattedJoke, null, 4), err => {
- if(!err)
- {
- // successfully wrote to file
- let responseObj = {
- "error": false,
- "message": tr(langCode, "submissionSaved"),
- "submission": reformattedJoke,
- "timestamp": new Date().getTime()
- };
- meter.update("submission", 1);
- let submissionObject = analyticsObject;
- submissionObject.submission = reformattedJoke;
- logRequest("submission", ip, submissionObject);
- return httpServer.pipeString(res, convertFileFormat.auto(fileFormat, responseObj, langCode), parseURL.getMimeTypeFromFileFormatString(fileFormat), 201);
- }
- // error while writing to file
- else return httpServer.respondWithError(res, 100, 500, fileFormat, tr(langCode, "errWhileSavingSubmission", err), langCode);
- });
- }
- /**
- * Coarse filter that ensures that a joke is formatted as expected.
- * This doesn't do any validation and omits missing properties!
- * @param {Joke|JokeSubmission} joke
- * @returns {Joke|JokeSubmission} Returns the reformatted joke
- */
- function reformatJoke(joke)
- {
- let retJoke = {};
- if(joke.formatVersion)
- retJoke.formatVersion = joke.formatVersion;
- retJoke = {
- ...retJoke,
- category: typeof joke.category === "string" ? parseJokes.resolveCategoryAlias(joke.category) : joke.category,
- type: joke.type
- };
- if(joke.type == "single")
- {
- retJoke.joke = joke.joke;
- }
- else if(joke.type == "twopart")
- {
- retJoke.setup = joke.setup;
- retJoke.delivery = joke.delivery;
- }
- retJoke.flags = {
- nsfw: joke.flags.nsfw,
- religious: joke.flags.religious,
- political: joke.flags.political,
- racist: joke.flags.racist,
- sexist: joke.flags.sexist,
- explicit: joke.flags.explicit,
- };
- if(joke.lang)
- retJoke.lang = joke.lang;
- if(typeof retJoke.lang === "string")
- retJoke.lang = retJoke.lang.toLowerCase();
- retJoke.safe = joke.safe || false;
-
- if(joke.id)
- retJoke.id = joke.id;
- return retJoke;
- }
- module.exports = jokeSubmission;
- module.exports.reformatJoke = reformatJoke;
|