This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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 + "]";
}
}
}

View 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;
}
}
}

View 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
* &amp; 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)));
}
}
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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 ヘクステット以上になりません。

View File

@@ -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개를 초과할 수 없습니다.

View File

@@ -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字符的有效范围为0255。
http.invalidSegmentEndState=状态[{0}]对于段的结尾无效。
http.noClosingBracket=ipv6 地址缺失一个闭合的圆括号
http.noOpeningBracket=IPv6地址缺少开括号(
http.singleColonEnd=IPv6地址不能以单个“.”结尾。
http.singleColonStart=一个IPv6地址也许不是以单个冒号":"开头的。
http.tooManyColons=IPv6地址不能包含超过2个连续冒号字符。
http.tooManyDoubleColons=一个IPv6地址只能包含一个 '::' 序列。

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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
}

View 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;
}
}

View 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);
}
}