Skip to content

Commit cc0765c

Browse files
committed
feat: add warning in case of bad regex
1 parent 116d6c8 commit cc0765c

File tree

8 files changed

+116
-56
lines changed

8 files changed

+116
-56
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package io.gravitee.gateway.core.logging;
2+
3+
import io.gravitee.gateway.reactive.api.ExecutionWarn;
4+
import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext;
5+
import java.util.Collection;
6+
import java.util.List;
7+
import java.util.function.Predicate;
8+
import java.util.function.Supplier;
9+
import java.util.regex.Pattern;
10+
import java.util.regex.PatternSyntaxException;
11+
import java.util.stream.Stream;
12+
import lombok.Getter;
13+
import lombok.RequiredArgsConstructor;
14+
import lombok.Setter;
15+
import org.jspecify.annotations.Nullable;
16+
17+
@RequiredArgsConstructor
18+
public class LoggableContentType {
19+
20+
private static final String DEFAULT_EXCLUDED_CONTENT_TYPES =
21+
"video.*|audio.*|image.*|application/octet-stream|application/pdf|text/event-stream";
22+
private Predicate<String> excludedContentTypesPattern;
23+
24+
@Setter
25+
@Getter
26+
private String excludedResponseTypes;
27+
28+
private final Supplier<String> customExcludedResponseTypes;
29+
30+
public LoggableContentType() {
31+
this(() -> null);
32+
}
33+
34+
private Predicate<String> matcher(Collection<Supplier<String>> regex, @Nullable BaseExecutionContext ctx) {
35+
return excludedContentTypesPattern != null
36+
? excludedContentTypesPattern
37+
: (excludedContentTypesPattern = regex
38+
.stream()
39+
.map(Supplier::get)
40+
.filter(pattern -> pattern != null && !pattern.isEmpty())
41+
.flatMap(pattern -> {
42+
try {
43+
return Stream.of(Pattern.compile(pattern));
44+
} catch (PatternSyntaxException e) {
45+
if (ctx != null) {
46+
ctx.warnWith(
47+
new ExecutionWarn("BAD_REGEX_CONTENT_TYPE")
48+
.cause(e)
49+
.message(
50+
"Invalid Content-Type filter regex provided ('%s'). Default one will be used.".formatted(
51+
pattern
52+
)
53+
)
54+
);
55+
}
56+
return Stream.empty();
57+
}
58+
})
59+
.findFirst()
60+
.map(Pattern::asPredicate)
61+
.orElse(contentType -> true));
62+
}
63+
64+
/**
65+
* Determines if body can be logged for APIv4
66+
*/
67+
public boolean isContentTypeLoggable(@Nullable final String contentType, BaseExecutionContext ctx) {
68+
return (
69+
contentType == null ||
70+
!matcher(List.of(customExcludedResponseTypes, () -> excludedResponseTypes, () -> DEFAULT_EXCLUDED_CONTENT_TYPES), ctx).test(
71+
contentType
72+
)
73+
);
74+
}
75+
76+
/**
77+
* Determines if body can be logged for APIv2
78+
*/
79+
public boolean isContentTypeLoggable(@Nullable final String contentType, final LoggingContext loggingContext) {
80+
return (
81+
contentType == null ||
82+
!matcher(List.of(loggingContext::getExcludedResponseTypes, () -> DEFAULT_EXCLUDED_CONTENT_TYPES), null).test(contentType)
83+
);
84+
}
85+
}

gravitee-apim-gateway/gravitee-apim-gateway-core/src/main/java/io/gravitee/gateway/core/logging/utils/LoggingUtils.java

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
import io.gravitee.definition.model.LoggingMode;
2121
import io.gravitee.gateway.api.ExecutionContext;
2222
import io.gravitee.gateway.api.buffer.Buffer;
23+
import io.gravitee.gateway.core.logging.LoggableContentType;
2324
import io.gravitee.gateway.core.logging.LoggingContext;
2425
import io.gravitee.gateway.reactive.core.v4.analytics.BufferUtils;
2526
import jakarta.annotation.Nonnull;
2627
import jakarta.annotation.Nullable;
27-
import java.util.regex.Pattern;
2828

2929
/**
3030
* @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -34,10 +34,7 @@
3434
*/
3535
public final class LoggingUtils {
3636

37-
private static final String DEFAULT_EXCLUDED_CONTENT_TYPES =
38-
"video.*|audio.*|image.*|application/octet-stream|application/pdf|text/event-stream";
39-
40-
private static Pattern EXCLUDED_CONTENT_TYPES_PATTERN;
37+
private static final LoggableContentType loggableContentType = new LoggableContentType();
4138

4239
@Nullable
4340
public static LoggingContext getLoggingContext(final Logging logging) {
@@ -76,17 +73,7 @@ public static boolean isContentTypeLoggable(final String contentType, final Exec
7673
}
7774

7875
public static boolean isContentTypeLoggable(final String contentType, final LoggingContext loggingContext) {
79-
// init pattern
80-
if (EXCLUDED_CONTENT_TYPES_PATTERN == null) {
81-
try {
82-
final String responseTypes = loggingContext.getExcludedResponseTypes();
83-
EXCLUDED_CONTENT_TYPES_PATTERN = Pattern.compile(responseTypes);
84-
} catch (Exception e) {
85-
EXCLUDED_CONTENT_TYPES_PATTERN = Pattern.compile(DEFAULT_EXCLUDED_CONTENT_TYPES);
86-
}
87-
}
88-
89-
return contentType == null || !EXCLUDED_CONTENT_TYPES_PATTERN.matcher(contentType).find();
76+
return loggableContentType.isContentTypeLoggable(contentType, loggingContext);
9077
}
9178

9279
public static boolean isRequestHeadersLoggable(final ExecutionContext executionContext) {

gravitee-apim-gateway/gravitee-apim-gateway-core/src/main/java/io/gravitee/gateway/reactive/core/v4/analytics/LoggingContext.java

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@
1818
import io.gravitee.common.utils.SizeUtils;
1919
import io.gravitee.definition.model.ConditionSupplier;
2020
import io.gravitee.definition.model.v4.analytics.logging.Logging;
21+
import io.gravitee.gateway.core.logging.LoggableContentType;
22+
import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext;
2123
import io.gravitee.gateway.report.guard.LogGuardService;
22-
import java.util.regex.Pattern;
23-
import java.util.regex.PatternSyntaxException;
24-
import java.util.stream.Stream;
2524
import lombok.Getter;
2625
import lombok.RequiredArgsConstructor;
2726
import lombok.Setter;
@@ -35,22 +34,20 @@
3534
@RequiredArgsConstructor
3635
public class LoggingContext implements ConditionSupplier {
3736

38-
private static final String DEFAULT_EXCLUDED_CONTENT_TYPES =
39-
"video.*|audio.*|image.*|application/octet-stream|application/pdf|text/event-stream";
40-
4137
protected final Logging logging;
4238

4339
@Getter
4440
private int maxSizeLogMessage = -1;
4541

4642
@Setter
47-
@Getter
48-
private String excludedResponseTypes;
43+
private LogGuardService logGuardService;
4944

50-
private Pattern excludedContentTypesPattern;
45+
private final LoggableContentType loggableContentType;
5146

52-
@Setter
53-
private LogGuardService logGuardService;
47+
public LoggingContext(Logging logging) {
48+
this.logging = logging;
49+
loggableContentType = new LoggableContentType(logging::getOverrideContentTypeValidation);
50+
}
5451

5552
@Override
5653
public String getCondition() {
@@ -105,6 +102,14 @@ public boolean endpointResponsePayload() {
105102
return logging.getMode().isEndpoint() && logging.getPhase().isResponse() && logging.getContent().isPayload();
106103
}
107104

105+
public String getExcludedResponseTypes() {
106+
return loggableContentType.getExcludedResponseTypes();
107+
}
108+
109+
public void setExcludedResponseTypes(String excludedResponseTypes) {
110+
loggableContentType.setExcludedResponseTypes(excludedResponseTypes);
111+
}
112+
108113
/**
109114
* Define the max size of the logging (for each payload, whatever it's about the request / response / consumer / proxy)
110115
* Which means that if size is define to 5, it will be 5 x 4 = 20 (at most).
@@ -123,27 +128,8 @@ public void setMaxSizeLogMessage(String maxSizeLogMessage) {
123128
}
124129
}
125130

126-
public boolean isContentTypeLoggable(final String contentType) {
127-
// init pattern
128-
if (excludedContentTypesPattern == null) {
129-
this.excludedContentTypesPattern = Stream.of(
130-
logging.getOverrideContentTypeValidation(),
131-
excludedResponseTypes,
132-
DEFAULT_EXCLUDED_CONTENT_TYPES
133-
)
134-
.filter(pattern -> pattern != null && !pattern.isEmpty())
135-
.flatMap(pattern -> {
136-
try {
137-
return Stream.of(Pattern.compile(pattern));
138-
} catch (PatternSyntaxException e) {
139-
return Stream.empty();
140-
}
141-
})
142-
.findFirst()
143-
.orElse(null);
144-
}
145-
146-
return contentType == null || !excludedContentTypesPattern.matcher(contentType).find();
131+
public boolean isContentTypeLoggable(final String contentType, BaseExecutionContext ctx) {
132+
return loggableContentType.isContentTypeLoggable(contentType, ctx);
147133
}
148134

149135
/**

gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/analytics/logging/LoggingHook.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public Completable pre(String id, HttpExecutionContext ctx, @Nullable ExecutionP
5050

5151
if (log != null && loggingContext != null) {
5252
if (loggingContext.endpointRequest()) {
53-
((LogEndpointRequest) log.getEndpointRequest()).capture();
53+
((LogEndpointRequest) log.getEndpointRequest()).capture(ctx);
5454
}
5555

5656
if (loggingContext.endpointResponseHeaders() || loggingContext.endpointResponse()) {
@@ -77,7 +77,7 @@ public Completable post(String id, HttpExecutionContext ctx, @Nullable Execution
7777
}
7878

7979
if (log != null && loggingContext != null && loggingContext.endpointResponse()) {
80-
((LogEndpointResponse) log.getEndpointResponse()).capture();
80+
((LogEndpointResponse) log.getEndpointResponse()).capture(ctx);
8181

8282
final HttpResponseInternal response = ((HttpExecutionContextInternal) ctx).response();
8383
response.setHeaders(((LogHeadersCaptor) response.headers()).getDelegate());

gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/analytics/logging/request/LogRequest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.gravitee.gateway.api.buffer.Buffer;
1919
import io.gravitee.gateway.api.http.HttpHeaderNames;
2020
import io.gravitee.gateway.api.http.HttpHeaders;
21+
import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext;
2122
import io.gravitee.gateway.reactive.api.context.http.HttpPlainRequest;
2223
import io.gravitee.gateway.reactive.core.v4.analytics.BufferUtils;
2324
import io.gravitee.gateway.reactive.core.v4.analytics.LoggingContext;
@@ -38,8 +39,8 @@ protected LogRequest(LoggingContext loggingContext, HttpPlainRequest request) {
3839
this.setMethod(request.method());
3940
}
4041

41-
public void capture() {
42-
if (isLogPayload() && loggingContext.isContentTypeLoggable(request.headers().get(HttpHeaderNames.CONTENT_TYPE))) {
42+
public void capture(BaseExecutionContext ctx) {
43+
if (isLogPayload() && loggingContext.isContentTypeLoggable(request.headers().get(HttpHeaderNames.CONTENT_TYPE), ctx)) {
4344
final Buffer buffer = Buffer.buffer();
4445

4546
if (loggingContext.isBodyLoggable()) {

gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/analytics/logging/response/LogResponse.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.gravitee.gateway.api.buffer.Buffer;
1919
import io.gravitee.gateway.api.http.HttpHeaderNames;
2020
import io.gravitee.gateway.api.http.HttpHeaders;
21+
import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext;
2122
import io.gravitee.gateway.reactive.api.context.http.HttpPlainResponse;
2223
import io.gravitee.gateway.reactive.core.v4.analytics.BufferUtils;
2324
import io.gravitee.gateway.reactive.core.v4.analytics.LoggingContext;
@@ -37,8 +38,8 @@ protected LogResponse(LoggingContext loggingContext, HttpPlainResponse response)
3738
this.response = response;
3839
}
3940

40-
public void capture() {
41-
if (isLogPayload() && loggingContext.isContentTypeLoggable(response.headers().get(HttpHeaderNames.CONTENT_TYPE))) {
41+
public void capture(BaseExecutionContext ctx) {
42+
if (isLogPayload() && loggingContext.isContentTypeLoggable(response.headers().get(HttpHeaderNames.CONTENT_TYPE), ctx)) {
4243
final Buffer buffer = Buffer.buffer();
4344
if (loggingContext.isBodyLoggable()) {
4445
response.chunks(

gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/processor/logging/LogRequestProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public Completable execute(final HttpExecutionContextInternal ctx) {
5151
LoggingContext loggingContext = analyticsContext.getLoggingContext();
5252

5353
if (log != null && loggingContext.entrypointRequest()) {
54-
((LogEntrypointRequest) log.getEntrypointRequest()).capture();
54+
((LogEntrypointRequest) log.getEntrypointRequest()).capture(ctx);
5555
}
5656
});
5757
}

gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/processor/logging/LogResponseProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public Completable execute(final HttpExecutionContextInternal ctx) {
5151
LoggingContext loggingContext = analyticsContext.getLoggingContext();
5252

5353
if (log != null && loggingContext.entrypointResponse()) {
54-
((LogEntrypointResponse) log.getEntrypointResponse()).capture();
54+
((LogEntrypointResponse) log.getEntrypointResponse()).capture(ctx);
5555
}
5656
});
5757
}

0 commit comments

Comments
 (0)