Skip to content

Commit b7b7947

Browse files
committed
more refresh processing
1 parent 06c48d6 commit b7b7947

File tree

7 files changed

+96
-17
lines changed

7 files changed

+96
-17
lines changed

src/changes/changes.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
<body>
1010
<release version="4.15.0" date="August xx, 2025" description="Chrome/Edge 139, Firefox 141, Bugfixes">
11+
<action type="add" dev="rbri">
12+
WebClient option pageRefreshLimit added. Refresh handling changed to support a clear limit.
13+
The initial value is 72 to be backward compatible.
14+
</action>
1115
<action type="add" dev="RhinoTeam">
1216
Dummy impl of PointerEvent#getPersistentDeviceId() returning always 0.
1317
</action>

src/main/java/org/htmlunit/WebClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1677,7 +1677,7 @@ && getOptions().isRedirectEnabled()) {
16771677
}
16781678

16791679
if (allowedRedirects == 0) {
1680-
throw new FailingHttpStatusCodeException("Too much redirect for "
1680+
throw new FailingHttpStatusCodeException("Too many redirects for "
16811681
+ webResponse.getWebRequest().getUrl(), webResponse);
16821682
}
16831683

src/main/java/org/htmlunit/WebClientOptions.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public class WebClientOptions implements Serializable {
5252
private boolean throwExceptionOnScriptError_ = true;
5353
private boolean popupBlockerEnabled_;
5454
private boolean isRedirectEnabled_ = true;
55+
// strange value 72 used to be backward compatible with 4.14.0
56+
private int pageRefreshLimit_ = 72;
5557
private File tempFileDirectory_;
5658

5759
private transient KeyStore sslClientCertificateStore_;
@@ -140,6 +142,19 @@ public void setRedirectEnabled(final boolean enabled) {
140142
isRedirectEnabled_ = enabled;
141143
}
142144

145+
/**
146+
* Sets the limit to be used when a page refreshes itself by using a
147+
* http refresh header or meta tag. Set this to -1 to allow endless refresh.
148+
* <p>
149+
* Please have in mind, the {@link NiceRefreshHandler} and the {@link ImmediateRefreshHandler}
150+
* have also some loop protection, that triggers first.
151+
*
152+
* @param pageRefreshLimit the number of refresh loops before throwing an exception
153+
*/
154+
public void setRedirectEnabled(final int pageRefreshLimit) {
155+
pageRefreshLimit_ = pageRefreshLimit;
156+
}
157+
143158
/**
144159
* Returns the directory to be used for storing the response content in
145160
* a temporary file see {@link #getMaxInMemory()}.
@@ -180,6 +195,17 @@ public boolean isRedirectEnabled() {
180195
return isRedirectEnabled_;
181196
}
182197

198+
/**
199+
* Returns the limit to be used when a page refreshes itself by using a
200+
* http refresh header or meta tag. Negative values are interpreted as
201+
* endless refresh support.
202+
*
203+
* @return pageRefreshLimit the number of refresh loops before throwing an exception
204+
*/
205+
public int getPageRefreshLimit() {
206+
return pageRefreshLimit_;
207+
}
208+
183209
/**
184210
* Sets the SSL client certificate {@link KeyStore} to use.
185211
* <p>

src/main/java/org/htmlunit/html/HtmlPage.java

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.concurrent.ConcurrentHashMap;
4444

4545
import org.apache.commons.lang3.StringUtils;
46+
import org.apache.commons.lang3.Strings;
4647
import org.apache.commons.logging.Log;
4748
import org.apache.commons.logging.LogFactory;
4849
import org.htmlunit.Cache;
@@ -938,7 +939,7 @@ public ScriptResult executeJavaScript(String sourceCode, final String sourceName
938939
return new ScriptResult(JavaScriptEngine.UNDEFINED);
939940
}
940941

941-
if (StringUtils.startsWithIgnoreCase(sourceCode, JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
942+
if (Strings.CI.startsWith(sourceCode, JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
942943
sourceCode = sourceCode.substring(JavaScriptURLConnection.JAVASCRIPT_PREFIX.length()).trim();
943944
if (sourceCode.startsWith("return ")) {
944945
sourceCode = sourceCode.substring("return ".length());
@@ -1425,23 +1426,45 @@ private void executeRefreshIfNeeded() throws IOException {
14251426
}
14261427
}
14271428

1428-
final int timeRounded = (int) time;
1429-
checkRecursion();
1430-
getWebClient().getRefreshHandler().handleRefresh(this, url, timeRounded);
1429+
processRefresh(url, time);
14311430
}
14321431

1433-
private void checkRecursion() {
1434-
final StackTraceElement[] elements = new Exception().getStackTrace();
1435-
if (elements.length > 500) {
1436-
for (int i = 0; i < 500; i++) {
1437-
if (!elements[i].getClassName().startsWith("org.htmlunit.")) {
1438-
return;
1439-
}
1440-
}
1432+
// this is different from what is done in org.htmlunit.WebClient.loadWebResponseFromWebConnection(WebRequest, int)
1433+
// because there we are directly replacing the response before loading the response into the window
1434+
// here we are replacing the page in the window (maybe after some time)
1435+
private void processRefresh(final URL url, final double time) throws IOException {
1436+
final WebClient webClient = getWebClient();
1437+
1438+
final int refreshLimit = webClient.getOptions().getPageRefreshLimit();
1439+
if (refreshLimit == 0) {
14411440
final WebResponse webResponse = getWebResponse();
1442-
throw new FailingHttpStatusCodeException("Too much redirect for "
1441+
throw new FailingHttpStatusCodeException("Too many redirects for "
14431442
+ webResponse.getWebRequest().getUrl(), webResponse);
14441443
}
1444+
1445+
if (refreshLimit >= 0) {
1446+
final StackTraceElement[] elements = new Exception().getStackTrace();
1447+
int count = 0;
1448+
final int elementCountLimit = refreshLimit > 50 ? 400 : refreshLimit > 10 ? 80 : 5;
1449+
final int elementCount = elements.length;
1450+
1451+
if (elementCount > elementCountLimit) {
1452+
for (int i = 0; i < elementCount; i++) {
1453+
if ("processRefresh".equals(elements[i].getMethodName())
1454+
&& "org.htmlunit.html.HtmlPage".equals(elements[i].getClassName())) {
1455+
count++;
1456+
if (count >= refreshLimit) {
1457+
final WebResponse webResponse = getWebResponse();
1458+
throw new FailingHttpStatusCodeException(
1459+
"Too many redirects (>= " + count + ") for "
1460+
+ webResponse.getWebRequest().getUrl(), webResponse);
1461+
}
1462+
}
1463+
}
1464+
}
1465+
}
1466+
1467+
webClient.getRefreshHandler().handleRefresh(this, url, (int) time);
14451468
}
14461469

14471470
/**

src/test/java/org/htmlunit/WebClient4Test.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void redirectInfinite303And307() throws Exception {
9595
client.getPage("http://localhost:" + PORT + RedirectServlet307.URL);
9696
}
9797
catch (final Exception e) {
98-
assertTrue(e.getMessage(), e.getMessage().contains("Too much redirect"));
98+
assertTrue(e.getMessage(), e.getMessage().contains("Too many redirects"));
9999
}
100100
}
101101

@@ -433,7 +433,7 @@ public void redirectInfiniteMeta() throws Exception {
433433
client.getPage(URL_FIRST + "test1");
434434
}
435435
catch (final Exception e) {
436-
assertTrue(e.getMessage(), e.getMessage().contains("Too much redirect"));
436+
assertTrue(e.getMessage(), e.getMessage().contains("Too many redirects"));
437437
}
438438
}
439439

src/test/java/org/htmlunit/WebClientTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ public void redirectionSameURL() throws Exception {
465465
getPageWithRedirectionsSameURL(30);
466466
}
467467
catch (final Exception e) {
468-
assertTrue(e.getMessage(), e.getMessage().contains("Too much redirect"));
468+
assertTrue(e.getMessage(), e.getMessage().contains("Too many redirects"));
469469
}
470470
}
471471

src/test/java/org/htmlunit/html/HtmlPageTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.commons.lang3.SerializationUtils;
3737
import org.htmlunit.CollectingAlertHandler;
3838
import org.htmlunit.ElementNotFoundException;
39+
import org.htmlunit.FailingHttpStatusCodeException;
3940
import org.htmlunit.HttpMethod;
4041
import org.htmlunit.ImmediateRefreshHandler;
4142
import org.htmlunit.IncorrectnessListener;
@@ -44,6 +45,7 @@
4445
import org.htmlunit.Page;
4546
import org.htmlunit.SimpleWebTestCase;
4647
import org.htmlunit.StringWebResponse;
48+
import org.htmlunit.WaitingRefreshHandler;
4749
import org.htmlunit.WebClient;
4850
import org.htmlunit.WebRequest;
4951
import org.htmlunit.WebResponse;
@@ -772,6 +774,30 @@ public void refresh_ImmediateRefresh_AvoidOOME() throws Exception {
772774
Thread.sleep(1000);
773775
}
774776

777+
/**
778+
* @throws Exception if the test fails
779+
*/
780+
@Test
781+
@Alerts("Too many redirects (>= 72) for §§URL§§")
782+
public void refresh_EndlessLoop() throws Exception {
783+
final String firstContent = DOCTYPE_HTML
784+
+ "<html><head><title>first</title>\n"
785+
+ "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\">\n"
786+
+ "</head><body></body></html>";
787+
788+
expandExpectedAlertsVariables(URL_FIRST);
789+
790+
final WebClient client = getWebClient();
791+
client.setRefreshHandler(new WaitingRefreshHandler());
792+
try {
793+
loadPage(firstContent);
794+
Assertions.fail("should have thrown");
795+
}
796+
catch (final FailingHttpStatusCodeException e) {
797+
assertEquals(getExpectedAlerts()[0], e.getMessage());
798+
}
799+
}
800+
775801
/**
776802
* Test auto-refresh from a meta tag inside noscript.
777803
* @throws Exception if the test fails

0 commit comments

Comments
 (0)