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);