rzo1 commented on code in PR #2125:
URL: https://github.com/apache/stormcrawler/pull/2125#discussion_r3944167684


##########
core/src/main/java/org/apache/stormcrawler/bolt/SiteMapParserBolt.java:
##########
@@ -103,22 +118,19 @@ public void execute(Tuple tuple) {
 
         LOG.debug("Processing {}", url);
 
-        boolean looksLikeSitemap = sniff(content);
-        // can force the mimetype as we know it is XML
-        if (looksLikeSitemap) {
+        String isSitemap = metadata.getFirstValue(isSitemapKey);
+
+        // only sniff when the operator asked for it: a page deciding how the
+        // pipeline treats it must not depend on a string in its body, and a
+        // sniffed document also needs a sitemap compatible content type
+        if (isSitemap == null && sniffContent && sniffsAsSitemap(ct, content)) 
{

Review Comment:
   Regression here.
   
   On `main`, `sniff()` ran unconditionally and set `ct = "application/xml"` 
whenever the namespace was found, **including** for documents already marked 
`isSitemap=true`. Now the override only happens when `isSitemap == null && 
sniffContent`, and `sniffContent` defaults to false.
   
   `parseSiteMap` passes `ct` straight to `parser.parseSiteMap(contentType, 
content, url)` unless it is blank or `octet-stream`. So a sitemap served as 
`text/html`, which is common and the reason the sniff existed, now throws 
`UnknownFormatException` and becomes `FETCH_ERROR` where it used to parse.
   
   Sniffing to *confirm* a document the crawl already declared a sitemap is not 
attacker-driven classification. Only sniffing to *promote* an unmarked document 
is.
   
   ```suggestion
           if (isSitemap == null && sniffContent && sniffsAsSitemap(ct, 
content)) {
               LOG.info("{} detected as sitemap based on content and content 
type", url);
               ct = "application/xml";
               isSitemap = "true";
           } else if (Boolean.parseBoolean(isSitemap) && sniff(content)) {
               // already declared a sitemap: the namespace only confirms the 
type,
               // it does not decide how the pipeline treats the document, so a
               // sitemap served with the wrong content type still parses
               ct = "application/xml";
           }
   ```



##########
core/src/main/java/org/apache/stormcrawler/bolt/SiteMapParserBolt.java:
##########
@@ -86,6 +86,21 @@ public class SiteMapParserBolt extends StatusEmitterBolt {
 
     private int maxOffsetGuess = 300;
 
+    /**
+     * Whether a document without the {@code isSitemap} key is classified as a 
sitemap by searching
+     * the first bytes for the sitemaps.org namespace. Any page that carries 
the namespace string
+     * early enough is reclassified as a sitemap and never reaches the parser 
bolt, so this defaults
+     * to false, like {@code feed.sniffContent} does for feeds.
+     */
+    private boolean sniffContent = false;
+
+    /**
+     * Whether the parser rejects documents which are not well formed 
sitemaps. Strict parsing keeps

Review Comment:
   This describes `strictNamespace`, not `strict`.
   
   crawler-commons `SiteMapParser` (1.6) has two separate flags:
   
   ```
   protected boolean strict;           // strict URL checking: same host and 
path prefix
   protected boolean strictNamespace;  // namespace / well-formedness
   ```
   
   The constructor argument used on line 360 is `strict`, which is URL scoping. 
So the javadoc, the yaml comment and the PR description all describe the other 
flag.
   
   If the namespace check is what you want, call `setStrictNamespace(true)` and 
leave URL scoping off. If URL scoping is what you want, say that, and see my 
comment on `crawler-default.yaml:290`.



##########
core/src/main/resources/crawler-default.yaml:
##########
@@ -275,6 +275,20 @@ config:
   # filters URLs in sitemaps based on their modified Date (if any)
   sitemap.filter.hours.since.modified: -1
 
+  # whether a document without the isSitemap key is classified as a sitemap
+  # by searching the first bytes of its content for the sitemaps.org
+  # namespace. Off by default: any page carrying the namespace string early
+  # enough would be reclassified as a sitemap and never reach the parser
+  # bolt. When enabled, a content type which rules a sitemap out (a page
+  # served as HTML) stops the sniffing.
+  sitemap.sniffContent: false
+
+  # whether the sitemap parser rejects documents which are not well formed
+  # sitemaps. Strict parsing also discards URLs a sitemap lists on hosts
+  # other than its own, and keeps an ordinary HTML page which mentions the
+  # sitemap namespace from being parsed leniently into half a sitemap.
+  sitemap.strict: true

Review Comment:
   Defaulting this to `true` silently shrinks existing crawls.
   
   crawler-commons `strict` is strict URL checking, so a sitemap may only list 
URLs under its own host and path. `www.example.com` vs `example.com`, CDN 
hosts, and a sitemap at `/sitemaps/foo.xml` covering `/` are all common and all 
spec violations. The two test fixtures this PR had to rewrite 
(`stormcrawler.sitemap.extensions.*.xml`, `http://www.example.com/` to 
`https://stormcrawler.apache.org/with-image.html`) are a direct consequence.
   
   Cross-host enrolment from a sitemap is a genuine concern, but under the 
security model this is a hardening decision, and this one costs users URLs 
without warning.
   
   Either default to `false` and document it as recommended, or ship it on and 
make it a headline item in the upgrade note. Also note the yaml comment here 
repeats the incorrect description of what `strict` does; see my comment on 
`SiteMapParserBolt.java:98`.



##########
core/src/main/java/org/apache/stormcrawler/bolt/SiteMapParserBolt.java:
##########
@@ -140,12 +152,21 @@ public void execute(Tuple tuple) {
             // exception while parsing the sitemap
             String errorMessage = "Exception while parsing " + url + ": " + e;
             LOG.error(errorMessage);
-            // send to status stream in case another component wants to update
-            // its status
+            /*
+             * A document which does not parse as a sitemap is most likely an
+             * ordinary page whose persisted metadata carried isSitemap=true.
+             * Dropping the marking and emitting it as FETCH_ERROR keeps it
+             * schedulable: a terminal ERROR would remove it from the crawl for
+             * good when fetchInterval.error is negative, which lets whoever
+             * controls the content remove URLs from the corpus. The document
+             * goes on to the parser bolt on its next fetch, like any other
+             * page.
+             */
+            metadata.remove(isSitemapKey);
             metadata.setValue(Constants.STATUS_ERROR_SOURCE, "sitemap 
parsing");
             metadata.setValue(Constants.STATUS_ERROR_MESSAGE, errorMessage);
             collector.emit(
-                    Constants.StatusStreamName, tuple, new Values(url, 
metadata, Status.ERROR));
+                    Constants.StatusStreamName, tuple, new Values(url, 
metadata, Status.FETCH_ERROR));

Review Comment:
   This line exceeds the limit and the formatter will rewrite it. CI runs the 
format check (`-Dskip.format.code=false`).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to