diff --git a/changelog.txt b/changelog.txt index 1011b5e8..dc52d712 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,17 +1,25 @@ ==================== JokeAPI Changelog - - Version 2.1.2 - + - Version 2.1.3 - ==================== -Planned for the next version (2.2.0): +[PLANNED: 2.2.0] - Allow definition of max requests per minute per each client (see GitHub issue #37) -[CURRENT: 2.1.2] - Plain Text update +[CURRENT: 2.1.3] - 2020 Q2 general patch #1 + - Added option to disable all console output but error messages (GitHub isse #72) + - The content of jokes in the joke submission form is now correctly escaped and can no longer mess up the page (GitHub issue #68) + - Fixed crash when parsing a malformatted URI (GitHub issue #69 (nice)) + - Re-flagged some jokes + - Updated dependencies + + +[2.1.2] - Plain Text update - Added file format "txt" to receive data as plain text - Fixed the joke submission URL in the /info endpoint data - Added HTTP error codes: @@ -19,26 +27,26 @@ Planned for the next version (2.2.0): - 414 URI Too Long -[OLD: 2.1.1] - Auth update hotfix +[2.1.1] - Auth update hotfix - Fixed incorrect error cause when using an out-of-range ID range parameter (see GitHub issue #54) - Added submission form (https://sv443.net/jokeapi/v2#submit) -[OLD: 2.1.0] - The auth update +[2.1.0] - The auth update - Added an authorization header to make whitelisting possible without needing to have a static IP - Added the script "npm run add-token [amount]" to add one or more tokens - Improved the documentation (see GitHub issue #52) - Fixed a few bugs in the documentation -[OLD: 2.0.1] - A few hotfixes for the big 2.0.0 updates and some very very minor features I wanted to add +[2.0.1] - A few hotfixes for the big 2.0.0 updates and some very very minor features I wanted to add - Hotfixed a few bugs from the big 2.0.0 update - Fixed joke ID caching (to not serve the same jokes multiple times) - Added three new commands that are run through CI and before contributing - Enabled automated code fixing with ESLint -[OLD: 2.0.0] - The complete rewrite - JokeAPI was completely rewritten and should now run like 100x better and be more easy to develop and maintain +[2.0.0] - The complete rewrite - JokeAPI was completely rewritten and should now run like 100x better and be more easy to develop and maintain - Massively improved the "Try it out" section in the docs - Reformatted the jokes to always contain all flags - Added support for selecting multiple categories at once (for example: "https://sv443.net/jokeapi/v2/joke/Dark,Miscellaneous/") @@ -69,24 +77,24 @@ Planned for the next version (2.2.0): - Updated the contributor guide (contributing.md file) -[OLD: 1.1.2] +[1.1.2] - joke categories are now case insensitive - trailing slashes now don't produce an "invalid category" error anymore -[OLD: 1.1.1] +[1.1.1] - better IP getter for the rate limiting - updated dependencies - very small improvements to the console window -[OLD: 1.1.0] +[1.1.0] - switched to ReadStreams instead of just loading the entire file to RAM to massively improve request performance (more details in GitHub issue #2) - this basically means it transmits the data over time, instead of loading it all to RAM and sending it at once - added rate limiting to counter DoS attacks (yes I've been getting some of those *sigh*) -[OLD: 1.0.0] +[1.0.0] - turned the single endpoint into multiple endpoints - "categories" to get all available categories - "info" to get all information about JokeAPI @@ -117,20 +125,20 @@ Planned for the next version (2.2.0): - added this changelog -[OLD: 0.1.2] +[0.1.2] - added "Dark" category - added joke submission form - improved interactive example - modified gitignore -[OLD: 0.1.1] +[0.1.1] - added interactive example on docs page - made icon on docs page smaller - added wrapper script -[OLD: 0.1.0] +[0.1.0] - basic functionality - 47 jokes - - category filter \ No newline at end of file + - category filter diff --git a/data/jokes.json b/data/jokes.json index 17ab2cba..c0f7d9fb 100644 --- a/data/jokes.json +++ b/data/jokes.json @@ -640,7 +640,7 @@ "religious": false, "political": false, "racist": false, - "sexist": false + "sexist": true }, "id": 47 }, @@ -2536,6 +2536,19 @@ "setup": "Why are men like lawn mowers?", "delivery": "They are very hard to get started, they make yucky smells and half the time they don’t even work.", "id": 184 + }, + { + "category": "Programming", + "type": "single", + "joke": "Knock knock.\nWho's there?\nRecursion.\nRecursion who?\nKnock knock.", + "flags": { + "nsfw": false, + "religious": false, + "political": false, + "racist": false, + "sexist": false + }, + "id": 185 } ] } \ No newline at end of file diff --git a/data/lists/ipBlacklist.json b/data/lists/ipBlacklist.json index 3e516dfd..1610ea14 100644 --- a/data/lists/ipBlacklist.json +++ b/data/lists/ipBlacklist.json @@ -1,9 +1,3 @@ [ - "91.235.54.111", - "78.98.8.142", - "86.242.118.49", - "46.193.128.115", - "80.215.65.120", - "5.40.54.7", - "89.186.3.202" + ] \ No newline at end of file diff --git a/docs/raw/index.html b/docs/raw/index.html index 50fd738b..d0f0c66c 100644 --- a/docs/raw/index.html +++ b/docs/raw/index.html @@ -1086,7 +1086,7 @@

By using this website and API you are agreeing to
I will hereby not claim any legal responsibility or liability for and the jokes it serves (especially those from the "Dark" category).
Whether it is used maliciously or breaks something in your project or someone gets offended by a joke, I can't be held accountable.
- Additionally, I will only be able to provide security updates for a small selection of versions, a list of which you can find here.
+ Additionally, I will only be able to provide security updates for a small selection of versions, a list of which you can find here.
I am doing my best to ensure security and stability but there's only so much a single developer can do.
Please report any issue that may arise to the GitHub issue tracker and I will try my best to fix it as soon as possible.
If you want to contact me, you can join my Discord server (fastest way to contact me) or send me an E-Mail at sven.fehler@web.de diff --git a/docs/raw/index.js b/docs/raw/index.js index 267f1514..834624d4 100644 --- a/docs/raw/index.js +++ b/docs/raw/index.js @@ -744,7 +744,18 @@ function buildSubmission() } var subDisp = document.getElementById("submissionDisplay"); - subDisp.innerHTML = JSON.stringify(submission, null, 4); + + var escapedSubmission = JSON.parse(JSON.stringify(submission)); // copy value without reference + if(type == "single") + { + escapedSubmission.joke = htmlEscape(submission.joke); + } + else if(type == "twopart") + { + escapedSubmission.setup = htmlEscape(submission.setup); + escapedSubmission.delivery = htmlEscape(submission.delivery); + } + subDisp.innerHTML = JSON.stringify(escapedSubmission, null, 4); var subCodeElem = document.getElementById("submissionCodeElement"); @@ -770,6 +781,19 @@ function buildSubmission() }, 5); } +/** + * Escapes unsafe HTML + * @param {String} unsafeHTML + * @returns {String} + */ +function htmlEscape(unsafeHTML) +{ + unsafeHTML = unsafeHTML.replace(//g, ">"); + + return unsafeHTML; +} + //#MARKER privacy policy function privPolMoreInfo() { diff --git a/package-lock.json b/package-lock.json index 3ef166c0..d10ff67d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@sv443/jokeapi", - "version": "2.1.2", + "version": "2.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -4098,9 +4098,9 @@ } }, "svjsl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/svjsl/-/svjsl-1.9.0.tgz", - "integrity": "sha512-aDa2/CDW3rrktILZ16TwieRkkaa25SHb9GgLTiQlYkBSA2VUpH8wvup4dop1juhPGWvtf61vQIz5/xHl2+dFgQ==" + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/svjsl/-/svjsl-1.9.4.tgz", + "integrity": "sha512-W3TItJKVKo8W8MO7S+Of88sX4agS4sXS7EXR/6terUBtvuKgciXNK0w/Sf8xc4CAJwb/V33QQgH6/qEVdpYTMw==" }, "tapable": { "version": "1.1.3", diff --git a/package.json b/package.json index a2eb1901..46a5effd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sv443/jokeapi", - "version": "2.1.2", + "version": "2.1.3", "description": "A RESTful API that serves jokes from many categories while also offering a lot of filtering methods", "main": "JokeAPI.js", "homepage": "https://sv443.net/jokeapi/v2", @@ -50,7 +50,7 @@ "json-to-pretty-yaml": "^1.2.2", "mysql": "^2.18.1", "node-wrap": "^0.2.0", - "svjsl": "^1.9.0", + "svjsl": "^1.9.4", "xss": "^1.0.6", "snyk": "^1.316.1" }, diff --git a/settings.js b/settings.js index 86ab56da..8ad1e119 100644 --- a/settings.js +++ b/settings.js @@ -7,6 +7,7 @@ const settings = { debug: { verboseLogging: false, // set to true to enable extra debug output progressBarDisabled: true, // set to true to disable the progress bar - greatly improves readability of verbose debug output + onlyLogErrors: true, // set to true to disable sending any console logs but error messages }, info: { name: "JokeAPI", // the name of JokeAPI @@ -24,6 +25,18 @@ const settings = { infoMsg: "If you want to be updated on the status and future updates of JokeAPI or need some help, please consider joining my Discord server: https://sv443.net/discord", privacyPolicyUrl: "https://sv443.net/privacypolicy/en" }, + wrapper: { + mainFilePath: "./src/main.js", // main script file + skipWrapping: true, // whether or not to skip the wrapping through node-wrap + wrapperSettings: { + console: true, // whether Node-Wrap should log to the console + crashTimeout: 2000, // timeout (in ms) until the process should be restarted after a crash + logFile: "./data/logs/wrapper.log", // Node-Wrap log file + logTimestamp: true, // whether to add a timestamp to the log + restartOnCrash: true, // whether to restart the process after a crash + restartTimeout: 0, // timeout (in ms) until the process should be started again after a restart has been requested + }, + }, init: { initDirs: [ // directories that should be generated if they don't exist - paths relative to root of project - doesn't necessarily need trailing slash "./data/logs", @@ -41,18 +54,6 @@ const settings = { disableLogging: false, // set to true to disable logging a character on each request blacklistLoggingEnabled: true, // whether or not to log the character when an IP is on the blacklist }, - wrapper: { - mainFilePath: "./src/main.js", // main script file - skipWrapping: false, // whether or not to skip the wrapping through node-wrap - wrapperSettings: { - console: true, // whether Node-Wrap should log to the console - crashTimeout: 2000, // timeout (in ms) until the process should be restarted after a crash - logFile: "./data/logs/wrapper.log", // Node-Wrap log file - logTimestamp: true, // whether to add a timestamp to the log - restartOnCrash: true, // whether to restart the process after a crash - restartTimeout: 0, // timeout (in ms) until the process should be started again after a restart has been requested - }, - }, jokes: { jokesFormatVersion: 2, // current joke format version jokesFilePath: "./data/jokes.json", // path to the jokes file @@ -182,4 +183,4 @@ const settings = { } } -module.exports = settings; \ No newline at end of file +module.exports = settings; diff --git a/src/classes/FilteredJoke.js b/src/classes/FilteredJoke.js index b54bb717..f6a0fd99 100644 --- a/src/classes/FilteredJoke.js +++ b/src/classes/FilteredJoke.js @@ -156,8 +156,16 @@ class FilteredJoke return false; } - this._searchString = decodeURIComponent(searchString); - return true; + try + { + this._searchString = decodeURIComponent(searchString); + return true; + } + catch(err) + { + this._errors.push("The URI is malformatted or the \"contains\" parameter isn't correctly percent-encoded"); + return false; + } } /** diff --git a/src/logRequest.js b/src/logRequest.js index ce5dba26..7b8ff4a3 100644 --- a/src/logRequest.js +++ b/src/logRequest.js @@ -26,6 +26,9 @@ const logRequest = (type, additionalInfo, analyticsData) => { let spacerDisabled = false; let logChar = settings.logging.logChar; + if(settings.debug.onlyLogErrors) + logDisabled = true; + switch(type) { case "success": @@ -75,6 +78,9 @@ const logRequest = (type, additionalInfo, analyticsData) => { } break; case "error": + if(settings.debug.onlyLogErrors) + logDisabled = false; + color = settings.colors.ratelimit; logType = "error"; @@ -93,7 +99,7 @@ const logRequest = (type, additionalInfo, analyticsData) => { break; case "docsrecompiled": color = settings.colors.docsrecompiled; - logChar = `r${jsl.colors.rst} `; + logChar = "r "; break; case "submission": logChar = `\n\n${jsl.colors.fg.blue}⯈ Got a submission${!jsl.isEmpty(additionalInfo) ? ` from ${jsl.colors.fg.yellow}${additionalInfo.substring(0, 8)}` : ""}${jsl.colors.rst}\n\n`; @@ -147,11 +153,16 @@ const initMsg = (initTimestamp) => { console.log(` ├─ Analytics database ${jsl.colors.fg.red}not connected${jsl.colors.rst}`); console.log(` ├─ ${settings.info.name} is listening at ${jsl.colors.fg.green}0.0.0.0:${settings.httpServer.port}${jsl.colors.rst}`); console.log(` └─ Initialization took ${jsl.colors.fg.green}${(new Date().getTime() - initTimestamp).toFixed(0)}ms${jsl.colors.rst}`); - console.log(`\n\n ${settings.colors.success}${settings.logging.logChar} Success ${settings.colors.docs}${settings.logging.logChar} Docs ${settings.colors.ratelimit}${settings.logging.logChar} RateLimited ${settings.colors.error}${settings.logging.logChar} Error${jsl.colors.rst}`); - process.stdout.write("\x1b[2m"); - process.stdout.write("└┬───────────────────────────────────────┘\n"); - process.stdout.write(" └─► "); - process.stdout.write("\x1b[0m"); + process.stdout.write("\n"); + + if(!settings.debug.onlyLogErrors) + { + console.log(`\n ${settings.colors.success}${settings.logging.logChar} Success ${settings.colors.docs}${settings.logging.logChar} Docs ${settings.colors.ratelimit}${settings.logging.logChar} RateLimited ${settings.colors.error}${settings.logging.logChar} Error${jsl.colors.rst}`); + process.stdout.write("\x1b[2m"); + process.stdout.write("└┬───────────────────────────────────────┘\n"); + process.stdout.write(" └─► "); + process.stdout.write(jsl.colors.rst); + } } module.exports = logRequest; diff --git a/tools/submissions.js b/tools/submissions.js index 5540bb5e..718560c4 100644 --- a/tools/submissions.js +++ b/tools/submissions.js @@ -16,7 +16,7 @@ const run = () => { return process.exit(0); } - pause(`Do you want to go through them all now? (Y/n):${jsl.colors.rst}`).then(key => { + jsl.pause(`Do you want to go through them all now? (Y/n):${jsl.colors.rst}`).then(key => { if(key.toLowerCase && key.toLowerCase() == "n") process.exit(0); else @@ -52,13 +52,13 @@ const run = () => { } else console.error(`${jsl.colors.fg.red}Error: Unsuppoted joke type "${submission.type}"${jsl.colors.rst}`); - pause("Do you want to add this joke? (y/N):").then(key => { + jsl.pause("Do you want to add this joke? (y/N):").then(key => { if(key.toLowerCase() === "y") { addJoke(submissions[idx]); - process.stdout.write(`${jsl.colors.fg.green}Adding joke.${jsl.colors.rst}\n\n`); + process.stdout.write(`${jsl.colors.fg.green}Adding joke.${jsl.colors.rst}\n\n\n\n`); } - else process.stdout.write(`${jsl.colors.fg.red}Not adding joke.${jsl.colors.rst}\n\n`); + else process.stdout.write(`${jsl.colors.fg.red}Not adding joke.${jsl.colors.rst}\n\n\n\n`); goThroughSubmission(++idx); }); @@ -119,7 +119,7 @@ const getFlags = joke => { }); if(flags.length == 0) - return ""; + return "(none)"; return jsl.readableArray(flags); }; @@ -136,38 +136,4 @@ const getSubmissions = () => { return submissions; }; -/** - * Waits for the user to press a key and then resolves a Promise - * @param {String} text The text to display - * @returns {Promise} Passes the pressed key in the resolution or the error message in the rejection - */ -function pause(text = "Press any key to continue...") -{ - if(!process.stdin.isRaw) - process.stdin.setRawMode(true); - - return new Promise((resolve, reject) => { - process.stdout.write(`${text} `); - process.stdin.resume(); - - let onData = function(chunk) - { - if(/\u0003/gu.test(chunk)) // eslint-disable-line no-control-regex - process.exit(0); - - process.stdout.write("\n"); - process.stdin.pause(); - - process.stdin.removeListener("data", onData); - return resolve(chunk.toString()); - } - - process.stdin.on("data", onData); - - process.stdin.on("error", err => { - return reject(err); - }); - }); -} - run(); \ No newline at end of file