1
- package com .coreoz .plume .jersey .security .control ;
1
+ package com .coreoz .plume .jersey .security .size ;
2
2
3
- import java .io .IOException ;
4
- import java .io .InputStream ;
5
- import java .lang .reflect .AnnotatedElement ;
6
-
7
- import jakarta .ws .rs .WebApplicationException ;
3
+ import com .coreoz .plume .jersey .errors .WsError ;
4
+ import jakarta .ws .rs .ClientErrorException ;
5
+ import jakarta .ws .rs .container .ContainerRequestContext ;
6
+ import jakarta .ws .rs .container .ContainerRequestFilter ;
8
7
import jakarta .ws .rs .container .DynamicFeature ;
9
8
import jakarta .ws .rs .container .ResourceInfo ;
10
9
import jakarta .ws .rs .core .FeatureContext ;
11
10
import jakarta .ws .rs .core .HttpHeaders ;
11
+ import jakarta .ws .rs .core .MediaType ;
12
12
import jakarta .ws .rs .core .Response ;
13
- import jakarta .ws .rs .ext .ReaderInterceptor ;
14
- import jakarta .ws .rs .ext .ReaderInterceptorContext ;
15
13
import lombok .extern .slf4j .Slf4j ;
16
14
17
- @ Slf4j
18
- public class ContentControlFeature implements DynamicFeature {
15
+ import java .io .IOException ;
16
+ import java .io .InputStream ;
17
+ import java .lang .reflect .AnnotatedElement ;
19
18
19
+ @ Slf4j
20
+ public class ContentSizeLimitFeature implements DynamicFeature {
20
21
public static final int DEFAULT_MAX_SIZE = 500 * 1024 ; // 500 KB
22
+ // We use a string response directly because Jersey does not accept an objet here (it would return a 500 error)
23
+ static final String JSON_ENTITY_TOO_LARGE_ERROR = "{\" errorCode\" :\" " +WsError .CONTENT_SIZE_LIMIT_EXCEEDED .name ()+"\" ,\" statusArguments\" :[]}" ;
21
24
private final Integer maxSize ;
22
25
23
- public ContentControlFeature (int maxSize ) {
26
+ public ContentSizeLimitFeature (int maxSize ) {
24
27
this .maxSize = maxSize ;
25
28
}
26
29
27
- public ContentControlFeature () {
30
+ public ContentSizeLimitFeature () {
28
31
this .maxSize = DEFAULT_MAX_SIZE ;
29
32
}
30
33
31
34
public Integer getContentSizeLimit () {
32
- if (maxSize == null ) {
33
- return DEFAULT_MAX_SIZE ;
34
- }
35
35
return maxSize ;
36
36
}
37
37
@@ -40,15 +40,14 @@ public void configure(ResourceInfo resourceInfo, FeatureContext context) {
40
40
addContentSizeFilter (resourceInfo .getResourceMethod (), context );
41
41
}
42
42
43
- private void addContentSizeFilter (AnnotatedElement annotatedElement , FeatureContext methodResourcecontext ) {
43
+ private void addContentSizeFilter (AnnotatedElement annotatedElement , FeatureContext methodResourceContext ) {
44
44
ContentSizeLimit contentSizeLimit = annotatedElement .getAnnotation (ContentSizeLimit .class );
45
- methodResourcecontext .register (new ContentSizeLimitInterceptor (
46
- contentSizeLimit != null ? contentSizeLimit .value () : maxSize
47
- ));
45
+ methodResourceContext .register (new ContentSizeLimitInterceptor (
46
+ contentSizeLimit != null ? contentSizeLimit .value () : maxSize
47
+ ));
48
48
}
49
49
50
- public static class ContentSizeLimitInterceptor implements ReaderInterceptor {
51
-
50
+ public static class ContentSizeLimitInterceptor implements ContainerRequestFilter {
52
51
private final int maxSize ;
53
52
54
53
public ContentSizeLimitInterceptor (int maxSize ) {
@@ -57,7 +56,7 @@ public ContentSizeLimitInterceptor(int maxSize) {
57
56
58
57
// https://stackoverflow.com/questions/24516444/best-way-to-make-jersey-2-x-refuse-requests-with-incorrect-content-length
59
58
@ Override
60
- public Object aroundReadFrom ( ReaderInterceptorContext context ) throws IOException {
59
+ public void filter ( ContainerRequestContext context ) {
61
60
int headerContentLength = maxSize ; // default value for GET or chunked body
62
61
String contentLengthHeader = context .getHeaders ().getFirst (HttpHeaders .CONTENT_LENGTH );
63
62
if (contentLengthHeader != null ) {
@@ -69,17 +68,12 @@ public Object aroundReadFrom(ReaderInterceptorContext context) throws IOExceptio
69
68
}
70
69
71
70
if (headerContentLength > maxSize ) {
72
- throw new WebApplicationException (
73
- Response .status (Response .Status .REQUEST_ENTITY_TOO_LARGE )
74
- .entity ("Content size limit exceeded." )
75
- .build ()
76
- );
71
+ logger .warn ("Client tried to send a request body that is too large: max allowed size={}, client request body size={}" , maxSize , headerContentLength );
72
+ throw makeEntityTooLargeException ();
77
73
}
78
74
79
- final InputStream contextInputStream = context .getInputStream ();
80
- context .setInputStream (new SizeLimitingInputStream (contextInputStream , headerContentLength ));
81
-
82
- return context .proceed ();
75
+ final InputStream contextInputStream = context .getEntityStream ();
76
+ context .setEntityStream (new SizeLimitingInputStream (contextInputStream , headerContentLength ));
83
77
}
84
78
85
79
public static final class SizeLimitingInputStream extends InputStream {
@@ -119,7 +113,7 @@ public int read(final byte[] b, final int off, final int len) throws IOException
119
113
@ Override
120
114
public long skip (final long n ) throws IOException {
121
115
final long skip = delegateInputStream .skip (n );
122
- readAndCheck (skip != - 1 ? skip : 0 );
116
+ readAndCheck (skip );
123
117
return skip ;
124
118
}
125
119
@@ -160,13 +154,20 @@ private void readAndCheck(final long read) {
160
154
} catch (IOException e ) {
161
155
logger .error ("Error while closing the input stream" , e );
162
156
}
163
- throw new WebApplicationException (
164
- Response .status (Response .Status .REQUEST_ENTITY_TOO_LARGE )
165
- .entity ("Content size limit exceeded." )
166
- .build ()
167
- );
157
+ logger .warn ("Client tried to send a request body that is too large while sending a content-length headers that fits the max allowed request body size" );
158
+ throw makeEntityTooLargeException ();
168
159
}
169
160
}
170
161
}
162
+
163
+ private static RuntimeException makeEntityTooLargeException () {
164
+ return new ClientErrorException (Response
165
+ .status (Response .Status .REQUEST_ENTITY_TOO_LARGE )
166
+ .header (HttpHeaders .CONTENT_TYPE , MediaType .APPLICATION_JSON )
167
+ // We use a string response directly because Jersey does not accept an objet here (it would return a 500 error)
168
+ .entity (JSON_ENTITY_TOO_LARGE_ERROR )
169
+ .build ()
170
+ );
171
+ }
171
172
}
172
173
}
0 commit comments