You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by bo...@apache.org on 2019/08/16 14:30:02 UTC

[commons-compress] 02/02: COMPRESS-479 allow strategy to control all parts of extra field parsing

This is an automated email from the ASF dual-hosted git repository.

bodewig pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git

commit 60a389c2ffa88f5a5de9e902ac2cfdf17fb57993
Author: Stefan Bodewig <bo...@apache.org>
AuthorDate: Fri Aug 16 16:29:05 2019 +0200

    COMPRESS-479 allow strategy to control all parts of extra field parsing
---
 .../archivers/zip/ExtraFieldParsingBehavior.java   |  62 ++++++++++++
 .../compress/archivers/zip/ExtraFieldUtils.java    | 110 +++++++++++----------
 .../compress/archivers/zip/ZipArchiveEntry.java    |  98 ++++++++++--------
 3 files changed, 177 insertions(+), 93 deletions(-)

diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldParsingBehavior.java b/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldParsingBehavior.java
new file mode 100644
index 0000000..2e29450
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldParsingBehavior.java
@@ -0,0 +1,62 @@
+/*
+ *  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.commons.compress.archivers.zip;
+
+import java.util.zip.ZipException;
+
+/**
+ * Controls details of parsing zip extra fields.
+ *
+ * @since 1.19
+ */
+public interface ExtraFieldParsingBehavior extends UnparseableExtraFieldBehavior {
+    /**
+     * Creates an instance of ZipExtraField for the given id.
+     *
+     * <p>A good default implementation would be {@link
+     * ExtraFieldUtils#createExtraField}.</p>
+     *
+     * @param headerId the id for the extra field
+     * @return an instance of ZipExtraField, must not be null
+     * @throws ZipException if an error occurs
+     * @throws InstantiationException if unable to instantiate the class
+     * @throws IllegalAccessException if not allowed to instantiate the class
+     */
+    ZipExtraField createExtraField(final ZipShort headerId)
+        throws ZipException, InstantiationException, IllegalAccessException;
+
+    /**
+     * Fills in the extra field data for a single extra field.
+     *
+     * <p>A good default implementation would be {@link
+     * ExtraFieldUtils#fillExtraField}.</p>
+     *
+     * @param field the extra field instance to fill
+     * @param data the array of extra field data
+     * @param off offset into data where this field's data starts
+     * @param len the length of this field's data
+     * @param local whether the extra field data stems from the local
+     * file header. If this is false then the data is part if the
+     * central directory header extra data.
+     * @return the filled field. Usually this is the same as {@code
+     * field} but it oculd be a replacement extra field as well
+     * @throws ZipException if an error occurs
+     */
+    ZipExtraField fill(ZipExtraField field, byte[] data, int off, int len, boolean local)
+        throws ZipException;
+}
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java b/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
index 3985972..3fb60c3 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
@@ -85,9 +85,9 @@ public class ExtraFieldUtils {
      */
     public static ZipExtraField createExtraField(final ZipShort headerId)
         throws InstantiationException, IllegalAccessException {
-        final Class<?> c = implementations.get(headerId);
-        if (c != null) {
-            return (ZipExtraField) c.newInstance();
+        ZipExtraField field = createExtraFieldNoDefault(headerId);
+        if (field != null) {
+            return field;
         }
         final UnrecognizedExtraField u = new UnrecognizedExtraField();
         u.setHeaderId(headerId);
@@ -95,6 +95,24 @@ public class ExtraFieldUtils {
     }
 
     /**
+     * Create an instance of the appropriate ExtraField.
+     * @param headerId the header identifier
+     * @return an instance of the appropriate ExtraField or null if
+     * the id is not supported
+     * @throws InstantiationException if unable to instantiate the class
+     * @throws IllegalAccessException if not allowed to instantiate the class
+     * @since 1.19
+     */
+    public static ZipExtraField createExtraFieldNoDefault(final ZipShort headerId)
+        throws InstantiationException, IllegalAccessException {
+        final Class<?> c = implementations.get(headerId);
+        if (c != null) {
+            return (ZipExtraField) c.newInstance();
+        }
+        return null;
+    }
+
+    /**
      * Split the array into ExtraFields and populate them with the
      * given data as local file data, throwing an exception if the
      * data cannot be parsed.
@@ -136,7 +154,25 @@ public class ExtraFieldUtils {
     public static ZipExtraField[] parse(final byte[] data, final boolean local,
                                         final UnparseableExtraFieldBehavior onUnparseableData)
         throws ZipException {
-        return parse(data, local, onUnparseableData, ParseErrorBehavior.THROW);
+        return parse(data, local, new ExtraFieldParsingBehavior() {
+            @Override
+            public ZipExtraField onUnparseableExtraField(byte[] data, int off, int len, boolean local,
+                int claimedLength) throws ZipException {
+                return onUnparseableData.onUnparseableExtraField(data, off, len, local, claimedLength);
+            }
+
+            @Override
+            public ZipExtraField createExtraField(final ZipShort headerId)
+                throws ZipException, InstantiationException, IllegalAccessException {
+                return ExtraFieldUtils.createExtraField(headerId);
+            }
+
+            @Override
+            public ZipExtraField fill(ZipExtraField field, byte[] data, int off, int len, boolean local)
+                throws ZipException {
+                return fillExtraField(field, data, off, len, local);
+            }
+        });
     }
 
     /**
@@ -157,8 +193,7 @@ public class ExtraFieldUtils {
      * @since 1.19
      */
     public static ZipExtraField[] parse(final byte[] data, final boolean local,
-                                        final UnparseableExtraFieldBehavior onUnparseableData,
-                                        final ParseErrorBehavior onParseError)
+                                        final ExtraFieldParsingBehavior parsingbehavior)
         throws ZipException {
         final List<ZipExtraField> v = new ArrayList<>();
         int start = 0;
@@ -167,7 +202,7 @@ public class ExtraFieldUtils {
             final ZipShort headerId = new ZipShort(data, start);
             final int length = new ZipShort(data, start + 2).getValue();
             if (start + WORD + length > data.length) {
-                ZipExtraField field = onUnparseableData.onUnparseableExtraField(data, start, data.length - start,
+                ZipExtraField field = parsingbehavior.onUnparseableExtraField(data, start, data.length - start,
                     local, length);
                 if (field != null) {
                     v.add(field);
@@ -177,23 +212,13 @@ public class ExtraFieldUtils {
                 // available data
                 break LOOP;
             }
-            switch (onParseError) {
-            case MAKE_UNRECOGNIZED:
-                try {
-                    v.add(parseField(headerId, local, data, start + WORD, length));
-                } catch (ZipException ex) {
-                    final UnrecognizedExtraField u = new UnrecognizedExtraField();
-                    u.setHeaderId(headerId);
-                    fillField(u, local, data, start + WORD, length);
-                    v.add(u);
-                }
-                break;
-            case THROW: // FALLTHROUGH
-            default:
-                v.add(parseField(headerId, local, data, start + WORD, length));
-                break;
+            try {
+                ZipExtraField ze = parsingbehavior.createExtraField(headerId);
+                v.add(parsingbehavior.fill(ze, data, start + WORD, length, local));
+                start += length + WORD;
+            } catch (final InstantiationException | IllegalAccessException ie) {
+                throw (ZipException) new ZipException(ie.getMessage()).initCause(ie);
             }
-            start += length + WORD;
         }
 
         final ZipExtraField[] result = new ZipExtraField[v.size()];
@@ -277,25 +302,22 @@ public class ExtraFieldUtils {
         return result;
     }
 
-    private static ZipExtraField parseField(final ZipShort headerId, final boolean local, final byte[] data,
-        final int off, final int len) throws ZipException {
-        try {
-            final ZipExtraField ze = createExtraField(headerId);
-            fillField(ze, local, data, off, len);
-            return ze;
-        } catch (final InstantiationException | IllegalAccessException ie) {
-            throw (ZipException) new ZipException(ie.getMessage()).initCause(ie);
-        }
-    }
-
-    private static void fillField(final ZipExtraField ze, final boolean local, final byte[] data, final int off,
-        final int len) throws ZipException {
+    /**
+     * Fills in the extra field data into the given instance.
+     *
+     * <p>Calls {@link ZipExtraField#parseFromCentralDirectoryData} or {@link ZipExtraField#parseFromLocalFileData} internally and wraps any {@link ArrayIndexOutOfBoundsException} thrown into a {@link ZipException}.</p>
+     *
+     * @since 1.19
+     */
+    public static ZipExtraField fillExtraField(final ZipExtraField ze, final byte[] data, final int off,
+        final int len, final boolean local) throws ZipException {
         try {
             if (local) {
                 ze.parseFromLocalFileData(data, off, len);
             } else {
                 ze.parseFromCentralDirectoryData(data, off, len);
             }
+            return ze;
         } catch (ArrayIndexOutOfBoundsException aiobe) {
             throw (ZipException) new ZipException("Failed to parse corrupt ZIP extra field of type "
                 + Integer.toHexString(ze.getHeaderId().getValue())).initCause(aiobe);
@@ -384,20 +406,4 @@ public class ExtraFieldUtils {
         }
 
     }
-
-    /**
-     * What shall {@link #parse} do if parsing the extra field fails.
-     *
-     * @since 1.19
-     */
-    public enum ParseErrorBehavior {
-        /**
-         * Throw an exception if parsing the extra field fails.
-         */
-        THROW,
-        /**
-         * Replace the extra field with an instance of an {@link UnrecognizedExtraField}.
-         */
-        MAKE_UNRECOGNIZED;
-    }
 }
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
index 585dfb2..68b34b4 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
@@ -173,9 +173,7 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
         setName(entry.getName());
         final byte[] extra = entry.getExtra();
         if (extra != null) {
-            setExtraFields(ExtraFieldUtils.parse(extra, true,
-                ExtraFieldUtils.UnparseableExtraField.READ, ExtraFieldUtils.ParseErrorBehavior.MAKE_UNRECOGNIZED));
-        } else {
+            setExtraFields(ExtraFieldUtils.parse(extra, true, ExtraFieldParsingMode.BEST_EFFORT));        } else {
             // initializes extra data to an empty byte array
             setExtra();
         }
@@ -445,27 +443,25 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
 
     /**
      * Retrieves extra fields.
-     * @param includeUnparseable whether to also return unparseable
-     * extra fields as {@link UnparseableExtraFieldData} if such data
-     * exists.
+     * @param parsingBehavior controls parsing of extra fields.
      * @return an array of the extra fields
      *
      * @throws ZipException if parsing fails, can not happen if {@code
-     * mode} is {@link ExtraFieldParsingMode.BEST_EFFORT}.
+     * parsingBehavior} is {@link ExtraFieldParsingMode.BEST_EFFORT}.
      *
      * @since 1.19
      */
-    public ZipExtraField[] getExtraFields(final ExtraFieldParsingMode mode)
+    public ZipExtraField[] getExtraFields(final ExtraFieldParsingBehavior parsingBehavior)
         throws ZipException {
-        if (mode == ExtraFieldParsingMode.BEST_EFFORT) {
+        if (parsingBehavior == ExtraFieldParsingMode.BEST_EFFORT) {
             return getExtraFields(true);
         }
         byte[] local = getExtra();
         List<ZipExtraField> localFields = new ArrayList<>(Arrays.asList(ExtraFieldUtils.parse(local, true,
-            mode.getOnUnparseableData(), mode.getOnParseError())));
+            parsingBehavior)));
         byte[] central = ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy());
         List<ZipExtraField> centralFields = new ArrayList<>(Arrays.asList(ExtraFieldUtils.parse(central, false,
-            mode.getOnUnparseableData(), mode.getOnParseError())));
+            parsingBehavior)));
         List<ZipExtraField> merged = new ArrayList<>();
         for (ZipExtraField l : localFields) {
             ZipExtraField c = null;
@@ -676,10 +672,7 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
     @Override
     public void setExtra(final byte[] extra) throws RuntimeException {
         try {
-            final ZipExtraField[] local =
-                ExtraFieldUtils.parse(extra, true,
-                    ExtraFieldUtils.UnparseableExtraField.READ,
-                    ExtraFieldUtils.ParseErrorBehavior.MAKE_UNRECOGNIZED);
+            final ZipExtraField[] local = ExtraFieldUtils.parse(extra, true, ExtraFieldParsingMode.BEST_EFFORT);
             mergeExtraFields(local, true);
         } catch (final ZipException e) {
             // actually this is not possible as of Commons Compress 1.1
@@ -704,10 +697,7 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
      */
     public void setCentralDirectoryExtra(final byte[] b) {
         try {
-            final ZipExtraField[] central =
-                ExtraFieldUtils.parse(b, false,
-                    ExtraFieldUtils.UnparseableExtraField.READ,
-                    ExtraFieldUtils.ParseErrorBehavior.MAKE_UNRECOGNIZED);
+            final ZipExtraField[] central = ExtraFieldUtils.parse(b, false, ExtraFieldParsingMode.BEST_EFFORT);
             mergeExtraFields(central, false);
         } catch (final ZipException e) {
             // actually this is not possible as of Commons Compress 1.19
@@ -1114,7 +1104,7 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
      *
      * @since 1.19
      */
-    public enum ExtraFieldParsingMode {
+    public enum ExtraFieldParsingMode implements ExtraFieldParsingBehavior {
         /**
          * Try to parse as many extra fields as possible and wrap
          * unknown extra fields as well as supported extra fields that
@@ -1126,8 +1116,12 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
          *
          * <p>This is the default behavior starting with Commons Compress 1.19.</p>
          */
-        BEST_EFFORT(ExtraFieldUtils.UnparseableExtraField.READ,
-            ExtraFieldUtils.ParseErrorBehavior.MAKE_UNRECOGNIZED),
+        BEST_EFFORT(ExtraFieldUtils.UnparseableExtraField.READ) {
+            @Override
+            public ZipExtraField fill(ZipExtraField field, byte[] data, int off, int len, boolean local) {
+                return fillAndMakeUnrecognizedOnError(field, data, off, len, local);
+            }
+        },
         /**
          * Try to parse as many extra fields as possible and wrap
          * unknown extra fields in {@link UnrecognizedExtraField}.
@@ -1142,8 +1136,7 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
          * <p>This used to be the default behavior prior to Commons
          * Compress 1.19.</p>
          */
-        STRICT_FOR_KNOW_EXTRA_FIELDS(ExtraFieldUtils.UnparseableExtraField.READ,
-            ExtraFieldUtils.ParseErrorBehavior.THROW),
+        STRICT_FOR_KNOW_EXTRA_FIELDS(ExtraFieldUtils.UnparseableExtraField.READ),
         /**
          * Try to parse as many extra fields as possible and wrap
          * unknown extra fields as well as supported extra fields that
@@ -1151,12 +1144,13 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
          *
          * <p>Ignore extra data that doesn't follow the recommended
          * pattern.</p>
-         *
-         * <p>This used to be the default behavior prior to Commons
-         * Compress 1.19.</p>
          */
-        ONLY_PARSEABLE_LENIENT(ExtraFieldUtils.UnparseableExtraField.SKIP,
-            ExtraFieldUtils.ParseErrorBehavior.THROW),
+        ONLY_PARSEABLE_LENIENT(ExtraFieldUtils.UnparseableExtraField.SKIP) {
+            @Override
+            public ZipExtraField fill(ZipExtraField field, byte[] data, int off, int len, boolean local) {
+                return fillAndMakeUnrecognizedOnError(field, data, off, len, local);
+            }
+        },
         /**
          * Try to parse as many extra fields as possible and wrap
          * unknown extra fields in {@link UnrecognizedExtraField}.
@@ -1167,30 +1161,52 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry
          * <p>Throw an exception if an extra field that is generally
          * supported cannot be parsed.</p>
          */
-        ONLY_PARSEABLE_STRICT(ExtraFieldUtils.UnparseableExtraField.SKIP,
-            ExtraFieldUtils.ParseErrorBehavior.THROW),
+        ONLY_PARSEABLE_STRICT(ExtraFieldUtils.UnparseableExtraField.SKIP),
         /**
          * Throw an exception if any of the recognized extra fields
          * cannot be parsed or any extra field violates the
          * recommended pattern.
          */
-        DRACONIC(ExtraFieldUtils.UnparseableExtraField.THROW,
-            ExtraFieldUtils.ParseErrorBehavior.THROW);
+        DRACONIC(ExtraFieldUtils.UnparseableExtraField.THROW);
 
         private final ExtraFieldUtils.UnparseableExtraField onUnparseableData;
-        private final ExtraFieldUtils.ParseErrorBehavior onParseError;
 
-        private ExtraFieldParsingMode(ExtraFieldUtils.UnparseableExtraField onUnparseableData,
-                                      ExtraFieldUtils.ParseErrorBehavior onParseError) {
+        private ExtraFieldParsingMode(ExtraFieldUtils.UnparseableExtraField onUnparseableData) {
             this.onUnparseableData = onUnparseableData;
-            this.onParseError = onParseError;
         }
 
-        public ExtraFieldUtils.UnparseableExtraField getOnUnparseableData() {
-            return onUnparseableData;
+        @Override
+        public ZipExtraField onUnparseableExtraField(byte[] data, int off, int len, boolean local,
+            int claimedLength) throws ZipException {
+            return onUnparseableData.onUnparseableExtraField(data, off, len, local, claimedLength);
         }
-        public ExtraFieldUtils.ParseErrorBehavior getOnParseError() {
-            return onParseError;
+
+        @Override
+        public ZipExtraField createExtraField(final ZipShort headerId)
+            throws ZipException, InstantiationException, IllegalAccessException {
+            return ExtraFieldUtils.createExtraField(headerId);
+        }
+
+        @Override
+        public ZipExtraField fill(ZipExtraField field, byte[] data, int off, int len, boolean local)
+            throws ZipException {
+            return ExtraFieldUtils.fillExtraField(field, data, off, len, local);
+        }
+
+        private static ZipExtraField fillAndMakeUnrecognizedOnError(ZipExtraField field, byte[] data, int off,
+            int len, boolean local) {
+            try {
+                return ExtraFieldUtils.fillExtraField(field, data, off, len, local);
+            } catch (ZipException ex) {
+                final UnrecognizedExtraField u = new UnrecognizedExtraField();
+                u.setHeaderId(field.getHeaderId());
+                if (local) {
+                    u.setLocalFileDataData(Arrays.copyOfRange(data, off, off + len));
+                } else {
+                    u.setCentralDirectoryData(Arrays.copyOfRange(data, off, off + len));
+                }
+                return u;
+            }
         }
     }
 }