Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip error routes when initial processing of malformed request fails #11161

Merged
merged 1 commit into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,31 +74,37 @@ void handleNormal(NettyHttpRequest<?> request) {

ExecutionFlow<HttpResponse<?>> result;

try {
// handle decoding failure
DecoderResult decoderResult = request.getNativeRequest().decoderResult();
if (decoderResult.isFailure()) {
Throwable cause = decoderResult.cause();
HttpStatus status = cause instanceof TooLongFrameException ? HttpStatus.REQUEST_ENTITY_TOO_LARGE : HttpStatus.BAD_REQUEST;
// handle decoding failure
DecoderResult decoderResult = request.getNativeRequest().decoderResult();
if (decoderResult.isFailure()) {
Throwable cause = decoderResult.cause();
HttpStatus status = cause instanceof TooLongFrameException ? HttpStatus.REQUEST_ENTITY_TOO_LARGE : HttpStatus.BAD_REQUEST;
try {
result = onStatusError(
request,
HttpResponse.status(status),
status.getReason()
);
} else {
result = normalFlow(request);
} catch (Exception e) {
result = ExecutionFlow.error(e);
}
ImperativeExecutionFlow<HttpResponse<?>> imperativeFlow = result.tryComplete();
if (imperativeFlow != null) {
Object value = ((ImperativeExecutionFlow<?>) imperativeFlow).getValue();
// usually this is a MutableHttpResponse, avoid scalability issues here
HttpResponse<?> response = value instanceof NettyMutableHttpResponse<?> mut ? mut : (HttpResponse<?>) value;
rib.writeResponse(outboundAccess, request, response, imperativeFlow.getError());
} else {
result.onComplete((response, throwable) -> rib.writeResponse(outboundAccess, request, response, throwable));
} else {
try {
result = normalFlow(request);
} catch (Exception e) {
handleException(request, e);
return;
}
} catch (Exception e) {
handleException(request, e);
}

ImperativeExecutionFlow<HttpResponse<?>> imperativeFlow = result.tryComplete();
if (imperativeFlow != null) {
Object value = ((ImperativeExecutionFlow<?>) imperativeFlow).getValue();
// usually this is a MutableHttpResponse, avoid scalability issues here
HttpResponse<?> response = value instanceof NettyMutableHttpResponse<?> mut ? mut : (HttpResponse<?>) value;
rib.writeResponse(outboundAccess, request, response, imperativeFlow.getError());
} else {
result.onComplete((response, throwable) -> rib.writeResponse(outboundAccess, request, response, throwable));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.micronaut.http.server.netty.errors

import io.micronaut.context.ApplicationContext
import io.micronaut.context.annotation.Requires
import io.micronaut.core.annotation.NonNull
import io.micronaut.http.HttpRequest
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.RequestFilter
import io.micronaut.http.annotation.ServerFilter
import io.micronaut.http.server.exceptions.response.ErrorContext
import io.micronaut.http.server.exceptions.response.ErrorResponseProcessor
import io.micronaut.runtime.server.EmbeddedServer
import jakarta.inject.Singleton
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class HeaderTooLongSpec extends Specification {

@Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, [
'spec.name': 'HeaderTooLongSpec',
'micronaut.server.netty.log-level': 'info'
])

def 'header too long'() {
given:
def connection = new URL("$embeddedServer.URL/malformed-proxy/xyz").openConnection()
connection.setRequestProperty("foo", "b".repeat(9000))

def myFilter = embeddedServer.applicationContext.getBean(MyFilter)

when:
connection.inputStream
then:
thrown IOException
((HttpURLConnection) connection).errorStream == null
myFilter.filteredRequest == null
}

@Singleton
@Requires(property = "spec.name", value = "HeaderTooLongSpec")
static class BrokenProcessor implements ErrorResponseProcessor<String> {
@Override
MutableHttpResponse<String> processResponse(@NonNull ErrorContext errorContext, @NonNull MutableHttpResponse<?> baseResponse) {
throw new Exception("This processor is intentionally broken")
}
}

@Singleton
@Requires(property = "spec.name", value = "HeaderTooLongSpec")
@ServerFilter("/**")
static class MyFilter {
HttpRequest<?> filteredRequest

@RequestFilter
void requestFilter(HttpRequest<?> request) {
filteredRequest = request
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.micronaut.http.annotation.ServerFilter
import io.micronaut.http.client.HttpClient
import io.micronaut.http.filter.HttpServerFilter
import io.micronaut.http.filter.ServerFilterChain
import io.micronaut.http.server.annotation.PreMatching
import io.micronaut.runtime.server.EmbeddedServer
import jakarta.inject.Singleton
import org.reactivestreams.Publisher
Expand Down Expand Up @@ -64,6 +65,31 @@ class MalformedUriSpec extends Specification {
result == 'Exception: Illegal character in path at index 17: /malformed-proxy/[]'
}

void "header too long"() {
given:
OncePerFilter filter = embeddedServer.applicationContext.getBean(OncePerFilter)
filter.filterCalled = false

MalformedUriFilter newFilter = embeddedServer.applicationContext.getBean(MalformedUriFilter)
newFilter.preMatchingCalled = false

def connection = new URL("$embeddedServer.URL/malformed-proxy/xyz").openConnection()
connection.setRequestProperty("foo", "b".repeat(9000))

when:
connection.inputStream
then:
thrown IOException

when:
def result = ((HttpURLConnection) connection).errorStream.text

then:
result == '{"message":"Request Entity Too Large","_links":{"self":{"href":"/malformed-proxy/xyz","templated":false}},"_embedded":{"errors":[{"message":"Request Entity Too Large"}]}}'
!filter.filterCalled
!newFilter.preMatchingCalled
}

@Requires(property = "spec.name", value = "MalformedUriSpec")
@Controller('/malformed')
static class SomeController {
Expand Down Expand Up @@ -97,10 +123,17 @@ class MalformedUriSpec extends Specification {
@Singleton
@ServerFilter("/malformed-proxy/**")
static class MalformedUriFilter {
boolean preMatchingCalled

@RequestFilter
HttpResponse<?> filter(HttpRequest<?> request) {
return HttpResponse.ok("ok: " + request.path)
}

@RequestFilter
@PreMatching
void preMatching(HttpRequest<?> request) {
preMatchingCalled = false
}
}
}
Loading