PathFenceResolverTest.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
*
* https://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.commons.text.lookup;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Tests {@link PathFenceResolver}.
*/
class PathFenceResolverTest {
private static final String DATA = "Hello World!";
private static final Path CURRENT_PATH = Paths.get(StringUtils.EMPTY); // NOT "."
/**
* Reads an InputSource byte stream as UTF-8.
*/
private static String read(final InputSource inputSource) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try (InputStream in = inputSource.getByteStream()) {
final byte[] buffer = new byte[1024];
for (int len = in.read(buffer); len != -1; len = in.read(buffer)) {
out.write(buffer, 0, len);
}
}
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
private static PathFenceResolver resolver(final Path... roots) {
return new PathFenceResolver(PathFence.builder().setRoots(roots).get());
}
/**
* Writes a file and gives back its path relative to the working directory.
*/
private static Path write(final Path path, final String content) throws IOException {
Files.createDirectories(path.getParent());
Files.write(path, content.getBytes(StandardCharsets.UTF_8));
return CURRENT_PATH.toAbsolutePath().relativize(path.toAbsolutePath());
}
@Test
void testAbsentFileInFence(@TempDir final Path tempDir) {
assertThrows(IOException.class, () -> resolver(tempDir).resolveEntity(null, tempDir.resolve("absent.txt").toUri().toString()));
}
@Test
void testDrivePathIsRefused(@TempDir final Path tempDir) {
// A bare Windows drive path is not a 'file:' URI, so it is refused like any other non-file system identifier.
assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, "C:/does-not-matter.txt"));
}
@Test
void testEmptySystemId(@TempDir final Path tempDir) throws Exception {
assertNull(resolver(tempDir).resolveEntity(null, StringUtils.EMPTY));
}
@Test
void testFileUrlInFence(@TempDir final Path tempDir) throws Exception {
final Path target = write(tempDir.resolve("entity.txt"), DATA);
final InputSource inputSource = resolver(tempDir).resolveEntity("publicId", target.toUri().toString());
assertNotNull(inputSource);
assertEquals("publicId", inputSource.getPublicId());
assertEquals(DATA, read(inputSource));
}
@Test
void testFileUrlOutsideFence(@TempDir final Path tempDir) throws Exception {
final Path target = write(tempDir.resolve("out/entity.txt"), DATA);
final SAXException e = assertThrows(SAXException.class, () -> resolver(tempDir.resolve("in")).resolveEntity(null, target.toUri().toString()));
assertInstanceOf(IllegalArgumentException.class, e.getCause());
}
@Test
void testMalformedSystemIdIsRefused(@TempDir final Path tempDir) {
// An unencoded space is illegal in a URI, so the identifier never becomes a path.
final SAXException e = assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, "file:/a b/entity.txt"));
assertInstanceOf(URISyntaxException.class, e.getCause());
}
@Test
void testNullFence() {
assertThrows(NullPointerException.class, () -> new PathFenceResolver(null));
}
@Test
void testNullSystemId(@TempDir final Path tempDir) throws Exception {
assertNull(resolver(tempDir).resolveEntity(null, null));
}
@Test
void testPercentEncodedFileUrlInFence(@TempDir final Path tempDir) throws Exception {
final Path target = write(tempDir.resolve("na me.txt"), DATA);
final InputSource inputSource = resolver(tempDir).resolveEntity(null, target.toUri().toString());
assertNotNull(inputSource);
assertEquals(DATA, read(inputSource));
}
@Test
void testRelativeSystemIdIsRefused(@TempDir final Path tempDir) throws Exception {
// XmlStringLookup parses with the document's own URI, so a relative system identifier always reaches us absolutized.
final Path target = write(tempDir.resolve("entity.txt"), DATA);
assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, target.toString().replace('\\', '/')));
}
@Test
void testRemoteSystemIdIsRefused(@TempDir final Path tempDir) {
// A remote reference names no path, so the fence can never opt it in.
assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, "http://localhost:1/entity.txt"));
assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, "https://localhost:1/entity.txt"));
assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, "ftp://localhost:1/entity.txt"));
assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, "jar:file:/lib.jar!/entity.txt"));
}
@Test
void testSystemIdIsEchoed(@TempDir final Path tempDir) throws Exception {
final Path target = write(tempDir.resolve("entity.txt"), DATA);
final String systemId = target.toUri().toString();
assertEquals(systemId, resolver(tempDir).resolveEntity(null, systemId).getSystemId());
}
@Test
void testUnconvertibleFileUrlIsRefused(@TempDir final Path tempDir) {
// A well-formed 'file:' URI that names no path: opaque, and carrying a fragment.
final SAXException opaque = assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, "file:entity.txt"));
assertInstanceOf(IllegalArgumentException.class, opaque.getCause());
final SAXException fragment = assertThrows(SAXException.class, () -> resolver(tempDir).resolveEntity(null, "file:/entity.txt#frag"));
assertInstanceOf(IllegalArgumentException.class, fragment.getCause());
}
}