This is an automated email from the ASF dual-hosted git repository.
pvillard31 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 d47787ab2de NIFI-16170 Improved Content-Encoding handling for REST API
(#11509)
d47787ab2de is described below
commit d47787ab2debc151ed05ee97f143c3743080d470
Author: David Handermann <[email protected]>
AuthorDate: Wed Aug 5 09:32:14 2026 -0500
NIFI-16170 Improved Content-Encoding handling for REST API (#11509)
- Added consistent handling of multiple Content-Encoding headers
- Excluded all paths from decompression
---
.../nifi/web/server/StandardServerProvider.java | 5 +-
.../handler/UnsupportedContentEncodingHandler.java | 32 +++++++--
.../web/server/StandardServerProviderTest.java | 77 ++++++++++++++--------
3 files changed, 79 insertions(+), 35 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
index ad5a02be511..8db7d1b5dd1 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
@@ -27,6 +27,7 @@ import
org.apache.nifi.web.server.log.StandardRequestLogProvider;
import org.eclipse.jetty.compression.gzip.GzipCompression;
import org.eclipse.jetty.compression.server.CompressionConfig;
import org.eclipse.jetty.compression.server.CompressionHandler;
+import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.Handler;
@@ -161,8 +162,10 @@ class StandardServerProvider implements ServerProvider {
final CompressionConfig compressionConfig = CompressionConfig.builder()
.defaults()
+ // Override default inclusion of decompression for POST method
+ .decompressExcludeMethod(HttpMethod.POST.asString())
// Disable decompression of requests
- .decompressExcludeEncoding(gzipCompression.getEncodingName())
+ .decompressExcludePath(ALL_PATHS_PATTERN)
.build();
compressionHandler.putConfiguration(ROOT_PATH, compressionConfig);
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/UnsupportedContentEncodingHandler.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/UnsupportedContentEncodingHandler.java
index d3668665384..82644ef12e0 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/UnsupportedContentEncodingHandler.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/UnsupportedContentEncodingHandler.java
@@ -23,6 +23,8 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
+import java.util.List;
+
/**
* Handler that rejects requests declaring an unsupported Content-Encoding
*/
@@ -34,14 +36,32 @@ public class UnsupportedContentEncodingHandler extends
Handler.Abstract {
@Override
public boolean handle(final Request request, final Response response,
final Callback callback) {
- final String contentEncoding =
request.getHeaders().get(HttpHeader.CONTENT_ENCODING);
+ // Handle one or more Content-Encoding request headers
+ final List<String> contentEncodings =
request.getHeaders().getValuesList(HttpHeader.CONTENT_ENCODING);
+
+ final boolean handled;
+ if (isContentEncodingUnsupported(contentEncodings)) {
+ Response.writeError(request, response, callback,
HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, UNSUPPORTED_MESSAGE);
+ handled = true;
+ } else {
+ handled = false;
+ }
+
+ return handled;
+ }
+
+ private boolean isContentEncodingUnsupported(final List<String>
contentEncodings) {
+ // Empty Content-Encoding header is allowed
+ boolean unsupported = false;
- // A request without a Content-Encoding, or one declaring only the
identity encoding, is passed to later Handlers
- if (contentEncoding == null || contentEncoding.isBlank() ||
IDENTITY_ENCODING.equalsIgnoreCase(contentEncoding.trim())) {
- return false;
+ for (final String contentEncoding : contentEncodings) {
+ // Content-Encoding with a value of identity is allowed indicating
no encoding
+ if (!IDENTITY_ENCODING.equalsIgnoreCase(contentEncoding)) {
+ unsupported = true;
+ break;
+ }
}
- Response.writeError(request, response, callback,
HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, UNSUPPORTED_MESSAGE);
- return true;
+ return unsupported;
}
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
index f7a79dcfcae..343466b47b7 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
@@ -108,6 +108,11 @@ class StandardServerProviderTest {
private static final String FRONTEND_PATH_TRAILING_SLASH = "/nifi/";
+ private static final String ALL_PATHS = "/*";
+ private static final String CONTENT_ENCODING_HEADER = "Content-Encoding";
+ private static final String GZIP_CONTENT_ENCODING = "gzip";
+ private static final String IDENTITY_CONTENT_ENCODING = "identity";
+
private static final List<String> STANDARD_RESPONSE_HEADERS = List.of(
"Content-Security-Policy",
"Strict-Transport-Security",
@@ -177,36 +182,14 @@ class StandardServerProviderTest {
assertTrue(compressMethods.contains(HttpMethod.GET.asString()));
assertTrue(compressMethods.contains(HttpMethod.POST.asString()));
- final UnsupportedContentEncodingHandler
unsupportedContentEncodingHandler =
handlerCollection.getDescendant(UnsupportedContentEncodingHandler.class);
- assertNotNull(unsupportedContentEncodingHandler);
- }
-
- @Timeout(15)
- @Test
- void testGetServerRejectsCompressedRequestBody() throws Exception {
- final Properties applicationProperties = new Properties();
- applicationProperties.setProperty(NiFiProperties.WEB_HTTP_PORT,
RANDOM_PORT);
- final NiFiProperties properties =
NiFiProperties.createBasicNiFiProperties((String) null, applicationProperties);
-
- final StandardServerProvider provider = new
StandardServerProvider(null);
+
assertFalse(compressionConfig.isDecompressMethodSupported(HttpMethod.POST.asString()));
+
assertFalse(compressionConfig.isDecompressMethodSupported(HttpMethod.PUT.asString()));
- final Server server = provider.getServer(properties);
+ final Set<String> decompressExcludePaths =
compressionConfig.getDecompressExcludePaths();
+ assertTrue(decompressExcludePaths.contains(ALL_PATHS));
- try {
- startServer(server);
- final URI localhostUri =
UriComponentsBuilder.fromUri(server.getURI()).host(LOCALHOST_NAME).build().toUri();
-
- try (HttpClient httpClient =
HttpClient.newBuilder().connectTimeout(TIMEOUT).build()) {
- final HttpRequest compressedRequest =
HttpRequest.newBuilder(localhostUri)
- .version(HttpClient.Version.HTTP_1_1)
- .header(HttpHeader.CONTENT_ENCODING.asString(), "gzip")
- .POST(HttpRequest.BodyPublishers.ofByteArray(new
byte[]{1, 2, 3}))
- .build();
- assertResponseStatusCode(httpClient, compressedRequest,
HttpStatus.UNSUPPORTED_MEDIA_TYPE_415);
- }
- } finally {
- server.stop();
- }
+ final UnsupportedContentEncodingHandler
unsupportedContentEncodingHandler =
handlerCollection.getDescendant(UnsupportedContentEncodingHandler.class);
+ assertNotNull(unsupportedContentEncodingHandler);
}
@Test
@@ -317,6 +300,7 @@ class StandardServerProviderTest {
assertFrontendRedirectRequestsCompleted(httpClient, localhostUri);
assertBadRequestsCompleted(httpClient, localhostUri);
assertMisdirectedRequestsCompleted(httpClient, localhostUri);
+ assertContentEncodingRequestsCompleted(httpClient, localhostUri);
assertReplicatedRequestCompleted(httpClient, localhostUri,
HttpStatus.MISDIRECTED_REQUEST_421);
assertForwardedToCoordinatorRequestCompleted(httpClient,
localhostUri, HttpStatus.MISDIRECTED_REQUEST_421);
@@ -335,6 +319,7 @@ class StandardServerProviderTest {
assertRedirectRequestsCompleted(httpClient, localhostUri);
assertBadRequestsCompleted(httpClient, localhostUri);
assertMisdirectedRequestsCompleted(httpClient, localhostUri);
+ assertContentEncodingRequestsCompleted(httpClient, localhostUri);
assertReplicatedRequestCompleted(httpClient, localhostUri,
HttpStatus.MOVED_TEMPORARILY_302);
assertForwardedToCoordinatorRequestCompleted(httpClient,
localhostUri, HttpStatus.MOVED_TEMPORARILY_302);
@@ -436,6 +421,42 @@ class StandardServerProviderTest {
assertResponseStatusCode(httpClient, localhostAddressRequest,
HttpStatus.BAD_REQUEST_400);
}
+ void assertContentEncodingRequestsCompleted(final HttpClient httpClient,
final URI localhostUri) throws IOException, InterruptedException {
+ final HttpRequest identityContentEncodingRequest =
HttpRequest.newBuilder(localhostUri)
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .header(CONTENT_ENCODING_HEADER, IDENTITY_CONTENT_ENCODING)
+ .version(HttpClient.Version.HTTP_1_1)
+ .build();
+ assertResponseStatusCode(httpClient, identityContentEncodingRequest,
HttpStatus.MOVED_TEMPORARILY_302);
+
+ final HttpRequest gzipContentEncodingRequest =
HttpRequest.newBuilder(localhostUri)
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .header(CONTENT_ENCODING_HEADER, GZIP_CONTENT_ENCODING)
+ .version(HttpClient.Version.HTTP_1_1)
+ .build();
+ assertResponseStatusCode(httpClient, gzipContentEncodingRequest,
HttpStatus.UNSUPPORTED_MEDIA_TYPE_415);
+
+ final HttpRequest identityGzipContentEncodingRequest =
HttpRequest.newBuilder(localhostUri)
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .headers(
+ CONTENT_ENCODING_HEADER, IDENTITY_CONTENT_ENCODING,
+ CONTENT_ENCODING_HEADER, GZIP_CONTENT_ENCODING
+ )
+ .version(HttpClient.Version.HTTP_2)
+ .build();
+ assertResponseStatusCode(httpClient,
identityGzipContentEncodingRequest, HttpStatus.UNSUPPORTED_MEDIA_TYPE_415);
+
+ final HttpRequest identityGzipUppercasedContentEncodingRequest =
HttpRequest.newBuilder(localhostUri)
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .headers(
+ CONTENT_ENCODING_HEADER,
IDENTITY_CONTENT_ENCODING.toUpperCase(),
+ CONTENT_ENCODING_HEADER,
GZIP_CONTENT_ENCODING.toUpperCase()
+ )
+ .version(HttpClient.Version.HTTP_2)
+ .build();
+ assertResponseStatusCode(httpClient,
identityGzipUppercasedContentEncodingRequest,
HttpStatus.UNSUPPORTED_MEDIA_TYPE_415);
+ }
+
void assertMisdirectedRequestsCompleted(final HttpClient httpClient, final
URI localhostUri) throws IOException, InterruptedException {
final HttpRequest localhostPortRequest =
HttpRequest.newBuilder(localhostUri)
.version(HttpClient.Version.HTTP_1_1)