Repository: jhy/jsoup
Pull request #1970 (2023-06-12 23:56:33)

Bump maven-failsafe-plugin from 3.1.0 to 3.1.2
Bumps [maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.1.0 to 3.1.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/apache/maven-surefire/releases">maven-failsafe-plugin's releases</a>.</em></p>
<blockquote>
<h2>3.1.2</h2>
<h2><a href="https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12317927&amp;version=12353294">Release Notes - Maven Surefire - Version 3.1.2</a></h2>
<!-- raw HTML omitted -->
<ul>
<li>update commons compress to 1.23.0 (<a href="https://redirect.github.com/apache/maven-surefire/pull/655">#655</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>Drop unused commons-lang 2.6 from management (<a href="https://redirect.github.com/apache/maven-surefire/pull/661">#661</a>) <a href="https://github.com/elilja"><code>@​elilja</code></a></li>
<li>Remove old junittoolbox dependency no longer used (<a href="https://redirect.github.com/apache/maven-surefire/pull/658">#658</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>update test libraries (<a href="https://redirect.github.com/apache/maven-surefire/pull/657">#657</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>update commons-io to 2.12.0 (<a href="https://redirect.github.com/apache/maven-surefire/pull/653">#653</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>remove descriptions of Maven 2.x and very old versions (<a href="https://redirect.github.com/apache/maven-surefire/pull/647">#647</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>Remove more details about Maven 2 and old versions (<a href="https://redirect.github.com/apache/maven-surefire/pull/654">#654</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>Simplify method (<a href="https://redirect.github.com/apache/maven-surefire/pull/651">#651</a>) <a href="https://github.com/michael-o"><code>@​michael-o</code></a></li>
<li>Update Sufefire (<a href="https://redirect.github.com/apache/maven-surefire/pull/646">#646</a>) <a href="https://github.com/michael-o"><code>@​michael-o</code></a></li>
<li>Backport (<a href="https://redirect.github.com/apache/maven-surefire/pull/635">#635</a>) <a href="https://github.com/michael-o"><code>@​michael-o</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/SUREFIRE-2166">[SUREFIRE-2166]</a> - Use ChoiceFormat to selective render percentage and elapsed time in SurefireReportRenderer (<a href="https://redirect.github.com/apache/maven-surefire/pull/639">#639</a>) <a href="https://github.com/michael-o"><code>@​michael-o</code></a></li>
<li>Simplify serialization/deserialization of elapsed time (SUREFIRE-2164 + SUREFIRE-2167) (<a href="https://redirect.github.com/apache/maven-surefire/pull/645">#645</a>) <a href="https://github.com/michael-o"><code>@​michael-o</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/SUREFIRE-2169">[SUREFIRE-2169]</a> - Potential NPE in WrappedReportEntry when #getElapsed(… (<a href="https://redirect.github.com/apache/maven-surefire/pull/643">#643</a>) <a href="https://github.com/michael-o"><code>@​michael-o</code></a></li>
<li>fix SAX bug (<a href="https://redirect.github.com/apache/maven-surefire/pull/642">#642</a>) <a href="https://github.com/michael-o"><code>@​michael-o</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MNG-6829">[MNG-6829]</a> - Replace StringUtils#isEmpty(String) &amp; #isNotEmpty(String) (<a href="https://redirect.github.com/apache/maven-surefire/pull/641">#641</a>) <a href="https://github.com/timtebeek"><code>@​timtebeek</code></a></li>
<li>Fixes (<a href="https://redirect.github.com/apache/maven-surefire/pull/636">#636</a>) <a href="https://github.com/michael-o"><code>@​michael-o</code></a></li>
</ul>
<h2>📦 Dependency updates</h2>
<ul>
<li>Bump animal-sniffer-maven-plugin from 1.21 to 1.23 (<a href="https://redirect.github.com/apache/maven-surefire/pull/624">#624</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
</ul>
<h2>👻 Maintenance</h2>
<ul>
<li><a href="https://issues.apache.org/jira/browse/SUREFIRE-2173">[SUREFIRE-2173]</a> - Pass build and tests with Java 20 (<a href="https://redirect.github.com/apache/maven-surefire/pull/648">#648</a>) <a href="https://github.com/slachiewicz"><code>@​slachiewicz</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/apache/maven-surefire/commit/5e097b58edfe8317ef043e0d2e553cb475305b67"><code>5e097b5</code></a> [maven-release-plugin] prepare release surefire-3.1.2</li>
<li><a href="https://github.com/apache/maven-surefire/commit/255bb51cc0cdc627d1570a073d8c38e52a6ca55d"><code>255bb51</code></a> Update commons compress to 1.23.0</li>
<li><a href="https://github.com/apache/maven-surefire/commit/a77dfb24f8872b44635204d8bab844c78a022cf6"><code>a77dfb2</code></a> Drop unused commons-lang 2.6 from management (<a href="https://redirect.github.com/apache/maven-surefire/issues/661">#661</a>)</li>
<li><a href="https://github.com/apache/maven-surefire/commit/95e8e95ee02ee0e12af70850440bd3d4c053d5db"><code>95e8e95</code></a> [SUREFIRE-2157] Upgrade junit-jupiter to 5.9.3/junit-platform to 1.9.2</li>
<li><a href="https://github.com/apache/maven-surefire/commit/9092b5ec59ef638a6d9d84ca940cca597594c560"><code>9092b5e</code></a> [SUREFIRE-2157] Upgrade surefire IT to 3.1.0</li>
<li><a href="https://github.com/apache/maven-surefire/commit/1af92f5ffe91d111278ee9b88dddcf6721885c6c"><code>1af92f5</code></a> Remove old junittoolbox dependency no longer used (<a href="https://redirect.github.com/apache/maven-surefire/issues/658">#658</a>)</li>
<li><a href="https://github.com/apache/maven-surefire/commit/8878ed5e5cda4e0d365bf19e1d5d15fece8a5d17"><code>8878ed5</code></a> update test libraries (<a href="https://redirect.github.com/apache/maven-surefire/issues/657">#657</a>)</li>
<li><a href="https://github.com/apache/maven-surefire/commit/a6613a815018c832cf0bd5fd1a7f37731a727e79"><code>a6613a8</code></a> Remove redundant space</li>
<li><a href="https://github.com/apache/maven-surefire/commit/e0e89e44ab78445b25cc99bf5f7f2b2b1430756b"><code>e0e89e4</code></a> update commons-io to 2.12.0</li>
<li><a href="https://github.com/apache/maven-surefire/commit/d8ac134c3ac8019043ce4c7f523e1eaf5b26055b"><code>d8ac134</code></a> [MNGSITE-393] remove descriptions of Maven 2.x and very old versions</li>
<li>Additional commits viewable in <a href="https://github.com/apache/maven-surefire/compare/surefire-3.1.0...surefire-3.1.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-failsafe-plugin&package-manager=maven&previous-version=3.1.0&new-version=3.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.gitignore b/.gitignore
index c696abb0..b22249a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ target/
 .settings/
 *Thrash*
 bin/
+.vscode/
diff --git a/CHANGES b/CHANGES
index 42d63a34..e24b64ad 100644
--- a/CHANGES
+++ b/CHANGES
@@ -24,9 +24,26 @@ Release 1.16.2 [PENDING]
 
   * Improvement: tweaked the performance of parsing :pseudo selectors.
 
+  * Improvement: when using the `:empty` pseudo-selector, blank textnodes are now considered empty. Previously,
+    an element containing any whitespace was not considered empty.
+    <https://github.com/jhy/jsoup/issues/1976>
+
   * Bugfix: `form` elements and empty elements (such as `img`) did not have their attributes de-duplicated.
     <https://github.com/jhy/jsoup/pull/1950>
 
+  * Bugfix: in Jsoup.connect(url), URL paths containing a %2B were incorrectly recoded to a '+', or a '+' was recoded
+    to a ' '. Fixed by reverting to the previous behavior of not encoding supplied paths, other than normalizing to
+    ASCII.
+    <https://github.com/jhy/jsoup/issues/1952>
+
+  * Bugfix: in Jsoup.connect(url), the ConstrainableInputStream would clear Thread interrupts when reading the body.
+    This precluded callers from spawning a thread, running a number of requests for a length of time, then joining that
+    thread after interrupting it.
+    <https://github.com/jhy/jsoup/issues/1991>
+
+  * Bugfix: when tracking HTML source positions, the closing tags for H1...H6 elements were not tracked correctly.
+    <https://github.com/jhy/jsoup/issues/1987>
+
   * Change: removed previously deprecated methods Document#normalise, Element#forEach(org.jsoup.helper.Consumer<>),
     Node#forEach(org.jsoup.helper.Consumer<>), and the org.jsoup.helper.Consumer interface; the latter being a
     previously required compatibility shim prior to Android's de-sugaring support.
diff --git a/pom.xml b/pom.xml
index 48ec01bb..a581a887 100644
--- a/pom.xml
+++ b/pom.xml
@@ -185,7 +185,7 @@
       </plugin>
       <plugin>
         <artifactId>maven-failsafe-plugin</artifactId>
-        <version>3.1.0</version>
+        <version>3.1.2</version>
         <executions>
           <execution>
             <goals>
@@ -307,7 +307,7 @@
         <plugins>
           <plugin>
             <artifactId>maven-failsafe-plugin</artifactId>
-            <version>3.1.0</version>
+            <version>3.1.2</version>
             <executions>
               <execution>
                 <goals>
diff --git a/src/main/java/org/jsoup/helper/UrlBuilder.java b/src/main/java/org/jsoup/helper/UrlBuilder.java
index 89f46a13..4deda367 100644
--- a/src/main/java/org/jsoup/helper/UrlBuilder.java
+++ b/src/main/java/org/jsoup/helper/UrlBuilder.java
@@ -38,24 +38,20 @@ final class UrlBuilder {
                 u.getUserInfo(),
                 IDN.toASCII(decodePart(u.getHost())), // puny-code
                 u.getPort(),
-                decodePart(u.getPath()),
-                null, null // query and fragment appended later so as not to encode
+                null, null, null // path, query and fragment appended later so as not to encode
             );
 
-            String normUrl = uri.toASCIIString();
-            if (q != null || u.getRef() != null) {
-                StringBuilder sb = StringUtil.borrowBuilder().append(normUrl);
-                if (q != null) {
-                    sb.append('?');
-                    appendToAscii(StringUtil.releaseBuilder(q), true, sb);
-                }
-                if (u.getRef() != null) {
-                    sb.append('#');
-                    appendToAscii(u.getRef(), false, sb);
-                }
-                normUrl = StringUtil.releaseBuilder(sb);
+            StringBuilder normUrl = StringUtil.borrowBuilder().append(uri.toASCIIString());
+            appendToAscii(u.getPath(), false, normUrl);
+            if (q != null) {
+                normUrl.append('?');
+                appendToAscii(StringUtil.releaseBuilder(q), true, normUrl);
             }
-            u =  new URL(normUrl);
+            if (u.getRef() != null) {
+                normUrl.append('#');
+                appendToAscii(u.getRef(), false, normUrl);
+            }
+            u = new URL(StringUtil.releaseBuilder(normUrl));
             return u;
         } catch (MalformedURLException | URISyntaxException | UnsupportedEncodingException e) {
             // we assert here so that any incomplete normalization issues can be caught in devel. but in practise,
diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java
index 54928f4e..aa2c2751 100644
--- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java
+++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java
@@ -49,8 +49,8 @@ public final class ConstrainableInputStream extends BufferedInputStream {
     public int read(byte[] b, int off, int len) throws IOException {
         if (interrupted || capped && remaining <= 0)
             return -1;
-        if (Thread.interrupted()) {
-            // interrupted latches, because parse() may call twice (and we still want the thread interupt to clear)
+        if (Thread.currentThread().isInterrupted()) {
+            // interrupted latches, because parse() may call twice
             interrupted = true;
             return -1;
         }
diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java
index be0498c5..2e49e605 100644
--- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java
+++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java
@@ -424,20 +424,12 @@ public class HtmlTreeBuilder extends TreeBuilder {
     // elnames is sorted, comes from Constants
     void popStackToClose(String... elNames) {
         for (int pos = stack.size() -1; pos >= 0; pos--) {
-            Element next = stack.get(pos);
+            Element el = stack.get(pos);
             stack.remove(pos);
-            if (inSorted(next.normalName(), elNames))
-                break;
-        }
-    }
-
-    void popStackToBefore(String elName) {
-        for (int pos = stack.size() -1; pos >= 0; pos--) {
-            Element next = stack.get(pos);
-            if (next.normalName().equals(elName)) {
+            if (inSorted(el.normalName(), elNames)) {
+                if (currentToken instanceof Token.EndTag)
+                    onNodeClosed(el, currentToken);
                 break;
-            } else {
-                stack.remove(pos);
             }
         }
     }
diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java
index cb5d644b..d8929fc3 100644
--- a/src/main/java/org/jsoup/select/Evaluator.java
+++ b/src/main/java/org/jsoup/select/Evaluator.java
@@ -710,7 +710,10 @@ public abstract class Evaluator {
 		public boolean matches(Element root, Element element) {
         	List<Node> family = element.childNodes();
             for (Node n : family) {
-                if (!(n instanceof Comment || n instanceof XmlDeclaration || n instanceof DocumentType)) return false;
+                if (n instanceof TextNode)
+                    return ((TextNode)n).isBlank();
+                if (!(n instanceof Comment || n instanceof XmlDeclaration || n instanceof DocumentType))
+                    return false;
             }
         	return true;
 		}


Test patch:
diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java
index 840d8a82..9834f72a 100644
--- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java
+++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java
@@ -255,13 +255,13 @@ public class HttpConnectionTest {
     }
 
     @Test public void encodeUrl() throws MalformedURLException {
-        URL url1 = new URL("https://test.com/foo bar/[One]?q=white space#frag");
+        URL url1 = new URL("https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag");
         URL url2 = new UrlBuilder(url1).build();
         assertEquals("https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag", url2.toExternalForm());
     }
 
     @Test void encodedUrlDoesntDoubleEncode() throws MalformedURLException {
-        URL url1 = new URL("https://test.com/foo bar/[One]?q=white space#frag ment");
+        URL url1 = new URL("https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag%20ment");
         URL url2 = new UrlBuilder(url1).build();
         URL url3 = new UrlBuilder(url2).build();
         assertEquals("https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag%20ment", url2.toExternalForm());
@@ -274,6 +274,20 @@ public class HttpConnectionTest {
         assertEquals("https://example.com/a%20b%20c?query+string", url.toExternalForm());
     }
 
+    @Test void encodedUrlPathIsPreserved() {
+        // https://github.com/jhy/jsoup/issues/1952
+        Connection connect = Jsoup.connect("https://example.com/%2B32");
+        URL url = connect.request().url();
+        assertEquals("https://example.com/%2B32", url.toExternalForm());
+    }
+
+    @Test void urlPathPlusIsPreserved() {
+        // https://github.com/jhy/jsoup/issues/1952
+        Connection connect = Jsoup.connect("https://example.com/123+456");
+        URL url = connect.request().url();
+        assertEquals("https://example.com/123+456", url.toExternalForm());
+    }
+
     @Test public void noUrlThrowsValidationError() throws IOException {
         HttpConnection con = new HttpConnection();
         boolean threw = false;
diff --git a/src/test/java/org/jsoup/integration/ConnectIT.java b/src/test/java/org/jsoup/integration/ConnectIT.java
index 84315b4a..c3789357 100644
--- a/src/test/java/org/jsoup/integration/ConnectIT.java
+++ b/src/test/java/org/jsoup/integration/ConnectIT.java
@@ -14,6 +14,7 @@ import java.io.IOException;
 import java.net.SocketTimeoutException;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -70,7 +71,29 @@ public class ConnectIT {
         assertTrue(runner.isInterrupted());
         runner.join();
 
-        assertEquals(0, body[0].length()); // doesn't ready a failed doc
+        assertEquals(0, body[0].length()); // doesn't read a failed doc
+    }
+
+    @Test public void canInterruptThenJoinASpawnedThread() throws InterruptedException {
+        // https://github.com/jhy/jsoup/issues/1991
+        AtomicBoolean ioException = new AtomicBoolean();
+        Thread runner = new Thread(() -> {
+            try {
+                while (!Thread.currentThread().isInterrupted()) {
+                    Document doc  = Jsoup.connect(SlowRider.Url)
+                        .timeout(4000)
+                        .get();
+                }
+            } catch (IOException e) {
+                ioException.set(true); // don't expect to catch, because the outer sleep will complete before this timeout
+            }
+        });
+
+        runner.start();
+        Thread.sleep(2 * 1000);
+        runner.interrupt();
+        runner.join();
+        assertFalse(ioException.get());
     }
 
     @Test
diff --git a/src/test/java/org/jsoup/nodes/PositionTest.java b/src/test/java/org/jsoup/nodes/PositionTest.java
index 069d5c6a..1db73c9e 100644
--- a/src/test/java/org/jsoup/nodes/PositionTest.java
+++ b/src/test/java/org/jsoup/nodes/PositionTest.java
@@ -203,4 +203,20 @@ class PositionTest {
         }
     }
 
+    @Test void tracksClosingHeadingTags() {
+        // https://github.com/jhy/jsoup/issues/1987
+        String html = "<h1>One</h1><h2>Two</h2><h10>Ten</h10>";
+        Document doc = Jsoup.parse(html, TrackingParser);
+
+        Elements els = doc.body().children();
+        for (Element el : els) {
+            assertTrue(el.sourceRange().isTracked());
+            assertTrue(el.endSourceRange().isTracked());
+        }
+
+        Element h2 = doc.expectFirst("h2");
+        assertEquals("1,13:12-1,17:16", h2.sourceRange().toString());
+        assertEquals("1,20:19-1,25:24", h2.endSourceRange().toString());
+    }
+
 }
\ No newline at end of file
diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java
index 5aa74a23..3196dc25 100644
--- a/src/test/java/org/jsoup/select/SelectorTest.java
+++ b/src/test/java/org/jsoup/select/SelectorTest.java
@@ -1164,4 +1164,21 @@ public class SelectorTest {
 
         assertEquals(1, map.size()); // root of doc 2
     }
+
+    @Test public void blankTextNodesAreConsideredEmpty() {
+        // https://github.com/jhy/jsoup/issues/1976
+        String html = "<li id=1>\n </li><li id=2></li><li id=3> </li><li id=4>One</li><li id=5><span></li>";
+        Document doc = Jsoup.parse(html);
+        Elements empty = doc.select("li:empty");
+        Elements notEmpty = doc.select("li:not(:empty)");
+
+        assertEquals(3, empty.size());
+        assertEquals(2, notEmpty.size());
+
+        assertEquals("1", empty.get(0).id());
+        assertEquals("2", empty.get(1).id());
+        assertEquals("3", empty.get(2).id());
+        assertEquals("4", notEmpty.get(0).id());
+        assertEquals("5", notEmpty.get(1).id());
+    }
 }