|
16 | 16 |
|
17 | 17 | import java.util.Arrays; |
18 | 18 | import java.util.Locale; |
| 19 | +import java.util.concurrent.CountDownLatch; |
| 20 | +import java.util.concurrent.atomic.AtomicInteger; |
| 21 | +import java.util.concurrent.atomic.AtomicReference; |
19 | 22 |
|
20 | 23 | import static org.junit.jupiter.api.Assertions.*; |
21 | 24 |
|
@@ -226,6 +229,53 @@ public void safeListedProtocolShouldBeRetained(Locale locale) { |
226 | 229 | assertFalse(new Cleaner(Safelist.none()).isValid(okDoc)); |
227 | 230 | } |
228 | 231 |
|
| 232 | + @Test void configuredCleanerMayBeSharedAcrossThreads() throws InterruptedException { |
| 233 | + // https://github.com/jhy/jsoup/issues/2473 |
| 234 | + String html = "<a href='/foo'>Link</a><img src='/bar' alt='Q'>"; |
| 235 | + String baseUri = "https://example.com/"; |
| 236 | + String expected = "<a href=\"https://example.com/foo\">Link</a><img src=\"https://example.com/bar\" alt=\"Q\">"; |
| 237 | + Cleaner cleaner = new Cleaner(Safelist.basicWithImages()); |
| 238 | + |
| 239 | + int numThreads = 10; |
| 240 | + int numLoops = 20; |
| 241 | + String[] cleaned = new String[numThreads * numLoops]; |
| 242 | + AtomicInteger next = new AtomicInteger(); |
| 243 | + AtomicReference<Throwable> failure = new AtomicReference<>(); |
| 244 | + CountDownLatch start = new CountDownLatch(1); |
| 245 | + CountDownLatch done = new CountDownLatch(numThreads); |
| 246 | + Thread[] threads = new Thread[numThreads]; |
| 247 | + |
| 248 | + for (int i = 0; i < numThreads; i++) { |
| 249 | + Thread thread = new Thread(() -> { |
| 250 | + try { |
| 251 | + start.await(); |
| 252 | + for (int j = 0; j < numLoops; j++) { |
| 253 | + Document dirty = Jsoup.parseBodyFragment(html, baseUri); |
| 254 | + cleaned[next.getAndIncrement()] = cleaner.clean(dirty).body().html(); |
| 255 | + } |
| 256 | + } catch (Throwable t) { |
| 257 | + failure.compareAndSet(null, t); |
| 258 | + if (t instanceof InterruptedException) Thread.currentThread().interrupt(); |
| 259 | + } finally { |
| 260 | + done.countDown(); |
| 261 | + } |
| 262 | + }); |
| 263 | + threads[i] = thread; |
| 264 | + thread.start(); |
| 265 | + } |
| 266 | + |
| 267 | + start.countDown(); |
| 268 | + done.await(); |
| 269 | + |
| 270 | + if (failure.get() != null) |
| 271 | + throw new AssertionError("Concurrent cleaner use failed", failure.get()); |
| 272 | + |
| 273 | + assertEquals(cleaned.length, next.get()); |
| 274 | + for (String clean : cleaned) { |
| 275 | + assertEquals(expected, clean); |
| 276 | + } |
| 277 | + } |
| 278 | + |
229 | 279 | @Test public void resolvesRelativeLinks() { |
230 | 280 | String html = "<a href='/foo'>Link</a><img src='/bar'>"; |
231 | 281 | String clean = Jsoup.clean(html, "http://example.com/", Safelist.basicWithImages()); |
|
0 commit comments