SystemComponentIdWireTest.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.tika.pipes.core.serialization;
import static org.apache.tika.pipes.core.serialization.WireTestUtil.root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.StringReader;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
import org.junit.jupiter.api.Test;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.pipes.api.FetchEmitTuple;
import org.apache.tika.pipes.api.emitter.EmitKey;
import org.apache.tika.pipes.api.fetcher.FetchKey;
/**
* A request must not be able to name a {@code __} component -- that is what keeps /pipes and
* /async away from the fetchers and emitters tika-server wires up for /tika and /unpack. The
* parent-to-child IPC builds those same tuples itself and must still be able to name them.
*/
public class SystemComponentIdWireTest {
private static String tuple(String fetcherId, String emitterId) {
return "{\"id\":\"t\",\"fetcher\":\"" + fetcherId + "\",\"fetchKey\":\"k\"," +
"\"emitter\":\"" + emitterId + "\",\"emitKey\":\"ek\"}";
}
/**
* The surrounding-whitespace cases are the point of normalizing before validating: checking
* the raw id and resolving the trimmed one would let these bind the component they name.
*/
@Test
public void requestCannotNameSystemFetcher() {
for (String fetcherId : new String[]{"__tika-server", " __tika-server", "__tika-server ",
"\\t__unpack"}) {
Exception e = assertThrows(Exception.class,
() -> JsonFetchEmitTuple.fromJson(new StringReader(tuple(fetcherId, "e"))),
"should have rejected fetcher id '" + fetcherId + "'");
assertTrue(root(e).contains("is reserved"),
"expected reserved rejection for '" + fetcherId + "', got: " + root(e));
}
}
@Test
public void requestCannotNameSystemEmitter() {
for (String emitterId : new String[]{"__unpack", " __unpack"}) {
Exception e = assertThrows(Exception.class,
() -> JsonFetchEmitTuple.fromJson(new StringReader(tuple("f", emitterId))),
"should have rejected emitter id '" + emitterId + "'");
assertTrue(root(e).contains("is reserved"),
"expected reserved rejection for '" + emitterId + "', got: " + root(e));
}
}
@Test
public void asyncEndpointCannotNameSystemFetcher() {
Exception e = assertThrows(Exception.class, () -> JsonFetchEmitTupleList.fromJson(
new StringReader("{\"tuples\":[" + tuple("__tika-server", "e") + "]}")));
assertTrue(root(e).contains("is reserved"), "expected reserved rejection, got: " + root(e));
}
/**
* The NBSP case is why the rule is an allowlist rather than a trim: trim() and strip() both
* leave U+00A0, so such an id would be neither reserved nor resolvable.
*/
@Test
public void requestCannotUseIllegalIdCharacters() {
for (String fetcherId : new String[]{"my fetcher", "../etc", "a/b", "f\u00A0oo", "f:oo"}) {
Exception e = assertThrows(Exception.class,
() -> JsonFetchEmitTuple.fromJson(new StringReader(tuple(fetcherId, "e"))),
"should have rejected fetcher id '" + fetcherId + "'");
assertTrue(root(e).contains("Illegal"),
"expected charset rejection for '" + fetcherId + "', got: " + root(e));
}
}
@Test
public void legalIdsAreTrimmed() throws Exception {
FetchEmitTuple t = JsonFetchEmitTuple.fromJson(new StringReader(tuple(" my-fetcher ", " e ")));
assertEquals("my-fetcher", t.getFetchKey().getFetcherId());
assertEquals("e", t.getEmitKey().getEmitterId());
}
@Test
public void internalIpcMayNameSystemComponents() throws Exception {
FetchEmitTuple t = new FetchEmitTuple("t", new FetchKey("__tika-server", "k"),
new EmitKey("__unpack", "ek"), new Metadata(), new ParseContext(),
FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP);
FetchEmitTuple back = JsonPipesIpc.fromBytes(JsonPipesIpc.toBytes(t), FetchEmitTuple.class);
assertEquals("__tika-server", back.getFetchKey().getFetcherId());
assertEquals("__unpack", back.getEmitKey().getEmitterId());
}
@Test
public void internalIpcStillRejectsIllegalIdCharacters() throws Exception {
byte[] smile = new ObjectMapper(new SmileFactory())
.writeValueAsBytes(new ObjectMapper().readTree(tuple("a/b", "e")));
Exception e = assertThrows(Exception.class,
() -> JsonPipesIpc.fromBytes(smile, FetchEmitTuple.class));
assertTrue(root(e).contains("Illegal"), "expected charset rejection, got: " + root(e));
}
}