Merge db0e08518a7d37b37a31ae6904e2a1d379e4545f into 72ca1aeec7a50f4c596d44b755a6ca71f792ffcf
This commit is contained in:
commit
5073a766fa
@ -49,6 +49,7 @@
|
|||||||
"Escape Unicode Characters",
|
"Escape Unicode Characters",
|
||||||
"Unescape Unicode Characters",
|
"Unescape Unicode Characters",
|
||||||
"Normalise Unicode",
|
"Normalise Unicode",
|
||||||
|
"To Fullwidth",
|
||||||
"To Quoted Printable",
|
"To Quoted Printable",
|
||||||
"From Quoted Printable",
|
"From Quoted Printable",
|
||||||
"To Punycode",
|
"To Punycode",
|
||||||
|
|||||||
57
src/core/operations/ToFullwidth.mjs
Normal file
57
src/core/operations/ToFullwidth.mjs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* @author jyeu [chen@jyeu.xyz]
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To Fullwidth operation
|
||||||
|
*/
|
||||||
|
class ToFullwidth extends Operation {
|
||||||
|
/**
|
||||||
|
* ToFullwidth constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "To Fullwidth";
|
||||||
|
this.module = "Encodings";
|
||||||
|
this.description =
|
||||||
|
"Converts ASCII (halfwidth) characters to their fullwidth Unicode equivalents (U+FF01–U+FF5E). Commonly used in security testing to bypass WAF keyword filters, evade regex-based blocklists, and exploit Unicode normalization (NFKC/NFKD) vulnerabilities in web applications and path parsers. For example, <code>/admin</code> may bypass a WAF rule matching <code>/admin</code> if the backend normalises Unicode before routing.";
|
||||||
|
this.infoURL =
|
||||||
|
"https://wikipedia.org/wiki/Halfwidth_and_fullwidth_forms_(Unicode_block)";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {never} _args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, _args) {
|
||||||
|
let output = "";
|
||||||
|
|
||||||
|
for (const char of input) {
|
||||||
|
const code = char.codePointAt(0);
|
||||||
|
|
||||||
|
if (code === 0x20) {
|
||||||
|
// Regular space -> Ideographic space (U+3000)
|
||||||
|
output += "\u3000";
|
||||||
|
} else if (code >= 0x21 && code <= 0x7e) {
|
||||||
|
// Visible ASCII characters -> Fullwidth equivalents (U+FF01–U+FF5E)
|
||||||
|
output += String.fromCodePoint(code + 0xfee0);
|
||||||
|
} else {
|
||||||
|
// Non-ASCII characters (CJK, newlines, etc.) are passed through unchanged
|
||||||
|
output += char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToFullwidth;
|
||||||
@ -178,6 +178,7 @@ import "./tests/TakeNthBytes.mjs";
|
|||||||
import "./tests/Template.mjs";
|
import "./tests/Template.mjs";
|
||||||
import "./tests/TextEncodingBruteForce.mjs";
|
import "./tests/TextEncodingBruteForce.mjs";
|
||||||
import "./tests/TextIntegerConverter.mjs";
|
import "./tests/TextIntegerConverter.mjs";
|
||||||
|
import "./tests/ToFullwidth.mjs";
|
||||||
import "./tests/ToFromInsensitiveRegex.mjs";
|
import "./tests/ToFromInsensitiveRegex.mjs";
|
||||||
import "./tests/TranslateDateTimeFormat.mjs";
|
import "./tests/TranslateDateTimeFormat.mjs";
|
||||||
import "./tests/Typex.mjs";
|
import "./tests/Typex.mjs";
|
||||||
|
|||||||
144
tests/operations/tests/ToFullwidth.mjs
Normal file
144
tests/operations/tests/ToFullwidth.mjs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* To Fullwidth tests.
|
||||||
|
*
|
||||||
|
* @author jyeu [chen@jyeu.xyz]
|
||||||
|
*
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: empty string",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: lowercase letters",
|
||||||
|
input: "admin",
|
||||||
|
expectedOutput: "\uFF41\uFF44\uFF4D\uFF49\uFF4E",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: uppercase letters",
|
||||||
|
input: "ADMIN",
|
||||||
|
expectedOutput: "\uFF21\uFF24\uFF2D\uFF29\uFF2E",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: digits",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "\uFF10\uFF11\uFF12\uFF13\uFF14\uFF15\uFF16\uFF17\uFF18\uFF19",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: space becomes ideographic space (U+3000)",
|
||||||
|
input: "hello world",
|
||||||
|
expectedOutput: "\uFF48\uFF45\uFF4C\uFF4C\uFF4F\u3000\uFF57\uFF4F\uFF52\uFF4C\uFF44",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: common punctuation and symbols",
|
||||||
|
input: "!@#$%^&*()_+-=[]{}|;':\",./<>?",
|
||||||
|
expectedOutput: "\uFF01\uFF20\uFF03\uFF04\uFF05\uFF3E\uFF06\uFF0A\uFF08\uFF09\uFF3F\uFF0B\uFF0D\uFF1D\uFF3B\uFF3D\uFF5B\uFF5D\uFF5C\uFF1B\uFF07\uFF1A\uFF02\uFF0C\uFF0E\uFF0F\uFF1C\uFF1E\uFF1F",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: slash for WAF bypass simulation",
|
||||||
|
input: "/admin/secret",
|
||||||
|
expectedOutput: "\uFF0F\uFF41\uFF44\uFF4D\uFF49\uFF4E\uFF0F\uFF53\uFF45\uFF43\uFF52\uFF45\uFF54",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: non-ASCII characters pass through unchanged",
|
||||||
|
input: "你好世界",
|
||||||
|
expectedOutput: "你好世界",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: newline passes through unchanged",
|
||||||
|
input: "line1\nline2",
|
||||||
|
expectedOutput: "\uFF4C\uFF49\uFF4E\uFF45\uFF11\n\uFF4C\uFF49\uFF4E\uFF45\uFF12",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: mixed ASCII and non-ASCII",
|
||||||
|
input: "hello,世界!",
|
||||||
|
expectedOutput: "\uFF48\uFF45\uFF4C\uFF4C\uFF4F\uFF0C世界\uFF01",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: boundary character 0x21 (!)",
|
||||||
|
input: "!",
|
||||||
|
expectedOutput: "\uFF01",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Fullwidth: boundary character 0x7E (~)",
|
||||||
|
input: "~",
|
||||||
|
expectedOutput: "\uFF5E",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Fullwidth",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
Loading…
x
Reference in New Issue
Block a user