Fixes issues 1549 and 1949: fixes and standardizes which characters get escaped
when they're used in history tokens. We were previously being overzealous by
using escapeURIComponent.
patch by: ajr and jgw
reviewed by: jgw
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2791 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImpl.java b/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
index 7feb616..7d5ca88 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
@@ -35,6 +35,16 @@
public abstract void newItem(String historyToken);
+ protected native String decodeFragment(String encodedFragment) /*-{
+ // decodeURI() does *not* decode the '#' character.
+ return decodeURI(encodedFragment.replace("%23", "#"));
+ }-*/;
+
+ protected native String encodeFragment(String fragment) /*-{
+ // encodeURI() does *not* encode the '#' character.
+ return encodeURI(fragment).replace("#", "%23");
+ }-*/;
+
protected native void setToken(String token) /*-{
$wnd.__gwt_historyToken = token;
}-*/;
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java b/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java
index 4b0140f..14343f8 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java
@@ -49,7 +49,7 @@
if (hash.length > 0) {
var token = '';
try {
- token = decodeURIComponent(hash.substring(1));
+ token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
} catch (e) {
// If there's a bad hash, always reload. This could only happen if
// if someone entered or linked to a bad url.
@@ -85,7 +85,7 @@
var hash = $wnd.location.hash;
if (hash.length > 0) {
try {
- $wnd.__gwt_historyToken = decodeURIComponent(hash.substring(1));
+ $wnd.__gwt_historyToken = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
} catch (e) {
// Clear the bad hash and __gwt_historyToken
// (this can't have been a valid token).
@@ -101,12 +101,16 @@
@Override
protected native void injectGlobalHandler() /*-{
+ var historyImplRef = this;
+
$wnd.__gwt_onHistoryLoad = function(token) {
// Change the URL and notify the application that its history frame
// is changing.
+
if (token != $wnd.__gwt_historyToken) {
$wnd.__gwt_historyToken = token;
- $wnd.location.hash = encodeURIComponent(token);
+ $wnd.location.hash = historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(token);
+
@com.google.gwt.user.client.impl.HistoryImpl::onHistoryChanged(Ljava/lang/String;)(token);
}
};
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java b/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java
index 2dc7830..3f1a189 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java
@@ -1,12 +1,12 @@
/*
* Copyright 2007 Google Inc.
- *
+ *
* 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
@@ -19,10 +19,9 @@
* History implementation for Mozilla-based browsers.
*/
class HistoryImplMozilla extends HistoryImplStandard {
-
+
@Override
public native void newItem(String historyToken) /*-{
-
// When the historyToken is blank or null, we are not able to set
// $wnd.location.hash to the empty string, due to a bug in Mozilla.
// Every time $wnd.location.hash is set to the empty string, one of the
@@ -39,7 +38,14 @@
$wnd.location = s + '#';
} else {
- $wnd.location.hash = encodeURIComponent(historyToken);
+ $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(historyToken);
}
}-*/;
+
+ @Override
+ protected String decodeFragment(String encodedFragment) {
+ // Mozilla browsers pre-decode the result of location.hash, so there's no
+ // need to decode it again (which would in fact be incorrect).
+ return encodedFragment;
+ }
}
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java b/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
index d30582d..81b2325 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
@@ -24,9 +24,9 @@
*/
class HistoryImplSafari extends HistoryImplStandard {
- private static boolean isOldSafari = detectOldSafari();
+ static boolean isOldSafari = detectOldSafari();
- private static native boolean detectOldSafari() /*-{
+ static native boolean detectOldSafari() /*-{
var exp = / AppleWebKit\/([\d]+)/;
var result = exp.exec(navigator.userAgent);
if (result) {
@@ -66,17 +66,6 @@
super.newItem(historyToken);
}
- protected native String decode(String historyToken) /*-{
- try {
- // Unlike most other browsers, Safari does not unescape location.hash.
- return decodeURIComponent(historyToken);
- } catch (e) {
- // This is very unlikely to be malformed, since it comes directly from
- // location.hash.
- return "";
- }
- }-*/;
-
private native void initImpl() /*-{
$wnd.__gwt_historyToken = '';
@@ -84,7 +73,7 @@
var hash = $wnd.location.hash;
if (hash.length > 0) {
$wnd.__gwt_historyToken =
- this.@com.google.gwt.user.client.impl.HistoryImplStandard::decode(Ljava/lang/String;)(hash.substring(1));
+ this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
}
@com.google.gwt.user.client.impl.HistoryImpl::onHistoryChanged(Ljava/lang/String;)($wnd.__gwt_historyToken);
@@ -96,7 +85,7 @@
var meta = $doc.createElement('meta');
meta.setAttribute('http-equiv','refresh');
- var newUrl = $wnd.location.href.split('#')[0] + '#' + encodeURIComponent(historyToken);
+ var newUrl = $wnd.location.href.split('#')[0] + '#' + this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(historyToken);
meta.setAttribute('content','0.01;url=' + newUrl);
$doc.body.appendChild(meta);
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java b/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
index bac0dff..e5fa7e6 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
@@ -34,9 +34,7 @@
$wnd.__checkHistory = function() {
var token = '', hash = $wnd.location.hash;
if (hash.length > 0) {
- // Not all browsers decode location.hash the same way, so the
- // implementation needs an opportunity to handle decoding.
- token = historyImpl.@com.google.gwt.user.client.impl.HistoryImplStandard::decode(Ljava/lang/String;)(hash.substring(1));
+ token = historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
}
if (token != $wnd.__gwt_historyToken) {
@@ -49,7 +47,6 @@
// Kick off the timer.
$wnd.__checkHistory();
-
return true;
}-*/;
@@ -58,10 +55,6 @@
if (historyToken == null) {
historyToken = "";
}
- $wnd.location.hash = encodeURIComponent(historyToken);
+ $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(historyToken);
}-*/;
-
- protected String decode(String historyToken) {
- return historyToken;
- }
}
diff --git a/user/test/com/google/gwt/user/client/ui/HistoryTest.java b/user/test/com/google/gwt/user/client/ui/HistoryTest.java
index 4835212..2c5c17b 100644
--- a/user/test/com/google/gwt/user/client/ui/HistoryTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HistoryTest.java
@@ -30,9 +30,54 @@
return "com.google.gwt.user.User";
}
- /* Tests against issue #572: Double unescaping of history tokens. */
+ private static native String getCurrentLocationHash() /*-{
+ var href = $wnd.location.href;
+
+ splitted = href.split("#");
+ if (splitted.length != 2) {
+ return null;
+ }
+
+ hashPortion = splitted[1];
+
+ return hashPortion;
+ }-*/;
+
public void testTokenEscaping() {
+ final String shouldBeEncoded = "% ^[]|\"<>{}\\`";
+ final String shouldBeEncodedAs = "%25%20%5E%5B%5D%7C%22%3C%3E%7B%7D%5C%60";
+
+ delayTestFinish(5000);
+ History.addHistoryListener(new HistoryListener() {
+ public void onHistoryChanged(String token) {
+ assertEquals(shouldBeEncodedAs, getCurrentLocationHash());
+ assertEquals(shouldBeEncoded, token);
+ finishTest();
+ History.removeHistoryListener(this);
+ }
+ });
+ History.newItem(shouldBeEncoded);
+ }
+
+ public void testTokenNonescaping() {
+ final String shouldNotChange = "abc;,/?:@&=+$-_.!~*'()ABC123foo";
+
+ delayTestFinish(5000);
+ History.addHistoryListener(new HistoryListener() {
+ public void onHistoryChanged(String token) {
+ assertEquals(shouldNotChange, getCurrentLocationHash());
+ assertEquals(shouldNotChange, token);
+ finishTest();
+ History.removeHistoryListener(this);
+ }
+ });
+ History.newItem(shouldNotChange);
+ }
+
+ /* Tests against issue #572: Double unescaping of history tokens. */
+ public void testDoubleEscaping() {
final String escToken = "%24%24%24";
+
delayTestFinish(5000);
History.addHistoryListener(new HistoryListener() {
public void onHistoryChanged(String token) {