This is an automated email from the ASF dual-hosted git repository.

dashorst pushed a commit to branch wicket-10.x
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 9d23805cd062a8b4a7b31b5b3964ab9a0f82ec7d
Author: Pedro Santos <[email protected]>
AuthorDate: Sun Sep 13 16:27:44 2026 -0300

    Escape the window name in AjaxNewWindowNotifyingBehavior
    
    The behaviour asks the browser for window.name, remembers it in
    boundName, and on every later render writes it back into the
    precondition of its Ajax call inside a single-quoted JavaScript string
    literal, so that the call is only made when the name has changed. The
    name went in as is, so a quote or a backslash in it ended the literal
    and left the rest of the name as script rather than text.
    
    The name now goes through JavaScriptUtils#escapeQuotes, and so does the
    generated name that respond writes into window.name for a nameless
    window, since a subclass may override newWindowName.
    
    There was no test for the class; the new one covers the escaping of both
    literals and that a different name still reports a new window.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
---
 .../ajax/AjaxNewWindowNotifyingBehaviorTest.java   | 140 +++++++++++++++++++++
 .../ajax/AjaxNewWindowNotifyingBehavior.java       |  10 +-
 2 files changed, 147 insertions(+), 3 deletions(-)

diff --git 
a/wicket-core-tests/src/test/java/org/apache/wicket/ajax/AjaxNewWindowNotifyingBehaviorTest.java
 
b/wicket-core-tests/src/test/java/org/apache/wicket/ajax/AjaxNewWindowNotifyingBehaviorTest.java
new file mode 100644
index 0000000000..a9bb77a540
--- /dev/null
+++ 
b/wicket-core-tests/src/test/java/org/apache/wicket/ajax/AjaxNewWindowNotifyingBehaviorTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.ajax;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.wicket.MockPageWithLinkAndComponent;
+import org.apache.wicket.markup.html.WebComponent;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link AjaxNewWindowNotifyingBehavior}
+ */
+class AjaxNewWindowNotifyingBehaviorTest extends WicketTestCase
+{
+       private static final String PARAM_WINDOW_NAME = "windowName";
+
+       /**
+        * The name the browser reports is written back into the precondition 
of the behavior's Ajax
+        * call, inside a JavaScript string literal, so its quotes and 
backslashes have to be escaped.
+        */
+       @Test
+       void windowNameIsEscapedInPrecondition()
+       {
+               AjaxNewWindowNotifyingBehavior behavior = new 
AjaxNewWindowNotifyingBehavior();
+               MockPageWithLinkAndComponent page = newPage(behavior);
+
+               tester.startPage(page);
+               assertNull(behavior.getWindowName());
+               
assertFalse(tester.getLastResponseAsString().contains("window.name !=="),
+                       "no precondition before the behavior is bound to a 
window");
+
+               String name = "d:\\it's \"home\"";
+               tester.getRequest().setParameter(PARAM_WINDOW_NAME, name);
+               tester.executeBehavior(behavior);
+               assertEquals(name, behavior.getWindowName());
+
+               tester.startPage(page);
+               String response = tester.getLastResponseAsString();
+               assertTrue(response.contains("return (window.name !== 
'd:\\\\it\\'s \\\"home\\\"');"),
+                       response);
+               assertFalse(response.contains("!== 'd:\\it's"), response);
+       }
+
+       /**
+        * A nameless window gets a generated name, which is also placed inside 
a string literal.
+        */
+       @Test
+       void generatedWindowNameIsEscaped()
+       {
+               AjaxNewWindowNotifyingBehavior behavior = new 
AjaxNewWindowNotifyingBehavior()
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected String newWindowName()
+                       {
+                               return "it's";
+                       }
+               };
+               MockPageWithLinkAndComponent page = newPage(behavior);
+
+               tester.startPage(page);
+               tester.getRequest().setParameter(PARAM_WINDOW_NAME, "");
+               tester.executeBehavior(behavior);
+
+               assertEquals("it's", behavior.getWindowName());
+               
assertTrue(tester.getLastResponseAsString().contains("window.name = 'it\\'s';"),
+                       tester.getLastResponseAsString());
+       }
+
+       /**
+        * The behavior still notices a different window name once it is bound.
+        */
+       @Test
+       void differentWindowNameIsNewWindow()
+       {
+               boolean[] newWindow = new boolean[1];
+               AjaxNewWindowNotifyingBehavior behavior = new 
AjaxNewWindowNotifyingBehavior()
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected void onNewWindow(AjaxRequestTarget target)
+                       {
+                               newWindow[0] = true;
+                       }
+               };
+               MockPageWithLinkAndComponent page = newPage(behavior);
+
+               tester.startPage(page);
+               tester.getRequest().setParameter(PARAM_WINDOW_NAME, "first");
+               tester.executeBehavior(behavior);
+               assertFalse(newWindow[0]);
+
+               tester.getRequest().setParameter(PARAM_WINDOW_NAME, "first");
+               tester.executeBehavior(behavior);
+               assertFalse(newWindow[0]);
+
+               tester.getRequest().setParameter(PARAM_WINDOW_NAME, "second");
+               tester.executeBehavior(behavior);
+               assertTrue(newWindow[0]);
+               assertEquals("first", behavior.getWindowName());
+       }
+
+       private static MockPageWithLinkAndComponent 
newPage(AjaxNewWindowNotifyingBehavior behavior)
+       {
+               MockPageWithLinkAndComponent page = new 
MockPageWithLinkAndComponent();
+               page.add(new 
WebComponent(MockPageWithLinkAndComponent.COMPONENT_ID).add(behavior));
+               page.add(new Link<Void>(MockPageWithLinkAndComponent.LINK_ID)
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       public void onClick()
+                       {
+                       }
+               });
+               return page;
+       }
+}
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxNewWindowNotifyingBehavior.java
 
b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxNewWindowNotifyingBehavior.java
index f7e982d1ad..1bd100c75f 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxNewWindowNotifyingBehavior.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxNewWindowNotifyingBehavior.java
@@ -22,6 +22,7 @@ import org.apache.wicket.Component;
 import org.apache.wicket.Page;
 import org.apache.wicket.ajax.attributes.AjaxCallListener;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.core.util.string.JavaScriptUtils;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.OnLoadHeaderItem;
 import org.apache.wicket.util.lang.Args;
@@ -72,13 +73,15 @@ public class AjaxNewWindowNotifyingBehavior extends 
AbstractDefaultAjaxBehavior
 
                if (boundName != null)
                {
-                       // already bound, send request only when changed
+                       // already bound, send request only when changed; the 
name was reported by the browser
+                       // and is placed inside a string literal, so it is 
escaped
                        attributes.getAjaxCallListeners().add(new 
AjaxCallListener()
                        {
                                @Override
                                public CharSequence getPrecondition(Component 
component)
                                {
-                                       return String.format("return 
(window.name !== '%s');", boundName);
+                                       return String.format("return 
(window.name !== '%s');",
+                                               
JavaScriptUtils.escapeQuotes(boundName));
                                }
                        });
                }
@@ -109,7 +112,8 @@ public class AjaxNewWindowNotifyingBehavior extends 
AbstractDefaultAjaxBehavior
                        {
                                // create new name
                                windowName = newWindowName();
-                               
target.appendJavaScript(String.format("window.name = '%s';", windowName));
+                               
target.appendJavaScript(String.format("window.name = '%s';",
+                                       
JavaScriptUtils.escapeQuotes(windowName)));
                        }
 
                        // now bound to window

Reply via email to