diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/JacksonResponse.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/JacksonResponse.java
index e4eb45cf1f..3f10d42f89 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/JacksonResponse.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/JacksonResponse.java
@@ -231,7 +231,12 @@ public class JacksonResponse extends Response {
}
@Override
- public String getHeaderString(String name) {
- return responseHeaders.getFirst(name);
+ public String getHeaderString(final String name) {
+ final String headerValue = responseHeaders.getFirst(name);
+ if (headerValue != null) {
+ return headerValue;
+ }
+
+ return responseHeaders.getFirst(name.toLowerCase());
}
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
index 79973fe147..7271b7ffaa 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
@@ -35,6 +35,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
@@ -113,11 +114,11 @@ public class StandardNiFiContentAccess implements ContentAccess {
}
// get the file name
- final String contentDisposition = responseHeaders.getFirst("Content-Disposition");
+ final String contentDisposition = getHeader(responseHeaders, "content-disposition");
final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\"");
// get the content type
- final String contentType = responseHeaders.getFirst("Content-Type");
+ final String contentType = getHeader(responseHeaders, "content-type");
// create the downloadable content
return new DownloadableContent(filename, contentType, nodeResponse.getInputStream());
@@ -159,6 +160,37 @@ public class StandardNiFiContentAccess implements ContentAccess {
}
}
+ /**
+ * Returns the value of the first header in the given header map whose name matches the given header name.
+ * In HTTP 2.0, all header names should be lower-case. Before that, they were not necessarily. The spec, however,
+ * indicates that header names are case-insensitive. That said, the code has them stored in a Map, and the keys in Maps
+ * are not case-insensitive. This method allows us to access the value of the first header with the given name,
+ * ignoring case.
+ *
+ * @param headers the map containing all headers
+ * @param headerName the case-insensitive name of the header to retrieve
+ * @return the value of the first header with the given name, or null
if the header is not found
+ */
+ private String getHeader(final MultivaluedMap headers, final String headerName) {
+ if (headers == null || headers.isEmpty() || headerName == null || headerName.isBlank()) {
+ return null;
+ }
+
+ final String exactMatch = headers.getFirst(headerName);
+ if (exactMatch != null) {
+ return exactMatch;
+ }
+
+ for (final Map.Entry> entry : headers.entrySet()) {
+ if (entry.getKey().equalsIgnoreCase(headerName)) {
+ final List values = entry.getValue();
+ return values == null || values.isEmpty() ? null : values.get(0);
+ }
+ }
+
+ return null;
+ }
+
private DownloadableContent getFlowFileContent(final String connectionId, final String flowfileId, final String dataUri) {
// user authorization is handled once we have the actual content so we can utilize the flow file attributes in the resource context
return serviceFacade.getContent(connectionId, flowfileId, dataUri);