diff --git a/src/core/lib/Base64.mjs b/src/core/lib/Base64.mjs index c96e579e..06ec5ab9 100644 --- a/src/core/lib/Base64.mjs +++ b/src/core/lib/Base64.mjs @@ -82,72 +82,74 @@ export function toBase64(data, alphabet="A-Za-z0-9+/=") { * // returns [72, 101, 108, 108, 111] * fromBase64("SGVsbG8=", null, "byteArray"); */ -export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) { +export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true, strictMode=false) { if (!data) { return returnType === "string" ? "" : []; } alphabet = alphabet || "A-Za-z0-9+/="; alphabet = Utils.expandAlphRange(alphabet).join(""); + + // Confirm alphabet is a valid length if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding - throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`); + throw new OperationError(`Error: Base64 alphabet should be 64 characters long, or 65 with a padding character. Found ${alphabet.length}: ${alphabet}`); } - const output = []; - let chr1, chr2, chr3, - encChr1, encChr2, encChr3, encChr4, - enc1, enc2, enc3, enc4, - i = 0; - + // Remove non-alphabet characters if (removeNonAlphChars) { const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); data = data.replace(re, ""); } - if (data.length % 4 === 1) { - throw new OperationError(`Invalid Base64 input length (${data.length}): it won't be 4n+1`); - } + if (strictMode) { + // Check for incorrect lengths (even without padding) + if (data.length % 4 === 1) { + throw new OperationError(`Error: Invalid Base64 input length (${data.length}). Cannot be 4n+1, even without padding chars.`); + } - if (alphabet.length === 65) { - const pad = alphabet.charAt(64); - const padPos = data.indexOf(pad); - if (padPos >= 0) { - // padding character should appear only at the end of the input - // there should be only one or two padding character(s) if it exists - if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) { - throw new OperationError("Invalid Base64 input: padding character misused"); - } - if (data.length % 4 !== 0) { - throw new OperationError("Invalid Base64 input: padded not to multiple of 4"); + if (alphabet.length === 65) { // Padding character included + const pad = alphabet.charAt(64); + const padPos = data.indexOf(pad); + if (padPos >= 0) { + // Check that the padding character is only used at the end and maximum of twice + if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) { + throw new OperationError(`Error: Base64 padding character (${pad}) not used in the correct place.`); + } + + // Check that input is padded to the correct length + if (data.length % 4 !== 0) { + throw new OperationError("Error: Base64 not padded to a multiple of 4."); + } } } } + const output = []; + let chr1, chr2, chr3, + enc1, enc2, enc3, enc4, + i = 0; + while (i < data.length) { - encChr1 = data.charAt(i++); - encChr2 = data.charAt(i++); - encChr3 = data.charAt(i++); - encChr4 = data.charAt(i++); + enc1 = alphabet.indexOf(data.charAt(i++)); + enc2 = alphabet.indexOf(data.charAt(i++)); + enc3 = alphabet.indexOf(data.charAt(i++)); + enc4 = alphabet.indexOf(data.charAt(i++)); - enc1 = alphabet.indexOf(encChr1); - enc2 = alphabet.indexOf(encChr2); - enc3 = alphabet.indexOf(encChr3); - enc4 = alphabet.indexOf(encChr4); - - if (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0) { - throw new OperationError("Invalid Base64 input: contains non-alphabet char(s)"); + if (strictMode && (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0)) { + throw new OperationError("Error: Base64 input contains non-alphabet char(s)"); } chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; - output.push(chr1); - - if (encChr3 !== "" && enc3 !== 64) { + if (chr1 < 256) { + output.push(chr1); + } + if (chr2 < 256 && enc3 !== 64) { output.push(chr2); } - if (encChr4 !== "" && enc4 !== 64) { + if (chr3 < 256 && enc4 !== 64) { output.push(chr3); } } diff --git a/src/core/operations/FromBase64.mjs b/src/core/operations/FromBase64.mjs index ccdd52b5..292de1e2 100644 --- a/src/core/operations/FromBase64.mjs +++ b/src/core/operations/FromBase64.mjs @@ -34,93 +34,98 @@ class FromBase64 extends Operation { name: "Remove non-alphabet chars", type: "boolean", value: true + }, + { + name: "Strict mode", + type: "boolean", + value: false } ]; this.checks = [ { pattern: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", flags: "i", - args: ["A-Za-z0-9+/=", true] + args: ["A-Za-z0-9+/=", true, false] }, { pattern: "^\\s*[A-Z\\d\\-_]{20,}\\s*$", flags: "i", - args: ["A-Za-z0-9-_", true] + args: ["A-Za-z0-9-_", true, false] }, { pattern: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$", flags: "i", - args: ["A-Za-z0-9+\\-=", true] + args: ["A-Za-z0-9+\\-=", true, false] }, { pattern: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$", flags: "i", - args: ["./0-9A-Za-z=", true] + args: ["./0-9A-Za-z=", true, false] }, { pattern: "^\\s*[A-Z\\d_.]{20,}\\s*$", flags: "i", - args: ["A-Za-z0-9_.", true] + args: ["A-Za-z0-9_.", true, false] }, { pattern: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$", flags: "i", - args: ["A-Za-z0-9._-", true] + args: ["A-Za-z0-9._-", true, false] }, { pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", flags: "i", - args: ["0-9a-zA-Z+/=", true] + args: ["0-9a-zA-Z+/=", true, false] }, { pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", flags: "i", - args: ["0-9A-Za-z+/=", true] + args: ["0-9A-Za-z+/=", true, false] }, { pattern: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$", flags: "", - args: [" -_", false] + args: [" -_", false, false] }, { pattern: "^\\s*[A-Z\\d+\\-]{20,}\\s*$", flags: "i", - args: ["+\\-0-9A-Za-z", true] + args: ["+\\-0-9A-Za-z", true, false] }, { pattern: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$", flags: "", - args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true] + args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true, false] }, { pattern: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$", flags: "i", - args: ["N-ZA-Mn-za-m0-9+/=", true] + args: ["N-ZA-Mn-za-m0-9+/=", true, false] }, { pattern: "^\\s*[A-Z\\d./]{20,}\\s*$", flags: "i", - args: ["./0-9A-Za-z", true] + args: ["./0-9A-Za-z", true, false] }, { pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}CC|[A-Z=\\d\\+/]{3}C)?\\s*$", flags: "i", - args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true] + args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true, false] }, { pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$", flags: "i", - args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true] + args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true, false] }, { pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}22|[A-Z=\\d\\+/]{3}2)?\\s*$", flags: "i", - args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true] + args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true, false] }, { pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$", flags: "i", - args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true] + args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true, false] } ]; } @@ -131,9 +136,9 @@ class FromBase64 extends Operation { * @returns {byteArray} */ run(input, args) { - const [alphabet, removeNonAlphChars] = args; + const [alphabet, removeNonAlphChars, strictMode] = args; - return fromBase64(input, alphabet, "byteArray", removeNonAlphChars); + return fromBase64(input, alphabet, "byteArray", removeNonAlphChars, strictMode); } /** diff --git a/src/core/operations/ToUpperCase.mjs b/src/core/operations/ToUpperCase.mjs index 08ffce87..8eb00731 100644 --- a/src/core/operations/ToUpperCase.mjs +++ b/src/core/operations/ToUpperCase.mjs @@ -5,6 +5,7 @@ */ import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; /** * To Upper case operation @@ -38,27 +39,29 @@ class ToUpperCase extends Operation { */ run(input, args) { if (!args || args.length === 0) { - throw new OperationException("No capitalization scope was provided."); + throw new OperationError("No capitalization scope was provided."); } + const scope = args[0]; + if (scope === "All") { return input.toUpperCase(); } + const scopeRegex = { "Word": /(\b\w)/gi, "Sentence": /(?:\.|^)\s*(\b\w)/gi, "Paragraph": /(?:\n|^)\s*(\b\w)/gi - }[ scope ]; - if (scopeRegex !== undefined) { - // Use the regexes to capitalize the input. - return input.replace(scopeRegex, function(m) { - return m.toUpperCase(); - }); - } - else { - // The selected scope was invalid. + }[scope]; + + if (scopeRegex === undefined) { throw new OperationError("Unrecognized capitalization scope"); } + + // Use the regex to capitalize the input + return input.replace(scopeRegex, function(m) { + return m.toUpperCase(); + }); } /** diff --git a/tests/operations/tests/Magic.mjs b/tests/operations/tests/Magic.mjs index 24353565..90401dc1 100644 --- a/tests/operations/tests/Magic.mjs +++ b/tests/operations/tests/Magic.mjs @@ -68,7 +68,7 @@ TestRegister.addTests([ { name: "Magic Chain: Base64", input: "WkVkV2VtUkRRbnBrU0Vwd1ltMWpQUT09", - expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true\)\nFrom_Base64\('A-Za-z0-9\+\/=',true\)\nFrom_Base64\('A-Za-z0-9\+\/=',true\)/, + expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true,false\)\nFrom_Base64\('A-Za-z0-9\+\/=',true,false\)\nFrom_Base64\('A-Za-z0-9\+\/=',true,false\)/, recipeConfig: [ { op: "Magic", @@ -79,7 +79,7 @@ TestRegister.addTests([ { name: "Magic Chain: Hex -> Hexdump -> Base64", input: "MDAwMDAwMDAgIDM3IDM0IDIwIDM2IDM1IDIwIDM3IDMzIDIwIDM3IDM0IDIwIDMyIDMwIDIwIDM3ICB8NzQgNjUgNzMgNzQgMjAgN3wKMDAwMDAwMTAgIDMzIDIwIDM3IDM0IDIwIDM3IDMyIDIwIDM2IDM5IDIwIDM2IDY1IDIwIDM2IDM3ICB8MyA3NCA3MiA2OSA2ZSA2N3w=", - expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true\)\nFrom_Hexdump\(\)\nFrom_Hex\('Space'\)/, + expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true,false\)\nFrom_Hexdump\(\)\nFrom_Hex\('Space'\)/, recipeConfig: [ { op: "Magic",