Author: fanningpj
Date: Wed Mar 19 20:38:10 2025
New Revision: 1924476

URL: http://svn.apache.org/viewvc?rev=1924476&view=rev
Log:
[github-775] Allow some OPC compliance checks to be tuned. Thanks to Ken Reese. 
This closes #775

Added:
    
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCComplianceFlags.java
Modified:
    
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCPackage.java
    
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java
    
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/unmarshallers/PackagePropertiesUnmarshaller.java
    
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/compliance/TestOPCComplianceCoreProperties.java

Added: 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCComplianceFlags.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCComplianceFlags.java?rev=1924476&view=auto
==============================================================================
--- 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCComplianceFlags.java
 (added)
+++ 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCComplianceFlags.java
 Wed Mar 19 20:38:10 2025
@@ -0,0 +1,118 @@
+/* ====================================================================
+   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.poi.openxml4j.opc;
+
+/**
+ * Allows disabling specific OPC compliance rules.
+ * By default, rules M4.2, M4.3, M4.4, and M4.5 are all enforced which will 
prevent
+ * non-compliant documents from being parsed.
+ *
+ * Consumers may disable these compliance checks individually or as a whole at 
their
+ * own discretion to allow certain non-compliant documents to be parsed.
+ * @since POI 5.4.1
+ */
+public class OPCComplianceFlags {
+
+    /*
+     * Rule M4.2: A format consumer shall consider the use of the Markup
+     * Compatibility namespace to be an error.
+     */
+    protected boolean ENFORCE_M4_2_FORBID_MARKUP_COMPATIBILITY_NAMESPACE;
+
+    /**
+     * Rule M4.3: Producers shall not create a document element that contains
+     * refinements to the Dublin Core elements, except for the two specified in
+     * the schema: <dcterms:created> and <dcterms:modified> 
Consumers shall
+     * consider a document element that violates this constraint to be an 
error.
+     */
+    protected boolean ENFORCE_M4_3_FORBID_REFINING_DUBLIN_CORE_ELEMENTS;
+
+    /**
+     * Rule M4.4: Producers shall not create a document element that contains
+     * the xml:lang attribute. Consumers shall consider a document element that
+     * violates this constraint to be an error.
+     */
+    protected boolean ENFORCE_M4_4_FORBID_XML_LANG_ATTRIBUTE;
+
+    /*
+     * Rule M4.5: Producers shall not create a document element that contains
+     * the xsi:type attribute, except for a <dcterms:created> or
+     * <dcterms:modified> element where the xsi:type attribute shall be 
present
+     * and shall hold the value dcterms:W3CDTF, where dcterms is the namespace
+     * prefix of the Dublin Core namespace. Consumers shall consider a document
+     * element that violates this constraint to be an error.
+     */
+    protected boolean ENFORCE_M4_5_RESTRICT_XSI_TYPE_ATTRIBUTE;
+
+    private OPCComplianceFlags(
+            boolean forbidMarkupCompatibilityNamespace,
+            boolean forbidRefiningDublinCoreElements,
+            boolean forbidXmlLangAttribute,
+            boolean restrictXsiTypeAttribute
+    ) {
+        this.ENFORCE_M4_2_FORBID_MARKUP_COMPATIBILITY_NAMESPACE = 
forbidMarkupCompatibilityNamespace;
+        this.ENFORCE_M4_3_FORBID_REFINING_DUBLIN_CORE_ELEMENTS = 
forbidRefiningDublinCoreElements;
+        this.ENFORCE_M4_4_FORBID_XML_LANG_ATTRIBUTE = forbidXmlLangAttribute;
+        this.ENFORCE_M4_5_RESTRICT_XSI_TYPE_ATTRIBUTE = 
restrictXsiTypeAttribute;
+    }
+
+    public static OPCComplianceFlags enforceAll() {
+        return new OPCComplianceFlags(true, true, true, true);
+    }
+
+    public static OPCComplianceFlags disableAll() {
+        return new OPCComplianceFlags(false, false, false, false);
+    }
+
+    public OPCComplianceFlags setForbidMarkupCompatibilityNamespace(boolean 
flag) {
+        ENFORCE_M4_2_FORBID_MARKUP_COMPATIBILITY_NAMESPACE = flag;
+        return this;
+    }
+
+    public OPCComplianceFlags setForbidRefiningDublinCoreElements(boolean 
flag) {
+        ENFORCE_M4_3_FORBID_REFINING_DUBLIN_CORE_ELEMENTS = flag;
+        return this;
+    }
+
+    public OPCComplianceFlags setForbidXmlLangAttribute(boolean flag) {
+        ENFORCE_M4_4_FORBID_XML_LANG_ATTRIBUTE = flag;
+        return this;
+    }
+
+    public OPCComplianceFlags setRestrictXsiTypeAttribute(boolean flag) {
+        ENFORCE_M4_5_RESTRICT_XSI_TYPE_ATTRIBUTE = flag;
+        return this;
+    }
+
+    public boolean getForbidMarkupCompatibilityNamespace() {
+        return ENFORCE_M4_2_FORBID_MARKUP_COMPATIBILITY_NAMESPACE;
+    }
+
+    public boolean getForbidRefiningDublinCoreElements() {
+        return ENFORCE_M4_3_FORBID_REFINING_DUBLIN_CORE_ELEMENTS;
+    }
+
+    public boolean getForbidXmlLangAttributes() {
+        return ENFORCE_M4_4_FORBID_XML_LANG_ATTRIBUTE;
+    }
+
+    public boolean getRestrictXsiTypeAttribute() {
+        return ENFORCE_M4_5_RESTRICT_XSI_TYPE_ATTRIBUTE;
+    }
+
+}

Modified: 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCPackage.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCPackage.java?rev=1924476&r1=1924475&r2=1924476&view=diff
==============================================================================
--- 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCPackage.java 
(original)
+++ 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/OPCPackage.java 
Wed Mar 19 20:38:10 2025
@@ -141,6 +141,19 @@ public abstract class OPCPackage impleme
      * @throws OpenXML4JRuntimeException if there are issues creating 
properties part
      */
     OPCPackage(PackageAccess access) {
+        this(access, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param access Package access.
+     * @param opcComplianceFlags Enable or disable specific OPC compliance 
flags.
+     *                           This is useful to allow parsing of certain 
non-compliant documents.
+     * @throws OpenXML4JRuntimeException if there are issues creating 
properties part
+     * @since POI 5.4.1
+     */
+    OPCPackage(PackageAccess access, OPCComplianceFlags opcComplianceFlags) {
         if (getClass() != ZipPackage.class) {
             throw new IllegalArgumentException("PackageBase may not be 
subclassed");
         }
@@ -148,7 +161,7 @@ public abstract class OPCPackage impleme
 
         final ContentType contentType = newCorePropertiesPart();
         // TODO Delocalize specialized marshallers
-        this.partUnmarshallers.put(contentType, new 
PackagePropertiesUnmarshaller());
+        this.partUnmarshallers.put(contentType, new 
PackagePropertiesUnmarshaller(opcComplianceFlags));
         this.partMarshallers.put(contentType, new 
ZipPackagePropertiesMarshaller());
     }
 
@@ -176,7 +189,24 @@ public abstract class OPCPackage impleme
      *             occur.
      */
     public static OPCPackage open(String path) throws InvalidFormatException {
-        return open(path, defaultPackageAccess);
+        return open(path, defaultPackageAccess, 
OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Open a package with read/write permission.
+     *
+     * @param path
+     *            The document path.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @return A Package object, else <b>null</b>.
+     * @throws InvalidFormatException
+     *             If the specified file doesn't exist, and a parsing error
+     *             occur.
+     * @since POI 5.4.1
+     */
+    public static OPCPackage open(String path, OPCComplianceFlags 
opcComplianceFlags) throws InvalidFormatException {
+        return open(path, defaultPackageAccess, opcComplianceFlags);
     }
 
    /**
@@ -193,6 +223,23 @@ public abstract class OPCPackage impleme
       return open(file, defaultPackageAccess);
    }
 
+    /**
+     * Open a package with read/write permission.
+     *
+     * @param file
+     *            The file to open.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @return A Package object, else <b>null</b>.
+     * @throws InvalidFormatException
+     *             If the specified file doesn't exist, and a parsing error
+     *             occur.
+     * @since POI 5.4.1
+     */
+    public static OPCPackage open(File file, OPCComplianceFlags 
opcComplianceFlags) throws InvalidFormatException {
+        return open(file, defaultPackageAccess, opcComplianceFlags);
+    }
+
    /**
     * Open a user provided {@link ZipEntrySource} with read-only permission.
     * This method can be used to stream data into POI.
@@ -204,22 +251,39 @@ public abstract class OPCPackage impleme
     * @throws InvalidFormatException if a parsing error occur.
     */
    public static OPCPackage open(ZipEntrySource zipEntry) throws 
InvalidFormatException {
-       OPCPackage pack = new ZipPackage(zipEntry, PackageAccess.READ);
-       try {
-           if (pack.partList == null) {
-               pack.getParts();
-           }
-           // pack.originalPackagePath = file.getAbsolutePath();
-           return pack;
-       } catch (InvalidFormatException | RuntimeException e) {
-           // use revert() to free resources when the package is opened 
read-only
-           pack.revert();
-
-           throw e;
-       }
+       return open(zipEntry, OPCComplianceFlags.enforceAll());
    }
 
     /**
+     * Open a user provided {@link ZipEntrySource} with read-only permission.
+     * This method can be used to stream data into POI.
+     * Opposed to other open variants, the data is read as-is, e.g. there 
aren't
+     * any zip-bomb protection put in place.
+     *
+     * @param zipEntry the custom source
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @return A Package object
+     * @throws InvalidFormatException if a parsing error occur.
+     * @since POI 5.4.1
+     */
+    public static OPCPackage open(ZipEntrySource zipEntry, OPCComplianceFlags 
opcComplianceFlags) throws InvalidFormatException {
+        OPCPackage pack = new ZipPackage(zipEntry, PackageAccess.READ, 
opcComplianceFlags);
+        try {
+            if (pack.partList == null) {
+                pack.getParts();
+            }
+            // pack.originalPackagePath = file.getAbsolutePath();
+            return pack;
+        } catch (InvalidFormatException | RuntimeException e) {
+            // use revert() to free resources when the package is opened 
read-only
+            pack.revert();
+
+            throw e;
+        }
+    }
+
+    /**
      * Open a package.
      *
      * @param path
@@ -235,6 +299,28 @@ public abstract class OPCPackage impleme
      */
     public static OPCPackage open(String path, PackageAccess access)
             throws InvalidFormatException, InvalidOperationException {
+        return open(path, access, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Open a package.
+     *
+     * @param path
+     *            The document path.
+     * @param access
+     *            PackageBase access.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @return A PackageBase object, else <b>null</b>.
+     * @throws InvalidFormatException
+     *             If the specified file doesn't exist, and a parsing error
+     *             occur.
+     * @throws InvalidOperationException If the zip file cannot be opened.
+     * @throws InvalidFormatException if the package is not valid.
+     * @since POI 5.4.1
+     */
+    public static OPCPackage open(String path, PackageAccess access, 
OPCComplianceFlags opcComplianceFlags)
+            throws InvalidFormatException, InvalidOperationException {
         if (StringUtil.isBlank(path)) {
             throw new IllegalArgumentException("'path' must be given");
         }
@@ -244,7 +330,7 @@ public abstract class OPCPackage impleme
             throw new IllegalArgumentException("path must not be a directory");
         }
 
-        OPCPackage pack = new ZipPackage(path, access); // NOSONAR
+        OPCPackage pack = new ZipPackage(path, access, opcComplianceFlags); // 
NOSONAR
         boolean success = false;
         if (pack.partList == null && access != PackageAccess.WRITE) {
             try {
@@ -275,38 +361,58 @@ public abstract class OPCPackage impleme
     */
    public static OPCPackage open(File file, PackageAccess access)
          throws InvalidFormatException {
-       if (file == null) {
-           throw new IllegalArgumentException("'file' must be given");
-       }
-       if (file.exists() && file.isDirectory()) {
-           throw new IllegalArgumentException("file must not be a directory");
-       }
-
-       final OPCPackage pack;
-       try {
-           pack = new ZipPackage(file, access); //NOSONAR
-       } catch (InvalidOperationException e) {
-           throw new InvalidFormatException(e.getMessage(), e);
-       }
-       try {
-           if (pack.partList == null && access != PackageAccess.WRITE) {
-               pack.getParts();
-           }
-           pack.originalPackagePath = file.getAbsolutePath();
-           return pack;
-       } catch (InvalidFormatException | RuntimeException e) {
-           if (access == PackageAccess.READ) {
-               pack.revert();
-           } else {
-               IOUtils.closeQuietly(pack);
-           }
-           throw e;
-       }
+       return open(file, access, OPCComplianceFlags.enforceAll());
    }
 
     /**
      * Open a package.
      *
+     * @param file
+     *            The file to open.
+     * @param access
+     *            PackageBase access.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @return A PackageBase object, else <b>null</b>.
+     * @throws InvalidFormatException
+     *             If the specified file doesn't exist, and a parsing error
+     *             occur.
+     * @since POI 5.4.1
+     */
+    public static OPCPackage open(File file, PackageAccess access, 
OPCComplianceFlags opcComplianceFlags)
+            throws InvalidFormatException {
+        if (file == null) {
+            throw new IllegalArgumentException("'file' must be given");
+        }
+        if (file.exists() && file.isDirectory()) {
+            throw new IllegalArgumentException("file must not be a directory");
+        }
+
+        final OPCPackage pack;
+        try {
+            pack = new ZipPackage(file, access, opcComplianceFlags); //NOSONAR
+        } catch (InvalidOperationException e) {
+            throw new InvalidFormatException(e.getMessage(), e);
+        }
+        try {
+            if (pack.partList == null && access != PackageAccess.WRITE) {
+                pack.getParts();
+            }
+            pack.originalPackagePath = file.getAbsolutePath();
+            return pack;
+        } catch (InvalidFormatException | RuntimeException e) {
+            if (access == PackageAccess.READ) {
+                pack.revert();
+            } else {
+                IOUtils.closeQuietly(pack);
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Open a package.
+     *
      * Note - uses quite a bit more memory than {@link #open(String)}, which
      * doesn't need to hold the whole zip file in memory, and can take 
advantage
      * of native methods
@@ -321,9 +427,32 @@ public abstract class OPCPackage impleme
      */
     public static OPCPackage open(InputStream in) throws 
InvalidFormatException,
             IOException {
+        return open(in, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Open a package.
+     *
+     * Note - uses quite a bit more memory than {@link #open(String)}, which
+     * doesn't need to hold the whole zip file in memory, and can take 
advantage
+     * of native methods
+     *
+     * @param in
+     *            The InputStream to read the package from. The stream is 
closed.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @return A PackageBase object
+     *
+     * @throws InvalidFormatException
+     *              Throws if the specified file exist and is not valid.
+     * @throws IOException If reading the stream fails
+     * @since POI 5.4.1
+     */
+    public static OPCPackage open(InputStream in, OPCComplianceFlags 
opcComplianceFlags) throws InvalidFormatException,
+            IOException {
         final OPCPackage pack;
         try {
-            pack = new ZipPackage(in, PackageAccess.READ_WRITE);
+            pack = new ZipPackage(in, PackageAccess.READ_WRITE, 
opcComplianceFlags);
         } catch (InvalidZipException e) {
             throw new InvalidFormatException(e.getMessage(), e);
         }
@@ -358,9 +487,34 @@ public abstract class OPCPackage impleme
      */
     public static OPCPackage open(InputStream in, boolean closeStream) throws 
InvalidFormatException,
             IOException {
+        return open(in, closeStream, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Open a package.
+     *
+     * Note - uses quite a bit more memory than {@link #open(String)}, which
+     * doesn't need to hold the whole zip file in memory, and can take 
advantage
+     * of native methods
+     *
+     * @param in
+     *            The InputStream to read the package from.
+     * @param closeStream
+     *            Whether to close the input stream.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @return A PackageBase object
+     *
+     * @throws InvalidFormatException
+     *              Throws if the specified file exist and is not valid.
+     * @throws IOException If reading the stream fails
+     * @since POI 5.4.1
+     */
+    public static OPCPackage open(InputStream in, boolean closeStream, 
OPCComplianceFlags opcComplianceFlags) throws InvalidFormatException,
+            IOException {
         final OPCPackage pack;
         try {
-            pack = new ZipPackage(in, PackageAccess.READ_WRITE, closeStream);
+            pack = new ZipPackage(in, PackageAccess.READ_WRITE, closeStream, 
opcComplianceFlags);
         } catch (InvalidZipException e) {
             throw new InvalidFormatException(e.getMessage(), e);
         }

Modified: 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java?rev=1924476&r1=1924475&r2=1924476&view=diff
==============================================================================
--- 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java 
(original)
+++ 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java 
Wed Mar 19 20:38:10 2025
@@ -103,7 +103,17 @@ public final class ZipPackage extends OP
      * Constructor. Creates a new, empty ZipPackage.
      */
     public ZipPackage() {
-        super(defaultPackageAccess);
+        this(OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Constructor. Creates a new, empty ZipPackage.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @since POI 5.4.1
+     */
+    public ZipPackage(OPCComplianceFlags opcComplianceFlags) {
+        super(defaultPackageAccess, opcComplianceFlags);
         this.zipArchive = null;
 
         try {
@@ -128,7 +138,28 @@ public final class ZipPackage extends OP
      *            if input stream cannot be opened, read, or closed
      */
     ZipPackage(InputStream in, PackageAccess access) throws IOException {
-        super(access);
+        this(in, access, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Constructor. Opens a Zip based Open XML document from
+     *  an InputStream. The InputStream is closed.
+     *
+     * @param in
+     *            Zip input stream to load.
+     * @param access
+     *            The package access mode.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @throws IllegalArgumentException
+     *             If the specified input stream is not an instance of
+     *             ZipInputStream.
+     * @throws IOException
+     *            if input stream cannot be opened, read, or closed
+     * @since POI 5.4.1
+     */
+    ZipPackage(InputStream in, PackageAccess access, OPCComplianceFlags 
opcComplianceFlags) throws IOException {
+        super(access, opcComplianceFlags);
         try (ZipArchiveThresholdInputStream zis = ZipHelper.openZipStream(in)) 
{
             this.zipArchive = new ZipInputStreamZipEntrySource(zis);
         }
@@ -152,7 +183,30 @@ public final class ZipPackage extends OP
      * @since POI 5.2.5
      */
     ZipPackage(InputStream in, PackageAccess access, boolean closeStream) 
throws IOException {
-        super(access);
+        this(in, access, closeStream, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Constructor. Opens a Zip based Open XML document from
+     *  an InputStream.
+     *
+     * @param in
+     *            Zip input stream to load.
+     * @param access
+     *            The package access mode.
+     * @param closeStream
+     *            Whether to close the input stream.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @throws IllegalArgumentException
+     *             If the specified input stream is not an instance of
+     *             ZipInputStream.
+     * @throws IOException
+     *            if input stream cannot be opened, read, or closed
+     * @since POI 5.4.1
+     */
+    ZipPackage(InputStream in, PackageAccess access, boolean closeStream, 
OPCComplianceFlags opcComplianceFlags) throws IOException {
+        super(access, opcComplianceFlags);
         try (ZipArchiveThresholdInputStream zis = ZipHelper.openZipStream(in, 
closeStream)) {
             this.zipArchive = new ZipInputStreamZipEntrySource(zis);
         }
@@ -168,7 +222,23 @@ public final class ZipPackage extends OP
      * @throws InvalidOperationException If the zip file cannot be opened.
      */
     ZipPackage(String path, PackageAccess access) throws 
InvalidOperationException {
-        this(new File(path), access);
+        this(path, access, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Constructor. Opens a Zip based Open XML document from a file.
+     *
+     * @param path
+     *            The path of the file to open or create.
+     * @param access
+     *            The package access mode.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @throws InvalidOperationException If the zip file cannot be opened.
+     * @since POI 5.4.1
+     */
+    ZipPackage(String path, PackageAccess access, OPCComplianceFlags 
opcComplianceFlags) throws InvalidOperationException {
+        this(new File(path), access, opcComplianceFlags);
     }
 
     /**
@@ -181,7 +251,23 @@ public final class ZipPackage extends OP
      * @throws InvalidOperationException If the zip file cannot be opened.
      */
     ZipPackage(File file, PackageAccess access) throws 
InvalidOperationException {
-        super(access);
+        this(file, access, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Constructor. Opens a Zip based Open XML document from a File.
+     *
+     * @param file
+     *            The file to open or create.
+     * @param access
+     *            The package access mode.
+     * @param opcComplianceFlags
+     *            The level of OPC compliance to enforce when reading the 
package
+     * @throws InvalidOperationException If the zip file cannot be opened.
+     * @since POI 5.4.1
+     */
+    ZipPackage(File file, PackageAccess access, OPCComplianceFlags 
opcComplianceFlags) throws InvalidOperationException {
+        super(access, opcComplianceFlags);
 
         ZipEntrySource ze;
         try {
@@ -248,7 +334,24 @@ public final class ZipPackage extends OP
      *            The package access mode.
      */
     ZipPackage(ZipEntrySource zipEntry, PackageAccess access) {
-        super(access);
+        this(zipEntry, access, OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * Constructor. Opens a Zip based Open XML document from
+     *  a custom ZipEntrySource, typically an open archive
+     *  from another system
+     *
+     * @param zipEntry
+     *            Zip data to load.
+     * @param access
+     *            The package access mode.
+     * @param access
+     *            The package access mode.
+     * @since POI 5.4.1
+     */
+    ZipPackage(ZipEntrySource zipEntry, PackageAccess access, 
OPCComplianceFlags opcComplianceFlags) {
+        super(access, opcComplianceFlags);
         this.zipArchive = zipEntry;
     }
 

Modified: 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/unmarshallers/PackagePropertiesUnmarshaller.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/unmarshallers/PackagePropertiesUnmarshaller.java?rev=1924476&r1=1924475&r2=1924476&view=diff
==============================================================================
--- 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/unmarshallers/PackagePropertiesUnmarshaller.java
 (original)
+++ 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/unmarshallers/PackagePropertiesUnmarshaller.java
 Wed Mar 19 20:38:10 2025
@@ -28,6 +28,7 @@ import org.apache.poi.openxml4j.opc.Pack
 import org.apache.poi.openxml4j.opc.PackagePart;
 import org.apache.poi.openxml4j.opc.PackageProperties;
 import org.apache.poi.openxml4j.opc.ZipPackage;
+import org.apache.poi.openxml4j.opc.OPCComplianceFlags;
 import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
 import org.apache.poi.openxml4j.opc.internal.PartUnmarshaller;
 import org.apache.poi.openxml4j.opc.internal.ZipHelper;
@@ -44,6 +45,23 @@ import org.xml.sax.SAXException;
  */
 public final class PackagePropertiesUnmarshaller implements PartUnmarshaller {
 
+    /**
+     *  Whether OPC compliance requirements are checked (e.g., M4.2, M4.3, 
M4.4, and M4.5)
+     */
+    private OPCComplianceFlags opcComplianceFlags;
+
+    public PackagePropertiesUnmarshaller() {
+        this(OPCComplianceFlags.enforceAll());
+    }
+
+    /**
+     * @param opcComplianceFlags Overrides the default OPC compliance settings
+     * @since POI 5.4.1
+     */
+    public PackagePropertiesUnmarshaller(OPCComplianceFlags 
opcComplianceFlags) {
+        this.opcComplianceFlags = opcComplianceFlags;
+    }
+
     protected static final String KEYWORD_CATEGORY = "category";
 
     protected static final String KEYWORD_CONTENT_STATUS = "contentStatus";
@@ -234,24 +252,28 @@ public final class PackagePropertiesUnma
      */
     public void checkElementForOPCCompliance(Element el)
             throws InvalidFormatException {
-        // Check the current element
-        NamedNodeMap namedNodeMap = el.getAttributes();
-        int namedNodeCount = namedNodeMap.getLength();
-        for (int i = 0; i < namedNodeCount; i++) {
-            Attr attr = (Attr)namedNodeMap.item(0);
-
-            if (attr != null && 
XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attr.getNamespaceURI())) {
-                // Rule M4.2
-                if 
(PackageNamespaces.MARKUP_COMPATIBILITY.equals(attr.getValue())) {
-                    throw new InvalidFormatException(
-                            "OPC Compliance error [M4.2]: A format consumer 
shall consider the use of the Markup Compatibility namespace to be an error.");
+
+        if (opcComplianceFlags.getForbidMarkupCompatibilityNamespace()) {
+            // Check the current element
+            NamedNodeMap namedNodeMap = el.getAttributes();
+            int namedNodeCount = namedNodeMap.getLength();
+            for (int i = 0; i < namedNodeCount; i++) {
+                Attr attr = (Attr)namedNodeMap.item(0);
+
+                if (attr != null && 
XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attr.getNamespaceURI())) {
+                    // Rule M4.2
+                    if 
(PackageNamespaces.MARKUP_COMPATIBILITY.equals(attr.getValue())) {
+                        throw new InvalidFormatException(
+                                "OPC Compliance error [M4.2]: A format 
consumer shall consider the use of the Markup Compatibility namespace to be an 
error.");
+                    }
                 }
             }
         }
 
         // Rule M4.3
         String elName = el.getLocalName();
-        if (PackageProperties.NAMESPACE_DCTERMS.equals(el.getNamespaceURI())) {
+        if (opcComplianceFlags.getForbidRefiningDublinCoreElements() &&
+                
PackageProperties.NAMESPACE_DCTERMS.equals(el.getNamespaceURI())) {
             if (!(KEYWORD_CREATED.equals(elName) || 
KEYWORD_MODIFIED.equals(elName))) {
                 throw new InvalidFormatException(
                         "OPC Compliance error [M4.3]: Producers shall not 
create a document element that contains refinements to the Dublin Core 
elements, except for the two specified in the schema: <dcterms:created> and 
<dcterms:modified> Consumers shall consider a document element that violates 
this constraint to be an error.");
@@ -259,13 +281,15 @@ public final class PackagePropertiesUnma
         }
 
         // Rule M4.4
-        if (el.getAttributeNodeNS(XMLConstants.XML_NS_URI, "lang") != null) {
+        if (opcComplianceFlags.getForbidXmlLangAttributes() &&
+                el.getAttributeNodeNS(XMLConstants.XML_NS_URI, "lang") != 
null) {
             throw new InvalidFormatException(
                     "OPC Compliance error [M4.4]: Producers shall not create a 
document element that contains the xml:lang attribute. Consumers shall consider 
a document element that violates this constraint to be an error.");
         }
 
         // Rule M4.5
-        if (PackageProperties.NAMESPACE_DCTERMS.equals(el.getNamespaceURI())) {
+        if (opcComplianceFlags.getRestrictXsiTypeAttribute() &&
+                
PackageProperties.NAMESPACE_DCTERMS.equals(el.getNamespaceURI())) {
             // DCTerms namespace only use with 'created' and 'modified' 
elements
             if (!(elName.equals(KEYWORD_CREATED) || 
elName.equals(KEYWORD_MODIFIED))) {
                 throw new InvalidFormatException("Namespace error : " + elName

Modified: 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/compliance/TestOPCComplianceCoreProperties.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/compliance/TestOPCComplianceCoreProperties.java?rev=1924476&r1=1924475&r2=1924476&view=diff
==============================================================================
--- 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/compliance/TestOPCComplianceCoreProperties.java
 (original)
+++ 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/compliance/TestOPCComplianceCoreProperties.java
 Wed Mar 19 20:38:10 2025
@@ -38,6 +38,7 @@ import org.apache.commons.io.output.Unsy
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
 import org.apache.poi.openxml4j.opc.ContentTypes;
+import org.apache.poi.openxml4j.opc.OPCComplianceFlags;
 import org.apache.poi.openxml4j.opc.OPCPackage;
 import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
 import org.apache.poi.openxml4j.opc.PackagingURIHelper;
@@ -87,17 +88,26 @@ public final class TestOPCComplianceCore
     private static String extractInvalidFormatMessage(String sampleNameSuffix) 
throws IOException {
         try (InputStream is = openSampleStream("OPCCompliance_CoreProperties_" 
+ sampleNameSuffix)) {
             InvalidFormatException e = 
assertThrows(InvalidFormatException.class,
-                () -> OPCPackage.open(is).revert(), "expected OPC compliance 
exception was not thrown");
+                    () -> OPCPackage.open(is).revert(), "expected OPC 
compliance exception was not thrown");
             return e.getMessage();
         }
     }
 
+    private static void testComplianceFlagOverridePreventsException(
+            String sampleNameSuffix,
+            OPCComplianceFlags opcComplianceFlags
+    ) throws IOException {
+        try (InputStream is = openSampleStream("OPCCompliance_CoreProperties_" 
+ sampleNameSuffix)) {
+            assertDoesNotThrow(() -> OPCPackage.open(is, 
opcComplianceFlags).revert(), "Unexpected compliance related exception thrown");
+        }
+    }
+
     /**
      * Test M4.1 rule.
      */
     @Test
     void testOnlyOneCorePropertiesPart() throws Exception {
-       // We have relaxed this check, so we can read the file anyway
+        // We have relaxed this check, so we can read the file anyway
         assertDoesNotThrow(() -> {
             try (InputStream is = 
openSampleStream("OPCCompliance_CoreProperties_OnlyOneCorePropertiesPartFAIL.docx");
                  OPCPackage pkg = OPCPackage.open(is)) {
@@ -105,21 +115,21 @@ public final class TestOPCComplianceCore
             }
         }, "M4.1 should be being relaxed");
 
-       // We will use the first core properties, and ignore the others
+        // We will use the first core properties, and ignore the others
 
-      try (InputStream is = openSampleStream("MultipleCoreProperties.docx");
-           OPCPackage pkg = OPCPackage.open(is)) {
+        try (InputStream is = openSampleStream("MultipleCoreProperties.docx");
+             OPCPackage pkg = OPCPackage.open(is)) {
 
-          // We can see 2 by type
-          assertEquals(2, 
pkg.getPartsByContentType(ContentTypes.CORE_PROPERTIES_PART).size());
-          // But only the first one by relationship
-          assertEquals(1, 
pkg.getPartsByRelationshipType(PackageRelationshipTypes.CORE_PROPERTIES).size());
-          // It should be core.xml not the older core1.xml
-          assertEquals(
-                  "/docProps/core.xml",
-                  
pkg.getPartsByRelationshipType(PackageRelationshipTypes.CORE_PROPERTIES).get(0).getPartName().toString()
-          );
-      }
+            // We can see 2 by type
+            assertEquals(2, 
pkg.getPartsByContentType(ContentTypes.CORE_PROPERTIES_PART).size());
+            // But only the first one by relationship
+            assertEquals(1, 
pkg.getPartsByRelationshipType(PackageRelationshipTypes.CORE_PROPERTIES).size());
+            // It should be core.xml not the older core1.xml
+            assertEquals(
+                    "/docProps/core.xml",
+                    
pkg.getPartsByRelationshipType(PackageRelationshipTypes.CORE_PROPERTIES).get(0).getPartName().toString()
+            );
+        }
     }
 
     private static URI createURI(String text) {
@@ -139,8 +149,8 @@ public final class TestOPCComplianceCore
             OPCPackage pkg = OPCPackage.open(is);
             URI partUri = createURI("/docProps/core2.xml");
             InvalidOperationException e = 
assertThrows(InvalidOperationException.class, () ->
-                    
pkg.addRelationship(PackagingURIHelper.createPartName(partUri), 
TargetMode.INTERNAL, PackageRelationshipTypes.CORE_PROPERTIES),
-                "expected OPC compliance exception was not thrown"
+                            
pkg.addRelationship(PackagingURIHelper.createPartName(partUri), 
TargetMode.INTERNAL, PackageRelationshipTypes.CORE_PROPERTIES),
+                    "expected OPC compliance exception was not thrown"
             );
             assertEquals("OPC Compliance error [M4.1]: can't add another core 
properties part ! Use the built-in package method instead.", e.getMessage());
             pkg.revert();
@@ -157,8 +167,8 @@ public final class TestOPCComplianceCore
 
             URI partUri = createURI("/docProps/core2.xml");
             InvalidOperationException e = 
assertThrows(InvalidOperationException.class, () ->
-                    pkg.createPart(PackagingURIHelper.createPartName(partUri), 
ContentTypes.CORE_PROPERTIES_PART),
-                "expected OPC compliance exception was not thrown");
+                            
pkg.createPart(PackagingURIHelper.createPartName(partUri), 
ContentTypes.CORE_PROPERTIES_PART),
+                    "expected OPC compliance exception was not thrown");
             assertEquals("OPC Compliance error [M4.1]: you try to add more 
than one core properties relationship in the package !", e.getMessage());
             pkg.revert();
         }
@@ -211,7 +221,7 @@ public final class TestOPCComplianceCore
 
     /**
      * Document with no core properties - testing at the OPC level,
-     *  saving into a new stream
+     * saving into a new stream
      */
     @Test
     void testNoCoreProperties_saveNew() throws Exception {
@@ -257,7 +267,7 @@ public final class TestOPCComplianceCore
 
     /**
      * Document with no core properties - testing at the OPC level,
-     *  from a temp-file, saving in-place
+     * from a temp-file, saving in-place
      */
     @Test
     void testNoCoreProperties_saveInPlace() throws Exception {
@@ -266,7 +276,7 @@ public final class TestOPCComplianceCore
         // Copy this into a temp file, so we can play with it
         File tmp = getOutputFile("poi-test.opc");
         try (FileOutputStream out = new FileOutputStream(tmp);
-            InputStream in = openSampleStream(sampleFileName)) {
+             InputStream in = openSampleStream(sampleFileName)) {
             IOUtils.copy(in, out);
         }
 
@@ -295,4 +305,50 @@ public final class TestOPCComplianceCore
         }
         assertTrue(tmp.delete());
     }
+
+    /**
+     * Test M4.2 rule not enforced when appropriate OPCComplianceFlag is set.
+     */
+    @Test
+    void testDisableEnforceCompatibilityMarkup() throws IOException {
+        testComplianceFlagOverridePreventsException(
+                "DoNotUseCompatibilityMarkupFAIL.docx",
+                
OPCComplianceFlags.enforceAll().setForbidMarkupCompatibilityNamespace(false)
+        );
+    }
+
+    /**
+     * Test M4.3 and M4.5 rules not enforced when appropriate 
OPCComplianceFlag is set.
+     */
+    @Test
+    void testDisableEnforceDublinCoreAndLimitedXSI() throws IOException {
+        testComplianceFlagOverridePreventsException(
+                "DCTermsNamespaceLimitedUseFAIL.docx",
+                OPCComplianceFlags.enforceAll() // This test document violates 
both M4.3 and M4.5
+                        .setRestrictXsiTypeAttribute(false)
+                        .setForbidRefiningDublinCoreElements(false)
+        );
+    }
+
+    /**
+     * Test M4.4 rule not enforced when appropriate OPCComplianceFlag is set.
+     */
+    @Test
+    void testDisableEnforceUnauthorizedXMLLangAttribute() throws IOException {
+        testComplianceFlagOverridePreventsException(
+                "UnauthorizedXMLLangAttributeFAIL.docx",
+                
OPCComplianceFlags.enforceAll().setForbidXmlLangAttribute(false)
+        );
+    }
+
+    /**
+     * Test M4.5 rule not enforced when appropriate OPCComplianceFlag is set.
+     */
+    @Test
+    void testDisableEnforceLimitedXSITypeAttribute_NotPresent() throws 
IOException {
+        testComplianceFlagOverridePreventsException(
+                "LimitedXSITypeAttribute_NotPresentFAIL.docx",
+                
OPCComplianceFlags.enforceAll().setRestrictXsiTypeAttribute(false)
+        );
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to