Repository: jhy/jsoup
Pull request #1586 (2021-07-12 23:02:32)

Bump maven-jar-plugin from 3.0.2 to 3.2.0
Bumps [maven-jar-plugin](https://github.com/apache/maven-jar-plugin) from 3.0.2 to 3.2.0.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/d91fff882f19b0ba9ba90aceca53447cada824ab"><code>d91fff8</code></a> [maven-release-plugin] prepare release maven-jar-plugin-3.2.0</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/64c5e6530b4712cd95501fffb2de6bb1a202cd89"><code>64c5e65</code></a> MJAR-263 make output jars reproducible like m-source-p</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/80f58a84aacff6e671f5a601d62a3a3800b507dc"><code>80f58a8</code></a> [maven-release-plugin] prepare for next development iteration</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/783d893461888533f64893c7e999f3b6c1608b0f"><code>783d893</code></a> [maven-release-plugin] prepare release maven-jar-plugin-3.1.2</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/8164b8a7372501f5f81a00b5d011a19410107b46"><code>8164b8a</code></a> [MJAR-259] - Archiving to jar is very slow</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/78dbb2918fcb2086fe5601eb95bb294bcc274639"><code>78dbb29</code></a> [MJAR-238] Allow setting of module main class</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/13d5a9ec187d813262d88a52f1ac0b8ca7d790d5"><code>13d5a9e</code></a> removing MockArtifact, not used</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/660cb854c104f92e6816285ba52b15e260fd2a26"><code>660cb85</code></a> (doc) fix broken link</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/bc84ec046173efccd2694da66923c1821822ede3"><code>bc84ec0</code></a> [maven-release-plugin] prepare for next development iteration</li>
<li><a href="https://github.com/apache/maven-jar-plugin/commit/e07216551316bc4cbbc7ae31c41bd494612b8c23"><code>e072165</code></a> [maven-release-plugin] prepare release maven-jar-plugin-3.1.1</li>
<li>Additional commits viewable in <a href="https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.0.2...maven-jar-plugin-3.2.0">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-jar-plugin&package-manager=maven&previous-version=3.0.2&new-version=3.2.0)](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/CHANGES b/CHANGES
index b86ecbf7..d60b728b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,6 +23,10 @@ jsoup changelog
   * Bugfix [Fuzz]: malformed deeply nested table elements could create a stack overflow.
     <https://github.com/jhy/jsoup/issues/1577>
 
+  * Bugfix [Fuzz]: Speed optimized malformed HTML creating elements with thousands of elements - limit the attribute
+    count per element when parsing to 512 (in real-world HTML, P99 is ~ 8).
+    <https://github.com/jhy/jsoup/issues/1578>
+
 *** Release 1.14.1 [2021-Jul-10]
   * Change: updated the minimum supported Java version from Java 7 to Java 8.
 
diff --git a/pom.xml b/pom.xml
index ab146c3c..a3f9fd29 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,7 +83,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>3.1.0</version>
+        <version>3.3.0</version>
         <configuration>
             <doclint>none</doclint>
             <source>7</source>
@@ -120,7 +120,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.2.0</version>
         <configuration>
           <archive>
             <manifestEntries>
@@ -156,7 +156,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-resources-plugin</artifactId>
-        <version>3.0.1</version>
+        <version>3.2.0</version>
       </plugin>
       <plugin>
         <artifactId>maven-release-plugin</artifactId>
@@ -165,7 +165,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
-        <version>3.0.0-M3</version>
+        <version>3.0.0-M5</version>
         <configuration>
           <!-- smaller stack to find stack overflows -->
           <argLine>-Xss256k</argLine>
@@ -308,7 +308,7 @@
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter</artifactId>
-      <version>5.6.0</version>
+      <version>5.7.2</version>
       <scope>test</scope>
     </dependency>
 
diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java
index b2dc18ef..6e0d7898 100644
--- a/src/main/java/org/jsoup/nodes/Attributes.java
+++ b/src/main/java/org/jsoup/nodes/Attributes.java
@@ -46,6 +46,7 @@ public class Attributes implements Iterable<Attribute>, Cloneable {
     static final int NotFound = -1;
     private static final String EmptyString = "";
 
+    // the number of instance fields is kept as low as possible giving an object size of 24 bytes
     private int size = 0; // number of slots used (not total capacity, which is keys.length)
     String[] keys = new String[InitialCapacity];
     String[] vals = new String[InitialCapacity];
@@ -246,16 +247,12 @@ public class Attributes implements Iterable<Attribute>, Cloneable {
     }
 
     /**
-     Get the number of attributes in this set.
+     Get the number of attributes in this set, including any jsoup internal-only attributes. Internal attributes are
+     excluded from the {@link #html()}, {@link #asList()}, and {@link #iterator()} methods.
      @return size
      */
     public int size() {
-        int s = 0;
-        for (int i = 0; i < size; i++) {
-            if (!isInternalKey(keys[i]))
-                s++;
-        }
-        return s;
+        return size;
     }
 
     /**
diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java
index 35f52fa7..e2297494 100644
--- a/src/main/java/org/jsoup/parser/CharacterReader.java
+++ b/src/main/java/org/jsoup/parser/CharacterReader.java
@@ -555,7 +555,7 @@ public final class CharacterReader {
     }
 
     /**
-     * Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks.
+     * Caches short strings, as a flyweight pattern, to reduce GC load. Just for this doc, to prevent leaks.
      * <p />
      * Simplistic, and on hash collisions just falls back to creating a new string, vs a full HashMap with Entry list.
      * That saves both having to create objects as hash keys, and running through the entry list, at the expense of
@@ -569,27 +569,22 @@ public final class CharacterReader {
             return "";
 
         // calculate hash:
-        int hash = 31 * count;
-        int offset = start;
+        int hash = 0;
         for (int i = 0; i < count; i++) {
-            hash = 31 * hash + charBuf[offset++];
+            hash = 31 * hash + charBuf[start + i];
         }
 
         // get from cache
         final int index = hash & stringCacheSize - 1;
         String cached = stringCache[index];
 
-        if (cached == null) { // miss, add
+        if (cached != null && rangeEquals(charBuf, start, count, cached)) // positive hit
+            return cached;
+        else {
             cached = new String(charBuf, start, count);
-            stringCache[index] = cached;
-        } else { // hashcode hit, check equality
-            if (rangeEquals(charBuf, start, count, cached)) { // hit
-                return cached;
-            } else { // hashcode conflict
-                cached = new String(charBuf, start, count);
-                stringCache[index] = cached; // update the cache, as recently used strings are more likely to show up again
-            }
+            stringCache[index] = cached; // add or replace, assuming most recently used are most likely to recur next
         }
+
         return cached;
     }
 
diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java
index 9ade8e0d..0b3cb6c5 100644
--- a/src/main/java/org/jsoup/parser/Token.java
+++ b/src/main/java/org/jsoup/parser/Token.java
@@ -97,11 +97,16 @@ abstract class Token {
             return this;
         }
 
+        /* Limits runaway crafted HTML from spewing attributes and getting a little sluggish in ensureCapacity.
+        Real-world HTML will P99 around 8 attributes, so plenty of headroom. Implemented here and not in the Attributes
+        object so that API users can add more if ever required. */
+        private static final int MaxAttributes = 512;
+
         final void newAttribute() {
             if (attributes == null)
                 attributes = new Attributes();
 
-            if (pendingAttributeName != null) {
+            if (pendingAttributeName != null && attributes.size() < MaxAttributes) {
                 // the tokeniser has skipped whitespace control chars, but trimming could collapse to empty for other control codes, so verify here
                 pendingAttributeName = pendingAttributeName.trim();
                 if (pendingAttributeName.length() > 0) {


Test patch:
diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java
index 84d9ed78..d2f986b0 100644
--- a/src/test/java/org/jsoup/nodes/AttributesTest.java
+++ b/src/test/java/org/jsoup/nodes/AttributesTest.java
@@ -225,6 +225,10 @@ public class AttributesTest {
 
         a.put(Attributes.internalKey("baseUri"), "example.com");
         a.put(Attributes.internalKey("another"), "example.com");
-        assertEquals(2, a.size());
+        a.put(Attributes.internalKey("last"), "example.com");
+        a.remove(Attributes.internalKey("last"));
+
+        assertEquals(4, a.size());
+        assertEquals(2, a.asList().size()); // excluded from lists
     }
 }