Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: Handling of encrypted entries #208

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,11 @@ public interface OdfPackage {
* @return true if the file uses any namespaces outside of the ODF
*/
public boolean isExtended();

/**
* Discover if the package had any encrypted entries.
*
* @return true if the package has encrypted entries
*/
public boolean isEncrypted();
}
Original file line number Diff line number Diff line change
Expand Up @@ -340,4 +340,17 @@ public boolean isExtended() {
}
return false;
}

@Override
public boolean isEncrypted() {
if (this.manifest == null) {
return false;
}
for (FileEntry entry : this.manifest.getEntries()) {
if (entry.isEncrypted()) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,15 @@ private final OdfPackage parsePackage(final Path toParse, final String name)
try {
this.format = sniff(toParse);
this.cache = Zips.zipArchiveCacheInstance(toParse);
Map<String, String> badEntries = checkZipEntries();
if (!badEntries.isEmpty()) {
throw new ParseException(badEntries);
}
checkZipEntries();
this.version = detectVersion();
this.mimetype = getMimeEntryValue();
} catch (final IOException e) {
// Simply catch the exception and return a sparsely populated OdfPackage
return OdfPackageImpl.Builder.builder().name(name).format(this.format).build();
}
try {
this.manifest = parseManifest();
this.processZipEntries();
return this.makePackage(name);
} catch (ParserConfigurationException | SAXException e) {
Expand All @@ -126,7 +125,13 @@ private final OdfPackage parsePackage(final Path toParse, final String name)
}
}

private final Map<String, String> checkZipEntries() {
private final String getMimeEntryValue() throws IOException {
return (this.cache.getZipEntry(OdfFormats.MIMETYPE) == null) ? ""
: new String(this.cache.getEntryInputStream(OdfFormats.MIMETYPE).readAllBytes(),
StandardCharsets.UTF_8);
}

private void checkZipEntries() throws ParseException {
final Map<String, String> badEntries = new HashMap<>();
for (ZipEntry entry : this.cache.getZipEntries()) {
try {
Expand All @@ -137,7 +142,9 @@ private final Map<String, String> checkZipEntries() {
badEntries.put(entry.getName(), String.format(MESS_IO_EXCEPTION, e.getMessage()));
}
}
return badEntries;
if (!badEntries.isEmpty()) {
throw new ParseException(badEntries);
}
}

final Version detectVersion() throws IOException {
Expand Down Expand Up @@ -168,30 +175,31 @@ private final void processZipEntries() throws ParserConfigurationException, SAXE
private final void processEntry(final ZipEntry entry)
throws ParserConfigurationException, SAXException, IOException {
final String path = entry.getName();
if (entry.isDirectory()) {
// No need to process directories
return;
}
if (OdfFormats.MIMETYPE.equals(path)) {
// Grab the mimetype value from the MIMETYPE file
this.mimetype = new String(this.cache.getEntryInputStream(entry.getName()).readAllBytes(),
StandardCharsets.UTF_8);
return;
}
if (!isOdfXml(path) && !isMetaInf(path)) {
if (entry.isDirectory() || (!isOdfXml(path) && !isMetaInf(path))) {
return;
}
try (InputStream is = this.cache.getEntryInputStream(path)) {
final OdfXmlDocument xmlDoc = OdfXmlDocuments.xmlDocumentFrom(is);
if (xmlDoc != null) {
this.xmlDocumentMap.put(path, xmlDoc);
if (xmlDoc.getParseResult().isWellFormed() && Constants.PATH_MANIFEST.equals(path)) {
this.manifest = ManifestImpl.from(this.cache.getEntryInputStream(path));
}
}
}
}

private final Manifest parseManifest() throws IOException, ParserConfigurationException, SAXException {
final ZipEntry manifestEntry = this.cache.getZipEntry(Constants.PATH_MANIFEST);
if (manifestEntry == null) {
return null;
}
try (InputStream is = this.cache.getEntryInputStream(Constants.PATH_MANIFEST)) {
final OdfXmlDocument xmlDoc = OdfXmlDocuments.xmlDocumentFrom(is);
if (xmlDoc != null && xmlDoc.getParseResult().isWellFormed()) {
return ManifestImpl.from(this.cache.getEntryInputStream(Constants.PATH_MANIFEST));
}
}
return null;
}

private final void initialise() {
this.format = Formats.UNKNOWN;
this.mimetype = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -104,19 +105,29 @@ private ValidationReport validate(final OdfPackage odfPackage) {

private final Map<String, List<Message>> validateOdfXmlEntries(final OdfPackage odfPackage) {
final Map<String, List<Message>> messages = new HashMap<>();
for (final String xmlPath : odfPackage.getXmlEntryPaths()) {
ParseResult parseResult = odfPackage.getEntryXmlParseResult(xmlPath);
if (parseResult == null) {
continue;
}
List<Message> messageList = (parseResult.isWellFormed())
? validateOdfXmlDocument(odfPackage, xmlPath, parseResult)
: parseResult.getMessages();
messages.put(xmlPath, messageList);
for (final FileEntry xmlEntry : odfPackage.getXmlEntries()) {
messages.put(xmlEntry.getFullPath(), validateXmlEntry(xmlEntry, odfPackage));
}
return messages;
}

private final List<Message> validateXmlEntry(final FileEntry xmlEntry, final OdfPackage odfPackage) {
final String xmlPath = xmlEntry.getFullPath();
if (xmlPath.equals("/")) {
return new ArrayList<>();
}
if (xmlEntry.isEncrypted()) {
return Arrays.asList(FACTORY.getWarning("PKG-10", xmlPath));
}
ParseResult parseResult = odfPackage.getEntryXmlParseResult(xmlPath);
if (parseResult == null) {
return new ArrayList<>();
}
return (parseResult.isWellFormed())
? validateOdfXmlDocument(odfPackage, xmlPath, parseResult)
: parseResult.getMessages();
}

private final List<Message> validateOdfXmlDocument(final OdfPackage odfPackage, final String xmlPath,
final ParseResult parseResult) {
List<Message> messageList = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ private MessageLog checkOdfScriptXml(final OdfPackage odfPackage) throws IOExcep
throw new IllegalStateException(e);
}
for (FileEntry entry : odfPackage.getXmlEntries()) {
if (entry.isEncrypted()) {
continue;
}
try (final InputStream entryStream = odfPackage.getEntryStream(entry)) {
if (entryStream == null) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public MessageLog check(final OdfPackage odfPackage) throws ParseException {
Objects.requireNonNull(odfPackage, "odfPackage must not be null");
final MessageLog messageLog = Messages.messageLogInstance();
for (final FileEntry entry : odfPackage.getXmlEntries()) {
if (!OdfPackages.isOdfXml(entry.getFullPath())) {
if (!OdfPackages.isOdfXml(entry.getFullPath()) || entry.isEncrypted()) {
continue;
}
try (InputStream is = odfPackage.getEntryStream(entry)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ PKG-4 = An OpenDocument Package SHOULD contain a file "mimetype".
PKG-5 = An OpenDocument Package SHALL only contain the "META-INF/manifest.xml" and files containg the term "signatures" in their name in the "META-INF" folder. File %s does not meet this criteria.
PKG-7 = An OpenDocument Package SHOULD contain a preview image Thumbnails/thumbnail.png.
PKG-8 = Encrypted file entries shall be flagged as "STORED" rather than "DEFLATED" in the zip file's central directory. Zip entry %s is encrypted and flagged as "DEFLATED".
PKG-10 = Encrypted file entry detected: %s.
MIM-1 = The "mimetype" file SHALL be the first file of the zip file.
MIM-2 = The "mimetype" file SHALL NOT be compressed.
MIM-3 = The "mimetype" file SHALL NOT use an 'extra field' in its header.
Expand Down
Loading
Loading