From 1437da1dbce433dff132c94770a2419fe0280ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Faria?= Date: Thu, 13 Aug 2026 15:45:01 +0100 Subject: [PATCH] fix: prevent validator crashes on malformed METS content Several SIP validation checks let exceptions from unchecked METS bean values or filesystem path resolution escape and crash the whole CLI run instead of being reported as validation errors: - StructMapValidator: fileGrp.getUSE() can be null, causing an NPE on .equals("Representations"); switched to the null-safe comparison. - EARKSIPValidator/EARKPyIPValidator: mets/metsHdr can be absent from the XML, causing an NPE when eagerly reading its OAISPACKAGETYPE before validation runs; now guarded so the missing header is instead reported by the metsHdr component validator (CSIP117/CSIP9). - FileSecValidator/DmdSecValidator/AmdSecValidator/StructMapValidator: resolving a decoded @href/@label against the IP path throws InvalidPathException when it contains characters invalid in a filesystem path (e.g. a NUL byte); now caught and reported as a validation failure at each call site. - DecoderUtils.normalizePath: hardened the same way as defense in depth, since it runs before decoding on every href processed by the validators above. - StructValidator.isZipFile: avoid opening the IP path as a RandomAccessFile when it's already known to be a directory, which always threw FileNotFoundException and spammed the log with a misleading stack trace on every folder-mode validation. Verified against the full erroneous_sips_v2 corpus (51 SIPs, dir + zip modes): baseline had 5 crashing runs, all now complete with proper validation reports and no regressions in the other 97. --- .../validator/EARKPyIPValidator.java | 4 +- .../validator/EARKSIPValidator.java | 7 +- .../AmdSecValidator.java | 189 ++++++++++++------ .../DmdSecValidator.java | 126 ++++++++---- .../fileComponent/StructValidator.java | 4 + .../FileSecValidator.java | 150 ++++++++++---- .../StructMapValidator.java | 40 +++- .../validator/utils/DecoderUtils.java | 12 +- 8 files changed, 368 insertions(+), 164 deletions(-) diff --git a/src/main/java/org/roda_project/commons_ip2/validator/EARKPyIPValidator.java b/src/main/java/org/roda_project/commons_ip2/validator/EARKPyIPValidator.java index 5bdc26f0..290d1e38 100644 --- a/src/main/java/org/roda_project/commons_ip2/validator/EARKPyIPValidator.java +++ b/src/main/java/org/roda_project/commons_ip2/validator/EARKPyIPValidator.java @@ -12,6 +12,7 @@ import javax.xml.parsers.ParserConfigurationException; import org.roda_project.commons_ip2.cli.model.exception.UnmarshallerException; +import org.roda_project.commons_ip2.mets_v1_12.beans.MetsType; import org.roda_project.commons_ip2.validator.common.InstatiateMets; import org.roda_project.commons_ip2.validator.components.MetsValidator; import org.roda_project.commons_ip2.validator.components.StructureValidatorImpl; @@ -264,7 +265,8 @@ private void validateSubMets(final Map subMets, final boole for (Map.Entry entry : subMets.entrySet()) { final InstatiateMets instatiateMets = new InstatiateMets(entry.getValue()); metsValidatorState.setMets(instatiateMets.instatiateMetsFile(entry.getKey())); - metsValidatorState.setIpType(metsValidatorState.getMets().getMetsHdr().getOAISPACKAGETYPE()); + final MetsType.MetsHdr subMetsHdr = metsValidatorState.getMets().getMetsHdr(); + metsValidatorState.setIpType(subMetsHdr == null ? null : subMetsHdr.getOAISPACKAGETYPE()); setupMetsValidatorState(entry.getKey(), isZip, false); validateComponents(); } diff --git a/src/main/java/org/roda_project/commons_ip2/validator/EARKSIPValidator.java b/src/main/java/org/roda_project/commons_ip2/validator/EARKSIPValidator.java index a06ad550..b00718c7 100644 --- a/src/main/java/org/roda_project/commons_ip2/validator/EARKSIPValidator.java +++ b/src/main/java/org/roda_project/commons_ip2/validator/EARKSIPValidator.java @@ -11,6 +11,7 @@ import javax.xml.parsers.ParserConfigurationException; import org.roda_project.commons_ip2.cli.model.exception.UnmarshallerException; +import org.roda_project.commons_ip2.mets_v1_12.beans.MetsType; import org.roda_project.commons_ip2.validator.common.InstatiateMets; import org.roda_project.commons_ip2.validator.components.MetsValidator; import org.roda_project.commons_ip2.validator.components.StructureValidatorImpl; @@ -245,7 +246,8 @@ private void validateSubMets(final Map subMets, final boole final InstatiateMets instatiateMets = new InstatiateMets(entry.getValue()); try { metsValidatorState.setMets(instatiateMets.instatiateMetsFile(entry.getKey())); - metsValidatorState.setIpType(metsValidatorState.getMets().getMetsHdr().getOAISPACKAGETYPE()); + final MetsType.MetsHdr subMetsHdr = metsValidatorState.getMets().getMetsHdr(); + metsValidatorState.setIpType(subMetsHdr == null ? null : subMetsHdr.getOAISPACKAGETYPE()); setupMetsValidatorState(entry.getKey(), isZip, false); validateComponents(); } catch (IOException | UnmarshallerException e) { @@ -319,7 +321,8 @@ private void validateRootMets() { metsValidatorState.setIsRootMets(true); metsValidatorState.setMets(metsRoot.instatiateMetsFile(Constants.METS_FILE)); - metsValidatorState.setIpType(metsValidatorState.getMets().getMetsHdr().getOAISPACKAGETYPE()); + final MetsType.MetsHdr rootMetsHdr = metsValidatorState.getMets().getMetsHdr(); + metsValidatorState.setIpType(rootMetsHdr == null ? null : rootMetsHdr.getOAISPACKAGETYPE()); validateComponents(); } catch (IOException | UnmarshallerException e) { final String message = createExceptionMessage(e, diff --git a/src/main/java/org/roda_project/commons_ip2/validator/components/administritiveMetadataComponent/AmdSecValidator.java b/src/main/java/org/roda_project/commons_ip2/validator/components/administritiveMetadataComponent/AmdSecValidator.java index 97c7c1f0..7efa92e7 100644 --- a/src/main/java/org/roda_project/commons_ip2/validator/components/administritiveMetadataComponent/AmdSecValidator.java +++ b/src/main/java/org/roda_project/commons_ip2/validator/components/administritiveMetadataComponent/AmdSecValidator.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLDecoder; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -174,9 +176,18 @@ protected ReporterDetails validateCSIP31(final StructureValidatorState structure final String hrefDecoded = URLDecoder.decode(DecoderUtils.normalizePath(mdRef.getHref()), Constants.UTF_8); if (hrefDecoded != null) { - final String path = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded).toString(); - if (metadataFiles.containsKey(path)) { - metadataFiles.replace(path, true); + try { + final String path = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded).toString(); + if (metadataFiles.containsKey(path)) { + metadataFiles.replace(path, true); + } + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/amdSec/digiprovMD/mdRef/@href (" + hrefDecoded + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); } } } @@ -189,9 +200,18 @@ protected ReporterDetails validateCSIP31(final StructureValidatorState structure final String hrefDecoded = URLDecoder.decode(DecoderUtils.normalizePath(mdRef.getHref()), Constants.UTF_8); if (hrefDecoded != null) { - final String path = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded).toString(); - if (metadataFiles.containsKey(path)) { - metadataFiles.replace(path, true); + try { + final String path = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded).toString(); + if (metadataFiles.containsKey(path)) { + metadataFiles.replace(path, true); + } + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/dmdSec/mdRef/@href (" + hrefDecoded + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); } } } @@ -444,10 +464,20 @@ protected ReporterDetails validateCSIP38(final StructureValidatorState structure message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } else { - if (!structureValidatorState.getFolderManager() - .checkPathExists(Paths.get(metsValidatorState.getMetsPath()).resolve(href))) { - message.append("mets/amdSec/digiprovMD/mdRef/@xlink:href (") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(href)).append(") doesn't exists (%1$s)"); + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(href); + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/amdSec/digiprovMD/mdRef/@xlink:href (" + href + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkPathExists(resolvedPath)) { + message.append("mets/amdSec/digiprovMD/mdRef/@xlink:href (").append(resolvedPath) + .append(") doesn't exists (%1$s)"); return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, message.toString(), false, false); } @@ -564,26 +594,25 @@ protected ReporterDetails validateCSIP41(final StructureValidatorState structure message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } else { - if (metsValidatorState.isRootMets()) { - if (!structureValidatorState.getFolderManager() - .verifySize(structureValidatorState.getIpPath().resolve(href), size)) { - message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") - .append(structureValidatorState.getIpPath().resolve(href)).append(") isn't equal"); - return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, - Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), - metsValidatorState.isRootMets()), - false, false); - } - } else { - if (!structureValidatorState.getFolderManager() - .verifySize(Paths.get(metsValidatorState.getMetsPath()).resolve(href), size)) { - message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(href)).append(") isn't equal"); - return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, - Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), - metsValidatorState.isRootMets()), - false, false); - } + final Path resolvedPath; + try { + resolvedPath = metsValidatorState.isRootMets() ? structureValidatorState.getIpPath().resolve(href) + : Paths.get(metsValidatorState.getMetsPath()).resolve(href); + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/amdSec/digiprovMD/mdRef/@href (" + href + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().verifySize(resolvedPath, size)) { + message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") + .append(resolvedPath).append(") isn't equal"); + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), + metsValidatorState.isRootMets()), + false, false); } } } @@ -673,10 +702,21 @@ protected ReporterDetails validateCSIP43(final StructureValidatorState structure false, false); } } else { - if (!structureValidatorState.getFolderManager() - .verifyChecksum(Paths.get(metsValidatorState.getMetsPath()).resolve(href), checksumType, checksum)) { + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(href); + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/amdSec/digiprovMD/mdRef/@href (" + href + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().verifyChecksum(resolvedPath, checksumType, + checksum)) { message.append("mets/dmdSec/mdRef/@CHECKSUM ").append(checksum).append(" in %1$s and size of file (") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(href)).append(") isn't equal"); + .append(resolvedPath).append(") isn't equal"); return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), @@ -972,10 +1012,19 @@ protected ReporterDetails validateCSIP51(final StructureValidatorState structure message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } else { - if (!structureValidatorState.getFolderManager() - .checkPathExists(Paths.get(metsValidatorState.getMetsPath()).resolve(href))) { - message.append("mets/amdSec/rightsMD/mdRef/@xlink:href ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(href)) + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(href); + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/amdSec/rightsMD/mdRef/@xlink:href (" + href + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkPathExists(resolvedPath)) { + message.append("mets/amdSec/rightsMD/mdRef/@xlink:href ").append(resolvedPath) .append(" doesn't exists (in %1$s)"); return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, Message.createErrorMessage( message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); @@ -1095,26 +1144,25 @@ protected ReporterDetails validateCSIP54(final StructureValidatorState structure false, false); } } else { - if (metsValidatorState.isRootMets()) { - if (!structureValidatorState.getFolderManager() - .verifySize(structureValidatorState.getIpPath().resolve(href), size)) { - message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") - .append(structureValidatorState.getIpPath().resolve(href)).append(") isn't equal"); - return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, - Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), - metsValidatorState.isRootMets()), - false, false); - } - } else { - if (!structureValidatorState.getFolderManager() - .verifySize(Paths.get(metsValidatorState.getMetsPath()).resolve(href), size)) { - message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(href)).append(") isn't equal"); - return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, - Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), - metsValidatorState.isRootMets()), - false, false); - } + final Path resolvedPath; + try { + resolvedPath = metsValidatorState.isRootMets() ? structureValidatorState.getIpPath().resolve(href) + : Paths.get(metsValidatorState.getMetsPath()).resolve(href); + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/amdSec/rightsMD/mdRef/@href (" + href + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().verifySize(resolvedPath, size)) { + message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") + .append(resolvedPath).append(") isn't equal"); + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), + metsValidatorState.isRootMets()), + false, false); } } } @@ -1193,20 +1241,35 @@ protected ReporterDetails validateCSIP56(final StructureValidatorState structure } if (!structureValidatorState.getZipManager().verifyChecksum(structureValidatorState.getIpPath(), filePath.toString(), checksumType, checksum)) { + Path displayPath; + try { + displayPath = Paths.get(metsValidatorState.getMetsPath()).resolve(filePath.toString()); + } catch (InvalidPathException e) { + displayPath = null; + } message.append("mets/dmdSec/mdRef/@CHECKSUM ").append(checksum).append(" and checksum of file (") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(filePath.toString())) - .append(") isn't equal (%1$s)"); + .append(displayPath != null ? displayPath : filePath).append(") isn't equal (%1$s)"); return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } else { - if (!structureValidatorState.getFolderManager().verifyChecksum( - Paths.get(metsValidatorState.getMetsPath()).resolve(href), checksumType, checksum)) { + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(href); + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/amdSec/rightsMD/mdRef/@href (" + href + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().verifyChecksum(resolvedPath, checksumType, + checksum)) { message.append("mets/dmdSec/mdRef/@CHECKSUM ").append(checksum).append(" and checksum of file (") - .append(Paths.get(metsValidatorState.getMetsPath()) - .resolve(Paths.get(metsValidatorState.getMetsPath()).resolve(href))) + .append(Paths.get(metsValidatorState.getMetsPath()).resolve(resolvedPath)) .append(") isn't equal (in %1$s)"); return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), diff --git a/src/main/java/org/roda_project/commons_ip2/validator/components/descriptiveMetadataComponent/DmdSecValidator.java b/src/main/java/org/roda_project/commons_ip2/validator/components/descriptiveMetadataComponent/DmdSecValidator.java index 4ab8b3f8..200f027c 100644 --- a/src/main/java/org/roda_project/commons_ip2/validator/components/descriptiveMetadataComponent/DmdSecValidator.java +++ b/src/main/java/org/roda_project/commons_ip2/validator/components/descriptiveMetadataComponent/DmdSecValidator.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLDecoder; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.NoSuchAlgorithmException; @@ -92,8 +93,15 @@ protected ReporterDetails validateCSIP17(final StructureValidatorState structure if (mdRef != null && mdRef.getHref() != null) { String hrefDecoded = URLDecoder.decode(DecoderUtils.normalizePath(mdRef.getHref()), Constants.UTF_8); - Path path = Paths.get(hrefDecoded); - hrefDecoded = path.normalize().toString(); + try { + hrefDecoded = Paths.get(hrefDecoded).normalize().toString(); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/dmdSec/mdRef/@href (" + hrefDecoded + ") in %1$s is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } if (metsValidatorState.isRootMets()) { if (metadataFiles.containsKey(mets.getOBJID() + Constants.SEPARATOR + hrefDecoded)) { metadataFiles.replace(mets.getOBJID() + Constants.SEPARATOR + hrefDecoded, true); @@ -183,9 +191,17 @@ protected ReporterDetails validateCSIP17(final StructureValidatorState structure final String hrefDecoded = URLDecoder.decode(DecoderUtils.normalizePath(mdRef.getHref()), Constants.UTF_8); if (hrefDecoded != null) { - final String path = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded).toString(); - if (metadataFiles.containsKey(path)) { - metadataFiles.replace(path, true); + try { + final String path = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded).toString(); + if (metadataFiles.containsKey(path)) { + metadataFiles.replace(path, true); + } + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/dmdSec/mdRef/@href (" + hrefDecoded + ") in %1$s is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); } } } @@ -198,9 +214,19 @@ protected ReporterDetails validateCSIP17(final StructureValidatorState structure final String hrefDecoded = URLDecoder.decode(DecoderUtils.normalizePath(mdRef.getHref()), Constants.UTF_8); if (hrefDecoded != null) { - final String path = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded).toString(); - if (metadataFiles.containsKey(path)) { - metadataFiles.replace(path, true); + try { + final String path = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded) + .toString(); + if (metadataFiles.containsKey(path)) { + metadataFiles.replace(path, true); + } + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/amdSec/*/mdRef/@href (" + hrefDecoded + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); } } } @@ -444,22 +470,28 @@ protected ReporterDetails validateCSIP24(final StructureValidatorState structure metsValidatorState.isRootMets())); } } else { - if (StringUtils.isBlank(href)){ - message.append("mets/dmdSec/mdRef/@xlink:href ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded)) - .append(" in %1$s is empty"); + Path resolvedPath = null; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded); + } catch (InvalidPathException e) { details.setValid(false); - details.addIssue(Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), - metsValidatorState.isRootMets())); + details.addIssue(Message.createErrorMessage( + "mets/dmdSec/mdRef/@xlink:href (" + hrefDecoded + ") in %1$s is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets())); } - if (!structureValidatorState.getFolderManager() - .checkPathExists(Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded))) { - message.append("mets/dmdSec/mdRef/@xlink:href ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded)) - .append(" in %1$s does not exist"); - details.setValid(false); - details.addIssue(Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), - metsValidatorState.isRootMets())); + if (resolvedPath != null) { + if (StringUtils.isBlank(href)) { + message.append("mets/dmdSec/mdRef/@xlink:href ").append(resolvedPath).append(" in %1$s is empty"); + details.setValid(false); + details.addIssue(Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), + metsValidatorState.isRootMets())); + } + if (!structureValidatorState.getFolderManager().checkPathExists(resolvedPath)) { + message.append("mets/dmdSec/mdRef/@xlink:href ").append(resolvedPath).append(" in %1$s does not exist"); + details.setValid(false); + details.addIssue(Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), + metsValidatorState.isRootMets())); + } } } } else { @@ -567,22 +599,23 @@ protected ReporterDetails validateCSIP27(final StructureValidatorState structure metsValidatorState.isRootMets()),false, false); } } else { - if (metsValidatorState.isRootMets()) { - if (!structureValidatorState.getFolderManager() - .verifySize(structureValidatorState.getIpPath().resolve(hrefDecoded), size)) { - message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") - .append(structureValidatorState.getIpPath().resolve(hrefDecoded)).append(") isn't equal"); - return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage( - message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); - } - } else { - if (!structureValidatorState.getFolderManager() - .verifySize(Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded), size)) { - message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") - .append(structureValidatorState.getIpPath().resolve(hrefDecoded).toString()).append(") isn't equal"); - return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage( - message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); - } + final Path resolvedHrefPath; + try { + resolvedHrefPath = metsValidatorState.isRootMets() + ? structureValidatorState.getIpPath().resolve(hrefDecoded) + : Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/dmdSec/mdRef/@href (" + hrefDecoded + ") in %1$s is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().verifySize(resolvedHrefPath, size)) { + message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") + .append(resolvedHrefPath).append(") isn't equal"); + return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } } else { @@ -662,11 +695,20 @@ protected ReporterDetails validateCSIP29(final StructureValidatorState structure false, false); } } else { - if (!structureValidatorState.getFolderManager() - .verifyChecksum(Paths.get(metsValidatorState.getMetsPath()).resolve(file), checksumType, checksum)) { + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(file); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/dmdSec/mdRef/@href (" + file + ") in %1$s is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().verifyChecksum(resolvedPath, checksumType, + checksum)) { message.append("mets/dmdSec/mdRef/@CHECKSUM ").append(checksum) - .append(" in %1$s and checksum of file (") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(file)).append(") isn't equal"); + .append(" in %1$s and checksum of file (").append(resolvedPath).append(") isn't equal"); return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), diff --git a/src/main/java/org/roda_project/commons_ip2/validator/components/fileComponent/StructValidator.java b/src/main/java/org/roda_project/commons_ip2/validator/components/fileComponent/StructValidator.java index e8e6d2a7..18626045 100644 --- a/src/main/java/org/roda_project/commons_ip2/validator/components/fileComponent/StructValidator.java +++ b/src/main/java/org/roda_project/commons_ip2/validator/components/fileComponent/StructValidator.java @@ -533,6 +533,10 @@ protected ReporterDetails validateCSIPSTR16(final StructureValidatorState struct * @return if the IP is a ZIP file */ protected boolean isZipFile(Path ipPath, byte[] zipMagicNumber, String moduleName) { + if (Files.isDirectory(ipPath)) { + return false; + } + boolean isZip = true; byte[] buffer = new byte[zipMagicNumber.length]; diff --git a/src/main/java/org/roda_project/commons_ip2/validator/components/fileSectionComponent/FileSecValidator.java b/src/main/java/org/roda_project/commons_ip2/validator/components/fileSectionComponent/FileSecValidator.java index 1905f5a2..6c4b4bce 100644 --- a/src/main/java/org/roda_project/commons_ip2/validator/components/fileSectionComponent/FileSecValidator.java +++ b/src/main/java/org/roda_project/commons_ip2/validator/components/fileSectionComponent/FileSecValidator.java @@ -4,6 +4,8 @@ import java.io.InputStream; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -118,10 +120,19 @@ protected ReporterDetails validateCSIP60(final StructureValidatorState structure for (FileType.FLocat flocat : fLocats) { final String filePath = URLDecoder.decode(DecoderUtils.normalizePath(flocat.getHref()), StandardCharsets.UTF_8); - if (!structureValidatorState.getFolderManager() - .checkPathExists(Paths.get(metsValidatorState.getMetsPath()).resolve(filePath))) { - message.append("mets/fileSec/fileGrp[@USE=’Documentation’] ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(filePath)) + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(filePath); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/fileSec/fileGrp[@USE=’Documentation’] (" + filePath + ") is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkPathExists(resolvedPath)) { + message.append("mets/fileSec/fileGrp[@USE=’Documentation’] ").append(resolvedPath) .append(" doesn't exists (in %1$s)"); return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); @@ -171,10 +182,18 @@ protected ReporterDetails validateCSIP113(final StructureValidatorState structur } else { for (FileType.FLocat flocat : fLocats) { final String filePath = URLDecoder.decode(DecoderUtils.normalizePath(flocat.getHref()), StandardCharsets.UTF_8); - if (!structureValidatorState.getFolderManager() - .checkPathExists(Paths.get(metsValidatorState.getMetsPath()).resolve(filePath))) { - message.append("mets/fileSec/fileGrp[@USE=’Schemas’] ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(filePath)) + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(filePath); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/fileSec/fileGrp[@USE=’Schemas’] (" + filePath + ") is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkPathExists(resolvedPath)) { + message.append("mets/fileSec/fileGrp[@USE=’Schemas’] ").append(resolvedPath) .append(" doesn't exists (%1$s)"); return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); @@ -222,10 +241,19 @@ protected ReporterDetails validateCSIP114(final StructureValidatorState structur } else { for (FileType.FLocat flocat : fLocats) { final String filePath = URLDecoder.decode(DecoderUtils.normalizePath(flocat.getHref()), StandardCharsets.UTF_8); - if (!structureValidatorState.getFolderManager() - .checkPathExists(Paths.get(metsValidatorState.getMetsPath()).resolve(filePath))) { - message.append("mets/fileSec/fileGrp[@USE=’Representations’] ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(filePath)) + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(filePath); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/fileSec/fileGrp[@USE=’Representations’] (" + filePath + ") is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkPathExists(resolvedPath)) { + message.append("mets/fileSec/fileGrp[@USE=’Representations’] ").append(resolvedPath) .append(" doesn't exists (%1$s)"); return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); @@ -385,11 +413,20 @@ protected ReporterDetails validateCSIP64(final StructureValidatorState structure metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } else { - if (!structureValidatorState.getFolderManager() - .checkDirectory(Paths.get(metsValidatorState.getMetsPath()).resolve(use.toLowerCase()))) { + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(use.toLowerCase()); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "Value " + use + " in %1$s for mets/fileSec/fileGrp/@USE is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkDirectory(resolvedPath)) { message.append("Value ").append(use) .append(" in %1$s for mets/fileSec/fileGrp/@USE " + "doesn't match with any directory in sip(") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(use.toLowerCase())).append(")"); + .append(resolvedPath).append(")"); return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } @@ -462,7 +499,16 @@ protected ReporterDetails validateCSIP66(final StructureValidatorState structure filePath.append(metsValidatorState.getMetsPath()).append(hrefDecoded); } } else { - filePath.append(Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded)); + try { + filePath.append(Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded)); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/fileSec/fileGrp/file/FLocat/@href (" + hrefDecoded + + ") in %1$s is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } } if (metsValidatorState.getMetsFiles().containsKey(filePath.toString())) { metsValidatorState.getMetsFiles().replace(filePath.toString(), true); @@ -576,26 +622,23 @@ protected ReporterDetails validateCSIP69(final StructureValidatorState structure metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } else { - if (metsValidatorState.isRootMets()) { - if (!structureValidatorState.getFolderManager() - .verifySize(structureValidatorState.getIpPath().resolve(href), size)) { - message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") - .append(structureValidatorState.getIpPath() - .resolve(structureValidatorState.getIpPath().resolve(href))) - .append(") isn't equal"); - return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), - metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); - } - } else { - if (!structureValidatorState.getFolderManager() - .verifySize(Paths.get(metsValidatorState.getMetsPath()).resolve(href), size)) { - message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") - .append(structureValidatorState.getIpPath() - .resolve(Paths.get(metsValidatorState.getMetsPath()).resolve(href))) - .append(") isn't equal"); - return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), - metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); - } + final Path resolvedHrefPath; + try { + resolvedHrefPath = metsValidatorState.isRootMets() + ? structureValidatorState.getIpPath().resolve(href) + : Paths.get(metsValidatorState.getMetsPath()).resolve(href); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/dmdSec/mdRef/@href (" + href + ") in %1$s is not a valid path: " + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().verifySize(resolvedHrefPath, size)) { + message.append("mets/dmdSec/mdRef/@SIZE ").append(size).append(" in %1$s and size of file (") + .append(structureValidatorState.getIpPath().resolve(resolvedHrefPath)).append(") isn't equal"); + return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } } else { @@ -715,11 +758,21 @@ protected ReporterDetails validateCSIP71(final StructureValidatorState structure metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } else { - if (!structureValidatorState.getFolderManager().verifyChecksum( - Paths.get(metsValidatorState.getMetsPath()).resolve(filePath), checksumType, checksum)) { + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(filePath); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/fileSec/fileGrp/file/flocat/@href (" + filePath + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().verifyChecksum(resolvedPath, checksumType, + checksum)) { message.append("mets/dmdSec/mdRef/@CHECKSUM ").append(checksum) - .append(" in %1$s and checksum of file (") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(filePath)).append(") isn't equal"); + .append(" in %1$s and checksum of file (").append(resolvedPath).append(") isn't equal"); return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } @@ -1047,10 +1100,19 @@ protected ReporterDetails validateCSIP79(final StructureValidatorState structure metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); } } else { - if (!structureValidatorState.getFolderManager() - .checkPathExists(Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded))) { - message.append("mets/fileSec/fileGrp/file/@xlink:href ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded)) + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(hrefDecoded); + } catch (InvalidPathException e) { + return new ReporterDetails(getCSIPVersion(), + Message.createErrorMessage( + "mets/fileSec/fileGrp/file/@xlink:href (" + hrefDecoded + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkPathExists(resolvedPath)) { + message.append("mets/fileSec/fileGrp/file/@xlink:href ").append(resolvedPath) .append(" does not exist (%1$s)"); return new ReporterDetails(getCSIPVersion(), Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), false, false); diff --git a/src/main/java/org/roda_project/commons_ip2/validator/components/structuralMapComponent/StructMapValidator.java b/src/main/java/org/roda_project/commons_ip2/validator/components/structuralMapComponent/StructMapValidator.java index ee7d557c..e0a60df0 100644 --- a/src/main/java/org/roda_project/commons_ip2/validator/components/structuralMapComponent/StructMapValidator.java +++ b/src/main/java/org/roda_project/commons_ip2/validator/components/structuralMapComponent/StructMapValidator.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLDecoder; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; @@ -1102,7 +1104,7 @@ protected ReporterDetails validateCSIP104(final MetsValidatorState metsValidator if (structMap != null) { if (fileGrps != null && !fileGrps.isEmpty()) { for (MetsType.FileSec.FileGrp fileGrp : fileGrps) { - if (fileGrp.getUSE().equals("Representations")) { + if ("Representations".equals(fileGrp.getUSE())) { fileGrpRepresentations++; } } @@ -1166,7 +1168,7 @@ protected ReporterDetails validateCSIP119(final MetsValidatorState metsValidator for (DivType.Fptr fptr : ftprs) { final String fileid = ((MetsType.FileSec.FileGrp) fptr.getFILEID()).getID(); for (MetsType.FileSec.FileGrp fileGrp : fileGrps) { - if (fileGrp.getUSE().equals("Representations")) { + if ("Representations".equals(fileGrp.getUSE())) { final String id = fileGrp.getID(); if (id.equals(fileid)) { found = true; @@ -1320,11 +1322,20 @@ protected ReporterDetails validateCSIP107(final StructureValidatorState structur } else { normalizedLable = label.toLowerCase(); } - if (!structureValidatorState.getFolderManager() - .checkDirectory(Paths.get(metsValidatorState.getMetsPath()).resolve(normalizedLable))) { + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(normalizedLable); + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/structMap[@LABEL='CSIP']/div/div/@LABEL (" + label + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkDirectory(resolvedPath)) { message.append("mets/structMap[@LABEL='CSIP']/div/div/@LABEL in %1$s ( ").append(label).append(" )") - .append("does not lead to a directory ( ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(label.toLowerCase())).append(" )"); + .append("does not lead to a directory ( ").append(resolvedPath).append(" )"); return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), @@ -1469,10 +1480,19 @@ protected ReporterDetails validateCSIP110(final StructureValidatorState structur false, false); } } else { - if (!structureValidatorState.getFolderManager() - .checkPathExists(Paths.get(metsValidatorState.getMetsPath()).resolve(href))) { - message.append("mets/structMap/div/div/mptr/@xlink:href ") - .append(Paths.get(metsValidatorState.getMetsPath()).resolve(href)) + final Path resolvedPath; + try { + resolvedPath = Paths.get(metsValidatorState.getMetsPath()).resolve(href); + } catch (InvalidPathException e) { + return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, + Message.createErrorMessage( + "mets/structMap/div/div/mptr/@xlink:href (" + href + ") in %1$s is not a valid path: " + + e.getReason(), + metsValidatorState.getMetsName(), metsValidatorState.isRootMets()), + false, false); + } + if (!structureValidatorState.getFolderManager().checkPathExists(resolvedPath)) { + message.append("mets/structMap/div/div/mptr/@xlink:href ").append(resolvedPath) .append(" doesn't exists (in %1$s)"); return new ReporterDetails(Constants.VALIDATION_REPORT_HEADER_CSIP_VERSION, Message.createErrorMessage(message.toString(), metsValidatorState.getMetsName(), diff --git a/src/main/java/org/roda_project/commons_ip2/validator/utils/DecoderUtils.java b/src/main/java/org/roda_project/commons_ip2/validator/utils/DecoderUtils.java index 5c7c2a72..a8d0f977 100644 --- a/src/main/java/org/roda_project/commons_ip2/validator/utils/DecoderUtils.java +++ b/src/main/java/org/roda_project/commons_ip2/validator/utils/DecoderUtils.java @@ -1,5 +1,6 @@ package org.roda_project.commons_ip2.validator.utils; +import java.nio.file.InvalidPathException; import java.nio.file.Paths; /** @@ -9,8 +10,15 @@ public class DecoderUtils { public static String normalizePath(String href) { - return Paths.get(href).normalize() // Removes unnecessary elements (like "." or "..") - .toString().replace("\\", "/"); // Assure it returns in unix-style format + try { + return Paths.get(href).normalize() // Removes unnecessary elements (like "." or "..") + .toString().replace("\\", "/"); // Assure it returns in unix-style format + } catch (InvalidPathException e) { + // href contains a character that isn't valid in a filesystem path (e.g. a NUL + // character); return it unchanged so callers can still report it as an issue + // instead of crashing. + return href; + } }