rzo1 opened a new issue, #2090:
URL: https://github.com/apache/stormcrawler/issues/2090
## What happens
`FetcherBolt` merges every response header into the tuple metadata under the
protocol prefix, lowercased. The okhttp protocol reads the `set-header`
directive from that same prefixed namespace. A server that returns a header
literally named `set-header` therefore produces the metadata key
`protocol.set-header`, which is exactly the key `addHeadersToRequest` uses to
add arbitrary headers to an outgoing request. The merge already strips
`robots.crawl.delay` for the same reason, so the collision class is known, but
`set-header` is not stripped. The other request shaping keys (`http.post.json`,
`http.method.head`, `http.content.limit`, `http.proxy*`) are read without the
prefix and are not reachable this way.
## Where
`core/src/main/java/org/apache/stormcrawler/bolt/FetcherBolt.java:802`, with
the existing strip four lines below at `:806`:
```java
mergedMetadata.putAll(response.getMetadata(),
protocolMetadataPrefix);
// Only the locally parsed robots.txt value may populate
this control signal.
// A colliding protocol prefix/header must not pace an
unrelated queue.
mergedMetadata.remove(Constants.ROBOTS_CRAWL_DELAY_KEY);
```
`core/src/main/java/org/apache/stormcrawler/protocol/okhttp/HttpProtocol.java:299`:
```java
final String[] headerStrings = md.getValues(SET_HEADER_BY_REQUEST,
protocolMetadataPrefix);
```
Config keys involved: `protocol.md.prefix`, `metadata.persist`,
`metadata.transfer`.
## Why it matters
The value only becomes live on a later fetch if the operator persists or
transfers it, and `metadata.transfer` is empty in every shipped configuration.
It is not an exotic setup, though: internals.adoc tells operators to persist
`protocol.` keys such as `protocol.etag` and `protocol.set-cookie`, and both
lists accept wildcards, so a `protocol.*` entry carries the forged key along
with the intended ones. The result is that a crawled site chooses headers sent
on subsequent requests, including requests to other hosts when the key is
transferred to outlinks. Request shaping should come from configuration, not
from a response body or header.
## Reproduction
Save as
`core/src/test/java/org/apache/stormcrawler/protocol/okhttp/ProtocolSetHeaderFromResponseTest.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.okhttp;
import okhttp3.Request;
import org.apache.storm.Config;
import org.apache.stormcrawler.Metadata;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* Documents the current handling of a response header named "set-header".
The header merge in
* FetcherBolt writes response headers under the protocol metadata prefix,
which is the same place
* addHeadersToRequest reads the set-header directive from. The value should
not become a request
* header on a later fetch.
*/
class ProtocolSetHeaderFromResponseTest {
private static Config protocolConfig() {
Config conf = new Config();
conf.put("http.agent.name", "test");
conf.put("http.agent.version", "1.0");
conf.put("http.agent.description", "test");
conf.put("http.agent.url", "http://test.example.com");
conf.put("http.agent.email", "[email protected]");
conf.put("protocol.md.prefix", "protocol.");
return conf;
}
@Test
void responseHeaderNamedSetHeaderBecomesARequestHeader() {
HttpProtocol protocol = new HttpProtocol();
protocol.configure(protocolConfig());
// what a server returned, lowercased and merged the way FetcherBolt
does it
Metadata fromResponse = new Metadata();
fromResponse.addValue("set-header", "X-Forged=1");
Metadata carried = new Metadata();
carried.putAll(fromResponse, "protocol.");
Assertions.assertEquals("X-Forged=1",
carried.getFirstValue("protocol.set-header"));
Request.Builder rb = new
Request.Builder().url("https://example.com/");
protocol.addHeadersToRequest(rb, carried);
Request request = rb.build();
// current behaviour: the server-supplied value is sent on the next
request.
// It should be ignored: only operator configuration may set request
headers.
Assertions.assertEquals("1", request.header("X-Forged"));
}
}
```
Run it:
```
mvn -pl core test -Dtest=ProtocolSetHeaderFromResponseTest
```
It asserts the present behaviour and passes on main, with a comment saying
what should happen instead; the fix can land either in the bolt or in the
protocol, so a failing test would prejudge where.
```
[INFO] Running
org.apache.stormcrawler.protocol.okhttp.ProtocolSetHeaderFromResponseTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.243
s -- in
org.apache.stormcrawler.protocol.okhttp.ProtocolSetHeaderFromResponseTest
```
The test builds metadata the way the bolt does (`putAll(responseMetadata,
"protocol.")`), calls `addHeadersToRequest`, and finds `X-Forged: 1` on the
request.
## Suggested fix
Strip `set-header` from the merged metadata in `FetcherBolt.fetch` right
after the `putAll` with the protocol prefix, next to the existing
`robots.crawl.delay` removal. Beyond that one key, treat this as a class:
reserve a namespace for directives that shape a request and have
`MetadataTransfer` refuse to transfer or persist keys in it unless the operator
names them one by one, so that a wildcard cannot pick them up. Operators who
rely on a `protocol.*` wildcard today will see fewer keys carried, which
belongs in the release notes.
--
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]