34
34
import java .util .Map ;
35
35
import java .util .Set ;
36
36
import java .util .TimeZone ;
37
+ import java .util .regex .Matcher ;
38
+ import java .util .regex .Pattern ;
37
39
38
40
import org .springframework .util .Assert ;
39
41
import org .springframework .util .LinkedCaseInsensitiveMap ;
55
57
*
56
58
* @author Arjen Poutsma
57
59
* @author Sebastien Deleuze
60
+ * @author Brian Clozel
58
61
* @since 3.0
59
62
*/
60
63
public class HttpHeaders implements MultiValueMap <String , String >, Serializable {
@@ -372,6 +375,12 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
372
375
"EEE MMM dd HH:mm:ss yyyy"
373
376
};
374
377
378
+ /**
379
+ * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match"
380
+ * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
381
+ */
382
+ private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern .compile ("\\ *|\\ s*((W\\ /)?(\" [^\" ]*\" ))\\ s*,?" );
383
+
375
384
private static TimeZone GMT = TimeZone .getTimeZone ("GMT" );
376
385
377
386
@@ -459,7 +468,7 @@ public void setAccessControlAllowHeaders(List<String> allowedHeaders) {
459
468
* Returns the value of the {@code Access-Control-Allow-Headers} response header.
460
469
*/
461
470
public List <String > getAccessControlAllowHeaders () {
462
- return getFirstValueAsList (ACCESS_CONTROL_ALLOW_HEADERS );
471
+ return getValuesAsList (ACCESS_CONTROL_ALLOW_HEADERS );
463
472
}
464
473
465
474
/**
@@ -476,7 +485,7 @@ public List<HttpMethod> getAccessControlAllowMethods() {
476
485
List <HttpMethod > result = new ArrayList <HttpMethod >();
477
486
String value = getFirst (ACCESS_CONTROL_ALLOW_METHODS );
478
487
if (value != null ) {
479
- String [] tokens = value . split ( ", \\ s*" );
488
+ String [] tokens = StringUtils . tokenizeToStringArray ( value , "," , true , true );
480
489
for (String token : tokens ) {
481
490
HttpMethod resolved = HttpMethod .resolve (token );
482
491
if (resolved != null ) {
@@ -498,7 +507,23 @@ public void setAccessControlAllowOrigin(String allowedOrigin) {
498
507
* Return the value of the {@code Access-Control-Allow-Origin} response header.
499
508
*/
500
509
public String getAccessControlAllowOrigin () {
501
- return getFirst (ACCESS_CONTROL_ALLOW_ORIGIN );
510
+ return getFieldValues (ACCESS_CONTROL_ALLOW_ORIGIN );
511
+ }
512
+
513
+ protected String getFieldValues (String headerName ) {
514
+ List <String > headerValues = this .headers .get (headerName );
515
+ if (headerValues != null ) {
516
+ StringBuilder builder = new StringBuilder ();
517
+ for (Iterator <String > iterator = headerValues .iterator (); iterator .hasNext (); ) {
518
+ String ifNoneMatch = iterator .next ();
519
+ builder .append (ifNoneMatch );
520
+ if (iterator .hasNext ()) {
521
+ builder .append (", " );
522
+ }
523
+ }
524
+ return builder .toString ();
525
+ }
526
+ return null ;
502
527
}
503
528
504
529
/**
@@ -512,7 +537,7 @@ public void setAccessControlExposeHeaders(List<String> exposedHeaders) {
512
537
* Returns the value of the {@code Access-Control-Expose-Headers} response header.
513
538
*/
514
539
public List <String > getAccessControlExposeHeaders () {
515
- return getFirstValueAsList (ACCESS_CONTROL_EXPOSE_HEADERS );
540
+ return getValuesAsList (ACCESS_CONTROL_EXPOSE_HEADERS );
516
541
}
517
542
518
543
/**
@@ -542,7 +567,7 @@ public void setAccessControlRequestHeaders(List<String> requestHeaders) {
542
567
* Returns the value of the {@code Access-Control-Request-Headers} request header.
543
568
*/
544
569
public List <String > getAccessControlRequestHeaders () {
545
- return getFirstValueAsList (ACCESS_CONTROL_REQUEST_HEADERS );
570
+ return getValuesAsList (ACCESS_CONTROL_REQUEST_HEADERS );
546
571
}
547
572
548
573
/**
@@ -643,7 +668,7 @@ public void setCacheControl(String cacheControl) {
643
668
* Return the value of the {@code Cache-Control} header.
644
669
*/
645
670
public String getCacheControl () {
646
- return getFirst (CACHE_CONTROL );
671
+ return getFieldValues (CACHE_CONTROL );
647
672
}
648
673
649
674
/**
@@ -664,7 +689,7 @@ public void setConnection(List<String> connection) {
664
689
* Return the value of the {@code Connection} header.
665
690
*/
666
691
public List <String > getConnection () {
667
- return getFirstValueAsList (CONNECTION );
692
+ return getValuesAsList (CONNECTION );
668
693
}
669
694
670
695
/**
@@ -782,6 +807,64 @@ public long getExpires() {
782
807
return getFirstDate (EXPIRES , false );
783
808
}
784
809
810
+ /**
811
+ * Set the (new) value of the {@code If-Match} header.
812
+ */
813
+ public void setIfMatch (String ifMatch ) {
814
+ set (IF_MATCH , ifMatch );
815
+ }
816
+
817
+ /**
818
+ * Set the (new) value of the {@code If-Match} header.
819
+ */
820
+ public void setIfMatch (List <String > ifMatchList ) {
821
+ set (IF_MATCH , toCommaDelimitedString (ifMatchList ));
822
+ }
823
+
824
+ protected String toCommaDelimitedString (List <String > list ) {
825
+ StringBuilder builder = new StringBuilder ();
826
+ for (Iterator <String > iterator = list .iterator (); iterator .hasNext (); ) {
827
+ String ifNoneMatch = iterator .next ();
828
+ builder .append (ifNoneMatch );
829
+ if (iterator .hasNext ()) {
830
+ builder .append (", " );
831
+ }
832
+ }
833
+ return builder .toString ();
834
+ }
835
+
836
+ /**
837
+ * Return the value of the {@code If-Match} header.
838
+ */
839
+ public List <String > getIfMatch () {
840
+ return getETagValuesAsList (IF_MATCH );
841
+ }
842
+
843
+ protected List <String > getETagValuesAsList (String headerName ) {
844
+ List <String > values = get (headerName );
845
+ if (values != null ) {
846
+ List <String > result = new ArrayList <String >();
847
+ for (String value : values ) {
848
+ if (value != null ) {
849
+ Matcher matcher = ETAG_HEADER_VALUE_PATTERN .matcher (value );
850
+ while (matcher .find ()) {
851
+ if ("*" .equals (matcher .group ())) {
852
+ result .add (matcher .group ());
853
+ }
854
+ else {
855
+ result .add (matcher .group (1 ));
856
+ }
857
+ }
858
+ if (result .size () == 0 ) {
859
+ throw new IllegalArgumentException ("Could not parse '" + headerName + "' value=" + value );
860
+ }
861
+ }
862
+ }
863
+ return result ;
864
+ }
865
+ return Collections .emptyList ();
866
+ }
867
+
785
868
/**
786
869
* Set the (new) value of the {@code If-Modified-Since} header.
787
870
* <p>The date should be specified as the number of milliseconds since
@@ -814,35 +897,51 @@ public void setIfNoneMatch(List<String> ifNoneMatchList) {
814
897
set (IF_NONE_MATCH , toCommaDelimitedString (ifNoneMatchList ));
815
898
}
816
899
817
- protected String toCommaDelimitedString (List <String > list ) {
818
- StringBuilder builder = new StringBuilder ();
819
- for (Iterator <String > iterator = list .iterator (); iterator .hasNext ();) {
820
- String ifNoneMatch = iterator .next ();
821
- builder .append (ifNoneMatch );
822
- if (iterator .hasNext ()) {
823
- builder .append (", " );
824
- }
825
- }
826
- return builder .toString ();
827
- }
828
-
829
900
/**
830
901
* Return the value of the {@code If-None-Match} header.
831
902
*/
832
903
public List <String > getIfNoneMatch () {
833
- return getFirstValueAsList (IF_NONE_MATCH );
904
+ return getETagValuesAsList (IF_NONE_MATCH );
834
905
}
835
906
836
- protected List <String > getFirstValueAsList (String header ) {
837
- List <String > result = new ArrayList <String >();
838
- String value = getFirst (header );
839
- if (value != null ) {
840
- String [] tokens = value .split (",\\ s*" );
841
- for (String token : tokens ) {
842
- result .add (token );
907
+ /**
908
+ * Return all values of a given header name,
909
+ * even if this header is set multiple times.
910
+ * @since 4.3.0
911
+ */
912
+ public List <String > getValuesAsList (String headerName ) {
913
+ List <String > values = get (headerName );
914
+ if (values != null ) {
915
+ List <String > result = new ArrayList <String >();
916
+ for (String value : values ) {
917
+ if (value != null ) {
918
+ String [] tokens = StringUtils .tokenizeToStringArray (value , "," );
919
+ for (String token : tokens ) {
920
+ result .add (token );
921
+ }
922
+ }
843
923
}
924
+ return result ;
844
925
}
845
- return result ;
926
+ return Collections .emptyList ();
927
+ }
928
+
929
+ /**
930
+ * Set the (new) value of the {@code If-Unmodified-Since} header.
931
+ * <p>The date should be specified as the number of milliseconds since
932
+ * January 1, 1970 GMT.
933
+ */
934
+ public void setIfUnmodifiedSince (long ifUnmodifiedSince ) {
935
+ setDate (IF_UNMODIFIED_SINCE , ifUnmodifiedSince );
936
+ }
937
+
938
+ /**
939
+ * Return the value of the {@code If-Unmodified-Since} header.
940
+ * <p>The date is returned as the number of milliseconds since
941
+ * January 1, 1970 GMT. Returns -1 when the date is unknown.
942
+ */
943
+ public long getIfUnmodifiedSince () {
944
+ return getFirstDate (IF_UNMODIFIED_SINCE , false );
846
945
}
847
946
848
947
/**
@@ -957,7 +1056,7 @@ public void setVary(List<String> requestHeaders) {
957
1056
* Return the request header names subject to content negotiation.
958
1057
*/
959
1058
public List <String > getVary () {
960
- return getFirstValueAsList (VARY );
1059
+ return getValuesAsList (VARY );
961
1060
}
962
1061
963
1062
/**
0 commit comments