1
1
/*
2
- * Copyright 2002-2018 the original author or authors.
2
+ * Copyright 2002-2022 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
@@ -206,97 +206,80 @@ public boolean checkNotModified(String etag) {
206
206
}
207
207
208
208
@ Override
209
- public boolean checkNotModified (@ Nullable String etag , long lastModifiedTimestamp ) {
209
+ public boolean checkNotModified (@ Nullable String eTag , long lastModifiedTimestamp ) {
210
210
HttpServletResponse response = getResponse ();
211
211
if (this .notModified || (response != null && HttpStatus .OK .value () != response .getStatus ())) {
212
212
return this .notModified ;
213
213
}
214
-
215
214
// Evaluate conditions in order of precedence.
216
- // See https://tools.ietf.org/html/rfc7232#section-6
217
-
218
- if (validateIfUnmodifiedSince (lastModifiedTimestamp )) {
219
- if (this .notModified && response != null ) {
220
- response .setStatus (HttpStatus .PRECONDITION_FAILED .value ());
221
- }
215
+ // See https://datatracker.ietf.org/doc/html/rfc9110#section-13.2.2
216
+ if (validateIfMatch (eTag )) {
217
+ updateResponseStateChanging ();
222
218
return this .notModified ;
223
219
}
224
-
225
- boolean validated = validateIfNoneMatch ( etag );
226
- if (! validated ) {
227
- validateIfModifiedSince ( lastModifiedTimestamp ) ;
220
+ // 2) If-Unmodified-Since
221
+ else if ( validateIfUnmodifiedSince ( lastModifiedTimestamp )) {
222
+ updateResponseStateChanging ();
223
+ return this . notModified ;
228
224
}
229
-
230
- // Update response
231
- if (response != null ) {
232
- boolean isHttpGetOrHead = SAFE_METHODS .contains (getRequest ().getMethod ());
233
- if (this .notModified ) {
234
- response .setStatus (isHttpGetOrHead ?
235
- HttpStatus .NOT_MODIFIED .value () : HttpStatus .PRECONDITION_FAILED .value ());
236
- }
237
- if (isHttpGetOrHead ) {
238
- if (lastModifiedTimestamp > 0 && parseDateValue (response .getHeader (HttpHeaders .LAST_MODIFIED )) == -1 ) {
239
- response .setDateHeader (HttpHeaders .LAST_MODIFIED , lastModifiedTimestamp );
240
- }
241
- if (StringUtils .hasLength (etag ) && response .getHeader (HttpHeaders .ETAG ) == null ) {
242
- response .setHeader (HttpHeaders .ETAG , padEtagIfNecessary (etag ));
243
- }
244
- }
225
+ // 3) If-None-Match
226
+ if (!validateIfNoneMatch (eTag )) {
227
+ // 4) If-Modified-Since
228
+ validateIfModifiedSince (lastModifiedTimestamp );
245
229
}
246
-
230
+ updateResponseIdempotent ( eTag , lastModifiedTimestamp );
247
231
return this .notModified ;
248
232
}
249
233
250
- private boolean validateIfUnmodifiedSince (long lastModifiedTimestamp ) {
251
- if (lastModifiedTimestamp < 0 ) {
234
+ private boolean validateIfMatch (@ Nullable String eTag ) {
235
+ Enumeration <String > ifMatchHeaders = getRequest ().getHeaders (HttpHeaders .IF_MATCH );
236
+ if (SAFE_METHODS .contains (getRequest ().getMethod ())) {
252
237
return false ;
253
238
}
254
- long ifUnmodifiedSince = parseDateHeader (HttpHeaders .IF_UNMODIFIED_SINCE );
255
- if (ifUnmodifiedSince == -1 ) {
239
+ if (!ifMatchHeaders .hasMoreElements ()) {
256
240
return false ;
257
241
}
258
- // We will perform this validation...
259
- this .notModified = (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000 ));
242
+ this .notModified = matchRequestedETags (ifMatchHeaders , eTag , false );
260
243
return true ;
261
244
}
262
245
263
- private boolean validateIfNoneMatch (@ Nullable String etag ) {
264
- if (!StringUtils .hasLength (etag )) {
265
- return false ;
266
- }
267
-
268
- Enumeration <String > ifNoneMatch ;
269
- try {
270
- ifNoneMatch = getRequest ().getHeaders (HttpHeaders .IF_NONE_MATCH );
271
- }
272
- catch (IllegalArgumentException ex ) {
273
- return false ;
274
- }
275
- if (!ifNoneMatch .hasMoreElements ()) {
246
+ private boolean validateIfNoneMatch (@ Nullable String eTag ) {
247
+ Enumeration <String > ifNoneMatchHeaders = getRequest ().getHeaders (HttpHeaders .IF_NONE_MATCH );
248
+ if (!ifNoneMatchHeaders .hasMoreElements ()) {
276
249
return false ;
277
250
}
251
+ this .notModified = !matchRequestedETags (ifNoneMatchHeaders , eTag , true );
252
+ return true ;
253
+ }
278
254
279
- // We will perform this validation...
280
- etag = padEtagIfNecessary (etag );
281
- if (etag .startsWith ("W/" )) {
282
- etag = etag .substring (2 );
283
- }
284
- while (ifNoneMatch .hasMoreElements ()) {
285
- String clientETags = ifNoneMatch .nextElement ();
286
- Matcher etagMatcher = ETAG_HEADER_VALUE_PATTERN .matcher (clientETags );
287
- // Compare weak/strong ETags as per https://tools.ietf.org/html/rfc7232#section-2.3
288
- while (etagMatcher .find ()) {
289
- if (StringUtils .hasLength (etagMatcher .group ()) && etag .equals (etagMatcher .group (3 ))) {
290
- this .notModified = true ;
291
- break ;
255
+ private boolean matchRequestedETags (Enumeration <String > requestedETags , @ Nullable String eTag , boolean weakCompare ) {
256
+ eTag = padEtagIfNecessary (eTag );
257
+ while (requestedETags .hasMoreElements ()) {
258
+ // Compare weak/strong ETags as per https://datatracker.ietf.org/doc/html/rfc9110#section-8.8.3
259
+ Matcher eTagMatcher = ETAG_HEADER_VALUE_PATTERN .matcher (requestedETags .nextElement ());
260
+ while (eTagMatcher .find ()) {
261
+ // only consider "lost updates" checks for unsafe HTTP methods
262
+ if ("*" .equals (eTagMatcher .group ()) && StringUtils .hasLength (eTag )
263
+ && !SAFE_METHODS .contains (getRequest ().getMethod ())) {
264
+ return false ;
265
+ }
266
+ if (weakCompare ) {
267
+ if (eTagWeakMatch (eTag , eTagMatcher .group (1 ))) {
268
+ return false ;
269
+ }
270
+ }
271
+ else {
272
+ if (eTagStrongMatch (eTag , eTagMatcher .group (1 ))) {
273
+ return false ;
274
+ }
292
275
}
293
276
}
294
277
}
295
-
296
278
return true ;
297
279
}
298
280
299
- private String padEtagIfNecessary (String etag ) {
281
+ @ Nullable
282
+ private String padEtagIfNecessary (@ Nullable String etag ) {
300
283
if (!StringUtils .hasLength (etag )) {
301
284
return etag ;
302
285
}
@@ -306,6 +289,44 @@ private String padEtagIfNecessary(String etag) {
306
289
return "\" " + etag + "\" " ;
307
290
}
308
291
292
+ private boolean eTagStrongMatch (@ Nullable String first , @ Nullable String second ) {
293
+ if (!StringUtils .hasLength (first ) || first .startsWith ("W/" )) {
294
+ return false ;
295
+ }
296
+ return first .equals (second );
297
+ }
298
+
299
+ private boolean eTagWeakMatch (@ Nullable String first , @ Nullable String second ) {
300
+ if (!StringUtils .hasLength (first ) || !StringUtils .hasLength (second )) {
301
+ return false ;
302
+ }
303
+ if (first .startsWith ("W/" )) {
304
+ first = first .substring (2 );
305
+ }
306
+ if (second .startsWith ("W/" )) {
307
+ second = second .substring (2 );
308
+ }
309
+ return first .equals (second );
310
+ }
311
+
312
+ private void updateResponseStateChanging () {
313
+ if (this .notModified && getResponse () != null ) {
314
+ getResponse ().setStatus (HttpStatus .PRECONDITION_FAILED .value ());
315
+ }
316
+ }
317
+
318
+ private boolean validateIfUnmodifiedSince (long lastModifiedTimestamp ) {
319
+ if (lastModifiedTimestamp < 0 ) {
320
+ return false ;
321
+ }
322
+ long ifUnmodifiedSince = parseDateHeader (HttpHeaders .IF_UNMODIFIED_SINCE );
323
+ if (ifUnmodifiedSince == -1 ) {
324
+ return false ;
325
+ }
326
+ this .notModified = (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000 ));
327
+ return true ;
328
+ }
329
+
309
330
private boolean validateIfModifiedSince (long lastModifiedTimestamp ) {
310
331
if (lastModifiedTimestamp < 0 ) {
311
332
return false ;
@@ -319,6 +340,24 @@ private boolean validateIfModifiedSince(long lastModifiedTimestamp) {
319
340
return true ;
320
341
}
321
342
343
+ private void updateResponseIdempotent (String eTag , long lastModifiedTimestamp ) {
344
+ if (getResponse () != null ) {
345
+ boolean isHttpGetOrHead = SAFE_METHODS .contains (getRequest ().getMethod ());
346
+ if (this .notModified ) {
347
+ getResponse ().setStatus (isHttpGetOrHead ?
348
+ HttpStatus .NOT_MODIFIED .value () : HttpStatus .PRECONDITION_FAILED .value ());
349
+ }
350
+ if (isHttpGetOrHead ) {
351
+ if (lastModifiedTimestamp > 0 && parseDateValue (getResponse ().getHeader (HttpHeaders .LAST_MODIFIED )) == -1 ) {
352
+ getResponse ().setDateHeader (HttpHeaders .LAST_MODIFIED , lastModifiedTimestamp );
353
+ }
354
+ if (StringUtils .hasLength (eTag ) && getResponse ().getHeader (HttpHeaders .ETAG ) == null ) {
355
+ getResponse ().setHeader (HttpHeaders .ETAG , padEtagIfNecessary (eTag ));
356
+ }
357
+ }
358
+ }
359
+ }
360
+
322
361
public boolean isNotModified () {
323
362
return this .notModified ;
324
363
}
0 commit comments