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());
+ }
+ }
+
}