rzo1 opened a new issue, #2082:
URL: https://github.com/apache/stormcrawler/issues/2082

   
   ## What happens
   `FetchItem.create` uses `u.getHost()` as the politeness queue id and 
`HttpRobotRulesParser.getCacheKey` builds `protocol:host:port` from the same 
string. Both only lowercase it. okhttp percent-decodes the host when it parses 
the URL, so `http://exampl%65.org/` and `http://example.org/` are one origin at 
connect time but two queue ids and two robots.txt cache entries in the bolt. 
`BasicURLNormalizer` does not close the gap either: it lowercases the host but 
leaves percent-escapes in place.
   
   ## Where
   `core/src/main/java/org/apache/stormcrawler/bolt/FetcherBolt.java:189` and 
`:197` (the same lowercasing at `:170`, and `SimpleFetcherBolt.java:628` and 
`:631`), 
`core/src/main/java/org/apache/stormcrawler/protocol/HttpRobotRulesParser.java:69-81`,
 
`core/src/main/java/org/apache/stormcrawler/filtering/basic/BasicURLNormalizer.java:136`.
 Config keys involved: `fetcher.queue.mode` (default `byHost`), 
`fetcher.server.delay`, `fetcher.threads.number`.
   
   ```java
       key = u.getHost();
   ...
       queueId = key.toLowerCase(Locale.ROOT);
   ```
   
   ```java
       String host = url.getHost().toLowerCase(Locale.ROOT);
       return protocol + ":" + host + ":" + port;
   ```
   
   ## Why it matters
   One server can be addressed under many host spellings that all resolve to 
the same origin, and each spelling gets its own delay clock and its own 
robots.txt download. The per-host delay is then applied several times in 
parallel to the same server, bounded by `fetcher.threads.number` (10 by 
default), not by the number of aliases. Robots rules are still obeyed, since 
every alias re-fetches and re-parses the same file, so this is wasted requests 
and a politeness overrun on the remote side rather than a rules bypass. A crawl 
that follows outlinks can pick these up from any page that links to the aliased 
forms.
   
   ## Reproduction
   
   Save as 
`core/src/test/java/org/apache/stormcrawler/protocol/HostAliasCacheKeyTest.java`.
   
   ```java
   /*
    * Licensed to the Apache Software Foundation (ASF) under one or more
    * contributor license agreements.  See the NOTICE file distributed with
    * this work for additional information regarding copyright ownership.
    * The ASF licenses this file to you under the Apache License, Version 2.0
    * (the "License"); you may not use this file except in compliance with
    * the License.  You may obtain a copy of the License at
    *
    *      http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
   
   package org.apache.stormcrawler.protocol;
   
   import java.net.URL;
   import okhttp3.HttpUrl;
   import org.junit.jupiter.api.Assertions;
   import org.junit.jupiter.api.Test;
   
   /**
    * Two URLs whose host strings differ only by percent-escaping or by a 
trailing dot are sent to the
    * same server by okhttp, so they must share one robots.txt cache entry.
    */
   class HostAliasCacheKeyTest {
   
       @Test
       void okhttpCollapsesHostAliases() {
           // what the client actually connects to
           Assertions.assertEquals(
                   "example.org", 
HttpUrl.parse("http://%65xample.org/a";).host(), "percent-escaped");
           Assertions.assertEquals(
                   "example.org", 
HttpUrl.parse("http://exampl%65.org/a";).host(), "percent-escaped");
           Assertions.assertEquals(
                   "example.org", HttpUrl.parse("http://EXAMPLE.org/a";).host(), 
"upper case");
       }
   
       @Test
       void robotsCacheKeyIsTheSameForHostAliases() throws Exception {
           String canonical = HttpRobotRulesParser.getCacheKey(new 
URL("http://example.org/a";));
           String escaped = HttpRobotRulesParser.getCacheKey(new 
URL("http://exampl%65.org/a";));
           Assertions.assertEquals(canonical, escaped);
       }
   }
   ```
   
   Run it:
   
   ```
   mvn -pl core test -Dtest=HostAliasCacheKeyTest
   ```
   
   `okhttpCollapsesHostAliases` passes and shows what the client connects to; 
`robotsCacheKeyIsTheSameForHostAliases` fails on main.
   
   ```
   [ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 
0.151 s <<< FAILURE! -- in 
org.apache.stormcrawler.protocol.HostAliasCacheKeyTest
   [ERROR] 
org.apache.stormcrawler.protocol.HostAliasCacheKeyTest.robotsCacheKeyIsTheSameForHostAliases
 -- Time elapsed: 0.018 s <<< FAILURE!
   org.opentest4j.AssertionFailedError: expected: <http:example.org:80> but 
was: <http:exampl%65.org:80>
   ```
   
   ## Suggested fix
   Add one host canonicalisation helper in `URLUtil` that percent-decodes the 
host, lowercases it and strips a trailing dot, and call it from 
`FetchItem.create`, `SimpleFetcherBolt.getPolitenessKey` and 
`HttpRobotRulesParser.getCacheKey` so all three agree with what okhttp will 
connect to. Alternatively reject URLs whose host contains a percent escape in 
`BasicURLNormalizer.filter`, which is stricter and drops such URLs from the 
crawl entirely; that changes what an existing crawl accepts, so it needs a 
release note. The first option is the smaller change and does not alter which 
URLs are crawled, only how they are grouped.
   


-- 
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