Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbb17ad8

Browse files
committed
Efficient ETag parsing
1 parent63486bf commitbb17ad8

File tree

4 files changed

+165
-45
lines changed

4 files changed

+165
-45
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
packageorg.springframework.http;
18+
19+
importjava.util.ArrayList;
20+
importjava.util.List;
21+
22+
importorg.apache.commons.logging.Log;
23+
importorg.apache.commons.logging.LogFactory;
24+
25+
importorg.springframework.util.StringUtils;
26+
27+
/**
28+
* Represents an ETag for HTTP conditional requests.
29+
*
30+
* @param tag the unquoted tag value
31+
* @param weak whether the entity tag is for weak or strong validation
32+
* @author Rossen Stoyanchev
33+
* @since 5.3.38
34+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7232">RFC 7232</a>
35+
*/
36+
publicrecordETag(Stringtag,booleanweak) {
37+
38+
privatestaticfinalLoglogger =LogFactory.getLog(ETag.class);
39+
40+
privatestaticfinalETagWILDCARD =newETag("*",false);
41+
42+
43+
/**
44+
* Whether this a wildcard tag matching to any entity tag value.
45+
*/
46+
publicbooleanisWildcard() {
47+
return (this ==WILDCARD);
48+
}
49+
50+
/**
51+
* Return the fully formatted tag including "W/" prefix and quotes.
52+
*/
53+
publicStringformattedTag() {
54+
if (isWildcard()) {
55+
return"*";
56+
}
57+
return (this.weak ?"W/" :"") +"\"" +this.tag +"\"";
58+
}
59+
60+
@Override
61+
publicStringtoString() {
62+
returnformattedTag();
63+
}
64+
65+
66+
/**
67+
* Parse entity tags from an "If-Match" or "If-None-Match" header.
68+
* @param source the source string to parse
69+
* @return the parsed ETags
70+
*/
71+
publicstaticList<ETag>parse(Stringsource) {
72+
73+
List<ETag>result =newArrayList<>();
74+
Statestate =State.BEFORE_QUOTES;
75+
intstartIndex = -1;
76+
booleanweak =false;
77+
78+
for (inti =0;i <source.length();i++) {
79+
charc =source.charAt(i);
80+
81+
if (state ==State.IN_QUOTES) {
82+
if (c =='"') {
83+
Stringtag =source.substring(startIndex,i);
84+
if (StringUtils.hasText(tag)) {
85+
result.add(newETag(tag,weak));
86+
}
87+
state =State.AFTER_QUOTES;
88+
startIndex = -1;
89+
weak =false;
90+
}
91+
continue;
92+
}
93+
94+
if (Character.isWhitespace(c)) {
95+
continue;
96+
}
97+
98+
if (c ==',') {
99+
state =State.BEFORE_QUOTES;
100+
continue;
101+
}
102+
103+
if (state ==State.BEFORE_QUOTES) {
104+
if (c =='*') {
105+
result.add(WILDCARD);
106+
state =State.AFTER_QUOTES;
107+
continue;
108+
}
109+
if (c =='"') {
110+
state =State.IN_QUOTES;
111+
startIndex =i +1;
112+
continue;
113+
}
114+
if (c =='W' &&source.length() >i +2) {
115+
if (source.charAt(i +1) =='/' &&source.charAt(i +2) =='"') {
116+
state =State.IN_QUOTES;
117+
i =i +2;
118+
startIndex =i +1;
119+
weak =true;
120+
continue;
121+
}
122+
}
123+
}
124+
125+
if (logger.isDebugEnabled()) {
126+
logger.debug("Unexpected char at index " +i);
127+
}
128+
}
129+
130+
if (state !=State.IN_QUOTES &&logger.isDebugEnabled()) {
131+
logger.debug("Expected closing '\"'");
132+
}
133+
134+
returnresult;
135+
}
136+
137+
138+
privateenumState {
139+
140+
BEFORE_QUOTES,IN_QUOTES,AFTER_QUOTES
141+
142+
}
143+
144+
}

‎spring-web/src/main/java/org/springframework/http/HttpHeaders.java‎

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@
4141
importjava.util.Set;
4242
importjava.util.StringJoiner;
4343
importjava.util.function.BiConsumer;
44-
importjava.util.regex.Matcher;
45-
importjava.util.regex.Pattern;
4644
importjava.util.stream.Collectors;
4745

4846
importorg.springframework.lang.Nullable;
@@ -394,12 +392,6 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
394392
*/
395393
publicstaticfinalHttpHeadersEMPTY =newReadOnlyHttpHeaders(newLinkedMultiValueMap<>());
396394

397-
/**
398-
* Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match".
399-
* @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
400-
*/
401-
privatestaticfinalPatternETAG_HEADER_VALUE_PATTERN =Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
402-
403395
privatestaticfinalDecimalFormatSymbolsDECIMAL_FORMAT_SYMBOLS =newDecimalFormatSymbols(Locale.ENGLISH);
404396

405397
privatestaticfinalZoneIdGMT =ZoneId.of("GMT");
@@ -1629,35 +1621,27 @@ public void clearContentHeaders() {
16291621

16301622
/**
16311623
* Retrieve a combined result from the field values of the ETag header.
1632-
* @paramheaderName the header name
1624+
* @paramname the header name
16331625
* @return the combined result
16341626
* @throws IllegalArgumentException if parsing fails
16351627
* @since 4.3
16361628
*/
1637-
protectedList<String>getETagValuesAsList(StringheaderName) {
1638-
List<String>values =get(headerName);
1639-
if (values !=null) {
1640-
List<String>result =newArrayList<>();
1641-
for (Stringvalue :values) {
1642-
if (value !=null) {
1643-
Matchermatcher =ETAG_HEADER_VALUE_PATTERN.matcher(value);
1644-
while (matcher.find()) {
1645-
if ("*".equals(matcher.group())) {
1646-
result.add(matcher.group());
1647-
}
1648-
else {
1649-
result.add(matcher.group(1));
1650-
}
1651-
}
1652-
if (result.isEmpty()) {
1653-
thrownewIllegalArgumentException(
1654-
"Could not parse header '" +headerName +"' with value '" +value +"'");
1655-
}
1629+
protectedList<String>getETagValuesAsList(Stringname) {
1630+
List<String>values =get(name);
1631+
if (values ==null) {
1632+
returnCollections.emptyList();
1633+
}
1634+
List<String>result =newArrayList<>();
1635+
for (Stringvalue :values) {
1636+
if (value !=null) {
1637+
List<ETag>tags =ETag.parse(value);
1638+
Assert.notEmpty(tags,"Could not parse header '" +name +"' with value '" +value +"'");
1639+
for (ETagtag :tags) {
1640+
result.add(tag.formattedTag());
16561641
}
16571642
}
1658-
returnresult;
16591643
}
1660-
returnCollections.emptyList();
1644+
returnresult;
16611645
}
16621646

16631647
/**

‎spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@
2525
importjava.util.Map;
2626
importjava.util.Set;
2727
importjava.util.TimeZone;
28-
importjava.util.regex.Matcher;
29-
importjava.util.regex.Pattern;
3028

3129
importjakarta.servlet.http.HttpServletRequest;
3230
importjakarta.servlet.http.HttpServletResponse;
3331
importjakarta.servlet.http.HttpSession;
3432

33+
importorg.springframework.http.ETag;
3534
importorg.springframework.http.HttpHeaders;
3635
importorg.springframework.http.HttpMethod;
3736
importorg.springframework.http.HttpStatus;
@@ -53,12 +52,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
5352

5453
privatestaticfinalSet<String>SAFE_METHODS =Set.of("GET","HEAD");
5554

56-
/**
57-
* Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match".
58-
* @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
59-
*/
60-
privatestaticfinalPatternETAG_HEADER_VALUE_PATTERN =Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
61-
6255
/**
6356
* Date formats as specified in the HTTP RFC.
6457
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
@@ -255,20 +248,19 @@ private boolean matchRequestedETags(Enumeration<String> requestedETags, @Nullabl
255248
etag =padEtagIfNecessary(etag);
256249
while (requestedETags.hasMoreElements()) {
257250
// Compare weak/strong ETags as per https://datatracker.ietf.org/doc/html/rfc9110#section-8.8.3
258-
MatcheretagMatcher =ETAG_HEADER_VALUE_PATTERN.matcher(requestedETags.nextElement());
259-
while (etagMatcher.find()) {
251+
for (ETagrequestedETag :ETag.parse(requestedETags.nextElement())) {
260252
// only consider "lost updates" checks for unsafe HTTP methods
261-
if ("*".equals(etagMatcher.group()) &&StringUtils.hasLength(etag)
253+
if (requestedETag.isWildcard() &&StringUtils.hasLength(etag)
262254
&& !SAFE_METHODS.contains(getRequest().getMethod())) {
263255
returnfalse;
264256
}
265257
if (weakCompare) {
266-
if (etagWeakMatch(etag,etagMatcher.group(1))) {
258+
if (etagWeakMatch(etag,requestedETag.formattedTag())) {
267259
returnfalse;
268260
}
269261
}
270262
else {
271-
if (etagStrongMatch(etag,etagMatcher.group(1))) {
263+
if (etagStrongMatch(etag,requestedETag.formattedTag())) {
272264
returnfalse;
273265
}
274266
}

‎spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestHttpMethodsTests.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -163,8 +163,8 @@ void ifNoneMatchShouldNotMatchDifferentETag(String method) {
163163
assertOkWithETag(etag);
164164
}
165165

166+
// gh-19127
166167
@SafeHttpMethodsTest
167-
// SPR-14559
168168
voidifNoneMatchShouldNotFailForUnquotedETag(Stringmethod) {
169169
setUpRequest(method);
170170
Stringetag ="\"etagvalue\"";

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp