This is an automated email from the ASF dual-hosted git repository. dashorst pushed a commit to branch wicket-8.x in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 7d5e227ccd5fa8817ede03da28d10ffd5c0fbcda Author: Pedro Santos <[email protected]> AuthorDate: Sat Sep 5 02:11:51 2026 -0300 Escape the backslash in JavaScriptUtils#escapeQuotes escapeQuotes replaced each quote with a backslash and that quote but left the backslash itself untouched, so a value carrying a backslash immediately before a quote came out with two backslashes in front of it: the first escapes the second, the quote closes the JavaScript string literal, and the rest of the value is script rather than text. The method now escapes the backslash, and escapes it first. AjaxEditableLabel#onError writes the editor's first error message into a window.status='...' literal, and a validation message can carry the submitted value through ${input}, a default message variable every form component supplies, so a message that interpolates a submitted value reaches this escaper. Applications that escaped the backslash themselves around a call to escapeQuotes now escape it twice and have to drop their own replacement - the javadoc says so. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- .../wicket/core/util/string/JavaScriptUtils.java | 18 +++-- .../core/util/string/JavaScriptUtilsTest.java | 21 ++++++ .../AjaxEditableLabelErrorMessageEscapeTest.java | 78 ++++++++++++++++++++++ 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptUtils.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptUtils.java index 8e4d042cd1..b960be05fc 100644 --- a/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptUtils.java +++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptUtils.java @@ -76,12 +76,21 @@ public class JavaScriptUtils /** - * Escape single and double quotes so that they can be part of e.g. an alert call. - * + * Escape the backslash and both quote characters, so that the value can be placed inside a + * JavaScript string literal, e.g. the argument of an alert call. + * <p> + * The backslash has to be escaped, and has to be escaped first. A value that already carries a + * backslash immediately before a quote would otherwise end up with two backslashes in front of + * that quote: the first escapes the second, the quote itself is left unescaped and closes the + * literal, and whatever the value carries after that point is no longer text but script. + * <p> + * Note: this method used to leave the backslash untouched. A caller that escaped the backslash + * itself before calling this method now escapes it twice, and has to drop its own replacement. + * <p> * Note: JSON values need to escape only the double quote, so this method wont help. - * + * * @param input - * the JavaScript which needs to be escaped + * the value which needs to be escaped * @return Escaped version of the input */ public static CharSequence escapeQuotes(final CharSequence input) @@ -89,6 +98,7 @@ public class JavaScriptUtils CharSequence s = input; if (s != null) { + s = Strings.replaceAll(s, "\\", "\\\\"); s = Strings.replaceAll(s, "'", "\\'"); s = Strings.replaceAll(s, "\"", "\\\""); } diff --git a/wicket-core/src/test/java/org/apache/wicket/core/util/string/JavaScriptUtilsTest.java b/wicket-core/src/test/java/org/apache/wicket/core/util/string/JavaScriptUtilsTest.java index 1c7f5c499a..1a6ddeba1b 100644 --- a/wicket-core/src/test/java/org/apache/wicket/core/util/string/JavaScriptUtilsTest.java +++ b/wicket-core/src/test/java/org/apache/wicket/core/util/string/JavaScriptUtilsTest.java @@ -89,4 +89,25 @@ public class JavaScriptUtilsTest extends Assert JavaScriptUtils.SCRIPT_OPEN_TAG); assertEquals("\n/*]]>*/\n</script>\n", JavaScriptUtils.SCRIPT_CLOSE_TAG); } + + @Test + public void escapeQuotes() + { + assertEquals("alert(\\'foo\\\\tbar\\')", + JavaScriptUtils.escapeQuotes("alert('foo\\tbar')").toString()); + } + + /** + * A backslash immediately before a quote must not be able to escape the backslash that this + * method puts in front of that quote, which would leave the quote free to close the literal. + */ + @Test + public void escapeQuotesKeepsTheValueInsideItsStringLiteral() + { + String backslashBeforeQuote = "a\\';b"; + + CharSequence escaped = JavaScriptUtils.escapeQuotes(backslashBeforeQuote); + + assertEquals("a\\\\\\';b", escaped.toString()); + } } diff --git a/wicket-extensions/src/test/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabelErrorMessageEscapeTest.java b/wicket-extensions/src/test/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabelErrorMessageEscapeTest.java new file mode 100644 index 0000000000..fee167d76f --- /dev/null +++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabelErrorMessageEscapeTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.extensions.ajax.markup.html; + +import static org.junit.Assert.assertTrue; + +import org.apache.wicket.behavior.AbstractAjaxBehavior; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.model.Model; +import org.apache.wicket.util.tester.WicketTestCase; +import org.apache.wicket.validation.IValidatable; +import org.apache.wicket.validation.IValidator; +import org.apache.wicket.validation.ValidationError; +import org.junit.Test; + +/** + * {@link AjaxEditableLabel#onError(org.apache.wicket.ajax.AjaxRequestTarget)} writes the editor's + * first error message into a single quoted JavaScript string literal, and a validation message may + * carry the value the user submitted. + * + * @author Pedro Santos + */ +public class AjaxEditableLabelErrorMessageEscapeTest extends WicketTestCase +{ + /** + * The backslash in front of the quote used to escape the backslash the escaper put there, + * leaving the quote free to close the window.status literal. + */ + private static final String BACKSLASH_BEFORE_QUOTE = "a\\';b"; + + @Test + public void errorMessageStaysInsideItsJavaScriptStringLiteral() + { + AjaxEditableLabel<String> editableLabel = new AjaxEditableLabel<>("editableLabel", + new Model<>("initial")); + editableLabel.add(new IValidator<String>() + { + private static final long serialVersionUID = 1L; + + @Override + public void validate(IValidatable<String> validatable) + { + validatable.error( + new ValidationError().setMessage("rejected: " + validatable.getValue())); + } + }); + + tester.startComponentInPage(editableLabel); + + // "click" the label to get into edit mode + tester.executeAjaxEvent(editableLabel.get("label"), "click"); + + // submit a value the validator will reject + FormComponent<?> editor = (FormComponent<?>)editableLabel.get("editor"); + tester.getRequest().setParameter(editor.getInputName(), BACKSLASH_BEFORE_QUOTE); + tester.getRequest().setParameter("save", "true"); + tester.getRequest().setMethod(Form.METHOD_GET); + tester.executeBehavior((AbstractAjaxBehavior)editor.getBehaviorById(0)); + + String response = tester.getLastResponseAsString(); + assertTrue("no error message was reported", response.contains("a\\\\\\';b")); + } +}
