init
This commit is contained in:
74
java/org/apache/tomcat/util/http/parser/AcceptEncoding.java
Normal file
74
java/org/apache/tomcat/util/http/parser/AcceptEncoding.java
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AcceptEncoding {
|
||||
|
||||
private final String encoding;
|
||||
private final double quality;
|
||||
|
||||
protected AcceptEncoding(String encoding, double quality) {
|
||||
this.encoding = encoding;
|
||||
this.quality = quality;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public double getQuality() {
|
||||
return quality;
|
||||
}
|
||||
|
||||
|
||||
public static List<AcceptEncoding> parse(StringReader input) throws IOException {
|
||||
|
||||
List<AcceptEncoding> result = new ArrayList<>();
|
||||
|
||||
do {
|
||||
String encoding = HttpParser.readToken(input);
|
||||
if (encoding == null) {
|
||||
// Invalid encoding, skip to the next one
|
||||
HttpParser.skipUntil(input, 0, ',');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (encoding.length() == 0) {
|
||||
// No more data to read
|
||||
break;
|
||||
}
|
||||
|
||||
// See if a quality has been provided
|
||||
double quality = 1;
|
||||
SkipResult lookForSemiColon = HttpParser.skipConstant(input, ";");
|
||||
if (lookForSemiColon == SkipResult.FOUND) {
|
||||
quality = HttpParser.readWeight(input, ',');
|
||||
}
|
||||
|
||||
if (quality > 0) {
|
||||
result.add(new AcceptEncoding(encoding, quality));
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
78
java/org/apache/tomcat/util/http/parser/AcceptLanguage.java
Normal file
78
java/org/apache/tomcat/util/http/parser/AcceptLanguage.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AcceptLanguage {
|
||||
|
||||
private final Locale locale;
|
||||
private final double quality;
|
||||
|
||||
protected AcceptLanguage(Locale locale, double quality) {
|
||||
this.locale = locale;
|
||||
this.quality = quality;
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public double getQuality() {
|
||||
return quality;
|
||||
}
|
||||
|
||||
|
||||
public static List<AcceptLanguage> parse(StringReader input) throws IOException {
|
||||
|
||||
List<AcceptLanguage> result = new ArrayList<>();
|
||||
|
||||
do {
|
||||
// Token is broader than what is permitted in a language tag
|
||||
// (alphanumeric + '-') but any invalid values that slip through
|
||||
// will be caught later
|
||||
String languageTag = HttpParser.readToken(input);
|
||||
if (languageTag == null) {
|
||||
// Invalid tag, skip to the next one
|
||||
HttpParser.skipUntil(input, 0, ',');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (languageTag.length() == 0) {
|
||||
// No more data to read
|
||||
break;
|
||||
}
|
||||
|
||||
// See if a quality has been provided
|
||||
double quality = 1;
|
||||
SkipResult lookForSemiColon = HttpParser.skipConstant(input, ";");
|
||||
if (lookForSemiColon == SkipResult.FOUND) {
|
||||
quality = HttpParser.readWeight(input, ',');
|
||||
}
|
||||
|
||||
if (quality > 0) {
|
||||
result.add(new AcceptLanguage(Locale.forLanguageTag(languageTag), quality));
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
146
java/org/apache/tomcat/util/http/parser/Authorization.java
Normal file
146
java/org/apache/tomcat/util/http/parser/Authorization.java
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Parser for an "Authorization" header.
|
||||
*/
|
||||
public class Authorization {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(Authorization.class);
|
||||
|
||||
@SuppressWarnings("unused") // Unused due to buggy client implementations
|
||||
private static final Integer FIELD_TYPE_TOKEN = Integer.valueOf(0);
|
||||
private static final Integer FIELD_TYPE_QUOTED_STRING = Integer.valueOf(1);
|
||||
private static final Integer FIELD_TYPE_TOKEN_OR_QUOTED_STRING = Integer.valueOf(2);
|
||||
private static final Integer FIELD_TYPE_LHEX = Integer.valueOf(3);
|
||||
private static final Integer FIELD_TYPE_QUOTED_TOKEN = Integer.valueOf(4);
|
||||
|
||||
private static final Map<String,Integer> fieldTypes = new HashMap<>();
|
||||
|
||||
static {
|
||||
// Digest field types.
|
||||
// Note: These are more relaxed than RFC2617. This adheres to the
|
||||
// recommendation of RFC2616 that servers are tolerant of buggy
|
||||
// clients when they can be so without ambiguity.
|
||||
fieldTypes.put("username", FIELD_TYPE_QUOTED_STRING);
|
||||
fieldTypes.put("realm", FIELD_TYPE_QUOTED_STRING);
|
||||
fieldTypes.put("nonce", FIELD_TYPE_QUOTED_STRING);
|
||||
fieldTypes.put("digest-uri", FIELD_TYPE_QUOTED_STRING);
|
||||
// RFC2617 says response is <">32LHEX<">. 32LHEX will also be accepted
|
||||
fieldTypes.put("response", FIELD_TYPE_LHEX);
|
||||
// RFC2617 says algorithm is token. <">token<"> will also be accepted
|
||||
fieldTypes.put("algorithm", FIELD_TYPE_QUOTED_TOKEN);
|
||||
fieldTypes.put("cnonce", FIELD_TYPE_QUOTED_STRING);
|
||||
fieldTypes.put("opaque", FIELD_TYPE_QUOTED_STRING);
|
||||
// RFC2617 says qop is token. <">token<"> will also be accepted
|
||||
fieldTypes.put("qop", FIELD_TYPE_QUOTED_TOKEN);
|
||||
// RFC2617 says nc is 8LHEX. <">8LHEX<"> will also be accepted
|
||||
fieldTypes.put("nc", FIELD_TYPE_LHEX);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an HTTP Authorization header for DIGEST authentication as per RFC
|
||||
* 2617 section 3.2.2.
|
||||
*
|
||||
* @param input The header value to parse
|
||||
*
|
||||
* @return A map of directives and values as {@link String}s or
|
||||
* <code>null</code> if a parsing error occurs. Although the
|
||||
* values returned are {@link String}s they will have been
|
||||
* validated to ensure that they conform to RFC 2617.
|
||||
*
|
||||
* @throws IllegalArgumentException If the header does not conform to RFC
|
||||
* 2617
|
||||
* @throws java.io.IOException If an error occurs while reading the input
|
||||
*/
|
||||
public static Map<String,String> parseAuthorizationDigest (StringReader input)
|
||||
throws IllegalArgumentException, IOException {
|
||||
|
||||
Map<String,String> result = new HashMap<>();
|
||||
|
||||
if (HttpParser.skipConstant(input, "Digest") != SkipResult.FOUND) {
|
||||
return null;
|
||||
}
|
||||
// All field names are valid tokens
|
||||
String field = HttpParser.readToken(input);
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
while (!field.equals("")) {
|
||||
if (HttpParser.skipConstant(input, "=") != SkipResult.FOUND) {
|
||||
return null;
|
||||
}
|
||||
String value;
|
||||
Integer type = fieldTypes.get(field.toLowerCase(Locale.ENGLISH));
|
||||
if (type == null) {
|
||||
// auth-param = token "=" ( token | quoted-string )
|
||||
type = FIELD_TYPE_TOKEN_OR_QUOTED_STRING;
|
||||
}
|
||||
switch (type.intValue()) {
|
||||
case 0:
|
||||
// FIELD_TYPE_TOKEN
|
||||
value = HttpParser.readToken(input);
|
||||
break;
|
||||
case 1:
|
||||
// FIELD_TYPE_QUOTED_STRING
|
||||
value = HttpParser.readQuotedString(input, false);
|
||||
break;
|
||||
case 2:
|
||||
// FIELD_TYPE_TOKEN_OR_QUOTED_STRING
|
||||
value = HttpParser.readTokenOrQuotedString(input, false);
|
||||
break;
|
||||
case 3:
|
||||
// FIELD_TYPE_LHEX
|
||||
value = HttpParser.readLhex(input);
|
||||
break;
|
||||
case 4:
|
||||
// FIELD_TYPE_QUOTED_TOKEN
|
||||
value = HttpParser.readQuotedToken(input);
|
||||
break;
|
||||
default:
|
||||
// Error
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("authorization.unknownType", type));
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
result.put(field, value);
|
||||
|
||||
if (HttpParser.skipConstant(input, ",") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
field = HttpParser.readToken(input);
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
108
java/org/apache/tomcat/util/http/parser/ContentRange.java
Normal file
108
java/org/apache/tomcat/util/http/parser/ContentRange.java
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class ContentRange {
|
||||
|
||||
private final String units;
|
||||
private final long start;
|
||||
private final long end;
|
||||
private final long length;
|
||||
|
||||
|
||||
public ContentRange(String units, long start, long end, long length) {
|
||||
this.units = units;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
|
||||
public String getUnits() {
|
||||
return units;
|
||||
}
|
||||
|
||||
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
public long getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a Content-Range header from an HTTP header.
|
||||
*
|
||||
* @param input a reader over the header text
|
||||
*
|
||||
* @return the range parsed from the input, or null if not valid
|
||||
*
|
||||
* @throws IOException if there was a problem reading the input
|
||||
*/
|
||||
public static ContentRange parse(StringReader input) throws IOException {
|
||||
// Units (required)
|
||||
String units = HttpParser.readToken(input);
|
||||
if (units == null || units.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Must be followed by '='
|
||||
if (HttpParser.skipConstant(input, "=") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Start
|
||||
long start = HttpParser.readLong(input);
|
||||
|
||||
// Must be followed by '-'
|
||||
if (HttpParser.skipConstant(input, "-") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// End
|
||||
long end = HttpParser.readLong(input);
|
||||
|
||||
// Must be followed by '/'
|
||||
if (HttpParser.skipConstant(input, "/") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Length
|
||||
long length = HttpParser.readLong(input);
|
||||
|
||||
// Doesn't matter what we look for, result should be EOF
|
||||
SkipResult skipResult = HttpParser.skipConstant(input, "X");
|
||||
|
||||
if (skipResult != SkipResult.EOF) {
|
||||
// Invalid range
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ContentRange(units, start, end, length);
|
||||
}
|
||||
}
|
||||
682
java/org/apache/tomcat/util/http/parser/Cookie.java
Normal file
682
java/org/apache/tomcat/util/http/parser/Cookie.java
Normal file
@@ -0,0 +1,682 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.http.ServerCookie;
|
||||
import org.apache.tomcat.util.http.ServerCookies;
|
||||
import org.apache.tomcat.util.log.UserDataHelper;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Cookie header parser based on RFC6265 and RFC2109.</p>
|
||||
* <p>The parsing of cookies using RFC6265 is more relaxed that the
|
||||
* specification in the following ways:</p>
|
||||
* <ul>
|
||||
* <li>Values 0x80 to 0xFF are permitted in cookie-octet to support the use of
|
||||
* UTF-8 in cookie values as used by HTML 5.</li>
|
||||
* <li>For cookies without a value, the '=' is not required after the name as
|
||||
* some browsers do not sent it.</li>
|
||||
* </ul>
|
||||
* <p>The parsing of cookies using RFC2109 is more relaxed that the
|
||||
* specification in the following ways:</p>
|
||||
* <ul>
|
||||
* <li>Values for the path attribute that contain a / character do not have to
|
||||
* be quoted even though / is not permitted in a token.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Implementation note:<br>
|
||||
* This class has been carefully tuned to ensure that it has equal or better
|
||||
* performance than the original Netscape/RFC2109 cookie parser. Before
|
||||
* committing and changes, ensure that the TesterCookiePerformance unit test
|
||||
* continues to give results within 1% for the old and new parsers.</p>
|
||||
*/
|
||||
public class Cookie {
|
||||
|
||||
private static final Log log = LogFactory.getLog(Cookie.class);
|
||||
private static final UserDataHelper invalidCookieVersionLog = new UserDataHelper(log);
|
||||
private static final UserDataHelper invalidCookieLog = new UserDataHelper(log);
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager("org.apache.tomcat.util.http.parser");
|
||||
|
||||
private static final boolean isCookieOctet[] = new boolean[256];
|
||||
private static final boolean isText[] = new boolean[256];
|
||||
private static final byte[] VERSION_BYTES = "$Version".getBytes(StandardCharsets.ISO_8859_1);
|
||||
private static final byte[] PATH_BYTES = "$Path".getBytes(StandardCharsets.ISO_8859_1);
|
||||
private static final byte[] DOMAIN_BYTES = "$Domain".getBytes(StandardCharsets.ISO_8859_1);
|
||||
private static final byte[] EMPTY_BYTES = new byte[0];
|
||||
private static final byte TAB_BYTE = (byte) 0x09;
|
||||
private static final byte SPACE_BYTE = (byte) 0x20;
|
||||
private static final byte QUOTE_BYTE = (byte) 0x22;
|
||||
private static final byte COMMA_BYTE = (byte) 0x2C;
|
||||
private static final byte FORWARDSLASH_BYTE = (byte) 0x2F;
|
||||
private static final byte SEMICOLON_BYTE = (byte) 0x3B;
|
||||
private static final byte EQUALS_BYTE = (byte) 0x3D;
|
||||
private static final byte SLASH_BYTE = (byte) 0x5C;
|
||||
private static final byte DEL_BYTE = (byte) 0x7F;
|
||||
|
||||
|
||||
static {
|
||||
// %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E (RFC6265)
|
||||
// %x80 to %xFF (UTF-8)
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (i < 0x21 || i == QUOTE_BYTE || i == COMMA_BYTE ||
|
||||
i == SEMICOLON_BYTE || i == SLASH_BYTE || i == DEL_BYTE) {
|
||||
isCookieOctet[i] = false;
|
||||
} else {
|
||||
isCookieOctet[i] = true;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (i < TAB_BYTE || (i > TAB_BYTE && i < SPACE_BYTE) || i == DEL_BYTE) {
|
||||
isText[i] = false;
|
||||
} else {
|
||||
isText[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Cookie() {
|
||||
// Hide default constructor
|
||||
}
|
||||
|
||||
|
||||
public static void parseCookie(byte[] bytes, int offset, int len,
|
||||
ServerCookies serverCookies) {
|
||||
|
||||
// ByteBuffer is used throughout this parser as it allows the byte[]
|
||||
// and position information to be easily passed between parsing methods
|
||||
ByteBuffer bb = new ByteBuffer(bytes, offset, len);
|
||||
|
||||
// Using RFC6265 parsing rules, check to see if the header starts with a
|
||||
// version marker. An RFC2109 version marker may be read using RFC6265
|
||||
// parsing rules. If version 1, use RFC2109. Else use RFC6265.
|
||||
|
||||
skipLWS(bb);
|
||||
|
||||
// Record position in case we need to return.
|
||||
int mark = bb.position();
|
||||
|
||||
SkipResult skipResult = skipBytes(bb, VERSION_BYTES);
|
||||
if (skipResult != SkipResult.FOUND) {
|
||||
// No need to reset position since skipConstant() will have done it
|
||||
parseCookieRfc6265(bb, serverCookies);
|
||||
return;
|
||||
}
|
||||
|
||||
skipLWS(bb);
|
||||
|
||||
skipResult = skipByte(bb, EQUALS_BYTE);
|
||||
if (skipResult != SkipResult.FOUND) {
|
||||
// Need to reset position as skipConstant() will only have reset to
|
||||
// position before it was called
|
||||
bb.position(mark);
|
||||
parseCookieRfc6265(bb, serverCookies);
|
||||
return;
|
||||
}
|
||||
|
||||
skipLWS(bb);
|
||||
|
||||
ByteBuffer value = readCookieValue(bb);
|
||||
if (value != null && value.remaining() == 1) {
|
||||
int version = value.get() - '0';
|
||||
if (version == 1 || version == 0) {
|
||||
// $Version=1 -> RFC2109
|
||||
// $Version=0 -> RFC2109
|
||||
skipLWS(bb);
|
||||
byte b = bb.get();
|
||||
if (b == SEMICOLON_BYTE || b == COMMA_BYTE) {
|
||||
parseCookieRfc2109(bb, serverCookies, version);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// Unrecognised version.
|
||||
// Ignore this header.
|
||||
value.rewind();
|
||||
logInvalidVersion(value);
|
||||
}
|
||||
} else {
|
||||
// Unrecognised version.
|
||||
// Ignore this header.
|
||||
logInvalidVersion(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String unescapeCookieValueRfc2109(String input) {
|
||||
if (input == null || input.length() < 2) {
|
||||
return input;
|
||||
}
|
||||
if (input.charAt(0) != '"' && input.charAt(input.length() - 1) != '"') {
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(input.length());
|
||||
char[] chars = input.toCharArray();
|
||||
boolean escaped = false;
|
||||
|
||||
for (int i = 1; i < input.length() - 1; i++) {
|
||||
if (chars[i] == '\\') {
|
||||
escaped = true;
|
||||
} else if (escaped) {
|
||||
escaped = false;
|
||||
if (chars[i] < 128) {
|
||||
sb.append(chars[i]);
|
||||
} else {
|
||||
sb.append('\\');
|
||||
sb.append(chars[i]);
|
||||
}
|
||||
} else {
|
||||
sb.append(chars[i]);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
private static void parseCookieRfc6265(ByteBuffer bb, ServerCookies serverCookies) {
|
||||
|
||||
boolean moreToProcess = true;
|
||||
|
||||
while (moreToProcess) {
|
||||
skipLWS(bb);
|
||||
|
||||
ByteBuffer name = readToken(bb);
|
||||
ByteBuffer value = null;
|
||||
|
||||
skipLWS(bb);
|
||||
|
||||
SkipResult skipResult = skipByte(bb, EQUALS_BYTE);
|
||||
if (skipResult == SkipResult.FOUND) {
|
||||
skipLWS(bb);
|
||||
value = readCookieValueRfc6265(bb);
|
||||
if (value == null) {
|
||||
logInvalidHeader(bb);
|
||||
// Invalid cookie value. Skip to the next semi-colon
|
||||
skipUntilSemiColon(bb);
|
||||
continue;
|
||||
}
|
||||
skipLWS(bb);
|
||||
}
|
||||
|
||||
skipResult = skipByte(bb, SEMICOLON_BYTE);
|
||||
if (skipResult == SkipResult.FOUND) {
|
||||
// NO-OP
|
||||
} else if (skipResult == SkipResult.NOT_FOUND) {
|
||||
logInvalidHeader(bb);
|
||||
// Invalid cookie. Ignore it and skip to the next semi-colon
|
||||
skipUntilSemiColon(bb);
|
||||
continue;
|
||||
} else {
|
||||
// SkipResult.EOF
|
||||
moreToProcess = false;
|
||||
}
|
||||
|
||||
if (name.hasRemaining()) {
|
||||
ServerCookie sc = serverCookies.addCookie();
|
||||
sc.getName().setBytes(name.array(), name.position(), name.remaining());
|
||||
if (value == null) {
|
||||
sc.getValue().setBytes(EMPTY_BYTES, 0, EMPTY_BYTES.length);
|
||||
} else {
|
||||
sc.getValue().setBytes(value.array(), value.position(), value.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void parseCookieRfc2109(ByteBuffer bb, ServerCookies serverCookies,
|
||||
int version) {
|
||||
|
||||
boolean moreToProcess = true;
|
||||
|
||||
while (moreToProcess) {
|
||||
skipLWS(bb);
|
||||
|
||||
boolean parseAttributes = true;
|
||||
|
||||
ByteBuffer name = readToken(bb);
|
||||
ByteBuffer value = null;
|
||||
ByteBuffer path = null;
|
||||
ByteBuffer domain = null;
|
||||
|
||||
skipLWS(bb);
|
||||
|
||||
SkipResult skipResult = skipByte(bb, EQUALS_BYTE);
|
||||
if (skipResult == SkipResult.FOUND) {
|
||||
skipLWS(bb);
|
||||
value = readCookieValueRfc2109(bb, false);
|
||||
if (value == null) {
|
||||
skipInvalidCookie(bb);
|
||||
continue;
|
||||
}
|
||||
skipLWS(bb);
|
||||
}
|
||||
|
||||
skipResult = skipByte(bb, COMMA_BYTE);
|
||||
if (skipResult == SkipResult.FOUND) {
|
||||
parseAttributes = false;
|
||||
}
|
||||
skipResult = skipByte(bb, SEMICOLON_BYTE);
|
||||
if (skipResult == SkipResult.EOF) {
|
||||
parseAttributes = false;
|
||||
moreToProcess = false;
|
||||
} else if (skipResult == SkipResult.NOT_FOUND) {
|
||||
skipInvalidCookie(bb);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parseAttributes) {
|
||||
skipResult = skipBytes(bb, PATH_BYTES);
|
||||
if (skipResult == SkipResult.FOUND) {
|
||||
skipLWS(bb);
|
||||
skipResult = skipByte(bb, EQUALS_BYTE);
|
||||
if (skipResult != SkipResult.FOUND) {
|
||||
skipInvalidCookie(bb);
|
||||
continue;
|
||||
}
|
||||
path = readCookieValueRfc2109(bb, true);
|
||||
if (path == null) {
|
||||
skipInvalidCookie(bb);
|
||||
continue;
|
||||
}
|
||||
skipLWS(bb);
|
||||
|
||||
skipResult = skipByte(bb, COMMA_BYTE);
|
||||
if (skipResult == SkipResult.FOUND) {
|
||||
parseAttributes = false;
|
||||
}
|
||||
skipResult = skipByte(bb, SEMICOLON_BYTE);
|
||||
if (skipResult == SkipResult.EOF) {
|
||||
parseAttributes = false;
|
||||
moreToProcess = false;
|
||||
} else if (skipResult == SkipResult.NOT_FOUND) {
|
||||
skipInvalidCookie(bb);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parseAttributes) {
|
||||
skipResult = skipBytes(bb, DOMAIN_BYTES);
|
||||
if (skipResult == SkipResult.FOUND) {
|
||||
skipLWS(bb);
|
||||
skipResult = skipByte(bb, EQUALS_BYTE);
|
||||
if (skipResult != SkipResult.FOUND) {
|
||||
skipInvalidCookie(bb);
|
||||
continue;
|
||||
}
|
||||
domain = readCookieValueRfc2109(bb, false);
|
||||
if (domain == null) {
|
||||
skipInvalidCookie(bb);
|
||||
continue;
|
||||
}
|
||||
|
||||
skipResult = skipByte(bb, COMMA_BYTE);
|
||||
if (skipResult == SkipResult.FOUND) {
|
||||
parseAttributes = false;
|
||||
}
|
||||
skipResult = skipByte(bb, SEMICOLON_BYTE);
|
||||
if (skipResult == SkipResult.EOF) {
|
||||
parseAttributes = false;
|
||||
moreToProcess = false;
|
||||
} else if (skipResult == SkipResult.NOT_FOUND) {
|
||||
skipInvalidCookie(bb);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (name.hasRemaining() && value != null && value.hasRemaining()) {
|
||||
ServerCookie sc = serverCookies.addCookie();
|
||||
sc.setVersion(version);
|
||||
sc.getName().setBytes(name.array(), name.position(), name.remaining());
|
||||
sc.getValue().setBytes(value.array(), value.position(), value.remaining());
|
||||
if (domain != null) {
|
||||
sc.getDomain().setBytes(domain.array(), domain.position(), domain.remaining());
|
||||
}
|
||||
if (path != null) {
|
||||
sc.getPath().setBytes(path.array(), path.position(), path.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void skipInvalidCookie(ByteBuffer bb) {
|
||||
logInvalidHeader(bb);
|
||||
// Invalid cookie value. Skip to the next semi-colon
|
||||
skipUntilSemiColonOrComma(bb);
|
||||
}
|
||||
|
||||
|
||||
private static void skipLWS(ByteBuffer bb) {
|
||||
while(bb.hasRemaining()) {
|
||||
byte b = bb.get();
|
||||
if (b != TAB_BYTE && b != SPACE_BYTE) {
|
||||
bb.rewind();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void skipUntilSemiColon(ByteBuffer bb) {
|
||||
while(bb.hasRemaining()) {
|
||||
if (bb.get() == SEMICOLON_BYTE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void skipUntilSemiColonOrComma(ByteBuffer bb) {
|
||||
while(bb.hasRemaining()) {
|
||||
byte b = bb.get();
|
||||
if (b == SEMICOLON_BYTE || b == COMMA_BYTE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static SkipResult skipByte(ByteBuffer bb, byte target) {
|
||||
|
||||
if (!bb.hasRemaining()) {
|
||||
return SkipResult.EOF;
|
||||
}
|
||||
if (bb.get() == target) {
|
||||
return SkipResult.FOUND;
|
||||
}
|
||||
|
||||
bb.rewind();
|
||||
return SkipResult.NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
private static SkipResult skipBytes(ByteBuffer bb, byte[] target) {
|
||||
int mark = bb.position();
|
||||
|
||||
for (int i = 0; i < target.length; i++) {
|
||||
if (!bb.hasRemaining()) {
|
||||
bb.position(mark);
|
||||
return SkipResult.EOF;
|
||||
}
|
||||
if (bb.get() != target[i]) {
|
||||
bb.position(mark);
|
||||
return SkipResult.NOT_FOUND;
|
||||
}
|
||||
}
|
||||
return SkipResult.FOUND;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Similar to readCookieValueRfc6265() but also allows a comma to terminate
|
||||
* the value (as permitted by RFC2109).
|
||||
*/
|
||||
private static ByteBuffer readCookieValue(ByteBuffer bb) {
|
||||
boolean quoted = false;
|
||||
if (bb.hasRemaining()) {
|
||||
if (bb.get() == QUOTE_BYTE) {
|
||||
quoted = true;
|
||||
} else {
|
||||
bb.rewind();
|
||||
}
|
||||
}
|
||||
int start = bb.position();
|
||||
int end = bb.limit();
|
||||
while (bb.hasRemaining()) {
|
||||
byte b = bb.get();
|
||||
if (isCookieOctet[(b & 0xFF)]) {
|
||||
// NO-OP
|
||||
} else if (b == SEMICOLON_BYTE || b == COMMA_BYTE || b == SPACE_BYTE || b == TAB_BYTE) {
|
||||
end = bb.position() - 1;
|
||||
bb.position(end);
|
||||
break;
|
||||
} else if (quoted && b == QUOTE_BYTE) {
|
||||
end = bb.position() - 1;
|
||||
break;
|
||||
} else {
|
||||
// Invalid cookie
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new ByteBuffer(bb.bytes, start, end - start);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Similar to readCookieValue() but treats a comma as part of an invalid
|
||||
* value.
|
||||
*/
|
||||
private static ByteBuffer readCookieValueRfc6265(ByteBuffer bb) {
|
||||
boolean quoted = false;
|
||||
if (bb.hasRemaining()) {
|
||||
if (bb.get() == QUOTE_BYTE) {
|
||||
quoted = true;
|
||||
} else {
|
||||
bb.rewind();
|
||||
}
|
||||
}
|
||||
int start = bb.position();
|
||||
int end = bb.limit();
|
||||
while (bb.hasRemaining()) {
|
||||
byte b = bb.get();
|
||||
if (isCookieOctet[(b & 0xFF)]) {
|
||||
// NO-OP
|
||||
} else if (b == SEMICOLON_BYTE || b == SPACE_BYTE || b == TAB_BYTE) {
|
||||
end = bb.position() - 1;
|
||||
bb.position(end);
|
||||
break;
|
||||
} else if (quoted && b == QUOTE_BYTE) {
|
||||
end = bb.position() - 1;
|
||||
break;
|
||||
} else {
|
||||
// Invalid cookie
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new ByteBuffer(bb.bytes, start, end - start);
|
||||
}
|
||||
|
||||
|
||||
private static ByteBuffer readCookieValueRfc2109(ByteBuffer bb, boolean allowForwardSlash) {
|
||||
if (!bb.hasRemaining()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (bb.peek() == QUOTE_BYTE) {
|
||||
return readQuotedString(bb);
|
||||
} else {
|
||||
if (allowForwardSlash) {
|
||||
return readTokenAllowForwardSlash(bb);
|
||||
} else {
|
||||
return readToken(bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static ByteBuffer readToken(ByteBuffer bb) {
|
||||
final int start = bb.position();
|
||||
int end = bb.limit();
|
||||
while (bb.hasRemaining()) {
|
||||
if (!HttpParser.isToken(bb.get())) {
|
||||
end = bb.position() - 1;
|
||||
bb.position(end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new ByteBuffer(bb.bytes, start, end - start);
|
||||
}
|
||||
|
||||
|
||||
private static ByteBuffer readTokenAllowForwardSlash(ByteBuffer bb) {
|
||||
final int start = bb.position();
|
||||
int end = bb.limit();
|
||||
while (bb.hasRemaining()) {
|
||||
byte b = bb.get();
|
||||
if (b != FORWARDSLASH_BYTE && !HttpParser.isToken(b)) {
|
||||
end = bb.position() - 1;
|
||||
bb.position(end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new ByteBuffer(bb.bytes, start, end - start);
|
||||
}
|
||||
|
||||
|
||||
private static ByteBuffer readQuotedString(ByteBuffer bb) {
|
||||
int start = bb.position();
|
||||
|
||||
// Read the opening quote
|
||||
bb.get();
|
||||
boolean escaped = false;
|
||||
while (bb.hasRemaining()) {
|
||||
byte b = bb.get();
|
||||
if (b == SLASH_BYTE) {
|
||||
// Escaping another character
|
||||
escaped = true;
|
||||
} else if (escaped && b > (byte) -1) {
|
||||
escaped = false;
|
||||
} else if (b == QUOTE_BYTE) {
|
||||
return new ByteBuffer(bb.bytes, start, bb.position() - start);
|
||||
} else if (isText[b & 0xFF]) {
|
||||
escaped = false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static void logInvalidHeader(ByteBuffer bb) {
|
||||
UserDataHelper.Mode logMode = invalidCookieLog.getNextMode();
|
||||
if (logMode != null) {
|
||||
String headerValue = new String(bb.array(), bb.position(), bb.limit() - bb.position(),
|
||||
StandardCharsets.UTF_8);
|
||||
String message = sm.getString("cookie.invalidCookieValue", headerValue);
|
||||
switch (logMode) {
|
||||
case INFO_THEN_DEBUG:
|
||||
message += sm.getString("cookie.fallToDebug");
|
||||
//$FALL-THROUGH$
|
||||
case INFO:
|
||||
log.info(message);
|
||||
break;
|
||||
case DEBUG:
|
||||
log.debug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void logInvalidVersion(ByteBuffer value) {
|
||||
UserDataHelper.Mode logMode = invalidCookieVersionLog.getNextMode();
|
||||
if (logMode != null) {
|
||||
String version;
|
||||
if (value == null) {
|
||||
version = sm.getString("cookie.valueNotPresent");
|
||||
} else {
|
||||
version = new String(value.bytes, value.position(),
|
||||
value.limit() - value.position(), StandardCharsets.UTF_8);
|
||||
}
|
||||
String message = sm.getString("cookie.invalidCookieVersion", version);
|
||||
switch (logMode) {
|
||||
case INFO_THEN_DEBUG:
|
||||
message += sm.getString("cookie.fallToDebug");
|
||||
//$FALL-THROUGH$
|
||||
case INFO:
|
||||
log.info(message);
|
||||
break;
|
||||
case DEBUG:
|
||||
log.debug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Custom implementation that skips many of the safety checks in
|
||||
* {@link java.nio.ByteBuffer}.
|
||||
*/
|
||||
private static class ByteBuffer {
|
||||
|
||||
private final byte[] bytes;
|
||||
private int limit;
|
||||
private int position = 0;
|
||||
|
||||
public ByteBuffer(byte[] bytes, int offset, int len) {
|
||||
this.bytes = bytes;
|
||||
this.position = offset;
|
||||
this.limit = offset + len;
|
||||
}
|
||||
|
||||
public int position() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void position(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public int limit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public int remaining() {
|
||||
return limit - position;
|
||||
}
|
||||
|
||||
public boolean hasRemaining() {
|
||||
return position < limit;
|
||||
}
|
||||
|
||||
public byte get() {
|
||||
return bytes[position++];
|
||||
}
|
||||
|
||||
public byte peek() {
|
||||
return bytes[position];
|
||||
}
|
||||
|
||||
public void rewind() {
|
||||
position--;
|
||||
}
|
||||
|
||||
public byte[] array() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// For debug purposes
|
||||
@Override
|
||||
public String toString() {
|
||||
return "position [" + position + "], limit [" + limit + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
139
java/org/apache/tomcat/util/http/parser/Host.java
Normal file
139
java/org/apache/tomcat/util/http/parser/Host.java
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.buf.MessageBytes;
|
||||
|
||||
public class Host {
|
||||
|
||||
/**
|
||||
* Parse the given input as an HTTP Host header value.
|
||||
*
|
||||
* @param mb The host header value
|
||||
*
|
||||
* @return The position of ':' that separates the host from the port or -1
|
||||
* if it is not present
|
||||
*
|
||||
* @throws IllegalArgumentException If the host header value is not
|
||||
* specification compliant
|
||||
*/
|
||||
public static int parse(MessageBytes mb) {
|
||||
return parse(new MessageBytesReader(mb));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the given input as an HTTP Host header value.
|
||||
*
|
||||
* @param string The host header value
|
||||
*
|
||||
* @return The position of ':' that separates the host from the port or -1
|
||||
* if it is not present
|
||||
*
|
||||
* @throws IllegalArgumentException If the host header value is not
|
||||
* specification compliant
|
||||
*/
|
||||
public static int parse(String string) {
|
||||
return parse(new StringReader(string));
|
||||
}
|
||||
|
||||
|
||||
private static int parse(Reader reader) {
|
||||
try {
|
||||
reader.mark(1);
|
||||
int first = reader.read();
|
||||
reader.reset();
|
||||
if (HttpParser.isAlpha(first)) {
|
||||
return HttpParser.readHostDomainName(reader);
|
||||
} else if (HttpParser.isNumeric(first)) {
|
||||
return HttpParser.readHostIPv4(reader, false);
|
||||
} else if ('[' == first) {
|
||||
return HttpParser.readHostIPv6(reader);
|
||||
} else {
|
||||
// Invalid
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// Should never happen
|
||||
throw new IllegalArgumentException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MessageBytesReader extends Reader {
|
||||
|
||||
private final byte[] bytes;
|
||||
private final int end;
|
||||
private int pos;
|
||||
private int mark;
|
||||
|
||||
public MessageBytesReader(MessageBytes mb) {
|
||||
ByteChunk bc = mb.getByteChunk();
|
||||
bytes = bc.getBytes();
|
||||
pos = bc.getOffset();
|
||||
end = bc.getEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||
for (int i = off; i < off + len; i++) {
|
||||
// Want output in range 0 to 255, not -128 to 127
|
||||
cbuf[i] = (char) (bytes[pos++] & 0xFF);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
// Over-ridden methods to improve performance
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (pos < end) {
|
||||
// Want output in range 0 to 255, not -128 to 127
|
||||
return bytes[pos++] & 0xFF;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Methods to support mark/reset
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int readAheadLimit) throws IOException {
|
||||
mark = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
pos = mark;
|
||||
}
|
||||
}
|
||||
}
|
||||
999
java/org/apache/tomcat/util/http/parser/HttpParser.java
Normal file
999
java/org/apache/tomcat/util/http/parser/HttpParser.java
Normal file
@@ -0,0 +1,999 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* HTTP header value parser implementation. Parsing HTTP headers as per RFC2616
|
||||
* is not always as simple as it first appears. For headers that only use tokens
|
||||
* the simple approach will normally be sufficient. However, for the other
|
||||
* headers, while simple code meets 99.9% of cases, there are often some edge
|
||||
* cases that make things far more complicated.
|
||||
*
|
||||
* The purpose of this parser is to let the parser worry about the edge cases.
|
||||
* It provides tolerant (where safe to do so) parsing of HTTP header values
|
||||
* assuming that wrapped header lines have already been unwrapped. (The Tomcat
|
||||
* header processing code does the unwrapping.)
|
||||
*
|
||||
*/
|
||||
public class HttpParser {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(HttpParser.class);
|
||||
|
||||
private static final Log log = LogFactory.getLog(HttpParser.class);
|
||||
|
||||
private static final int ARRAY_SIZE = 128;
|
||||
|
||||
private static final boolean[] IS_CONTROL = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_HEX = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_HTTP_PROTOCOL = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_ALPHA = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_NUMERIC = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] REQUEST_TARGET_ALLOW = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_UNRESERVED = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_SUBDELIM = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_USERINFO = new boolean[ARRAY_SIZE];
|
||||
private static final boolean[] IS_RELAXABLE = new boolean[ARRAY_SIZE];
|
||||
|
||||
private static final HttpParser DEFAULT;
|
||||
|
||||
|
||||
static {
|
||||
for (int i = 0; i < ARRAY_SIZE; i++) {
|
||||
// Control> 0-31, 127
|
||||
if (i < 32 || i == 127) {
|
||||
IS_CONTROL[i] = true;
|
||||
}
|
||||
|
||||
// Separator
|
||||
if ( i == '(' || i == ')' || i == '<' || i == '>' || i == '@' ||
|
||||
i == ',' || i == ';' || i == ':' || i == '\\' || i == '\"' ||
|
||||
i == '/' || i == '[' || i == ']' || i == '?' || i == '=' ||
|
||||
i == '{' || i == '}' || i == ' ' || i == '\t') {
|
||||
IS_SEPARATOR[i] = true;
|
||||
}
|
||||
|
||||
// Token: Anything 0-127 that is not a control and not a separator
|
||||
if (!IS_CONTROL[i] && !IS_SEPARATOR[i] && i < 128) {
|
||||
IS_TOKEN[i] = true;
|
||||
}
|
||||
|
||||
// Hex: 0-9, a-f, A-F
|
||||
if ((i >= '0' && i <='9') || (i >= 'a' && i <= 'f') || (i >= 'A' && i <= 'F')) {
|
||||
IS_HEX[i] = true;
|
||||
}
|
||||
|
||||
// Not valid for HTTP protocol
|
||||
// "HTTP/" DIGIT "." DIGIT
|
||||
if (i == 'H' || i == 'T' || i == 'P' || i == '/' || i == '.' || (i >= '0' && i <= '9')) {
|
||||
IS_HTTP_PROTOCOL[i] = true;
|
||||
}
|
||||
|
||||
if (i >= '0' && i <= '9') {
|
||||
IS_NUMERIC[i] = true;
|
||||
}
|
||||
|
||||
if (i >= 'a' && i <= 'z' || i >= 'A' && i <= 'Z') {
|
||||
IS_ALPHA[i] = true;
|
||||
}
|
||||
|
||||
if (IS_ALPHA[i] || IS_NUMERIC[i] || i == '-' || i == '.' || i == '_' || i == '~') {
|
||||
IS_UNRESERVED[i] = true;
|
||||
}
|
||||
|
||||
if (i == '!' || i == '$' || i == '&' || i == '\'' || i == '(' || i == ')' || i == '*' ||
|
||||
i == '+' || i == ',' || i == ';' || i == '=') {
|
||||
IS_SUBDELIM[i] = true;
|
||||
}
|
||||
|
||||
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
|
||||
if (IS_UNRESERVED[i] || i == '%' || IS_SUBDELIM[i] || i == ':') {
|
||||
IS_USERINFO[i] = true;
|
||||
}
|
||||
|
||||
// The characters that are normally not permitted for which the
|
||||
// restrictions may be relaxed when used in the path and/or query
|
||||
// string
|
||||
if (i == '\"' || i == '<' || i == '>' || i == '[' || i == '\\' || i == ']' ||
|
||||
i == '^' || i == '`' || i == '{' || i == '|' || i == '}') {
|
||||
IS_RELAXABLE[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
String prop = System.getProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow");
|
||||
if (prop != null) {
|
||||
for (int i = 0; i < prop.length(); i++) {
|
||||
char c = prop.charAt(i);
|
||||
if (c == '{' || c == '}' || c == '|') {
|
||||
REQUEST_TARGET_ALLOW[c] = true;
|
||||
} else {
|
||||
log.warn(sm.getString("http.invalidRequestTargetCharacter",
|
||||
Character.valueOf(c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEFAULT = new HttpParser(null, null);
|
||||
}
|
||||
|
||||
|
||||
private final boolean[] IS_NOT_REQUEST_TARGET = new boolean[ARRAY_SIZE];
|
||||
private final boolean[] IS_ABSOLUTEPATH_RELAXED = new boolean[ARRAY_SIZE];
|
||||
private final boolean[] IS_QUERY_RELAXED = new boolean[ARRAY_SIZE];
|
||||
|
||||
|
||||
public HttpParser(String relaxedPathChars, String relaxedQueryChars) {
|
||||
for (int i = 0; i < ARRAY_SIZE; i++) {
|
||||
// Not valid for request target.
|
||||
// Combination of multiple rules from RFC7230 and RFC 3986. Must be
|
||||
// ASCII, no controls plus a few additional characters excluded
|
||||
if (IS_CONTROL[i] ||
|
||||
i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' ||
|
||||
i == '^' || i == '`' || i == '{' || i == '|' || i == '}') {
|
||||
if (!REQUEST_TARGET_ALLOW[i]) {
|
||||
IS_NOT_REQUEST_TARGET[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* absolute-path = 1*( "/" segment )
|
||||
* segment = *pchar
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
*
|
||||
* Note pchar allows everything userinfo allows plus "@"
|
||||
*/
|
||||
if (IS_USERINFO[i] || i == '@' || i == '/' || REQUEST_TARGET_ALLOW[i]) {
|
||||
IS_ABSOLUTEPATH_RELAXED[i] = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* query = *( pchar / "/" / "?" )
|
||||
*
|
||||
* Note query allows everything absolute-path allows plus "?"
|
||||
*/
|
||||
if (IS_ABSOLUTEPATH_RELAXED[i] || i == '?' || REQUEST_TARGET_ALLOW[i]) {
|
||||
IS_QUERY_RELAXED[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
relax(IS_ABSOLUTEPATH_RELAXED, relaxedPathChars);
|
||||
relax(IS_QUERY_RELAXED, relaxedQueryChars);
|
||||
}
|
||||
|
||||
|
||||
public boolean isNotRequestTargetRelaxed(int c) {
|
||||
// Fast for valid request target characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_NOT_REQUEST_TARGET[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isAbsolutePathRelaxed(int c) {
|
||||
// Fast for valid user info characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_ABSOLUTEPATH_RELAXED[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isQueryRelaxed(int c) {
|
||||
// Fast for valid user info characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_QUERY_RELAXED[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String unquote(String input) {
|
||||
if (input == null || input.length() < 2) {
|
||||
return input;
|
||||
}
|
||||
|
||||
int start;
|
||||
int end;
|
||||
|
||||
// Skip surrounding quotes if there are any
|
||||
if (input.charAt(0) == '"') {
|
||||
start = 1;
|
||||
end = input.length() - 1;
|
||||
} else {
|
||||
start = 0;
|
||||
end = input.length();
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = start ; i < end; i++) {
|
||||
char c = input.charAt(i);
|
||||
if (input.charAt(i) == '\\') {
|
||||
i++;
|
||||
result.append(input.charAt(i));
|
||||
} else {
|
||||
result.append(c);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
public static boolean isToken(int c) {
|
||||
// Fast for correct values, slower for incorrect ones
|
||||
try {
|
||||
return IS_TOKEN[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isHex(int c) {
|
||||
// Fast for correct values, slower for some incorrect ones
|
||||
try {
|
||||
return IS_HEX[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isNotRequestTarget(int c) {
|
||||
return DEFAULT.isNotRequestTargetRelaxed(c);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isHttpProtocol(int c) {
|
||||
// Fast for valid HTTP protocol characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_HTTP_PROTOCOL[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAlpha(int c) {
|
||||
// Fast for valid alpha characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_ALPHA[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isNumeric(int c) {
|
||||
// Fast for valid numeric characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_NUMERIC[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isUserInfo(int c) {
|
||||
// Fast for valid user info characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_USERINFO[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean isRelaxable(int c) {
|
||||
// Fast for valid user info characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_RELAXABLE[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAbsolutePath(int c) {
|
||||
return DEFAULT.isAbsolutePathRelaxed(c);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isQuery(int c) {
|
||||
return DEFAULT.isQueryRelaxed(c);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isControl(int c) {
|
||||
// Fast for valid control characters, slower for some incorrect
|
||||
// ones
|
||||
try {
|
||||
return IS_CONTROL[c];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Skip any LWS and position to read the next character. The next character
|
||||
// is returned as being able to 'peek()' it allows a small optimisation in
|
||||
// some cases.
|
||||
static int skipLws(Reader input) throws IOException {
|
||||
|
||||
input.mark(1);
|
||||
int c = input.read();
|
||||
|
||||
while (c == 32 || c == 9 || c == 10 || c == 13) {
|
||||
input.mark(1);
|
||||
c = input.read();
|
||||
}
|
||||
|
||||
input.reset();
|
||||
return c;
|
||||
}
|
||||
|
||||
static SkipResult skipConstant(Reader input, String constant) throws IOException {
|
||||
int len = constant.length();
|
||||
|
||||
skipLws(input);
|
||||
input.mark(len);
|
||||
int c = input.read();
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i == 0 && c == -1) {
|
||||
return SkipResult.EOF;
|
||||
}
|
||||
if (c != constant.charAt(i)) {
|
||||
input.reset();
|
||||
return SkipResult.NOT_FOUND;
|
||||
}
|
||||
if (i != (len - 1)) {
|
||||
c = input.read();
|
||||
}
|
||||
}
|
||||
return SkipResult.FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the token if one was found, the empty string if no data was
|
||||
* available to read or <code>null</code> if data other than a
|
||||
* token was found
|
||||
*/
|
||||
static String readToken(Reader input) throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
skipLws(input);
|
||||
input.mark(1);
|
||||
int c = input.read();
|
||||
|
||||
while (c != -1 && isToken(c)) {
|
||||
result.append((char) c);
|
||||
input.mark(1);
|
||||
c = input.read();
|
||||
}
|
||||
// Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
|
||||
// once the end of the String has been reached.
|
||||
input.reset();
|
||||
|
||||
if (c != -1 && result.length() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the digits if any were found, the empty string if no data was
|
||||
* found or if data other than digits was found
|
||||
*/
|
||||
static String readDigits(Reader input) throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
skipLws(input);
|
||||
input.mark(1);
|
||||
int c = input.read();
|
||||
|
||||
while (c != -1 && isNumeric(c)) {
|
||||
result.append((char) c);
|
||||
input.mark(1);
|
||||
c = input.read();
|
||||
}
|
||||
// Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
|
||||
// once the end of the String has been reached.
|
||||
input.reset();
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number if digits were found, -1 if no data was found
|
||||
* or if data other than digits was found
|
||||
*/
|
||||
static long readLong(Reader input) throws IOException {
|
||||
String digits = readDigits(input);
|
||||
|
||||
if (digits.length() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Long.parseLong(digits);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the quoted string if one was found, null if data other than a
|
||||
* quoted string was found or null if the end of data was reached
|
||||
* before the quoted string was terminated
|
||||
*/
|
||||
static String readQuotedString(Reader input, boolean returnQuoted) throws IOException {
|
||||
|
||||
skipLws(input);
|
||||
int c = input.read();
|
||||
|
||||
if (c != '"') {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (returnQuoted) {
|
||||
result.append('\"');
|
||||
}
|
||||
c = input.read();
|
||||
|
||||
while (c != '"') {
|
||||
if (c == -1) {
|
||||
return null;
|
||||
} else if (c == '\\') {
|
||||
c = input.read();
|
||||
if (returnQuoted) {
|
||||
result.append('\\');
|
||||
}
|
||||
result.append((char) c);
|
||||
} else {
|
||||
result.append((char) c);
|
||||
}
|
||||
c = input.read();
|
||||
}
|
||||
if (returnQuoted) {
|
||||
result.append('\"');
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
static String readTokenOrQuotedString(Reader input, boolean returnQuoted)
|
||||
throws IOException {
|
||||
|
||||
// Peek at next character to enable correct method to be called
|
||||
int c = skipLws(input);
|
||||
|
||||
if (c == '"') {
|
||||
return readQuotedString(input, returnQuoted);
|
||||
} else {
|
||||
return readToken(input);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Token can be read unambiguously with or without surrounding quotes so
|
||||
* this parsing method for token permits optional surrounding double quotes.
|
||||
* This is not defined in any RFC. It is a special case to handle data from
|
||||
* buggy clients (known buggy clients for DIGEST auth include Microsoft IE 8
|
||||
* & 9, Apple Safari for OSX and iOS) that add quotes to values that
|
||||
* should be tokens.
|
||||
*
|
||||
* @return the token if one was found, null if data other than a token or
|
||||
* quoted token was found or null if the end of data was reached
|
||||
* before a quoted token was terminated
|
||||
*/
|
||||
static String readQuotedToken(Reader input) throws IOException {
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
boolean quoted = false;
|
||||
|
||||
skipLws(input);
|
||||
input.mark(1);
|
||||
int c = input.read();
|
||||
|
||||
if (c == '"') {
|
||||
quoted = true;
|
||||
} else if (c == -1 || !isToken(c)) {
|
||||
return null;
|
||||
} else {
|
||||
result.append((char) c);
|
||||
}
|
||||
input.mark(1);
|
||||
c = input.read();
|
||||
|
||||
while (c != -1 && isToken(c)) {
|
||||
result.append((char) c);
|
||||
input.mark(1);
|
||||
c = input.read();
|
||||
}
|
||||
|
||||
if (quoted) {
|
||||
if (c != '"') {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
|
||||
// once the end of the String has been reached.
|
||||
input.reset();
|
||||
}
|
||||
|
||||
if (c != -1 && result.length() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LHEX can be read unambiguously with or without surrounding quotes so this
|
||||
* parsing method for LHEX permits optional surrounding double quotes. Some
|
||||
* buggy clients (libwww-perl for DIGEST auth) are known to send quoted LHEX
|
||||
* when the specification requires just LHEX.
|
||||
*
|
||||
* <p>
|
||||
* LHEX are, literally, lower-case hexadecimal digits. This implementation
|
||||
* allows for upper-case digits as well, converting the returned value to
|
||||
* lower-case.
|
||||
*
|
||||
* @return the sequence of LHEX (minus any surrounding quotes) if any was
|
||||
* found, or <code>null</code> if data other LHEX was found
|
||||
*/
|
||||
static String readLhex(Reader input) throws IOException {
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
boolean quoted = false;
|
||||
|
||||
skipLws(input);
|
||||
input.mark(1);
|
||||
int c = input.read();
|
||||
|
||||
if (c == '"') {
|
||||
quoted = true;
|
||||
} else if (c == -1 || !isHex(c)) {
|
||||
return null;
|
||||
} else {
|
||||
if ('A' <= c && c <= 'F') {
|
||||
c -= ('A' - 'a');
|
||||
}
|
||||
result.append((char) c);
|
||||
}
|
||||
input.mark(1);
|
||||
c = input.read();
|
||||
|
||||
while (c != -1 && isHex(c)) {
|
||||
if ('A' <= c && c <= 'F') {
|
||||
c -= ('A' - 'a');
|
||||
}
|
||||
result.append((char) c);
|
||||
input.mark(1);
|
||||
c = input.read();
|
||||
}
|
||||
|
||||
if (quoted) {
|
||||
if (c != '"') {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
|
||||
// once the end of the String has been reached.
|
||||
input.reset();
|
||||
}
|
||||
|
||||
if (c != -1 && result.length() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static double readWeight(Reader input, char delimiter) throws IOException {
|
||||
skipLws(input);
|
||||
int c = input.read();
|
||||
if (c == -1 || c == delimiter) {
|
||||
// No q value just whitespace
|
||||
return 1;
|
||||
} else if (c != 'q') {
|
||||
// Malformed. Use quality of zero so it is dropped.
|
||||
skipUntil(input, c, delimiter);
|
||||
return 0;
|
||||
}
|
||||
// RFC 7231 does not allow whitespace here but be tolerant
|
||||
skipLws(input);
|
||||
c = input.read();
|
||||
if (c != '=') {
|
||||
// Malformed. Use quality of zero so it is dropped.
|
||||
skipUntil(input, c, delimiter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// RFC 7231 does not allow whitespace here but be tolerant
|
||||
skipLws(input);
|
||||
c = input.read();
|
||||
|
||||
// Should be no more than 3 decimal places
|
||||
StringBuilder value = new StringBuilder(5);
|
||||
int decimalPlacesRead = -1;
|
||||
|
||||
if (c == '0' || c == '1') {
|
||||
value.append((char) c);
|
||||
c = input.read();
|
||||
|
||||
while (true) {
|
||||
if (decimalPlacesRead == -1 && c == '.') {
|
||||
value.append('.');
|
||||
decimalPlacesRead = 0;
|
||||
} else if (decimalPlacesRead > -1 && c >= '0' && c <= '9') {
|
||||
if (decimalPlacesRead < 3) {
|
||||
value.append((char) c);
|
||||
decimalPlacesRead++;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
c = input.read();
|
||||
}
|
||||
} else {
|
||||
// Malformed. Use quality of zero so it is dropped and skip until
|
||||
// EOF or the next delimiter
|
||||
skipUntil(input, c, delimiter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (c == 9 || c == 32) {
|
||||
skipLws(input);
|
||||
c = input.read();
|
||||
}
|
||||
|
||||
// Must be at delimiter or EOF
|
||||
if (c != delimiter && c != -1) {
|
||||
// Malformed. Use quality of zero so it is dropped and skip until
|
||||
// EOF or the next delimiter
|
||||
skipUntil(input, c, delimiter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
double result = Double.parseDouble(value.toString());
|
||||
if (result > 1) {
|
||||
return 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return If inIPv6 is false, the position of ':' that separates the host
|
||||
* from the port or -1 if it is not present. If inIPv6 is true, the
|
||||
* number of characters read
|
||||
*/
|
||||
static int readHostIPv4(Reader reader, boolean inIPv6) throws IOException {
|
||||
int octet = -1;
|
||||
int octetCount = 1;
|
||||
int c;
|
||||
int pos = 0;
|
||||
|
||||
// readAheadLimit doesn't matter as all the readers passed to this
|
||||
// method buffer the entire content.
|
||||
reader.mark(1);
|
||||
do {
|
||||
c = reader.read();
|
||||
if (c == '.') {
|
||||
if (octet > -1 && octet < 256) {
|
||||
// Valid
|
||||
octetCount++;
|
||||
octet = -1;
|
||||
} else if (inIPv6 || octet == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("http.invalidOctet", Integer.toString(octet)));
|
||||
} else {
|
||||
// Might not be an IPv4 address. Could be a host / FQDN with
|
||||
// a fully numeric component.
|
||||
reader.reset();
|
||||
return readHostDomainName(reader);
|
||||
}
|
||||
} else if (isNumeric(c)) {
|
||||
if (octet == -1) {
|
||||
octet = c - '0';
|
||||
} else if (octet == 0) {
|
||||
// Leading zero in non-zero octet. Not valid (ambiguous).
|
||||
if (inIPv6) {
|
||||
throw new IllegalArgumentException(sm.getString("http.invalidLeadingZero"));
|
||||
} else {
|
||||
// Could be a host/FQDN
|
||||
reader.reset();
|
||||
return readHostDomainName(reader);
|
||||
}
|
||||
} else {
|
||||
octet = octet * 10 + c - '0';
|
||||
}
|
||||
} else if (c == ':') {
|
||||
break;
|
||||
} else if (c == -1) {
|
||||
if (inIPv6) {
|
||||
throw new IllegalArgumentException(sm.getString("http.noClosingBracket"));
|
||||
} else {
|
||||
pos = -1;
|
||||
break;
|
||||
}
|
||||
} else if (c == ']') {
|
||||
if (inIPv6) {
|
||||
pos++;
|
||||
break;
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString("http.closingBracket"));
|
||||
}
|
||||
} else if (!inIPv6 && (isAlpha(c) || c == '-')) {
|
||||
// Go back to the start and parse as a host / FQDN
|
||||
reader.reset();
|
||||
return readHostDomainName(reader);
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"http.illegalCharacterIpv4", Character.toString((char) c)));
|
||||
}
|
||||
pos++;
|
||||
} while (true);
|
||||
|
||||
if (octetCount != 4 || octet < 0 || octet > 255) {
|
||||
// Might not be an IPv4 address. Could be a host name or a FQDN with
|
||||
// fully numeric components. Go back to the start and parse as a
|
||||
// host / FQDN.
|
||||
reader.reset();
|
||||
return readHostDomainName(reader);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return The position of ':' that separates the host from the port or -1
|
||||
* if it is not present
|
||||
*/
|
||||
static int readHostIPv6(Reader reader) throws IOException {
|
||||
// Must start with '['
|
||||
int c = reader.read();
|
||||
if (c != '[') {
|
||||
throw new IllegalArgumentException(sm.getString("http.noOpeningBracket"));
|
||||
}
|
||||
|
||||
int h16Count = 0;
|
||||
int h16Size = 0;
|
||||
int pos = 1;
|
||||
boolean parsedDoubleColon = false;
|
||||
int precedingColonsCount = 0;
|
||||
|
||||
do {
|
||||
c = reader.read();
|
||||
if (h16Count == 0 && precedingColonsCount == 1 && c != ':') {
|
||||
// Can't start with a single :
|
||||
throw new IllegalArgumentException(sm.getString("http.singleColonStart"));
|
||||
}
|
||||
if (HttpParser.isHex(c)) {
|
||||
if (h16Size == 0) {
|
||||
// Start of a new h16 block
|
||||
precedingColonsCount = 0;
|
||||
h16Count++;
|
||||
}
|
||||
h16Size++;
|
||||
if (h16Size > 4) {
|
||||
throw new IllegalArgumentException(sm.getString("http.invalidHextet"));
|
||||
}
|
||||
} else if (c == ':') {
|
||||
if (precedingColonsCount >=2 ) {
|
||||
// ::: is not allowed
|
||||
throw new IllegalArgumentException(sm.getString("http.tooManyColons"));
|
||||
} else {
|
||||
if(precedingColonsCount == 1) {
|
||||
// End of ::
|
||||
if (parsedDoubleColon ) {
|
||||
// Only allowed one :: sequence
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("http.tooManyDoubleColons"));
|
||||
}
|
||||
parsedDoubleColon = true;
|
||||
// :: represents at least one h16 block
|
||||
h16Count++;
|
||||
}
|
||||
precedingColonsCount++;
|
||||
// mark if the next symbol is hex before the actual read
|
||||
reader.mark(4);
|
||||
}
|
||||
h16Size = 0;
|
||||
} else if (c == ']') {
|
||||
if (precedingColonsCount == 1) {
|
||||
// Can't end on a single ':'
|
||||
throw new IllegalArgumentException(sm.getString("http.singleColonEnd"));
|
||||
}
|
||||
pos++;
|
||||
break;
|
||||
} else if (c == '.') {
|
||||
if (h16Count == 7 || h16Count < 7 && parsedDoubleColon) {
|
||||
reader.reset();
|
||||
pos -= h16Size;
|
||||
pos += readHostIPv4(reader, true);
|
||||
h16Count++;
|
||||
break;
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString("http.invalidIpv4Location"));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"http.illegalCharacterIpv6", Character.toString((char) c)));
|
||||
}
|
||||
pos++;
|
||||
} while (true);
|
||||
|
||||
if (h16Count > 8) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("http.tooManyHextets", Integer.toString(h16Count)));
|
||||
} else if (h16Count != 8 && !parsedDoubleColon) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("http.tooFewHextets", Integer.toString(h16Count)));
|
||||
}
|
||||
|
||||
c = reader.read();
|
||||
if (c == ':') {
|
||||
return pos;
|
||||
} else {
|
||||
if(c == -1) {
|
||||
return -1;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("http.illegalAfterIpv6", Character.toString((char) c)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The position of ':' that separates the host from the port or -1
|
||||
* if it is not present
|
||||
*/
|
||||
static int readHostDomainName(Reader reader) throws IOException {
|
||||
DomainParseState state = DomainParseState.NEW;
|
||||
int pos = 0;
|
||||
|
||||
while (state.mayContinue()) {
|
||||
state = state.next(reader.read());
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (DomainParseState.COLON == state) {
|
||||
// State identifies the state of the previous character
|
||||
return pos - 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Skips all characters until EOF or the specified target is found. Normally
|
||||
* used to skip invalid input until the next separator.
|
||||
*/
|
||||
static SkipResult skipUntil(Reader input, int c, char target) throws IOException {
|
||||
while (c != -1 && c != target) {
|
||||
c = input.read();
|
||||
}
|
||||
if (c == -1) {
|
||||
return SkipResult.EOF;
|
||||
} else {
|
||||
return SkipResult.FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void relax(boolean[] flags, String relaxedChars) {
|
||||
if (relaxedChars != null && relaxedChars.length() > 0) {
|
||||
char[] chars = relaxedChars.toCharArray();
|
||||
for (char c : chars) {
|
||||
if (isRelaxable(c)) {
|
||||
flags[c] = true;
|
||||
IS_NOT_REQUEST_TARGET[c] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum DomainParseState {
|
||||
NEW( true, false, false, false, "http.invalidCharacterDomain.atStart"),
|
||||
ALPHA( true, true, true, true, "http.invalidCharacterDomain.afterLetter"),
|
||||
NUMERIC( true, true, true, true, "http.invalidCharacterDomain.afterNumber"),
|
||||
PERIOD( true, false, false, true, "http.invalidCharacterDomain.afterPeriod"),
|
||||
HYPHEN( true, true, false, false, "http.invalidCharacterDomain.afterHyphen"),
|
||||
COLON( false, false, false, false, "http.invalidCharacterDomain.afterColon"),
|
||||
END( false, false, false, false, "http.invalidCharacterDomain.atEnd");
|
||||
|
||||
private final boolean mayContinue;
|
||||
private final boolean allowsHyphen;
|
||||
private final boolean allowsPeriod;
|
||||
private final boolean allowsEnd;
|
||||
private final String errorMsg;
|
||||
|
||||
private DomainParseState(boolean mayContinue, boolean allowsHyphen, boolean allowsPeriod,
|
||||
boolean allowsEnd, String errorMsg) {
|
||||
this.mayContinue = mayContinue;
|
||||
this.allowsHyphen = allowsHyphen;
|
||||
this.allowsPeriod = allowsPeriod;
|
||||
this.allowsEnd = allowsEnd;
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
public boolean mayContinue() {
|
||||
return mayContinue;
|
||||
}
|
||||
|
||||
public DomainParseState next(int c) {
|
||||
if (c == -1) {
|
||||
if (allowsEnd) {
|
||||
return END;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("http.invalidSegmentEndState", this.name()));
|
||||
}
|
||||
} else if (HttpParser.isAlpha(c)) {
|
||||
return ALPHA;
|
||||
} else if (HttpParser.isNumeric(c)) {
|
||||
return NUMERIC;
|
||||
} else if (c == '.') {
|
||||
if (allowsPeriod) {
|
||||
return PERIOD;
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString(errorMsg,
|
||||
Character.toString((char) c)));
|
||||
}
|
||||
} else if (c == ':') {
|
||||
if (allowsEnd) {
|
||||
return COLON;
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString(errorMsg,
|
||||
Character.toString((char) c)));
|
||||
}
|
||||
} else if (c == '-') {
|
||||
if (allowsHyphen) {
|
||||
return HYPHEN;
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString(errorMsg,
|
||||
Character.toString((char) c)));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"http.illegalCharacterDomain", Character.toString((char) c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
authorization.unknownType=Unknown Type [{0}]
|
||||
|
||||
cookie.fallToDebug=\n\
|
||||
\ Note: further occurrences of this error will be logged at DEBUG level.
|
||||
cookie.invalidCookieValue=A cookie header was received [{0}] that contained an invalid cookie. That cookie will be ignored.
|
||||
cookie.invalidCookieVersion=A cookie header was received using an unrecognised cookie version of [{0}]. The header and the cookies it contains will be ignored.
|
||||
cookie.valueNotPresent=<not present>
|
||||
|
||||
http.closingBracket=A closing bracket ']' was found in a non-IPv6 host name.
|
||||
http.illegalAfterIpv6=The character [{0}] is not permitted to follow an IPv6 address in a host name
|
||||
http.illegalCharacterDomain=The character [{0}] is never valid in a domain name.
|
||||
http.illegalCharacterIpv4=The character [{0}] is never valid in an IPv4 address.
|
||||
http.illegalCharacterIpv6=The character [{0}] is never valid in an IPv6 address.
|
||||
http.invalidCharacterDomain.afterColon=The character [{0}] is not valid after a colon in a domain name.
|
||||
http.invalidCharacterDomain.afterHyphen=The character [{0}] is not valid after a hyphen in a domain name.
|
||||
http.invalidCharacterDomain.afterLetter=The character [{0}] is not valid after a letter in a domain name.
|
||||
http.invalidCharacterDomain.afterNumber=The character [{0}] is not valid after a number in a domain name.
|
||||
http.invalidCharacterDomain.afterPeriod=The character [{0}] is not valid after a period in a domain name.
|
||||
http.invalidCharacterDomain.atEnd=The character [{0}] is not valid at the end of a domain name.
|
||||
http.invalidCharacterDomain.atStart=The character [{0}] is not valid at the start of a domain name.
|
||||
http.invalidHextet=Invalid hextet. A hextet must consist of 4 or less hex characters.
|
||||
http.invalidIpv4Location=The IPv6 address contains an embedded IPv4 address at an invalid location.
|
||||
http.invalidLeadingZero=A non-zero IPv4 octet may not contain a leading zero.
|
||||
http.invalidOctet=Invalid octet [{0}]. The valid range for IPv4 octets is 0 to 255.
|
||||
http.invalidRequestTargetCharacter=Character [{0}] is not allowed and will continue to be rejected.
|
||||
http.invalidSegmentEndState=The state [{0}] is not valid for the end of a segment.
|
||||
http.noClosingBracket=The IPv6 address is missing a closing bracket.
|
||||
http.noOpeningBracket=The IPv6 address is missing an opening bracket.
|
||||
http.singleColonEnd=An IPv6 address may not end with a single ':'.
|
||||
http.singleColonStart=An IPv6 address may not start with a single ':'.
|
||||
http.tooFewHextets=An IPv6 address must consist of 8 hextets but this address contains [{0}] hextets and no ''::'' sequence to represent one or more zero hextets.
|
||||
http.tooManyColons=An IPv6 address may not contain more than 2 sequential colon characters.
|
||||
http.tooManyDoubleColons=An IPv6 address may only contain a single '::' sequence.
|
||||
http.tooManyHextets=The IPv6 address contains [{0}] hextets but a valid IPv6 address may not have more than 8.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cookie.valueNotPresent=<nicht vorhanden>
|
||||
|
||||
http.illegalCharacterIpv4=Das Zeichen [{0}] ist in einer IPv4-Adresse niemals erlaubt.
|
||||
http.invalidHextet=Ungültiges Hextet. Ein Hextet muss aus 4 oder weniger Hexadecimalzeichen bestehen.
|
||||
http.invalidIpv4Location=Die IPV6-Adresse enthält eine eingebettete IPv4-Adresse an einer ungültigen Position.
|
||||
http.invalidOctet=Invalides Oktett [{0}]. Der gültige Bereich für IPv4 Oktette geht von 0 bis 255.
|
||||
http.invalidSegmentEndState=Der Zustand [{0}] ist nicht gültig für das Ende eines Segments.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cookie.valueNotPresent=<no presente>
|
||||
|
||||
http.illegalCharacterIpv4=El caracter [{0}] nunca es válido en una dirección IPv4.\n
|
||||
http.illegalCharacterIpv6=El caracter [{0}] nunca es válido en una dirección IPv6.\n
|
||||
http.invalidHextet=Hextet no válido. Hextet debe consistir de 4 caracteres hexadecimales o menos.
|
||||
http.singleColonEnd=Una dirección IPv6 no puede terminar con solo un ':'.\n
|
||||
http.tooManyColons=Una dirección IPv6 no puede contener más de 2 caracteres ":" seguidos
|
||||
@@ -0,0 +1,45 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cookie.fallToDebug=\ Note: toutes les occurrences suivantes de cette erreur seront enregistrées au niveau DEBUG
|
||||
cookie.invalidCookieValue=Un en-tête de cookie a été reçu [{0}] qui contenait un cookie invalide, celui ci sera ignoré
|
||||
cookie.invalidCookieVersion=Un en-tête de cookie a été reçu utilisant une version [{0}] non reconnue, les cookies seront ignorés
|
||||
cookie.valueNotPresent=<non présent>
|
||||
|
||||
http.closingBracket=Un crochet ']' a été trouvé dans un nom d'hôte non IPv6
|
||||
http.illegalAfterIpv6=Le caractère [{0}] n''est pas permis dans un nom d''hôte à la suite d''une adresse IPv6
|
||||
http.illegalCharacterDomain=Le caractère [{0}] n''est jamais valide pour un nom de domaine
|
||||
http.illegalCharacterIpv4=Le caractère [{0}] n''est pas valide pour une adresse IPV4.
|
||||
http.illegalCharacterIpv6=Le caractère [{0}] n''est jamais valide dans une adresse IPv6
|
||||
http.invalidCharacterDomain.afterColon=Le caractère [{0}] n''est pas valide après deux-point pour un nom de domaine
|
||||
http.invalidCharacterDomain.afterHyphen=Le caractère [{0}] n''est pas valide après un trait d''union pour un nom de domaine
|
||||
http.invalidCharacterDomain.afterLetter=Le caractère [{0}] n''est pas valide après une lettre pour un nom de domaine
|
||||
http.invalidCharacterDomain.afterNumber=Le caractère [{0}] n''est pas valide après un nombre pour un nom de domaine
|
||||
http.invalidCharacterDomain.afterPeriod=Le caractère [{0}] n''est pas valide après une virgule pour un nom de domaine
|
||||
http.invalidCharacterDomain.atEnd=Le caractère [{0}] n''est pas valide à la fin d''un nom de domaine
|
||||
http.invalidCharacterDomain.atStart=Le caractère [{0}] n''est pas valide au début d''un nom de domaine
|
||||
http.invalidHextet="hextet" invalide. Un "hextet" doit consister au maximum de 4 caractères hexadécimaux.
|
||||
http.invalidIpv4Location=L'adresse IPv6 contient une adresse IPv4 incluse à un endroit invalide
|
||||
http.invalidLeadingZero=Un octet IPv4 non nul ne doit pas commencer par un zéro
|
||||
http.invalidOctet=Octet [{0}] invalide. L''éventail valide pour les octets IPv4 est 0-255.
|
||||
http.invalidSegmentEndState=L''état [{0}] n''est pas valide à la fin d''un segment
|
||||
http.noClosingBracket=L'adresse IPv6 n'a pas de crochet de fermeture
|
||||
http.noOpeningBracket=Cette adresse IPv6 n'a pas de crochet d'ouverture '['
|
||||
http.singleColonEnd=Une adresse IPv6 ne doit pas se terminer par un seul ':'
|
||||
http.singleColonStart=Une adresse IPv6 ne doit pas commencer par un seul ':'
|
||||
http.tooFewHextets=Une adresse IPv6 doit être constitué de 8 groupes de 4 octets mais cette adresse en contient [{0}] et pas de séquence "::" pour représenter un ou plusieurs groupes de 4 octets
|
||||
http.tooManyColons=Une adresse IPv6 ne peut pas contenir plus de deux caractères deux-points à la suite
|
||||
http.tooManyDoubleColons=Une adresse IPv6 ne peut contenir qu'une seule séquence "::"
|
||||
http.tooManyHextets=L''adresse IPv6 contient [{0}] groupes de 4 octets mais une adresse IPv6 valide ne doit pas en avoir plus de 8
|
||||
@@ -0,0 +1,45 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cookie.fallToDebug=注:さらなるこのエラーの発生はDEBUGレベルで記録されます。
|
||||
cookie.invalidCookieValue=無効なCookieを含むCookieヘッダーが受信されました[{0}]。 そのクッキーは無視されます。
|
||||
cookie.invalidCookieVersion=[{0}]の認識できないクッキーバージョンを使用して、Cookieヘッダーが受信されました。 ヘッダーとそれに含まれるクッキーは無視されます。
|
||||
cookie.valueNotPresent=<値が存在しません>
|
||||
|
||||
http.closingBracket=非IPv6ホスト名に閉じ括弧 ']'が見つかりました。
|
||||
http.illegalAfterIpv6=文字[{0}]はホスト名のIPv6アドレスに従うことはできません。
|
||||
http.illegalCharacterDomain=文字 [{0}] をドメイン名に含めることはできません。
|
||||
http.illegalCharacterIpv4=文字 [{0}] は正常な IPv4 アドレスに利用できません。
|
||||
http.illegalCharacterIpv6=IPv6 アドレスに文字 [{0}] を使用することはできません。
|
||||
http.invalidCharacterDomain.afterColon=ドメイン名のコロンの後の文字[{0}]は無効です。
|
||||
http.invalidCharacterDomain.afterHyphen=ドメイン名のハイフンの後の文字[{0}]は無効です
|
||||
http.invalidCharacterDomain.afterLetter=文字 [{0}] はドメイン名に利用できません。
|
||||
http.invalidCharacterDomain.afterNumber=ドメイン名の数字の後の文字[{0}]は無効です。
|
||||
http.invalidCharacterDomain.afterPeriod=ドメイン名のピリオドの後の文字[{0}]は無効です。
|
||||
http.invalidCharacterDomain.atEnd=文字[{0}]はドメイン名の最後には無効です。
|
||||
http.invalidCharacterDomain.atStart=文字[{0}]はドメイン名の先頭には無効です。
|
||||
http.invalidHextet=不正な 16 進数文字列です。16 進数文字列に使用できるのは 4 文字以下の 16 進数だけです。
|
||||
http.invalidIpv4Location=IPv6 アドレスは不正な位置に埋め込み IPv4 アドレスを含んでいます。
|
||||
http.invalidLeadingZero=IPv4 アドレスの 0 でないオクテットは先行する0を含まないかもしれません。
|
||||
http.invalidOctet=無効なオクテット[{0}]。 IPv4オクテットの有効範囲は0〜255です。
|
||||
http.invalidSegmentEndState=状態[{0}]はセグメントの最後には無効です。
|
||||
http.noClosingBracket=IPv6アドレスに閉じ括弧がありません。
|
||||
http.noOpeningBracket=IPv6 アドレスに開き括弧がありません。
|
||||
http.singleColonEnd=IPv6 アドレス文字列は単独のコロン (:) で終端してはなりません。
|
||||
http.singleColonStart=IPv6アドレスは単一の ':'で始まらない場合があります
|
||||
http.tooFewHextets=IPv6 アドレスは 8 個のヘクステットで構成しなければなりませんが [{0}] 個しかありません。また1つ以上のヘクステットを意味する "::" もありません。
|
||||
http.tooManyColons=IPv6 アドレスでは文字 : を 2 つ以上連続することはできません。
|
||||
http.tooManyDoubleColons=IPv6アドレスは単一の '::'シーケンスのみを含むことができます。
|
||||
http.tooManyHextets=IPv6 アドレスは [{0}] ヘクステットで構成されていますが、正常な IPv6 アドレスなら 8 ヘクステット以上になりません。
|
||||
@@ -0,0 +1,46 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cookie.fallToDebug=\n\
|
||||
비고: 이 오류가 더 발생하는 경우 DEBUG 레벨 로그로 기록될 것입니다.
|
||||
cookie.invalidCookieValue=유효하지 않은 쿠키가 포함된 쿠키 헤더 [{0}]을(를) 받았습니다. 이 쿠키는 무시될 것입니다.
|
||||
cookie.invalidCookieVersion=인식되지 않는 쿠키 버전 [{0}]을(를) 사용한 쿠키 헤더를 받았습니다. 해당 헤더와 그에 포함된 쿠키들은 무시될 것입니다.
|
||||
cookie.valueNotPresent=<not present>
|
||||
|
||||
http.closingBracket=닫는 대괄호(']')가 IPv6 이름이 아닌 호스트 이름에서 발견되었습니다.
|
||||
http.illegalAfterIpv6=호스트 이름 내에서, IPv6 주소 이후에 문자 [{0}]은(는) 허용되지 않습니다.
|
||||
http.illegalCharacterDomain=문자 [{0}]은(는) 도메인 이름 내에서 유효하지 않은 문자입니다.
|
||||
http.illegalCharacterIpv4=문자 [{0}]은(는) IPv4 주소에서 절대 유효하지 않은 것입니다.
|
||||
http.illegalCharacterIpv6=문자 [{0}]은(는) IPv6 주소 내에서 유효하지 않은 것입니다.
|
||||
http.invalidCharacterDomain.afterColon=도메인 이름 내에서, 콜론 이후의 문자 [{0}]은(는) 유효하지 않습니다.
|
||||
http.invalidCharacterDomain.afterHyphen=도메인 이름 내에서, 붙임표(하이픈) 이후의 문자 [{0}]은(는) 유효하지 않습니다.
|
||||
http.invalidCharacterDomain.afterLetter=도메인 이름 내에서, 한 글자 이후의 문자 [{0}]은(는) 유효하지 않습니다.
|
||||
http.invalidCharacterDomain.afterNumber=도메인 이름 내에서, 숫자 이후의 문자 [{0}]은(는) 유효하지 않습니다.
|
||||
http.invalidCharacterDomain.afterPeriod=도메인 이름 내에서, 마침표 이후의 문자 [{0}]은(는) 유효하지 않습니다.
|
||||
http.invalidCharacterDomain.atEnd=도메인 이름의 끝 위치에, 문자 [{0}]은(는) 유효하지 않습니다.
|
||||
http.invalidCharacterDomain.atStart=도메인 이름의 시작 위치에, 문자 [{0}]은(는) 유효하지 않습니다.
|
||||
http.invalidHextet=유효하지 않은 헥스텟(hextet)입니다. 헥스텟은 반드시 네 개 이하의 문자들이어야 합니다.
|
||||
http.invalidIpv4Location=IPv6 주소가, 유효하지 않은 위치에 내장 IPv4 주소를 포함하고 있습니다.
|
||||
http.invalidLeadingZero=IPv4 옥텟(octet)은, 값이 0이 아닌 이상, 0으로 시작해서는 안됩니다.
|
||||
http.invalidOctet=유효하지 않은 옥텟(octet) [{0}]. IPv4 옥텟의 유효한 범위는 0에서 255까지입니다.
|
||||
http.invalidSegmentEndState=상태 [{0}]은(는) segment의 끝으로 유효하지 않습니다.
|
||||
http.noClosingBracket=IPv6 주소에 닫는 대괄호가 없습니다.
|
||||
http.noOpeningBracket=IPv6 주소에 여는 대괄호가 없습니다.
|
||||
http.singleColonEnd=IPv6 주소는 단일 ':' 문자로 끝나서는 안됩니다.
|
||||
http.singleColonStart=IPv6 주소는 단일의 ':'으로 시작할 수 없습니다.
|
||||
http.tooFewHextets=IPv6 주소는 반드시 8개의 헥스텟(hextet)들로 이루어져야 하지만, 이 주소는 [{0}] 개의 헥스텟들으로 이루어져 있고, 하나 이상의 0 헥스텟들을 표시하기 위한 ''::'' 시퀀스도 존재하지 않습니다.
|
||||
http.tooManyColons=IPv6 주소는 연속으로 두 개를 초과한 콜론 문자('':'')들을 포함할 수 없습니다.
|
||||
http.tooManyDoubleColons=IPv6 주소는 단일한 '::' 시퀀스만을 포함해야 합니다.
|
||||
http.tooManyHextets=IPv6 주소가 [{0}]개의 헥스텟(hextet)들을 포함하고 있지만, 유효한 IPv6 주소는 8개를 초과할 수 없습니다.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cookie.valueNotPresent=<不存在>
|
||||
|
||||
http.closingBracket=在非IPv6主机名中找到了右括号']'。
|
||||
http.illegalCharacterIpv4=字符[{0}]为非法的IPv4地址。
|
||||
http.illegalCharacterIpv6=字符[{0}]为非法的IPv6地址。
|
||||
http.invalidCharacterDomain.afterColon=字符 [{0}] 在域名中的冒号后无效。
|
||||
http.invalidCharacterDomain.afterHyphen=字符 [{0}] 在域名中的连字符后无效。
|
||||
http.invalidCharacterDomain.afterLetter=字符 [{0}] 在域名中的字母后无效。
|
||||
http.invalidCharacterDomain.afterNumber=字符 [{0}] 在域名中的数字后无效。
|
||||
http.invalidCharacterDomain.afterPeriod=字符 [{0}] 在域名中的句号后无效。
|
||||
http.invalidCharacterDomain.atEnd=字符 [{0}] 在域名末尾无效。
|
||||
http.invalidCharacterDomain.atStart=字符 [{0}] 在域名开头无效。
|
||||
http.invalidHextet=hextet无效。 hextet必须包含4个或更少的十六进制字符。
|
||||
http.invalidIpv4Location=IPv6地址在无效位置包含嵌入的IPv4地址。
|
||||
http.invalidLeadingZero=非零的IPv4字符可能不包含前导零。
|
||||
http.invalidOctet=无效字符[{0}].IPv4字符的有效范围为0~255。
|
||||
http.invalidSegmentEndState=状态[{0}]对于段的结尾无效。
|
||||
http.noClosingBracket=ipv6 地址缺失一个闭合的圆括号
|
||||
http.noOpeningBracket=IPv6地址缺少开括号(
|
||||
http.singleColonEnd=IPv6地址不能以单个“.”结尾。
|
||||
http.singleColonStart=一个IPv6地址也许不是以单个冒号":"开头的。
|
||||
http.tooManyColons=IPv6地址不能包含超过2个连续冒号字符。
|
||||
http.tooManyDoubleColons=一个IPv6地址只能包含一个 '::' 序列。
|
||||
178
java/org/apache/tomcat/util/http/parser/MediaType.java
Normal file
178
java/org/apache/tomcat/util/http/parser/MediaType.java
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class MediaType {
|
||||
|
||||
private final String type;
|
||||
private final String subtype;
|
||||
private final LinkedHashMap<String,String> parameters;
|
||||
private final String charset;
|
||||
private volatile String noCharset;
|
||||
private volatile String withCharset;
|
||||
|
||||
protected MediaType(String type, String subtype, LinkedHashMap<String,String> parameters) {
|
||||
this.type = type;
|
||||
this.subtype = subtype;
|
||||
this.parameters = parameters;
|
||||
|
||||
String cs = parameters.get("charset");
|
||||
if (cs != null && cs.length() > 0 && cs.charAt(0) == '"') {
|
||||
cs = HttpParser.unquote(cs);
|
||||
}
|
||||
this.charset = cs;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public int getParameterCount() {
|
||||
return parameters.size();
|
||||
}
|
||||
|
||||
public String getParameterValue(String parameter) {
|
||||
return parameters.get(parameter.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (withCharset == null) {
|
||||
synchronized (this) {
|
||||
if (withCharset == null) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(type);
|
||||
result.append('/');
|
||||
result.append(subtype);
|
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||
String value = entry.getValue();
|
||||
if (value == null || value.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
result.append(';');
|
||||
// Workaround for Adobe Read 9 plug-in on IE bug
|
||||
// Can be removed after 26 June 2013 (EOL of Reader 9)
|
||||
// See BZ 53814
|
||||
result.append(' ');
|
||||
result.append(entry.getKey());
|
||||
result.append('=');
|
||||
result.append(value);
|
||||
}
|
||||
|
||||
withCharset = result.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return withCharset;
|
||||
}
|
||||
|
||||
public String toStringNoCharset() {
|
||||
if (noCharset == null) {
|
||||
synchronized (this) {
|
||||
if (noCharset == null) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(type);
|
||||
result.append('/');
|
||||
result.append(subtype);
|
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||
if (entry.getKey().equalsIgnoreCase("charset")) {
|
||||
continue;
|
||||
}
|
||||
result.append(';');
|
||||
// Workaround for Adobe Read 9 plug-in on IE bug
|
||||
// Can be removed after 26 June 2013 (EOL of Reader 9)
|
||||
// See BZ 53814
|
||||
result.append(' ');
|
||||
result.append(entry.getKey());
|
||||
result.append('=');
|
||||
result.append(entry.getValue());
|
||||
}
|
||||
|
||||
noCharset = result.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return noCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MediaType value, either from an HTTP header or from an application.
|
||||
*
|
||||
* @param input a reader over the header text
|
||||
* @return a MediaType parsed from the input, or null if not valid
|
||||
* @throws IOException if there was a problem reading the input
|
||||
*/
|
||||
public static MediaType parseMediaType(StringReader input) throws IOException {
|
||||
|
||||
// Type (required)
|
||||
String type = HttpParser.readToken(input);
|
||||
if (type == null || type.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (HttpParser.skipConstant(input, "/") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Subtype (required)
|
||||
String subtype = HttpParser.readToken(input);
|
||||
if (subtype == null || subtype.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LinkedHashMap<String,String> parameters = new LinkedHashMap<>();
|
||||
|
||||
SkipResult lookForSemiColon = HttpParser.skipConstant(input, ";");
|
||||
if (lookForSemiColon == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
while (lookForSemiColon == SkipResult.FOUND) {
|
||||
String attribute = HttpParser.readToken(input);
|
||||
|
||||
String value = "";
|
||||
if (HttpParser.skipConstant(input, "=") == SkipResult.FOUND) {
|
||||
value = HttpParser.readTokenOrQuotedString(input, true);
|
||||
}
|
||||
|
||||
if (attribute != null) {
|
||||
parameters.put(attribute.toLowerCase(Locale.ENGLISH), value);
|
||||
}
|
||||
|
||||
lookForSemiColon = HttpParser.skipConstant(input, ";");
|
||||
if (lookForSemiColon == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new MediaType(type, subtype, parameters);
|
||||
}
|
||||
|
||||
}
|
||||
65
java/org/apache/tomcat/util/http/parser/MediaTypeCache.java
Normal file
65
java/org/apache/tomcat/util/http/parser/MediaTypeCache.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import org.apache.tomcat.util.collections.ConcurrentCache;
|
||||
|
||||
/**
|
||||
* Caches the results of parsing content-type headers.
|
||||
*/
|
||||
public class MediaTypeCache {
|
||||
|
||||
private final ConcurrentCache<String,String[]> cache;
|
||||
|
||||
public MediaTypeCache(int size) {
|
||||
cache = new ConcurrentCache<>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks in the cache and returns the cached value if one is present. If no
|
||||
* match exists in the cache, a new parser is created, the input parsed and
|
||||
* the results placed in the cache and returned to the user.
|
||||
*
|
||||
* @param input The content-type header value to parse
|
||||
* @return The results are provided as a two element String array. The
|
||||
* first element is the media type less the charset and
|
||||
* the second element is the charset
|
||||
*/
|
||||
public String[] parse(String input) {
|
||||
String[] result = cache.get(input);
|
||||
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
MediaType m = null;
|
||||
try {
|
||||
m = MediaType.parseMediaType(new StringReader(input));
|
||||
} catch (IOException e) {
|
||||
// Ignore - return null
|
||||
}
|
||||
if (m != null) {
|
||||
result = new String[] {m.toStringNoCharset(), m.getCharset()};
|
||||
cache.put(input, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
124
java/org/apache/tomcat/util/http/parser/Ranges.java
Normal file
124
java/org/apache/tomcat/util/http/parser/Ranges.java
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Ranges {
|
||||
|
||||
private final String units;
|
||||
private final List<Entry> entries;
|
||||
|
||||
|
||||
private Ranges(String units, List<Entry> entries) {
|
||||
this.units = units;
|
||||
this.entries = Collections.unmodifiableList(entries);
|
||||
}
|
||||
|
||||
|
||||
public List<Entry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public String getUnits() {
|
||||
return units;
|
||||
}
|
||||
|
||||
|
||||
public static class Entry {
|
||||
|
||||
private final long start;
|
||||
private final long end;
|
||||
|
||||
|
||||
public Entry(long start, long end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
public long getEnd() {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a Range header from an HTTP header.
|
||||
*
|
||||
* @param input a reader over the header text
|
||||
*
|
||||
* @return a set of ranges parsed from the input, or null if not valid
|
||||
*
|
||||
* @throws IOException if there was a problem reading the input
|
||||
*/
|
||||
public static Ranges parse(StringReader input) throws IOException {
|
||||
|
||||
// Units (required)
|
||||
String units = HttpParser.readToken(input);
|
||||
if (units == null || units.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Must be followed by '='
|
||||
if (HttpParser.skipConstant(input, "=") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Range entries
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
|
||||
SkipResult skipResult;
|
||||
do {
|
||||
long start = HttpParser.readLong(input);
|
||||
// Must be followed by '-'
|
||||
if (HttpParser.skipConstant(input, "-") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
long end = HttpParser.readLong(input);
|
||||
|
||||
if (start == -1 && end == -1) {
|
||||
// Invalid range
|
||||
return null;
|
||||
}
|
||||
|
||||
entries.add(new Entry(start, end));
|
||||
|
||||
skipResult = HttpParser.skipConstant(input, ",");
|
||||
if (skipResult == SkipResult.NOT_FOUND) {
|
||||
// Invalid range
|
||||
return null;
|
||||
}
|
||||
} while (skipResult == SkipResult.FOUND);
|
||||
|
||||
// There must be at least one entry
|
||||
if (entries.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Ranges(units, entries);
|
||||
}
|
||||
}
|
||||
23
java/org/apache/tomcat/util/http/parser/SkipResult.java
Normal file
23
java/org/apache/tomcat/util/http/parser/SkipResult.java
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
enum SkipResult {
|
||||
FOUND,
|
||||
NOT_FOUND,
|
||||
EOF
|
||||
}
|
||||
113
java/org/apache/tomcat/util/http/parser/TokenList.java
Normal file
113
java/org/apache/tomcat/util/http/parser/TokenList.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
|
||||
public class TokenList {
|
||||
|
||||
private TokenList() {
|
||||
// Utility class. Hide default constructor.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses an enumeration of header values of the form 1#token, forcing all
|
||||
* parsed values to lower case.
|
||||
*
|
||||
* @param inputs The headers to parse
|
||||
* @param collection The Collection (usually a list of a set) to which the
|
||||
* parsed tokens should be added
|
||||
*
|
||||
* @return {@code} true if the header values were parsed cleanly, otherwise
|
||||
* {@code false} (e.g. if a non-token value was encountered)
|
||||
*
|
||||
* @throws IOException If an I/O error occurs reading the header
|
||||
*/
|
||||
public static boolean parseTokenList(Enumeration<String> inputs, Collection<String> collection) throws IOException {
|
||||
boolean result = true;
|
||||
while (inputs.hasMoreElements()) {
|
||||
String nextHeaderValue = inputs.nextElement();
|
||||
if (nextHeaderValue != null) {
|
||||
if (!TokenList.parseTokenList(new StringReader(nextHeaderValue), collection)) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a header of the form 1#token, forcing all parsed values to lower
|
||||
* case. This is typically used when header values are case-insensitive.
|
||||
*
|
||||
* @param input The header to parse
|
||||
* @param collection The Collection (usually a list of a set) to which the
|
||||
* parsed tokens should be added
|
||||
*
|
||||
* @return {@code} true if the header was parsed cleanly, otherwise
|
||||
* {@code false} (e.g. if a non-token value was encountered)
|
||||
*
|
||||
* @throws IOException If an I/O error occurs reading the header
|
||||
*/
|
||||
public static boolean parseTokenList(Reader input, Collection<String> collection) throws IOException {
|
||||
boolean invalid = false;
|
||||
boolean valid = false;
|
||||
|
||||
do {
|
||||
String fieldName = HttpParser.readToken(input);
|
||||
if (fieldName == null) {
|
||||
// Invalid field-name, skip to the next one
|
||||
invalid = true;
|
||||
HttpParser.skipUntil(input, 0, ',');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldName.length() == 0) {
|
||||
// No more data to read
|
||||
break;
|
||||
}
|
||||
|
||||
SkipResult skipResult = HttpParser.skipConstant(input, ",");
|
||||
if (skipResult == SkipResult.EOF) {
|
||||
// EOF
|
||||
valid = true;
|
||||
collection.add(fieldName.toLowerCase(Locale.ENGLISH));
|
||||
break;
|
||||
} else if (skipResult == SkipResult.FOUND) {
|
||||
valid = true;
|
||||
collection.add(fieldName.toLowerCase(Locale.ENGLISH));
|
||||
continue;
|
||||
} else {
|
||||
// Not a token - ignore it
|
||||
invalid = true;
|
||||
HttpParser.skipUntil(input, 0, ',');
|
||||
continue;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
// Only return true if at least one valid token was read and no invalid
|
||||
// entries were found
|
||||
return valid && !invalid;
|
||||
}
|
||||
}
|
||||
37
java/org/apache/tomcat/util/http/parser/Vary.java
Normal file
37
java/org/apache/tomcat/util/http/parser/Vary.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http.parser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link TokenList}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class Vary {
|
||||
|
||||
private Vary() {
|
||||
// Utility class. Hide default constructor.
|
||||
}
|
||||
|
||||
|
||||
public static void parseVary(StringReader input, Set<String> result) throws IOException {
|
||||
TokenList.parseTokenList(input, result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user