This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new f988a65b4e TIKA-5050: resolve EPUB manifest hrefs as URI references
(#3546)
f988a65b4e is described below
commit f988a65b4eff7eb84405364711e22bcbd2c07387
Author: Dominik Schmidt <[email protected]>
AuthorDate: Fri Nov 6 17:41:43 2026 +0100
TIKA-5050: resolve EPUB manifest hrefs as URI references (#3546)
* TIKA-5050: resolve EPUB manifest hrefs as URI references
URLDecoder decodes form data and made a '+' a space, and the href was
appended to the OPF's folder without resolving '../'. ZipEntryPaths
(zip-commons) resolves a reference against the entry holding it per RFC 3986
and percent-decodes it as a URI path; EpubParser uses it for manifest hrefs and
encryption.xml URIs alike.
* TIKA-5050: address review: literal base entry, no entry for a reference
without a path
The entry a reference sits in is a name, not a URI: a %, # or ? in it was
interpreted, so My%20Book/content.opf resolved its chapters into My Book/. A
reference that is empty or only a fragment or query resolved to the OPF or its
folder; it now points at no entry.
---------
Co-authored-by: Tim Allison <[email protected]>
---
CHANGES.txt | 5 +
.../org/apache/tika/parser/epub/EpubParser.java | 35 ++-----
.../apache/tika/parser/epub/EpubParserTest.java | 116 +++++++++++++++++++++
.../org/apache/tika/zip/utils/ZipEntryPaths.java | 73 +++++++++++++
.../apache/tika/zip/utils/ZipEntryPathsTest.java | 93 +++++++++++++++++
5 files changed, 298 insertions(+), 24 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index fd37309e65..d96cda2cb8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -81,6 +81,11 @@ Release 4.3.0 - ???
EXTRACTION AND FORMATS
+ * EpubParser resolves manifest hrefs as URI references relative to the OPF:
a '+' is
+ no longer read as a space, and '../' is followed, so such chapters and
images are no
+ longer skipped. encryption.xml URIs are resolved the same way. The new
+ ZipEntryPaths in tika-parser-zip-commons does the resolving (TIKA-5050).
+
* Canon CR3 raws have a parser of their own, Cr3Parser in the image module,
instead
of going through the MP4 parser: their Exif data (IFD0, Exif IFD, Canon
maker note,
GPS) is extracted, the JPEG preview (PRVW, 1620x1080 on current bodies)
is the
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java
index ad0ad4ee90..6faf3dd212 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java
@@ -20,8 +20,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -65,6 +63,7 @@ import org.apache.tika.sax.EmbeddedContentHandler;
import org.apache.tika.sax.XHTMLBalancingHandler;
import org.apache.tika.sax.XHTMLContentHandler;
import org.apache.tika.utils.XMLReaderUtils;
+import org.apache.tika.zip.utils.ZipEntryPaths;
import org.apache.tika.zip.utils.ZipFileHelper;
/**
@@ -201,11 +200,6 @@ public class EpubParser implements Parser {
return fallbackParseAllHtmlEntries(zipFile, bodyHandler,
normalizer, metadata, context,
"OPF declared no spine items in (possibly truncated)
container");
}
- String relativePath = "";
- if (rootOPF.lastIndexOf("/") > -1) {
- relativePath = rootOPF.substring(0, rootOPF.lastIndexOf("/") + 1);
- }
-
extractMetadata(zipFile, metadata, context);
Set<String> encryptedItems = checkForDRM(zipFile);
Set<String> processed = new HashSet<>();
@@ -228,12 +222,12 @@ public class EpubParser implements Parser {
shouldParse = true;
}
if (shouldParse) {
- String path = relativePath + hRefMediaPair.href;
+ String path = ZipEntryPaths.resolve(rootOPF,
hRefMediaPair.href);
//if content is encrypted, do not parse it, throw an
exception now
if (encryptedItems.contains(path)) {
maybeThrowEncryptedException(encryptedItems);
}
- zae = zipFile.getEntry(relativePath + hRefMediaPair.href);
+ zae = path == null ? null : zipFile.getEntry(path);
if (zae != null) {
try (TikaInputStream tis = entryStream(zipFile, zae)) {
content.parse(tis, bodyHandler, metadata, context);
@@ -278,12 +272,12 @@ public class EpubParser implements Parser {
for (String id : contentOrderScraper.locationMap.keySet()) {
if (!processed.contains(id)) {
HRefMediaPair hRefMediaPair =
contentOrderScraper.locationMap.get(id);
- String fullPath = relativePath + hRefMediaPair.href;
- if (encryptedItems.contains(fullPath)) {
+ String fullPath = ZipEntryPaths.resolve(rootOPF,
hRefMediaPair.href);
+ if (fullPath == null || encryptedItems.contains(fullPath)) {
continue;
}
if (shouldHandleEmbedded(hRefMediaPair.media)) {
- handleEmbedded(zipFile, relativePath, hRefMediaPair,
+ handleEmbedded(zipFile, fullPath, hRefMediaPair,
id.equals(contentOrderScraper.getCoverId()),
embeddedDocumentExtractor, xhtml, metadata,
context);
}
@@ -434,17 +428,12 @@ public class EpubParser implements Parser {
return TikaInputStream.get(() -> zipFile.getInputStream(entry), new
TemporaryResources(), null);
}
- private void handleEmbedded(ZipFile zipFile, String relativePath,
HRefMediaPair hRefMediaPair,
+ private void handleEmbedded(ZipFile zipFile, String fullPath,
HRefMediaPair hRefMediaPair,
boolean cover,
EmbeddedDocumentExtractor
embeddedDocumentExtractor,
XHTMLContentHandler xhtml, Metadata
parentMetadata,
ParseContext context)
throws IOException, SAXException, TikaException {
- if (hRefMediaPair.href == null) {
- return;
- }
- String fullPath = relativePath + hRefMediaPair.href;
-
ZipArchiveEntry ze = zipFile.getEntry(fullPath);
if (ze == null || !zipFile.canReadEntryData(ze)) {
return;
@@ -569,11 +558,6 @@ public class EpubParser implements Parser {
String href = XMLReaderUtils.getAttrValue("href", atts);
String mime = XMLReaderUtils.getAttrValue("media-type",
atts);
if (id != null && href != null) {
- try {
- href = URLDecoder.decode(href, UTF_8.name());
- } catch (UnsupportedEncodingException e) {
- //swallow
- }
locationMap.put(id, new HRefMediaPair(href, mime));
String properties =
XMLReaderUtils.getAttrValue("properties", atts);
if (coverImageItem == null && properties != null
@@ -640,7 +624,10 @@ public class EpubParser implements Parser {
public void startElement(String uri, String localName, String qName,
Attributes attributes) {
if ("CipherReference".equals(localName)) {
String encryptedUri = XMLReaderUtils.getAttrValue("URI",
attributes);
- encryptedItems.add(encryptedUri);
+ if (encryptedUri != null) {
+ String path = ZipEntryPaths.resolve("", encryptedUri);
+ encryptedItems.add(path != null ? path : encryptedUri);
+ }
}
}
public Set<String> getEncryptedItems() {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java
index 0e133b3a02..32a5d6ea02 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java
@@ -21,10 +21,16 @@ import static
org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -185,6 +191,116 @@ public class EpubParserTest extends TikaTest {
assertEquals("test rights", m.getValues(TikaCoreProperties.RIGHTS)[1]);
}
+ /**
+ * Manifest hrefs are URI references relative to the OPF: a '+' is a '+',
percent
+ * escapes are decoded, '../' and fragments are resolved.
+ */
+ @Test
+ public void testHrefsAreResolvedAsUris() throws Exception {
+ String[][] items = {
+ {"a", "Text/plain.xhtml", "OEBPS/Text/plain.xhtml",
"PLAINCHAPTER"},
+ {"b", "Text/c++.xhtml", "OEBPS/Text/c++.xhtml", "PLUSCHAPTER"},
+ {"c", "Text/My%20Chapter.xhtml", "OEBPS/Text/My
Chapter.xhtml", "SPACECHAPTER"},
+ {"d", "../Shared/up.xhtml", "Shared/up.xhtml", "UPCHAPTER"},
+ {"e", "Text/frag.xhtml#start", "OEBPS/Text/frag.xhtml",
"FRAGMENTCHAPTER"}};
+ List<Metadata> list;
+ try (TikaInputStream tis = TikaInputStream.get(epub(items, null))) {
+ list = getRecursiveMetadata(tis, true);
+ }
+ String content = list.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+ for (String[] item : items) {
+ assertContains(item[3], content);
+ }
+ Metadata cover = list.get(1);
+ assertEquals("OEBPS/Images/cover art.png",
cover.get(TikaCoreProperties.RESOURCE_NAME_KEY));
+
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
+ cover.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
+ }
+
+ /**
+ * encryption.xml names its items by URI too: an item named with an escape
is still known
+ * as encrypted, so its bytes are neither parsed as a chapter nor emitted.
+ */
+ @Test
+ public void testEscapedEncryptedItems() throws Exception {
+ String[][] items = {{"a", "Text/My%20Chapter.xhtml", "OEBPS/Text/My
Chapter.xhtml",
+ "SPACECHAPTER"}};
+ List<Metadata> list;
+ try (TikaInputStream tis = TikaInputStream.get(epub(items,
+ "OEBPS/Text/My%20Chapter.xhtml"))) {
+ list = getRecursiveMetadata(tis, true);
+ }
+ assertContains("EncryptedDocumentException",
+ list.get(0).get(TikaCoreProperties.CONTAINER_EXCEPTION));
+ assertNotContained("SPACECHAPTER",
+
String.valueOf(list.get(0).get(TikaCoreProperties.TIKA_CONTENT)));
+
+ try (TikaInputStream tis = TikaInputStream.get(epub(items,
+ "OEBPS/Images/cover%20art.png"))) {
+ list = getRecursiveMetadata(tis, true);
+ }
+ assertContains("SPACECHAPTER",
list.get(0).get(TikaCoreProperties.TIKA_CONTENT));
+ assertEquals(1, list.size(), "the encrypted cover is not emitted");
+ }
+
+ /**
+ * An EPUB with its OPF in OEBPS/, one spine item per {id, href, entry
name, text}, a
+ * cover image whose href holds an escaped space, and, if given, an
encrypted URI.
+ */
+ private static byte[] epub(String[][] items, String encryptedUri) throws
IOException {
+ StringBuilder manifest = new StringBuilder();
+ StringBuilder spine = new StringBuilder();
+ for (String[] item : items) {
+ manifest.append("<item id=\"").append(item[0]).append("\"
href=\"").append(item[1])
+ .append("\" media-type=\"application/xhtml+xml\"/>");
+ spine.append("<itemref idref=\"").append(item[0]).append("\"/>");
+ }
+ manifest.append("<item id=\"cover\" href=\"Images/cover%20art.png\" "
+ + "media-type=\"image/png\" properties=\"cover-image\"/>");
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ try (ZipOutputStream zip = new ZipOutputStream(bytes)) {
+ byte[] mimetype =
"application/epub+zip".getBytes(StandardCharsets.US_ASCII);
+ ZipEntry first = new ZipEntry("mimetype");
+ first.setMethod(ZipEntry.STORED);
+ first.setSize(mimetype.length);
+ CRC32 crc = new CRC32();
+ crc.update(mimetype);
+ first.setCrc(crc.getValue());
+ zip.putNextEntry(first);
+ zip.write(mimetype);
+ put(zip, "META-INF/container.xml", "<?xml
version=\"1.0\"?><container version=\"1.0\" "
+ +
"xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\"><rootfiles>"
+ + "<rootfile full-path=\"OEBPS/content.opf\" "
+ +
"media-type=\"application/oebps-package+xml\"/></rootfiles></container>");
+ if (encryptedUri != null) {
+ put(zip, "META-INF/encryption.xml", "<?xml
version=\"1.0\"?><encryption "
+ +
"xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\" "
+ +
"xmlns:enc=\"http://www.w3.org/2001/04/xmlenc#\"><enc:EncryptedData>"
+ + "<enc:CipherData><enc:CipherReference URI=\"" +
encryptedUri + "\"/>"
+ +
"</enc:CipherData></enc:EncryptedData></encryption>");
+ }
+ put(zip, "OEBPS/content.opf", "<?xml version=\"1.0\"?><package "
+ + "xmlns=\"http://www.idpf.org/2007/opf\" version=\"3.0\" "
+ + "unique-identifier=\"id\"><metadata "
+ +
"xmlns:dc=\"http://purl.org/dc/elements/1.1/\"><dc:identifier id=\"id\">x"
+ +
"</dc:identifier><dc:title>T</dc:title></metadata><manifest>" + manifest
+ + "</manifest><spine>" + spine + "</spine></package>");
+ for (String[] item : items) {
+ put(zip, item[2], "<?xml version=\"1.0\"?><html "
+ +
"xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>t</title></head>"
+ + "<body><p>" + item[3] + "</p></body></html>");
+ }
+ zip.putNextEntry(new ZipEntry("OEBPS/Images/cover art.png"));
+ zip.write(new byte[]{(byte) 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a,
'\n'});
+ }
+ return bytes.toByteArray();
+ }
+
+ private static void put(ZipOutputStream zip, String name, String content)
throws IOException {
+ zip.putNextEntry(new ZipEntry(name));
+ zip.write(content.getBytes(StandardCharsets.UTF_8));
+ }
+
private Set<String> set(Metadata m, Property property) {
return new HashSet<>(Arrays.asList(m.getValues(property)));
}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-zip-commons/src/main/java/org/apache/tika/zip/utils/ZipEntryPaths.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-zip-commons/src/main/java/org/apache/tika/zip/utils/ZipEntryPaths.java
new file mode 100644
index 0000000000..090406f498
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-zip-commons/src/main/java/org/apache/tika/zip/utils/ZipEntryPaths.java
@@ -0,0 +1,73 @@
+/*
+ * 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.zip.utils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Resolves the URI references one entry of a zip package makes to another, as
an EPUB
+ * manifest's hrefs or an OPC package's relationship targets.
+ */
+public final class ZipEntryPaths {
+
+ private ZipEntryPaths() {
+ }
+
+ /**
+ * The name of the entry a reference points to: the reference resolved
against the entry
+ * holding it (RFC 3986, section 5.2) and percent-decoded as a URI path,
without the
+ * leading slash. A {@code +} stays a {@code +}. A reference that is not a
valid URI, as
+ * one with a raw space, is taken as a literal path. The entry name it is
resolved against
+ * is literal: a {@code %}, {@code #} or {@code ?} in it is part of the
name.
+ *
+ * @param from the name of the entry holding the reference, "" for
the package root
+ * @param reference the URI reference
+ * @return the entry name, or {@code null} for a reference with a scheme
or an
+ * authority, one without a path (empty or only a fragment), or one that
leads above the
+ * package root
+ */
+ public static String resolve(String from, String reference) {
+ if (from == null || reference == null) {
+ return null;
+ }
+ try {
+ URI ref = uri(reference.trim());
+ if (ref.getScheme() != null || ref.getRawAuthority() != null
+ || ref.getRawPath() == null || ref.getRawPath().isEmpty())
{
+ return null;
+ }
+ String base = from.startsWith("/") ? from : "/" + from;
+ String path = new URI(null, null, base,
null).resolve(ref).normalize().getPath();
+ if (path == null || !path.startsWith("/") || path.equals("/..")
+ || path.startsWith("/../")) {
+ return null;
+ }
+ return path.substring(1);
+ } catch (URISyntaxException | IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private static URI uri(String s) throws URISyntaxException {
+ try {
+ return new URI(s);
+ } catch (URISyntaxException e) {
+ return new URI(null, null, s, null);
+ }
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-zip-commons/src/test/java/org/apache/tika/zip/utils/ZipEntryPathsTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-zip-commons/src/test/java/org/apache/tika/zip/utils/ZipEntryPathsTest.java
new file mode 100644
index 0000000000..62815a8e1b
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-zip-commons/src/test/java/org/apache/tika/zip/utils/ZipEntryPathsTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.zip.utils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+public class ZipEntryPathsTest {
+
+ @Test
+ public void testRelativeToTheEntry() {
+ assertEquals("OEBPS/Text/ch1.xhtml",
ZipEntryPaths.resolve("OEBPS/content.opf",
+ "Text/ch1.xhtml"));
+ assertEquals("OEBPS/Text/ch1.xhtml",
ZipEntryPaths.resolve("OEBPS/content.opf",
+ "./Text/ch1.xhtml"));
+ assertEquals("Images/cover.jpg",
ZipEntryPaths.resolve("OEBPS/content.opf",
+ "../Images/cover.jpg"));
+ assertEquals("ch1.xhtml", ZipEntryPaths.resolve("content.opf",
"ch1.xhtml"));
+ assertEquals("Metadata/thumbnail.png", ZipEntryPaths.resolve("",
"Metadata/thumbnail.png"));
+ }
+
+ @Test
+ public void testAbsolutePath() {
+ assertEquals("Metadata/thumbnail.png", ZipEntryPaths.resolve("",
+ "/Metadata/thumbnail.png"));
+ assertEquals("Thumbnails/box.png",
ZipEntryPaths.resolve("3D/3dmodel.model",
+ "/Thumbnails/box.png"));
+ }
+
+ @Test
+ public void testDecoding() {
+ assertEquals("OEBPS/Text/c++ b.xhtml",
ZipEntryPaths.resolve("OEBPS/content.opf",
+ "Text/c++%20b.xhtml"));
+ assertEquals("OEBPS/Text/Kapitel รค.xhtml",
ZipEntryPaths.resolve("OEBPS/content.opf",
+ "Text/Kapitel%20%C3%A4.xhtml"));
+ assertEquals("OEBPS/Text/ch1.xhtml",
ZipEntryPaths.resolve("OEBPS/content.opf",
+ "Text/ch1.xhtml#section2"));
+ }
+
+ @Test
+ public void testNotAValidUri() {
+ assertEquals("OEBPS/Text/my chapter.xhtml",
ZipEntryPaths.resolve("OEBPS/content.opf",
+ "Text/my chapter.xhtml"));
+ assertEquals("OEBPS/Text/100%.xhtml",
ZipEntryPaths.resolve("OEBPS/content.opf",
+ "Text/100%.xhtml"));
+ assertEquals("My Book/Text/ch1.xhtml", ZipEntryPaths.resolve("My
Book/content.opf",
+ "Text/ch1.xhtml"));
+ }
+
+ /** The entry the reference sits in is a name, not a URI: its %, # and ?
are literal. */
+ @Test
+ public void testLiteralBase() {
+ assertEquals("My%20Book/Text/ch1.xhtml",
ZipEntryPaths.resolve("My%20Book/content.opf",
+ "Text/ch1.xhtml"));
+ assertEquals("OEBPS#1/Text/ch1.xhtml",
ZipEntryPaths.resolve("OEBPS#1/content.opf",
+ "Text/ch1.xhtml"));
+ assertEquals("a?b/ch1.xhtml", ZipEntryPaths.resolve("a?b/content.opf",
"ch1.xhtml"));
+ }
+
+ /** A reference without a path points at no other entry. */
+ @Test
+ public void testNoPath() {
+ assertNull(ZipEntryPaths.resolve("OEBPS/content.opf", ""));
+ assertNull(ZipEntryPaths.resolve("OEBPS/content.opf", "#frag"));
+ assertNull(ZipEntryPaths.resolve("OEBPS/content.opf", "?query"));
+ }
+
+ @Test
+ public void testOutsideThePackage() {
+ assertNull(ZipEntryPaths.resolve("OEBPS/content.opf",
"../../etc/passwd"));
+ assertNull(ZipEntryPaths.resolve("", "../x"));
+ assertNull(ZipEntryPaths.resolve("", "http://example.com/x.png"));
+ assertNull(ZipEntryPaths.resolve("", "//example.com/x.png"));
+ assertNull(ZipEntryPaths.resolve("", "file:///etc/passwd"));
+ assertNull(ZipEntryPaths.resolve("", null));
+ }
+}