You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@stormcrawler.apache.org by jn...@apache.org on 2026/07/21 16:31:14 UTC
(stormcrawler) 01/01: Resolve relative outlinks against in JSoupParserBolt
This is an automated email from the ASF dual-hosted git repository.
jnioche pushed a commit to branch JSOUPbaseURI
in repository https://gitbox.apache.org/repos/asf/stormcrawler.git
commit d5987a12ccb743a557689e218ac420883fdba6a1
Author: Julien Nioche <ju...@digitalpebble.com>
AuthorDate: Tue Jul 21 17:31:03 2026 +0100
Resolve relative outlinks against <base href> in JSoupParserBolt
Relative links were resolved against the document URL, ignoring any
<base href> element. On a page served from a sub-path with a base
pointing at the domain root this doubled the path segment. Resolve
against jsoupDoc.baseUri() instead, which honours <base> and falls
back to the document URL when absent.
---
.../apache/stormcrawler/bolt/JSoupParserBolt.java | 11 +++++++-
.../stormcrawler/bolt/JSoupParserBoltTest.java | 33 ++++++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/core/src/main/java/org/apache/stormcrawler/bolt/JSoupParserBolt.java b/core/src/main/java/org/apache/stormcrawler/bolt/JSoupParserBolt.java
index 3d63fd6c..577d828f 100644
--- a/core/src/main/java/org/apache/stormcrawler/bolt/JSoupParserBolt.java
+++ b/core/src/main/java/org/apache/stormcrawler/bolt/JSoupParserBolt.java
@@ -338,7 +338,16 @@ public class JSoupParserBolt extends StatusEmitterBolt {
} else {
final Elements links = jsoupDoc.select("a[href]");
slinks = new HashMap<>(links.size());
- final URL baseUrl = URLUtil.toURL(url);
+ // Resolve relative links against the document's base URI
+ // rather than its URL: when the page contains a <base href>
+ // element jsoup updates baseUri() accordingly, which is what
+ // browsers use to resolve relative hrefs. Falls back to the
+ // document URL when no (valid) <base> is present.
+ String baseUri = jsoupDoc.baseUri();
+ if (StringUtils.isBlank(baseUri)) {
+ baseUri = url;
+ }
+ final URL baseUrl = URLUtil.toURL(baseUri);
for (Element link : links) {
// nofollow
String[] relkeywords = link.attr("rel").split(" ");
diff --git a/core/src/test/java/org/apache/stormcrawler/bolt/JSoupParserBoltTest.java b/core/src/test/java/org/apache/stormcrawler/bolt/JSoupParserBoltTest.java
index 100344d4..05ae595c 100644
--- a/core/src/test/java/org/apache/stormcrawler/bolt/JSoupParserBoltTest.java
+++ b/core/src/test/java/org/apache/stormcrawler/bolt/JSoupParserBoltTest.java
@@ -304,4 +304,37 @@ class JSoupParserBoltTest extends ParsingTester {
Assertions.assertEquals(
"http://www.javascriptlinks.com/mylink", statusTuples.get(0).get(0));
}
+
+ /**
+ * Relative outlinks must be resolved against the document's {@code <base href>} element when
+ * one is present, matching browser behaviour, rather than against the document URL. Otherwise a
+ * page served from a sub-path with a base pointing at the domain root doubles the path segment.
+ */
+ @Test
+ void testBaseHrefOutlinkResolution() throws IOException {
+ bolt.prepare(stormConf, TestUtil.getMockedTopologyContext(), new OutputCollector(output));
+
+ String html =
+ "<html><head>"
+ + "<base href=\"https://www.example.be/\">"
+ + "</head><body>"
+ + "<a href=\"fr/rayons/stockage-archivage-de-donnees/218725\">Stockage</a>"
+ + "</body></html>";
+
+ Metadata metadata = new Metadata();
+ metadata.setValue("Content-Type", "text/html; charset=UTF-8");
+ parse(
+ "https://www.example.be/fr/rayons/actualites-reportages/216943",
+ html.getBytes(StandardCharsets.UTF_8),
+ metadata);
+
+ List<List<Object>> statusTuples = output.getEmitted(Constants.StatusStreamName);
+ Assertions.assertEquals(1, statusTuples.size());
+ Assertions.assertEquals(Status.DISCOVERED, statusTuples.get(0).get(2));
+ // resolved against the <base href> (domain root), NOT against the document path,
+ // which would have produced .../actualites-reportages/fr/rayons/stockage-...
+ Assertions.assertEquals(
+ "https://www.example.be/fr/rayons/stockage-archivage-de-donnees/218725",
+ statusTuples.get(0).get(0));
+ }
}