diff --git a/addOns/encoder/CHANGELOG.md b/addOns/encoder/CHANGELOG.md index 3009a6b06f8..c357d3e5565 100644 --- a/addOns/encoder/CHANGELOG.md +++ b/addOns/encoder/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added - A predefined processor "ASCify" which converts text removing accents/diacritics/ligatures (perhaps not fully, due to operation in compatibility mode) leaving only ASCII characters. +- Predefined processors for encoding and decoding Morse Code. ## [1.5.0] - 2024-05-07 ### Added diff --git a/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/EncodeDecodeProcessors.java b/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/EncodeDecodeProcessors.java index 9301282ab62..8b06df5781b 100644 --- a/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/EncodeDecodeProcessors.java +++ b/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/EncodeDecodeProcessors.java @@ -45,6 +45,8 @@ import org.zaproxy.addon.encoder.processors.predefined.JavaScriptStringDecoder; import org.zaproxy.addon.encoder.processors.predefined.JavaScriptStringEncoder; import org.zaproxy.addon.encoder.processors.predefined.Md5Hasher; +import org.zaproxy.addon.encoder.processors.predefined.MorseDecoder; +import org.zaproxy.addon.encoder.processors.predefined.MorseEncoder; import org.zaproxy.addon.encoder.processors.predefined.PowerShellEncoder; import org.zaproxy.addon.encoder.processors.predefined.Sha1Hasher; import org.zaproxy.addon.encoder.processors.predefined.Sha256Hasher; @@ -103,8 +105,11 @@ public class EncodeDecodeProcessors { addPredefined("reverse", Reverse.getSingleton()); addPredefined("lowercase", LowerCase.getSingleton()); addPredefined("uppercase", UpperCase.getSingleton()); + addPredefined("powershellencode", PowerShellEncoder.getSingleton()); addPredefined("ascify", Ascify.getSingleton()); + addPredefined("morsecodeencode", MorseEncoder.getSingleton()); + addPredefined("morsecodeeecode", MorseDecoder.getSingleton()); } private Map scriptProcessors = new HashMap<>(); @@ -149,7 +154,7 @@ private List getScriptProcessors() { return scriptProcessors.values().stream().collect(Collectors.toList()); } - private EncodeDecodeProcessorItem createItemFromScriptWrapper(ScriptWrapper ws) { + private static EncodeDecodeProcessorItem createItemFromScriptWrapper(ScriptWrapper ws) { String scriptName = ws.getName(); ScriptBasedEncodeDecodeProcessor processor = new ScriptBasedEncodeDecodeProcessor(ws.getName()); diff --git a/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/predefined/MorseDecoder.java b/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/predefined/MorseDecoder.java new file mode 100644 index 00000000000..8dd51d7f80f --- /dev/null +++ b/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/predefined/MorseDecoder.java @@ -0,0 +1,62 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.encoder.processors.predefined; + +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.parosproxy.paros.Constant; +import org.zaproxy.addon.encoder.processors.EncodeDecodeProcessor; +import org.zaproxy.addon.encoder.processors.EncodeDecodeResult; + +public class MorseDecoder implements EncodeDecodeProcessor { + + private static final Pattern VALID_CHARS = Pattern.compile("[// .-]*"); + private static final Map CHARACTER_MAP = + MorseEncoder.CHARACTER_MAP.entrySet().stream() + .collect(Collectors.toUnmodifiableMap(Map.Entry::getValue, Map.Entry::getKey)); + + private static final MorseDecoder INSTANCE = new MorseDecoder(); + + @Override + public EncodeDecodeResult process(String value) { + if (value.isBlank()) { + return new EncodeDecodeResult(""); + } + // Replace em dash with standard hyphen + value = value.replace("—", "-"); + if (!VALID_CHARS.matcher(value).matches()) { + return EncodeDecodeResult.withError(Constant.messages.getString("encoder.morse.error")); + } + + StringBuilder out = new StringBuilder(value.length() * 3); + for (String words : value.split("/")) { + for (String character : words.split(" ")) { + out.append(CHARACTER_MAP.get(character)); + } + out.append(' '); + } + return new EncodeDecodeResult(out.toString().trim()); + } + + public static MorseDecoder getSingleton() { + return INSTANCE; + } +} diff --git a/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/predefined/MorseEncoder.java b/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/predefined/MorseEncoder.java new file mode 100644 index 00000000000..b5917d07dec --- /dev/null +++ b/addOns/encoder/src/main/java/org/zaproxy/addon/encoder/processors/predefined/MorseEncoder.java @@ -0,0 +1,102 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.encoder.processors.predefined; + +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; +import org.parosproxy.paros.Constant; +import org.zaproxy.addon.encoder.processors.EncodeDecodeProcessor; +import org.zaproxy.addon.encoder.processors.EncodeDecodeResult; + +public class MorseEncoder implements EncodeDecodeProcessor { + + private static final Pattern VALID_CHARS = Pattern.compile("[A-Z0-9 ]*"); + protected static final Map CHARACTER_MAP = + Map.ofEntries( + Map.entry('A', ".-"), + Map.entry('B', "-..."), + Map.entry('C', "-.-."), + Map.entry('D', "-.."), + Map.entry('E', "."), + Map.entry('F', "..-."), + Map.entry('G', "--."), + Map.entry('H', "...."), + Map.entry('I', ".."), + Map.entry('J', ".---"), + Map.entry('K', "-.-"), + Map.entry('L', ".-.."), + Map.entry('M', "--"), + Map.entry('N', "-."), + Map.entry('O', "---"), + Map.entry('P', ".--."), + Map.entry('Q', "--.-"), + Map.entry('R', ".-."), + Map.entry('S', "..."), + Map.entry('T', "-"), + Map.entry('U', "..-"), + Map.entry('V', "...-"), + Map.entry('W', ".--"), + Map.entry('X', "-..-"), + Map.entry('Y', "-.--"), + Map.entry('Z', "--.."), + + // Replace space as slash which is word separator + // https://morsecode.world/international/translator.html + Map.entry(' ', "/"), + Map.entry('1', ".----"), + Map.entry('2', "..---"), + Map.entry('3', "...--"), + Map.entry('4', "....-"), + Map.entry('5', "....."), + Map.entry('6', "-...."), + Map.entry('7', "--..."), + Map.entry('8', "---.."), + Map.entry('9', "----."), + Map.entry('0', "-----")); + + private static final MorseEncoder INSTANCE = new MorseEncoder(); + + @Override + public EncodeDecodeResult process(String value) { + value = value.toUpperCase(Locale.ROOT); + if (!VALID_CHARS.matcher(value).matches()) { + return EncodeDecodeResult.withError(Constant.messages.getString("encoder.morse.error")); + } + + StringBuilder out = new StringBuilder(value.length() * 3); + String outSeq; + for (Character c : value.toCharArray()) { + outSeq = CHARACTER_MAP.get(c); + if (outSeq.equals("/")) { + // This is a word break, remove the previous trailing space + out.deleteCharAt(out.length() - 1); + out.append(outSeq); + } else { + out.append(outSeq).append(' '); + } + } + return new EncodeDecodeResult(out.toString().trim()); + } + + public static MorseEncoder getSingleton() { + return INSTANCE; + } +} diff --git a/addOns/encoder/src/main/javahelp/org/zaproxy/addon/encoder/resources/help/contents/encoder.html b/addOns/encoder/src/main/javahelp/org/zaproxy/addon/encoder/resources/help/contents/encoder.html index c25943a4532..14df35396d6 100644 --- a/addOns/encoder/src/main/javahelp/org/zaproxy/addon/encoder/resources/help/contents/encoder.html +++ b/addOns/encoder/src/main/javahelp/org/zaproxy/addon/encoder/resources/help/contents/encoder.html @@ -144,6 +144,10 @@

Unescaped Unicode Text

Will display the unescaped Unicode characters. For example, the text %u0041%u00e7%u006f%u0072%u0065%u0073 would be decoded as Açores. +

Morse Code Encoder

+Will display dits (.) and dahs (-) and word breaks (/) representing the provided AlphaNumeric (including space) input. +For example, the text SOS SOS would be encoded as ... --- .../... --- .... +

Decoders

ASCII Hex Decode

@@ -174,6 +178,10 @@

URL Decode

Full URL Decode

Will display the URL decoding of the text you enter (percent signs removed and HEX decoded). +

Morse Code Decoder

+Will display AlphaNumeric (including space) output representing the provided morrse code input. +For example, the text ... --- .../... --- ... would be encoded as SOS SOS. +

Hashers

MD5 Hash

diff --git a/addOns/encoder/src/main/resources/org/zaproxy/addon/encoder/resources/Messages.properties b/addOns/encoder/src/main/resources/org/zaproxy/addon/encoder/resources/Messages.properties index 7cd11e821de..6cae18dc2d9 100644 --- a/addOns/encoder/src/main/resources/org/zaproxy/addon/encoder/resources/Messages.properties +++ b/addOns/encoder/src/main/resources/org/zaproxy/addon/encoder/resources/Messages.properties @@ -18,6 +18,8 @@ encoder.dialog.reset.button.tooltip = Reset all tabs and output panels to defaul encoder.dialog.reset.confirm = All Encode/Decode/Hash tabs and output panels will be restored to their default state. Continue? encoder.dialog.title = Encode/Decode/Hash +encoder.morse.error = Input contains one or more characters which can't be converted. + encoder.name = Encoder Addon encoder.optionspanel.base64 = Base64 @@ -30,7 +32,6 @@ encoder.optionspanel.name = Encode/Decode encoder.popup.delete = Delete Output Panel encoder.popup.replace.input = Replace Input Text encoder.popup.title = Encode/Decode/Hash... - encoder.predefined.ascify = ASCify (Strip accents, etc) encoder.predefined.base64decode = Base64 Decode encoder.predefined.base64encode = Base64 Encode @@ -50,6 +51,9 @@ encoder.predefined.javascriptdecode = JavaScript Decode encoder.predefined.javascriptencode = JavaScript Encode encoder.predefined.lowercase = To Lower Case encoder.predefined.md5hash = MD5 Hash + +encoder.predefined.morsecodedecode = Morse Code Decoder +encoder.predefined.morsecodeencode = Morse Code Encoder encoder.predefined.powershellencode = PowerShell Encode encoder.predefined.removewhitespace = Remove Whitespace encoder.predefined.reverse = Reverse diff --git a/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/MorseDecoderUnitTest.java b/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/MorseDecoderUnitTest.java new file mode 100644 index 00000000000..1d5f4c65b9c --- /dev/null +++ b/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/MorseDecoderUnitTest.java @@ -0,0 +1,67 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2022 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.encoder.processors.predefined; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.zaproxy.addon.encoder.ExtensionEncoder; +import org.zaproxy.addon.encoder.processors.EncodeDecodeResult; + +class MorseDecoderUnitTest extends ProcessorTests { + + @BeforeAll + static void setup() { + mockMessages(new ExtensionEncoder()); + } + + @Override + protected MorseDecoder createProcessor() { + return MorseDecoder.getSingleton(); + } + + @ParameterizedTest + @ValueSource( + strings = { + "... --- .../... --- ...", + // em dashes not hyphens + "... ——— .../... ——— ..." + }) + void shouldDecodeWithoutError(String input) throws Exception { + // Given / When + EncodeDecodeResult result = processor.process(input); + // Then + assertThat(result.hasError(), is(equalTo(false))); + assertThat(result.getResult(), is(equalTo("SOS SOS"))); + } + + @Test + void shouldErrorIfInputContainsOutOfScopeCharacter() throws Exception { + // Given / When + EncodeDecodeResult result = processor.process("abc"); + // Then + assertThat(result.hasError(), is(equalTo(true))); + } +} diff --git a/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/MorseEncoderUnitTest.java b/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/MorseEncoderUnitTest.java new file mode 100644 index 00000000000..bbc7549530b --- /dev/null +++ b/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/MorseEncoderUnitTest.java @@ -0,0 +1,62 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2022 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.encoder.processors.predefined; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.zaproxy.addon.encoder.ExtensionEncoder; +import org.zaproxy.addon.encoder.processors.EncodeDecodeResult; + +class MorseEncoderUnitTest extends ProcessorTests { + + @BeforeAll + static void setup() { + mockMessages(new ExtensionEncoder()); + } + + @Override + protected MorseEncoder createProcessor() { + return MorseEncoder.getSingleton(); + } + + @ParameterizedTest + @ValueSource(strings = {"SOS SOS", "SOS sos"}) + void shouldEncodeWithoutError(String input) throws Exception { + // Given / When + EncodeDecodeResult result = processor.process(input); + // Then + assertThat(result.hasError(), is(equalTo(false))); + assertThat(result.getResult(), is(equalTo("... --- .../... --- ..."))); + } + + @Test + void shouldErrorIfInputContainsOutOfScopeCharacter() throws Exception { + // Given / When + EncodeDecodeResult result = processor.process("s*s"); + // Then + assertThat(result.hasError(), is(equalTo(true))); + } +} diff --git a/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/ProcessorTests.java b/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/ProcessorTests.java index 613215c457b..d128a403d32 100644 --- a/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/ProcessorTests.java +++ b/addOns/encoder/src/test/java/org/zaproxy/addon/encoder/processors/predefined/ProcessorTests.java @@ -41,8 +41,9 @@ import org.zaproxy.addon.encoder.ExtensionEncoder; import org.zaproxy.addon.encoder.processors.EncodeDecodeProcessor; import org.zaproxy.addon.encoder.processors.EncodeDecodeResult; +import org.zaproxy.zap.testutils.TestUtils; -public abstract class ProcessorTests { +public abstract class ProcessorTests extends TestUtils { protected T processor; @@ -58,7 +59,7 @@ public void setUp() throws Exception { mock(ExtensionEncoder.class, withSettings().strictness(Strictness.LENIENT)); given(extensionLoader.getExtension(ExtensionEncoder.class)).willReturn(extEnc); Control.initSingletonForTesting(mock(Model.class), extensionLoader); - options = mock(EncodeDecodeOptions.class); + options = mock(EncodeDecodeOptions.class, withSettings().strictness(Strictness.LENIENT)); given(extEnc.getOptions()).willReturn(options); given(options.getBase64Charset()).willReturn(EncodeDecodeOptions.DEFAULT_CHARSET); }