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

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new b3eb35c1cb NIFI-15174 Handle Out-of-Order MANIFEST Entries When 
Uploading NARs to NiFi Registry (#10491)
b3eb35c1cb is described below

commit b3eb35c1cba9d4016ea36fff13c0bb2d0df755b4
Author: Pierre Villard <[email protected]>
AuthorDate: Mon Nov 3 22:03:18 2025 +0100

    NIFI-15174 Handle Out-of-Order MANIFEST Entries When Uploading NARs to NiFi 
Registry (#10491)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../bundle/extract/nar/NarBundleExtractor.java     | 108 ++++++++-------------
 .../bundle/extract/nar/TestNarBundleExtractor.java |  49 ++++++++++
 2 files changed, 91 insertions(+), 66 deletions(-)

diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/main/java/org/apache/nifi/registry/bundle/extract/nar/NarBundleExtractor.java
 
b/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/main/java/org/apache/nifi/registry/bundle/extract/nar/NarBundleExtractor.java
index aa287c207e..81f5de2630 100644
--- 
a/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/main/java/org/apache/nifi/registry/bundle/extract/nar/NarBundleExtractor.java
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/main/java/org/apache/nifi/registry/bundle/extract/nar/NarBundleExtractor.java
@@ -27,7 +27,6 @@ import org.apache.nifi.registry.extension.bundle.BuildInfo;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
@@ -36,9 +35,12 @@ import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarInputStream;
+import java.util.jar.JarFile;
 import java.util.jar.Manifest;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -73,7 +75,31 @@ public class NarBundleExtractor implements BundleExtractor {
     @Override
     public BundleDetails extract(final InputStream inputStream) throws 
IOException {
         try (final JarInputStream jarInputStream = new 
JarInputStream(inputStream)) {
-            final Manifest manifest = jarInputStream.getManifest();
+            Manifest manifest = jarInputStream.getManifest();
+            byte[] extensionManifestBytes = null;
+            final Map<String, String> additionalDetails = new HashMap<>();
+
+            JarEntry jarEntry;
+            while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
+                final String jarEntryName = jarEntry.getName();
+                try {
+                    if (JarFile.MANIFEST_NAME.equals(jarEntryName) && manifest 
== null) {
+                        manifest = new Manifest(jarInputStream);
+                    } else if 
(EXTENSION_DESCRIPTOR_ENTRY.equals(jarEntryName)) {
+                        extensionManifestBytes = toByteArray(jarInputStream);
+                    } else {
+                        final Matcher matcher = 
ADDITIONAL_DETAILS_ENTRY_PATTERN.matcher(jarEntryName);
+                        if (matcher.matches()) {
+                            final String extensionName = matcher.group(1);
+                            final String additionalDetailsContent = new 
String(toByteArray(jarInputStream), StandardCharsets.UTF_8);
+                            additionalDetails.put(extensionName, 
additionalDetailsContent);
+                        }
+                    }
+                } finally {
+                    jarInputStream.closeEntry();
+                }
+            }
+
             if (manifest == null) {
                 throw new BundleException("NAR bundles must contain a valid 
MANIFEST");
             }
@@ -88,7 +114,20 @@ public class NarBundleExtractor implements BundleExtractor {
                     .addDependencyCoordinate(dependencyCoordinate)
                     .buildInfo(buildInfo);
 
-            parseExtensionDocs(jarInputStream, builder);
+            if (extensionManifestBytes != null) {
+                try (final ByteArrayInputStream docsInputStream = new 
ByteArrayInputStream(extensionManifestBytes)) {
+                    final ExtensionManifestParser docsParser = new 
JAXBExtensionManifestParser();
+                    final ExtensionManifest extensionManifest = 
docsParser.parse(docsInputStream);
+                    builder.addExtensions(extensionManifest.getExtensions());
+                    
builder.systemApiVersion(extensionManifest.getSystemApiVersion());
+                } catch (Exception e) {
+                    throw new BundleException("Unable to obtain extension info 
for bundle due to: " + e.getMessage(), e);
+                }
+            } else {
+                builder.systemApiVersion(NA);
+            }
+
+            additionalDetails.forEach(builder::addAdditionalDetails);
 
             return builder.build();
         }
@@ -156,40 +195,6 @@ public class NarBundleExtractor implements BundleExtractor 
{
         return (value == null || value.isBlank());
     }
 
-    private void parseExtensionDocs(final JarInputStream jarInputStream, final 
BundleDetails.Builder builder) throws IOException {
-        JarEntry jarEntry;
-        boolean foundExtensionDocs = false;
-        while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
-            final String jarEntryName = jarEntry.getName();
-            if (EXTENSION_DESCRIPTOR_ENTRY.equals(jarEntryName)) {
-                try {
-                    final byte[] rawDocsContent = toByteArray(jarInputStream);
-                    final ExtensionManifestParser docsParser = new 
JAXBExtensionManifestParser();
-                    final InputStream inputStream = new 
NonCloseableInputStream(new ByteArrayInputStream(rawDocsContent));
-
-                    final ExtensionManifest extensionManifest = 
docsParser.parse(inputStream);
-                    builder.addExtensions(extensionManifest.getExtensions());
-                    
builder.systemApiVersion(extensionManifest.getSystemApiVersion());
-
-                    foundExtensionDocs = true;
-                } catch (Exception e) {
-                    throw new BundleException("Unable to obtain extension info 
for bundle due to: " + e.getMessage(), e);
-                }
-            } else {
-                final Matcher matcher = 
ADDITIONAL_DETAILS_ENTRY_PATTERN.matcher(jarEntryName);
-                if (matcher.matches()) {
-                    final String extensionName = matcher.group(1);
-                    final String additionalDetailsContent = new 
String(toByteArray(jarInputStream), StandardCharsets.UTF_8);
-                    builder.addAdditionalDetails(extensionName, 
additionalDetailsContent);
-                }
-            }
-        }
-
-        if (!foundExtensionDocs) {
-            builder.systemApiVersion(NA);
-        }
-    }
-
     private byte[] toByteArray(final InputStream input) throws IOException {
         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
 
@@ -202,33 +207,4 @@ public class NarBundleExtractor implements BundleExtractor 
{
         return buffer.toByteArray();
     }
 
-    private static class NonCloseableInputStream extends FilterInputStream {
-
-        private final InputStream toWrap;
-
-        public NonCloseableInputStream(final InputStream toWrap) {
-            super(toWrap);
-            this.toWrap = toWrap;
-        }
-
-        @Override
-        public int read() throws IOException {
-            return toWrap.read();
-        }
-
-        @Override
-        public int read(byte[] b) throws IOException {
-            return toWrap.read(b);
-        }
-
-        @Override
-        public int read(byte[] b, int off, int len) throws IOException {
-            return toWrap.read(b, off, len);
-        }
-
-        @Override
-        public void close() {
-            // do nothing
-        }
-    }
 }
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/java/org/apache/nifi/registry/bundle/extract/nar/TestNarBundleExtractor.java
 
b/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/java/org/apache/nifi/registry/bundle/extract/nar/TestNarBundleExtractor.java
index 8b096b4482..29f1dc88bb 100644
--- 
a/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/java/org/apache/nifi/registry/bundle/extract/nar/TestNarBundleExtractor.java
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/java/org/apache/nifi/registry/bundle/extract/nar/TestNarBundleExtractor.java
@@ -23,10 +23,16 @@ import org.apache.nifi.registry.bundle.model.BundleDetails;
 import org.apache.nifi.registry.extension.bundle.BuildInfo;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
 import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -151,4 +157,47 @@ public class TestNarBundleExtractor {
         }
     }
 
+    @Test
+    public void testExtractFromNarWithMetaInfDirectoryBeforeManifest(@TempDir 
final Path tempDir) throws IOException {
+        final Path narPath = tempDir.resolve("manifest-after-dir.nar");
+
+        try (final JarOutputStream jarOutputStream = new 
JarOutputStream(Files.newOutputStream(narPath))) {
+            // META-INF directory entry before the manifest file
+            final JarEntry metaInfDir = new JarEntry("META-INF/");
+            jarOutputStream.putNextEntry(metaInfDir);
+            jarOutputStream.closeEntry();
+
+            final JarEntry manifestEntry = new 
JarEntry("META-INF/MANIFEST.MF");
+            jarOutputStream.putNextEntry(manifestEntry);
+            jarOutputStream.write((
+                    "Manifest-Version: 1.0\n" +
+                    "Nar-Group: org.example\n" +
+                    "Nar-Id: example-nar\n" +
+                    "Nar-Version: 1.0.0\n" +
+                    "Build-Timestamp: 2024-01-01T00:00:00Z\n\n"
+            ).getBytes(StandardCharsets.UTF_8));
+            jarOutputStream.closeEntry();
+
+            final JarEntry docsDir = new JarEntry("META-INF/docs/");
+            jarOutputStream.putNextEntry(docsDir);
+            jarOutputStream.closeEntry();
+
+            final JarEntry descriptorEntry = new 
JarEntry("META-INF/docs/extension-manifest.xml");
+            jarOutputStream.putNextEntry(descriptorEntry);
+            
jarOutputStream.write("<extensionManifest><systemApiVersion>1.0.0</systemApiVersion></extensionManifest>".getBytes(StandardCharsets.UTF_8));
+            jarOutputStream.closeEntry();
+        }
+
+        try (final InputStream in = Files.newInputStream(narPath)) {
+            final BundleDetails bundleDetails = extractor.extract(in);
+            assertNotNull(bundleDetails);
+
+            final BundleIdentifier bundleIdentifier = 
bundleDetails.getBundleIdentifier();
+            assertEquals("org.example", bundleIdentifier.getGroupId());
+            assertEquals("example-nar", bundleIdentifier.getArtifactId());
+            assertEquals("1.0.0", bundleIdentifier.getVersion());
+            assertEquals("1.0.0", bundleDetails.getSystemApiVersion());
+        }
+    }
+
 }

Reply via email to