init
This commit is contained in:
181
java/org/apache/tomcat/util/buf/AbstractChunk.java
Normal file
181
java/org/apache/tomcat/util/buf/AbstractChunk.java
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Base class for the *Chunk implementation to reduce duplication.
|
||||
*/
|
||||
public abstract class AbstractChunk implements Cloneable, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/*
|
||||
* JVMs may limit the maximum array size to slightly less than
|
||||
* Integer.MAX_VALUE. On markt's desktop the limit is MAX_VALUE - 2.
|
||||
* Comments in the JRE source code for ArrayList and other classes indicate
|
||||
* that it may be as low as MAX_VALUE - 8 on some systems.
|
||||
*/
|
||||
public static final int ARRAY_MAX_SIZE = Integer.MAX_VALUE - 8;
|
||||
|
||||
private int hashCode = 0;
|
||||
protected boolean hasHashCode = false;
|
||||
|
||||
protected boolean isSet;
|
||||
|
||||
private int limit = -1;
|
||||
|
||||
protected int start;
|
||||
protected int end;
|
||||
|
||||
|
||||
/**
|
||||
* Maximum amount of data in this buffer. If -1 or not set, the buffer will
|
||||
* grow to {{@link #ARRAY_MAX_SIZE}. Can be smaller than the current buffer
|
||||
* size ( which will not shrink ). When the limit is reached, the buffer
|
||||
* will be flushed (if out is set) or throw exception.
|
||||
*
|
||||
* @param limit The new limit
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
|
||||
protected int getLimitInternal() {
|
||||
if (limit > 0) {
|
||||
return limit;
|
||||
} else {
|
||||
return ARRAY_MAX_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the start position of the data in the buffer
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
public void setEnd(int i) {
|
||||
end = i;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Deprecate offset and use start
|
||||
|
||||
public int getOffset() {
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
public void setOffset(int off) {
|
||||
if (end < off) {
|
||||
end = off;
|
||||
}
|
||||
start = off;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the length of the data in the buffer
|
||||
*/
|
||||
public int getLength() {
|
||||
return end - start;
|
||||
}
|
||||
|
||||
|
||||
public boolean isNull() {
|
||||
if (end > 0) {
|
||||
return false;
|
||||
}
|
||||
return !isSet;
|
||||
}
|
||||
|
||||
|
||||
public int indexOf(String src, int srcOff, int srcLen, int myOff) {
|
||||
char first = src.charAt(srcOff);
|
||||
|
||||
// Look for first char
|
||||
int srcEnd = srcOff + srcLen;
|
||||
|
||||
mainLoop: for (int i = myOff + start; i <= (end - srcLen); i++) {
|
||||
if (getBufferElement(i) != first) {
|
||||
continue;
|
||||
}
|
||||
// found first char, now look for a match
|
||||
int myPos = i + 1;
|
||||
for (int srcPos = srcOff + 1; srcPos < srcEnd;) {
|
||||
if (getBufferElement(myPos++) != src.charAt(srcPos++)) {
|
||||
continue mainLoop;
|
||||
}
|
||||
}
|
||||
return i - start; // found it
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resets the chunk to an uninitialized state.
|
||||
*/
|
||||
public void recycle() {
|
||||
hasHashCode = false;
|
||||
isSet = false;
|
||||
start = 0;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (hasHashCode) {
|
||||
return hashCode;
|
||||
}
|
||||
int code = 0;
|
||||
|
||||
code = hash();
|
||||
hashCode = code;
|
||||
hasHashCode = true;
|
||||
return code;
|
||||
}
|
||||
|
||||
|
||||
public int hash() {
|
||||
int code = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
code = code * 37 + getBufferElement(i);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
|
||||
protected abstract int getBufferElement(int index);
|
||||
}
|
||||
103
java/org/apache/tomcat/util/buf/Ascii.java
Normal file
103
java/org/apache/tomcat/util/buf/Ascii.java
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
/**
|
||||
* This class implements some basic ASCII character handling functions.
|
||||
*
|
||||
* @author dac@eng.sun.com
|
||||
* @author James Todd [gonzo@eng.sun.com]
|
||||
*/
|
||||
public final class Ascii {
|
||||
/*
|
||||
* Character translation tables.
|
||||
*/
|
||||
private static final byte[] toLower = new byte[256];
|
||||
|
||||
/*
|
||||
* Character type tables.
|
||||
*/
|
||||
private static final boolean[] isDigit = new boolean[256];
|
||||
|
||||
private static final long OVERFLOW_LIMIT = Long.MAX_VALUE / 10;
|
||||
|
||||
/*
|
||||
* Initialize character translation and type tables.
|
||||
*/
|
||||
static {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
toLower[i] = (byte)i;
|
||||
}
|
||||
|
||||
for (int lc = 'a'; lc <= 'z'; lc++) {
|
||||
int uc = lc + 'A' - 'a';
|
||||
|
||||
toLower[uc] = (byte)lc;
|
||||
}
|
||||
|
||||
for (int d = '0'; d <= '9'; d++) {
|
||||
isDigit[d] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lower case equivalent of the specified ASCII character.
|
||||
* @param c The char
|
||||
* @return the lower case equivalent char
|
||||
*/
|
||||
public static int toLower(int c) {
|
||||
return toLower[c & 0xff] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the specified ASCII character is a digit.
|
||||
* @param c The char
|
||||
*/
|
||||
private static boolean isDigit(int c) {
|
||||
return isDigit[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an unsigned long from the specified subarray of bytes.
|
||||
* @param b the bytes to parse
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
* @return the long value
|
||||
* @exception NumberFormatException if the long format was invalid
|
||||
*/
|
||||
public static long parseLong(byte[] b, int off, int len)
|
||||
throws NumberFormatException
|
||||
{
|
||||
int c;
|
||||
|
||||
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
|
||||
long n = c - '0';
|
||||
while (--len > 0) {
|
||||
if (isDigit(c = b[off++]) &&
|
||||
(n < OVERFLOW_LIMIT || (n == OVERFLOW_LIMIT && (c - '0') < 8))) {
|
||||
n = n * 10 + c - '0';
|
||||
} else {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
95
java/org/apache/tomcat/util/buf/Asn1Parser.java
Normal file
95
java/org/apache/tomcat/util/buf/Asn1Parser.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* This is a very basic ASN.1 parser that provides the limited functionality
|
||||
* required by Tomcat. It is a long way from a complete parser.
|
||||
*
|
||||
* TODO: Consider extending this parser and refactoring the SpnegoTokenFixer to
|
||||
* use it.
|
||||
*/
|
||||
public class Asn1Parser {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(Asn1Parser.class);
|
||||
|
||||
private final byte[] source;
|
||||
|
||||
private int pos = 0;
|
||||
|
||||
|
||||
public Asn1Parser(byte[] source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
|
||||
public void parseTag(int tag) {
|
||||
int value = next();
|
||||
if (value != tag) {
|
||||
throw new IllegalArgumentException(sm.getString("asn1Parser.tagMismatch",
|
||||
Integer.valueOf(tag), Integer.valueOf(value)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void parseFullLength() {
|
||||
int len = parseLength();
|
||||
if (len + pos != source.length) {
|
||||
throw new IllegalArgumentException(sm.getString("asn1Parser.lengthInvalid",
|
||||
Integer.valueOf(len), Integer.valueOf(source.length - pos)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int parseLength() {
|
||||
int len = next();
|
||||
if (len > 127) {
|
||||
int bytes = len - 128;
|
||||
len = 0;
|
||||
for (int i = 0; i < bytes; i++) {
|
||||
len = len << 8;
|
||||
len = len + next();
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
public BigInteger parseInt() {
|
||||
parseTag(0x02);
|
||||
int len = parseLength();
|
||||
byte[] val = new byte[len];
|
||||
System.arraycopy(source, pos, val, 0, len);
|
||||
pos += len;
|
||||
return new BigInteger(val);
|
||||
}
|
||||
|
||||
|
||||
public void parseBytes(byte[] dest) {
|
||||
System.arraycopy(source, pos, dest, 0, dest.length);
|
||||
pos += dest.length;
|
||||
}
|
||||
|
||||
|
||||
private int next() {
|
||||
return source[pos++] & 0xFF;
|
||||
}
|
||||
}
|
||||
95
java/org/apache/tomcat/util/buf/Asn1Writer.java
Normal file
95
java/org/apache/tomcat/util/buf/Asn1Writer.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
public class Asn1Writer {
|
||||
|
||||
public static byte[] writeSequence(byte[]... components) {
|
||||
int len = 0;
|
||||
for (byte[] component : components) {
|
||||
len += component.length;
|
||||
}
|
||||
|
||||
byte[] combined = new byte[len];
|
||||
int pos = 0;
|
||||
for (byte[] component : components) {
|
||||
System.arraycopy(component, 0, combined, pos, component.length);
|
||||
pos += component.length;
|
||||
}
|
||||
|
||||
return writeTag((byte) 0x30, combined);
|
||||
}
|
||||
|
||||
|
||||
public static byte[] writeInteger(int value) {
|
||||
// How many bytes required to write the value? No more than 4 for int.
|
||||
int valueSize = 1;
|
||||
while ((value >> (valueSize * 8)) > 0) {
|
||||
valueSize++;
|
||||
}
|
||||
|
||||
byte[] valueBytes = new byte[valueSize];
|
||||
int i = 0;
|
||||
while (valueSize > 0) {
|
||||
valueBytes[i] = (byte) (value >> (8 * (valueSize - 1)));
|
||||
value = value >> 8;
|
||||
valueSize--;
|
||||
i++;
|
||||
}
|
||||
|
||||
return writeTag((byte) 0x02, valueBytes);
|
||||
}
|
||||
|
||||
public static byte[] writeOctetString(byte[] data) {
|
||||
return writeTag((byte) 0x04, data);
|
||||
}
|
||||
|
||||
public static byte[] writeTag(byte tagId, byte[] data) {
|
||||
int dataSize = data.length;
|
||||
// How many bytes to write the length?
|
||||
int lengthSize = 1;
|
||||
if (dataSize >127) {
|
||||
// 1 byte we have is now used to record how many bytes we need to
|
||||
// record a length > 127
|
||||
// Result is lengthSize = 1 + number of bytes to record length
|
||||
do {
|
||||
lengthSize++;
|
||||
}
|
||||
while ((dataSize >> (lengthSize * 8)) > 0);
|
||||
}
|
||||
|
||||
// 1 for tag + lengthSize + dataSize
|
||||
byte[] result = new byte[1 + lengthSize + dataSize];
|
||||
result[0] = tagId;
|
||||
if (dataSize < 128) {
|
||||
result[1] = (byte) dataSize;
|
||||
} else {
|
||||
// lengthSize is 1 + number of bytes for length
|
||||
result[1] = (byte) (127 + lengthSize);
|
||||
int i = lengthSize;
|
||||
while (dataSize > 0) {
|
||||
result[i] = (byte) (dataSize & 0xFF);
|
||||
dataSize = dataSize >> 8;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
System.arraycopy(data, 0, result, 1 + lengthSize, data.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
284
java/org/apache/tomcat/util/buf/B2CConverter.java
Normal file
284
java/org/apache/tomcat/util/buf/B2CConverter.java
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* NIO based character decoder.
|
||||
*/
|
||||
public class B2CConverter {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(Constants.Package);
|
||||
|
||||
private static final CharsetCache charsetCache = new CharsetCache();
|
||||
|
||||
|
||||
// Protected so unit tests can use it
|
||||
protected static final int LEFTOVER_SIZE = 9;
|
||||
|
||||
/**
|
||||
* Obtain the Charset for the given encoding
|
||||
*
|
||||
* @param enc The name of the encoding for the required charset
|
||||
*
|
||||
* @return The Charset corresponding to the requested encoding
|
||||
*
|
||||
* @throws UnsupportedEncodingException If the requested Charset is not
|
||||
* available
|
||||
*/
|
||||
public static Charset getCharset(String enc)
|
||||
throws UnsupportedEncodingException {
|
||||
|
||||
// Encoding names should all be ASCII
|
||||
String lowerCaseEnc = enc.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
return getCharsetLower(lowerCaseEnc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Only to be used when it is known that the encoding name is in lower case.
|
||||
* @param lowerCaseEnc The name of the encoding for the required charset in
|
||||
* lower case
|
||||
*
|
||||
* @return The Charset corresponding to the requested encoding
|
||||
*
|
||||
* @throws UnsupportedEncodingException If the requested Charset is not
|
||||
* available
|
||||
*
|
||||
* @deprecated Will be removed in Tomcat 9.0.x
|
||||
*/
|
||||
@Deprecated
|
||||
public static Charset getCharsetLower(String lowerCaseEnc)
|
||||
throws UnsupportedEncodingException {
|
||||
|
||||
Charset charset = charsetCache.getCharset(lowerCaseEnc);
|
||||
|
||||
if (charset == null) {
|
||||
// Pre-population of the cache means this must be invalid
|
||||
throw new UnsupportedEncodingException(
|
||||
sm.getString("b2cConverter.unknownEncoding", lowerCaseEnc));
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
|
||||
private final CharsetDecoder decoder;
|
||||
private ByteBuffer bb = null;
|
||||
private CharBuffer cb = null;
|
||||
|
||||
/**
|
||||
* Leftover buffer used for incomplete characters.
|
||||
*/
|
||||
private final ByteBuffer leftovers;
|
||||
|
||||
public B2CConverter(Charset charset) {
|
||||
this(charset, false);
|
||||
}
|
||||
|
||||
public B2CConverter(Charset charset, boolean replaceOnError) {
|
||||
byte[] left = new byte[LEFTOVER_SIZE];
|
||||
leftovers = ByteBuffer.wrap(left);
|
||||
CodingErrorAction action;
|
||||
if (replaceOnError) {
|
||||
action = CodingErrorAction.REPLACE;
|
||||
} else {
|
||||
action = CodingErrorAction.REPORT;
|
||||
}
|
||||
// Special case. Use the Apache Harmony based UTF-8 decoder because it
|
||||
// - a) rejects invalid sequences that the JVM decoder does not
|
||||
// - b) fails faster for some invalid sequences
|
||||
if (charset.equals(StandardCharsets.UTF_8)) {
|
||||
decoder = new Utf8Decoder();
|
||||
} else {
|
||||
decoder = charset.newDecoder();
|
||||
}
|
||||
decoder.onMalformedInput(action);
|
||||
decoder.onUnmappableCharacter(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the decoder state.
|
||||
*/
|
||||
public void recycle() {
|
||||
decoder.reset();
|
||||
leftovers.position(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given bytes to characters.
|
||||
*
|
||||
* @param bc byte input
|
||||
* @param cc char output
|
||||
* @param endOfInput Is this all of the available data
|
||||
*
|
||||
* @throws IOException If the conversion can not be completed
|
||||
*/
|
||||
public void convert(ByteChunk bc, CharChunk cc, boolean endOfInput)
|
||||
throws IOException {
|
||||
if ((bb == null) || (bb.array() != bc.getBuffer())) {
|
||||
// Create a new byte buffer if anything changed
|
||||
bb = ByteBuffer.wrap(bc.getBuffer(), bc.getStart(), bc.getLength());
|
||||
} else {
|
||||
// Initialize the byte buffer
|
||||
bb.limit(bc.getEnd());
|
||||
bb.position(bc.getStart());
|
||||
}
|
||||
if ((cb == null) || (cb.array() != cc.getBuffer())) {
|
||||
// Create a new char buffer if anything changed
|
||||
cb = CharBuffer.wrap(cc.getBuffer(), cc.getEnd(),
|
||||
cc.getBuffer().length - cc.getEnd());
|
||||
} else {
|
||||
// Initialize the char buffer
|
||||
cb.limit(cc.getBuffer().length);
|
||||
cb.position(cc.getEnd());
|
||||
}
|
||||
CoderResult result = null;
|
||||
// Parse leftover if any are present
|
||||
if (leftovers.position() > 0) {
|
||||
int pos = cb.position();
|
||||
// Loop until one char is decoded or there is a decoder error
|
||||
do {
|
||||
leftovers.put(bc.substractB());
|
||||
leftovers.flip();
|
||||
result = decoder.decode(leftovers, cb, endOfInput);
|
||||
leftovers.position(leftovers.limit());
|
||||
leftovers.limit(leftovers.array().length);
|
||||
} while (result.isUnderflow() && (cb.position() == pos));
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
}
|
||||
bb.position(bc.getStart());
|
||||
leftovers.position(0);
|
||||
}
|
||||
// Do the decoding and get the results into the byte chunk and the char
|
||||
// chunk
|
||||
result = decoder.decode(bb, cb, endOfInput);
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
} else if (result.isOverflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk, if
|
||||
// this continues the char buffer will get resized
|
||||
bc.setOffset(bb.position());
|
||||
cc.setEnd(cb.position());
|
||||
} else if (result.isUnderflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk
|
||||
bc.setOffset(bb.position());
|
||||
cc.setEnd(cb.position());
|
||||
// Put leftovers in the leftovers byte buffer
|
||||
if (bc.getLength() > 0) {
|
||||
leftovers.limit(leftovers.array().length);
|
||||
leftovers.position(bc.getLength());
|
||||
bc.substract(leftovers.array(), 0, bc.getLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given bytes to characters.
|
||||
*
|
||||
* @param bc byte input
|
||||
* @param cc char output
|
||||
* @param ic byte input channel
|
||||
* @param endOfInput Is this all of the available data
|
||||
*
|
||||
* @throws IOException If the conversion can not be completed
|
||||
*/
|
||||
public void convert(ByteBuffer bc, CharBuffer cc, ByteChunk.ByteInputChannel ic, boolean endOfInput)
|
||||
throws IOException {
|
||||
if ((bb == null) || (bb.array() != bc.array())) {
|
||||
// Create a new byte buffer if anything changed
|
||||
bb = ByteBuffer.wrap(bc.array(), bc.arrayOffset() + bc.position(), bc.remaining());
|
||||
} else {
|
||||
// Initialize the byte buffer
|
||||
bb.limit(bc.limit());
|
||||
bb.position(bc.position());
|
||||
}
|
||||
if ((cb == null) || (cb.array() != cc.array())) {
|
||||
// Create a new char buffer if anything changed
|
||||
cb = CharBuffer.wrap(cc.array(), cc.limit(), cc.capacity() - cc.limit());
|
||||
} else {
|
||||
// Initialize the char buffer
|
||||
cb.limit(cc.capacity());
|
||||
cb.position(cc.limit());
|
||||
}
|
||||
CoderResult result = null;
|
||||
// Parse leftover if any are present
|
||||
if (leftovers.position() > 0) {
|
||||
int pos = cb.position();
|
||||
// Loop until one char is decoded or there is a decoder error
|
||||
do {
|
||||
byte chr;
|
||||
if (bc.remaining() == 0) {
|
||||
int n = ic.realReadBytes();
|
||||
chr = n < 0 ? -1 : bc.get();
|
||||
} else {
|
||||
chr = bc.get();
|
||||
}
|
||||
leftovers.put(chr);
|
||||
leftovers.flip();
|
||||
result = decoder.decode(leftovers, cb, endOfInput);
|
||||
leftovers.position(leftovers.limit());
|
||||
leftovers.limit(leftovers.array().length);
|
||||
} while (result.isUnderflow() && (cb.position() == pos));
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
}
|
||||
bb.position(bc.position());
|
||||
leftovers.position(0);
|
||||
}
|
||||
// Do the decoding and get the results into the byte chunk and the char
|
||||
// chunk
|
||||
result = decoder.decode(bb, cb, endOfInput);
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
} else if (result.isOverflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk, if
|
||||
// this continues the char buffer will get resized
|
||||
bc.position(bb.position());
|
||||
cc.limit(cb.position());
|
||||
} else if (result.isUnderflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk
|
||||
bc.position(bb.position());
|
||||
cc.limit(cb.position());
|
||||
// Put leftovers in the leftovers byte buffer
|
||||
if (bc.remaining() > 0) {
|
||||
leftovers.limit(leftovers.array().length);
|
||||
leftovers.position(bc.remaining());
|
||||
bc.get(leftovers.array(), 0, bc.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Charset getCharset() {
|
||||
return decoder.charset();
|
||||
}
|
||||
}
|
||||
55
java/org/apache/tomcat/util/buf/ByteBufferHolder.java
Normal file
55
java/org/apache/tomcat/util/buf/ByteBufferHolder.java
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Simple wrapper for a {@link ByteBuffer} that remembers if the buffer has been
|
||||
* flipped or not.
|
||||
*/
|
||||
public class ByteBufferHolder {
|
||||
|
||||
private final ByteBuffer buf;
|
||||
private final AtomicBoolean flipped;
|
||||
|
||||
public ByteBufferHolder(ByteBuffer buf, boolean flipped) {
|
||||
this.buf = buf;
|
||||
this.flipped = new AtomicBoolean(flipped);
|
||||
}
|
||||
|
||||
|
||||
public ByteBuffer getBuf() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
public boolean isFlipped() {
|
||||
return flipped.get();
|
||||
}
|
||||
|
||||
|
||||
public boolean flip() {
|
||||
if (flipped.compareAndSet(false, true)) {
|
||||
buf.flip();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
144
java/org/apache/tomcat/util/buf/ByteBufferUtils.java
Normal file
144
java/org/apache/tomcat/util/buf/ByteBufferUtils.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.compat.JreCompat;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class ByteBufferUtils {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(Constants.Package);
|
||||
private static final Log log = LogFactory.getLog(ByteBufferUtils.class);
|
||||
|
||||
private static final Object unsafe;
|
||||
private static final Method cleanerMethod;
|
||||
private static final Method cleanMethod;
|
||||
private static final Method invokeCleanerMethod;
|
||||
|
||||
static {
|
||||
ByteBuffer tempBuffer = ByteBuffer.allocateDirect(0);
|
||||
Method cleanerMethodLocal = null;
|
||||
Method cleanMethodLocal = null;
|
||||
Object unsafeLocal = null;
|
||||
Method invokeCleanerMethodLocal = null;
|
||||
if (JreCompat.isJre9Available()) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("sun.misc.Unsafe");
|
||||
Field theUnsafe = clazz.getDeclaredField("theUnsafe");
|
||||
theUnsafe.setAccessible(true);
|
||||
unsafeLocal = theUnsafe.get(null);
|
||||
invokeCleanerMethodLocal = clazz.getMethod("invokeCleaner", ByteBuffer.class);
|
||||
invokeCleanerMethodLocal.invoke(unsafeLocal, tempBuffer);
|
||||
} catch (IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException | NoSuchMethodException | SecurityException
|
||||
| ClassNotFoundException | NoSuchFieldException e) {
|
||||
log.warn(sm.getString("byteBufferUtils.cleaner"), e);
|
||||
unsafeLocal = null;
|
||||
invokeCleanerMethodLocal = null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
cleanerMethodLocal = tempBuffer.getClass().getMethod("cleaner");
|
||||
cleanerMethodLocal.setAccessible(true);
|
||||
Object cleanerObject = cleanerMethodLocal.invoke(tempBuffer);
|
||||
cleanMethodLocal = cleanerObject.getClass().getMethod("clean");
|
||||
cleanMethodLocal.invoke(cleanerObject);
|
||||
} catch (NoSuchMethodException | SecurityException | IllegalAccessException |
|
||||
IllegalArgumentException | InvocationTargetException e) {
|
||||
log.warn(sm.getString("byteBufferUtils.cleaner"), e);
|
||||
cleanerMethodLocal = null;
|
||||
cleanMethodLocal = null;
|
||||
}
|
||||
}
|
||||
cleanerMethod = cleanerMethodLocal;
|
||||
cleanMethod = cleanMethodLocal;
|
||||
unsafe = unsafeLocal;
|
||||
invokeCleanerMethod = invokeCleanerMethodLocal;
|
||||
}
|
||||
|
||||
private ByteBufferUtils() {
|
||||
// Hide the default constructor since this is a utility class.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expands buffer to the given size unless it is already as big or bigger.
|
||||
* Buffers are assumed to be in 'write to' mode since there would be no need
|
||||
* to expand a buffer while it was in 'read from' mode.
|
||||
*
|
||||
* @param in Buffer to expand
|
||||
* @param newSize The size t which the buffer should be expanded
|
||||
* @return The expanded buffer with any data from the input buffer
|
||||
* copied in to it or the original buffer if there was no
|
||||
* need for expansion
|
||||
*/
|
||||
public static ByteBuffer expand(ByteBuffer in, int newSize) {
|
||||
if (in.capacity() >= newSize) {
|
||||
return in;
|
||||
}
|
||||
|
||||
ByteBuffer out;
|
||||
boolean direct = false;
|
||||
if (in.isDirect()) {
|
||||
out = ByteBuffer.allocateDirect(newSize);
|
||||
direct = true;
|
||||
} else {
|
||||
out = ByteBuffer.allocate(newSize);
|
||||
}
|
||||
|
||||
// Copy data
|
||||
in.flip();
|
||||
out.put(in);
|
||||
|
||||
if (direct) {
|
||||
cleanDirectBuffer(in);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static void cleanDirectBuffer(ByteBuffer buf) {
|
||||
if (cleanMethod != null) {
|
||||
try {
|
||||
cleanMethod.invoke(cleanerMethod.invoke(buf));
|
||||
} catch (IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException | SecurityException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("byteBufferUtils.cleaner"), e);
|
||||
}
|
||||
}
|
||||
} else if (invokeCleanerMethod != null) {
|
||||
try {
|
||||
invokeCleanerMethod.invoke(unsafe, buf);
|
||||
} catch (IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException | SecurityException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("byteBufferUtils.cleaner"), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
834
java/org/apache/tomcat/util/buf/ByteChunk.java
Normal file
834
java/org/apache/tomcat/util/buf/ByteChunk.java
Normal file
@@ -0,0 +1,834 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/*
|
||||
* In a server it is very important to be able to operate on
|
||||
* the original byte[] without converting everything to chars.
|
||||
* Some protocols are ASCII only, and some allow different
|
||||
* non-UNICODE encodings. The encoding is not known beforehand,
|
||||
* and can even change during the execution of the protocol.
|
||||
* ( for example a multipart message may have parts with different
|
||||
* encoding )
|
||||
*
|
||||
* For HTTP it is not very clear how the encoding of RequestURI
|
||||
* and mime values can be determined, but it is a great advantage
|
||||
* to be able to parse the request without converting to string.
|
||||
*/
|
||||
|
||||
// TODO: This class could either extend ByteBuffer, or better a ByteBuffer
|
||||
// inside this way it could provide the search/etc on ByteBuffer, as a helper.
|
||||
|
||||
/**
|
||||
* This class is used to represent a chunk of bytes, and utilities to manipulate
|
||||
* byte[].
|
||||
*
|
||||
* The buffer can be modified and used for both input and output.
|
||||
*
|
||||
* There are 2 modes: The chunk can be associated with a sink - ByteInputChannel
|
||||
* or ByteOutputChannel, which will be used when the buffer is empty (on input)
|
||||
* or filled (on output). For output, it can also grow. This operating mode is
|
||||
* selected by calling setLimit() or allocate(initial, limit) with limit != -1.
|
||||
*
|
||||
* Various search and append method are defined - similar with String and
|
||||
* StringBuffer, but operating on bytes.
|
||||
*
|
||||
* This is important because it allows processing the http headers directly on
|
||||
* the received bytes, without converting to chars and Strings until the strings
|
||||
* are needed. In addition, the charset is determined later, from headers or
|
||||
* user code.
|
||||
*
|
||||
* @author dac@sun.com
|
||||
* @author James Todd [gonzo@sun.com]
|
||||
* @author Costin Manolache
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public final class ByteChunk extends AbstractChunk {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Input interface, used when the buffer is empty.
|
||||
*
|
||||
* Same as java.nio.channels.ReadableByteChannel
|
||||
*/
|
||||
public static interface ByteInputChannel {
|
||||
|
||||
/**
|
||||
* Read new bytes.
|
||||
*
|
||||
* @return The number of bytes read
|
||||
*
|
||||
* @throws IOException If an I/O error occurs during reading
|
||||
*/
|
||||
public int realReadBytes() throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we need more space we'll either grow the buffer ( up to the limit )
|
||||
* or send it to a channel.
|
||||
*
|
||||
* Same as java.nio.channel.WritableByteChannel.
|
||||
*/
|
||||
public static interface ByteOutputChannel {
|
||||
|
||||
/**
|
||||
* Send the bytes ( usually the internal conversion buffer ). Expect 8k
|
||||
* output if the buffer is full.
|
||||
*
|
||||
* @param buf bytes that will be written
|
||||
* @param off offset in the bytes array
|
||||
* @param len length that will be written
|
||||
* @throws IOException If an I/O occurs while writing the bytes
|
||||
*/
|
||||
public void realWriteBytes(byte buf[], int off, int len) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Send the bytes ( usually the internal conversion buffer ). Expect 8k
|
||||
* output if the buffer is full.
|
||||
*
|
||||
* @param from bytes that will be written
|
||||
* @throws IOException If an I/O occurs while writing the bytes
|
||||
*/
|
||||
public void realWriteBytes(ByteBuffer from) throws IOException;
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
/**
|
||||
* Default encoding used to convert to strings. It should be UTF8, as most
|
||||
* standards seem to converge, but the servlet API requires 8859_1, and this
|
||||
* object is used mostly for servlets.
|
||||
*/
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
|
||||
|
||||
private transient Charset charset;
|
||||
|
||||
// byte[]
|
||||
private byte[] buff;
|
||||
|
||||
// transient as serialization is primarily for values via, e.g. JMX
|
||||
private transient ByteInputChannel in = null;
|
||||
private transient ByteOutputChannel out = null;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized ByteChunk object.
|
||||
*/
|
||||
public ByteChunk() {
|
||||
}
|
||||
|
||||
|
||||
public ByteChunk(int initial) {
|
||||
allocate(initial, -1);
|
||||
}
|
||||
|
||||
|
||||
private void writeObject(ObjectOutputStream oos) throws IOException {
|
||||
oos.defaultWriteObject();
|
||||
oos.writeUTF(getCharset().name());
|
||||
}
|
||||
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
ois.defaultReadObject();
|
||||
this.charset = Charset.forName(ois.readUTF());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
charset = null;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Setup --------------------
|
||||
|
||||
public void allocate(int initial, int limit) {
|
||||
if (buff == null || buff.length < initial) {
|
||||
buff = new byte[initial];
|
||||
}
|
||||
setLimit(limit);
|
||||
start = 0;
|
||||
end = 0;
|
||||
isSet = true;
|
||||
hasHashCode = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the buffer to the specified subarray of bytes.
|
||||
*
|
||||
* @param b the ascii bytes
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
*/
|
||||
public void setBytes(byte[] b, int off, int len) {
|
||||
buff = b;
|
||||
start = off;
|
||||
end = start + len;
|
||||
isSet = true;
|
||||
hasHashCode = false;
|
||||
}
|
||||
|
||||
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
|
||||
public Charset getCharset() {
|
||||
if (charset == null) {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the buffer.
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return getBuffer();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the buffer.
|
||||
*/
|
||||
public byte[] getBuffer() {
|
||||
return buff;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When the buffer is empty, read the data from the input channel.
|
||||
*
|
||||
* @param in The input channel
|
||||
*/
|
||||
public void setByteInputChannel(ByteInputChannel in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When the buffer is full, write the data to the output channel. Also used
|
||||
* when large amount of data is appended. If not set, the buffer will grow
|
||||
* to the limit.
|
||||
*
|
||||
* @param out The output channel
|
||||
*/
|
||||
public void setByteOutputChannel(ByteOutputChannel out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Adding data to the buffer --------------------
|
||||
|
||||
public void append(byte b) throws IOException {
|
||||
makeSpace(1);
|
||||
int limit = getLimitInternal();
|
||||
|
||||
// couldn't make space
|
||||
if (end >= limit) {
|
||||
flushBuffer();
|
||||
}
|
||||
buff[end++] = b;
|
||||
}
|
||||
|
||||
|
||||
public void append(ByteChunk src) throws IOException {
|
||||
append(src.getBytes(), src.getStart(), src.getLength());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add data to the buffer.
|
||||
*
|
||||
* @param src Bytes array
|
||||
* @param off Offset
|
||||
* @param len Length
|
||||
* @throws IOException Writing overflow data to the output channel failed
|
||||
*/
|
||||
public void append(byte src[], int off, int len) throws IOException {
|
||||
// will grow, up to limit
|
||||
makeSpace(len);
|
||||
int limit = getLimitInternal();
|
||||
|
||||
// Optimize on a common case.
|
||||
// If the buffer is empty and the source is going to fill up all the
|
||||
// space in buffer, may as well write it directly to the output,
|
||||
// and avoid an extra copy
|
||||
if (len == limit && end == start && out != null) {
|
||||
out.realWriteBytes(src, off, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are below the limit
|
||||
if (len <= limit - end) {
|
||||
System.arraycopy(src, off, buff, end, len);
|
||||
end += len;
|
||||
return;
|
||||
}
|
||||
|
||||
// Need more space than we can afford, need to flush buffer.
|
||||
|
||||
// The buffer is already at (or bigger than) limit.
|
||||
|
||||
// We chunk the data into slices fitting in the buffer limit, although
|
||||
// if the data is written directly if it doesn't fit.
|
||||
|
||||
int avail = limit - end;
|
||||
System.arraycopy(src, off, buff, end, avail);
|
||||
end += avail;
|
||||
|
||||
flushBuffer();
|
||||
|
||||
int remain = len - avail;
|
||||
|
||||
while (remain > (limit - end)) {
|
||||
out.realWriteBytes(src, (off + len) - remain, limit - end);
|
||||
remain = remain - (limit - end);
|
||||
}
|
||||
|
||||
System.arraycopy(src, (off + len) - remain, buff, end, remain);
|
||||
end += remain;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add data to the buffer.
|
||||
*
|
||||
* @param from the ByteBuffer with the data
|
||||
* @throws IOException Writing overflow data to the output channel failed
|
||||
*/
|
||||
public void append(ByteBuffer from) throws IOException {
|
||||
int len = from.remaining();
|
||||
|
||||
// will grow, up to limit
|
||||
makeSpace(len);
|
||||
int limit = getLimitInternal();
|
||||
|
||||
// Optimize on a common case.
|
||||
// If the buffer is empty and the source is going to fill up all the
|
||||
// space in buffer, may as well write it directly to the output,
|
||||
// and avoid an extra copy
|
||||
if (len == limit && end == start && out != null) {
|
||||
out.realWriteBytes(from);
|
||||
from.position(from.limit());
|
||||
return;
|
||||
}
|
||||
// if we have limit and we're below
|
||||
if (len <= limit - end) {
|
||||
// makeSpace will grow the buffer to the limit,
|
||||
// so we have space
|
||||
from.get(buff, end, len);
|
||||
end += len;
|
||||
return;
|
||||
}
|
||||
|
||||
// need more space than we can afford, need to flush
|
||||
// buffer
|
||||
|
||||
// the buffer is already at ( or bigger than ) limit
|
||||
|
||||
// We chunk the data into slices fitting in the buffer limit, although
|
||||
// if the data is written directly if it doesn't fit
|
||||
|
||||
int avail = limit - end;
|
||||
from.get(buff, end, avail);
|
||||
end += avail;
|
||||
|
||||
flushBuffer();
|
||||
|
||||
int fromLimit = from.limit();
|
||||
int remain = len - avail;
|
||||
avail = limit - end;
|
||||
while (remain >= avail) {
|
||||
from.limit(from.position() + avail);
|
||||
out.realWriteBytes(from);
|
||||
from.position(from.limit());
|
||||
remain = remain - avail;
|
||||
}
|
||||
|
||||
from.limit(fromLimit);
|
||||
from.get(buff, end, remain);
|
||||
end += remain;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Removing data from the buffer --------------------
|
||||
|
||||
public int substract() throws IOException {
|
||||
if (checkEof()) {
|
||||
return -1;
|
||||
}
|
||||
return buff[start++] & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
public byte substractB() throws IOException {
|
||||
if (checkEof()) {
|
||||
return -1;
|
||||
}
|
||||
return buff[start++];
|
||||
}
|
||||
|
||||
|
||||
public int substract(byte dest[], int off, int len) throws IOException {
|
||||
if (checkEof()) {
|
||||
return -1;
|
||||
}
|
||||
int n = len;
|
||||
if (len > getLength()) {
|
||||
n = getLength();
|
||||
}
|
||||
System.arraycopy(buff, start, dest, off, n);
|
||||
start += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transfers bytes from the buffer to the specified ByteBuffer. After the
|
||||
* operation the position of the ByteBuffer will be returned to the one
|
||||
* before the operation, the limit will be the position incremented by the
|
||||
* number of the transfered bytes.
|
||||
*
|
||||
* @param to the ByteBuffer into which bytes are to be written.
|
||||
* @return an integer specifying the actual number of bytes read, or -1 if
|
||||
* the end of the stream is reached
|
||||
* @throws IOException if an input or output exception has occurred
|
||||
*/
|
||||
public int substract(ByteBuffer to) throws IOException {
|
||||
if (checkEof()) {
|
||||
return -1;
|
||||
}
|
||||
int n = Math.min(to.remaining(), getLength());
|
||||
to.put(buff, start, n);
|
||||
to.limit(to.position());
|
||||
to.position(to.position() - n);
|
||||
start += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
private boolean checkEof() throws IOException {
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return true;
|
||||
}
|
||||
int n = in.realReadBytes();
|
||||
if (n < 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the buffer to the sink. Called by append() when the limit is
|
||||
* reached. You can also call it explicitly to force the data to be written.
|
||||
*
|
||||
* @throws IOException Writing overflow data to the output channel failed
|
||||
*/
|
||||
public void flushBuffer() throws IOException {
|
||||
// assert out!=null
|
||||
if (out == null) {
|
||||
throw new IOException("Buffer overflow, no sink " + getLimit() + " " + buff.length);
|
||||
}
|
||||
out.realWriteBytes(buff, start, end - start);
|
||||
end = start;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make space for len bytes. If len is small, allocate a reserve space too.
|
||||
* Never grow bigger than the limit or {@link AbstractChunk#ARRAY_MAX_SIZE}.
|
||||
*
|
||||
* @param count The size
|
||||
*/
|
||||
public void makeSpace(int count) {
|
||||
byte[] tmp = null;
|
||||
|
||||
int limit = getLimitInternal();
|
||||
|
||||
long newSize;
|
||||
long desiredSize = end + count;
|
||||
|
||||
// Can't grow above the limit
|
||||
if (desiredSize > limit) {
|
||||
desiredSize = limit;
|
||||
}
|
||||
|
||||
if (buff == null) {
|
||||
if (desiredSize < 256) {
|
||||
desiredSize = 256; // take a minimum
|
||||
}
|
||||
buff = new byte[(int) desiredSize];
|
||||
}
|
||||
|
||||
// limit < buf.length (the buffer is already big)
|
||||
// or we already have space XXX
|
||||
if (desiredSize <= buff.length) {
|
||||
return;
|
||||
}
|
||||
// grow in larger chunks
|
||||
if (desiredSize < 2L * buff.length) {
|
||||
newSize = buff.length * 2L;
|
||||
} else {
|
||||
newSize = buff.length * 2L + count;
|
||||
}
|
||||
|
||||
if (newSize > limit) {
|
||||
newSize = limit;
|
||||
}
|
||||
tmp = new byte[(int) newSize];
|
||||
|
||||
// Compacts buffer
|
||||
System.arraycopy(buff, start, tmp, 0, end - start);
|
||||
buff = tmp;
|
||||
tmp = null;
|
||||
end = end - start;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Conversion and getters --------------------
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (null == buff) {
|
||||
return null;
|
||||
} else if (end - start == 0) {
|
||||
return "";
|
||||
}
|
||||
return StringCache.toString(this);
|
||||
}
|
||||
|
||||
|
||||
public String toStringInternal() {
|
||||
if (charset == null) {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
// new String(byte[], int, int, Charset) takes a defensive copy of the
|
||||
// entire byte array. This is expensive if only a small subset of the
|
||||
// bytes will be used. The code below is from Apache Harmony.
|
||||
CharBuffer cb = charset.decode(ByteBuffer.wrap(buff, start, end - start));
|
||||
return new String(cb.array(), cb.arrayOffset(), cb.length());
|
||||
}
|
||||
|
||||
|
||||
public long getLong() {
|
||||
return Ascii.parseLong(buff, start, end - start);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- equals --------------------
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ByteChunk) {
|
||||
return equals((ByteChunk) obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
*
|
||||
* @param s the String to compare
|
||||
* @return <code>true</code> if the comparison succeeded, <code>false</code>
|
||||
* otherwise
|
||||
*/
|
||||
public boolean equals(String s) {
|
||||
// XXX ENCODING - this only works if encoding is UTF8-compat
|
||||
// ( ok for tomcat, where we compare ascii - header names, etc )!!!
|
||||
|
||||
byte[] b = buff;
|
||||
int len = end - start;
|
||||
if (b == null || len != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int off = start;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (b[off++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
*
|
||||
* @param s the String to compare
|
||||
* @return <code>true</code> if the comparison succeeded, <code>false</code>
|
||||
* otherwise
|
||||
*/
|
||||
public boolean equalsIgnoreCase(String s) {
|
||||
byte[] b = buff;
|
||||
int len = end - start;
|
||||
if (b == null || len != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int off = start;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (Ascii.toLower(b[off++]) != Ascii.toLower(s.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(ByteChunk bb) {
|
||||
return equals(bb.getBytes(), bb.getStart(), bb.getLength());
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(byte b2[], int off2, int len2) {
|
||||
byte b1[] = buff;
|
||||
if (b1 == null && b2 == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int len = end - start;
|
||||
if (len != len2 || b1 == null || b2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int off1 = start;
|
||||
|
||||
while (len-- > 0) {
|
||||
if (b1[off1++] != b2[off2++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(CharChunk cc) {
|
||||
return equals(cc.getChars(), cc.getStart(), cc.getLength());
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(char c2[], int off2, int len2) {
|
||||
// XXX works only for enc compatible with ASCII/UTF !!!
|
||||
byte b1[] = buff;
|
||||
if (c2 == null && b1 == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (b1 == null || c2 == null || end - start != len2) {
|
||||
return false;
|
||||
}
|
||||
int off1 = start;
|
||||
int len = end - start;
|
||||
|
||||
while (len-- > 0) {
|
||||
if ((char) b1[off1++] != c2[off2++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the buffer starts with the specified string when tested
|
||||
* in a case sensitive manner.
|
||||
*
|
||||
* @param s the string
|
||||
* @param pos The position
|
||||
*
|
||||
* @return <code>true</code> if the start matches
|
||||
*/
|
||||
public boolean startsWith(String s, int pos) {
|
||||
byte[] b = buff;
|
||||
int len = s.length();
|
||||
if (b == null || len + pos > end - start) {
|
||||
return false;
|
||||
}
|
||||
int off = start + pos;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (b[off++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the buffer starts with the specified string when tested
|
||||
* in a case insensitive manner.
|
||||
*
|
||||
* @param s the string
|
||||
* @param pos The position
|
||||
*
|
||||
* @return <code>true</code> if the start matches
|
||||
*/
|
||||
public boolean startsWithIgnoreCase(String s, int pos) {
|
||||
byte[] b = buff;
|
||||
int len = s.length();
|
||||
if (b == null || len + pos > end - start) {
|
||||
return false;
|
||||
}
|
||||
int off = start + pos;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (Ascii.toLower(b[off++]) != Ascii.toLower(s.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int getBufferElement(int index) {
|
||||
return buff[index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first instance of the given character in this ByteChunk
|
||||
* starting at the specified byte. If the character is not found, -1 is
|
||||
* returned. <br>
|
||||
* NOTE: This only works for characters in the range 0-127.
|
||||
*
|
||||
* @param c The character
|
||||
* @param starting The start position
|
||||
* @return The position of the first instance of the character or -1 if the
|
||||
* character is not found.
|
||||
*/
|
||||
public int indexOf(char c, int starting) {
|
||||
int ret = indexOf(buff, start + starting, end, c);
|
||||
return (ret >= start) ? ret - start : -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first instance of the given character in the given byte array
|
||||
* between the specified start and end. <br>
|
||||
* NOTE: This only works for characters in the range 0-127.
|
||||
*
|
||||
* @param bytes The array to search
|
||||
* @param start The point to start searching from in the array
|
||||
* @param end The point to stop searching in the array
|
||||
* @param s The character to search for
|
||||
* @return The position of the first instance of the character or -1 if the
|
||||
* character is not found.
|
||||
*/
|
||||
public static int indexOf(byte bytes[], int start, int end, char s) {
|
||||
int offset = start;
|
||||
|
||||
while (offset < end) {
|
||||
byte b = bytes[offset];
|
||||
if (b == s) {
|
||||
return offset;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first instance of the given byte in the byte array between
|
||||
* the specified start and end.
|
||||
*
|
||||
* @param bytes The byte array to search
|
||||
* @param start The point to start searching from in the byte array
|
||||
* @param end The point to stop searching in the byte array
|
||||
* @param b The byte to search for
|
||||
* @return The position of the first instance of the byte or -1 if the byte
|
||||
* is not found.
|
||||
*/
|
||||
public static int findByte(byte bytes[], int start, int end, byte b) {
|
||||
int offset = start;
|
||||
while (offset < end) {
|
||||
if (bytes[offset] == b) {
|
||||
return offset;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first instance of any of the given bytes in the byte array
|
||||
* between the specified start and end.
|
||||
*
|
||||
* @param bytes The byte array to search
|
||||
* @param start The point to start searching from in the byte array
|
||||
* @param end The point to stop searching in the byte array
|
||||
* @param b The array of bytes to search for
|
||||
* @return The position of the first instance of the byte or -1 if the byte
|
||||
* is not found.
|
||||
*/
|
||||
public static int findBytes(byte bytes[], int start, int end, byte b[]) {
|
||||
int blen = b.length;
|
||||
int offset = start;
|
||||
while (offset < end) {
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (bytes[offset] == b[i]) {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert specified String to a byte array. This ONLY WORKS for ascii, UTF
|
||||
* chars will be truncated.
|
||||
*
|
||||
* @param value to convert to byte array
|
||||
* @return the byte array value
|
||||
*/
|
||||
public static final byte[] convertToBytes(String value) {
|
||||
byte[] result = new byte[value.length()];
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
result[i] = (byte) value.charAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
192
java/org/apache/tomcat/util/buf/C2BConverter.java
Normal file
192
java/org/apache/tomcat/util/buf/C2BConverter.java
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
|
||||
/**
|
||||
* NIO based character encoder.
|
||||
*/
|
||||
public final class C2BConverter {
|
||||
|
||||
private final CharsetEncoder encoder;
|
||||
private ByteBuffer bb = null;
|
||||
private CharBuffer cb = null;
|
||||
|
||||
/**
|
||||
* Leftover buffer used for multi-characters characters.
|
||||
*/
|
||||
private final CharBuffer leftovers;
|
||||
|
||||
public C2BConverter(Charset charset) {
|
||||
encoder = charset.newEncoder();
|
||||
encoder.onUnmappableCharacter(CodingErrorAction.REPLACE)
|
||||
.onMalformedInput(CodingErrorAction.REPLACE);
|
||||
char[] left = new char[4];
|
||||
leftovers = CharBuffer.wrap(left);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the encoder state.
|
||||
*/
|
||||
public void recycle() {
|
||||
encoder.reset();
|
||||
leftovers.position(0);
|
||||
}
|
||||
|
||||
public boolean isUndeflow() {
|
||||
return (leftovers.position() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given characters to bytes.
|
||||
*
|
||||
* @param cc char input
|
||||
* @param bc byte output
|
||||
* @throws IOException An encoding error occurred
|
||||
*/
|
||||
public void convert(CharChunk cc, ByteChunk bc) throws IOException {
|
||||
if ((bb == null) || (bb.array() != bc.getBuffer())) {
|
||||
// Create a new byte buffer if anything changed
|
||||
bb = ByteBuffer.wrap(bc.getBuffer(), bc.getEnd(), bc.getBuffer().length - bc.getEnd());
|
||||
} else {
|
||||
// Initialize the byte buffer
|
||||
bb.limit(bc.getBuffer().length);
|
||||
bb.position(bc.getEnd());
|
||||
}
|
||||
if ((cb == null) || (cb.array() != cc.getBuffer())) {
|
||||
// Create a new char buffer if anything changed
|
||||
cb = CharBuffer.wrap(cc.getBuffer(), cc.getStart(), cc.getLength());
|
||||
} else {
|
||||
// Initialize the char buffer
|
||||
cb.limit(cc.getEnd());
|
||||
cb.position(cc.getStart());
|
||||
}
|
||||
CoderResult result = null;
|
||||
// Parse leftover if any are present
|
||||
if (leftovers.position() > 0) {
|
||||
int pos = bb.position();
|
||||
// Loop until one char is encoded or there is a encoder error
|
||||
do {
|
||||
leftovers.put((char) cc.substract());
|
||||
leftovers.flip();
|
||||
result = encoder.encode(leftovers, bb, false);
|
||||
leftovers.position(leftovers.limit());
|
||||
leftovers.limit(leftovers.array().length);
|
||||
} while (result.isUnderflow() && (bb.position() == pos));
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
}
|
||||
cb.position(cc.getStart());
|
||||
leftovers.position(0);
|
||||
}
|
||||
// Do the decoding and get the results into the byte chunk and the char
|
||||
// chunk
|
||||
result = encoder.encode(cb, bb, false);
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
} else if (result.isOverflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk
|
||||
bc.setEnd(bb.position());
|
||||
cc.setOffset(cb.position());
|
||||
} else if (result.isUnderflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk
|
||||
bc.setEnd(bb.position());
|
||||
cc.setOffset(cb.position());
|
||||
// Put leftovers in the leftovers char buffer
|
||||
if (cc.getLength() > 0) {
|
||||
leftovers.limit(leftovers.array().length);
|
||||
leftovers.position(cc.getLength());
|
||||
cc.substract(leftovers.array(), 0, cc.getLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given characters to bytes.
|
||||
*
|
||||
* @param cc char input
|
||||
* @param bc byte output
|
||||
* @throws IOException An encoding error occurred
|
||||
*/
|
||||
public void convert(CharBuffer cc, ByteBuffer bc) throws IOException {
|
||||
if ((bb == null) || (bb.array() != bc.array())) {
|
||||
// Create a new byte buffer if anything changed
|
||||
bb = ByteBuffer.wrap(bc.array(), bc.limit(), bc.capacity() - bc.limit());
|
||||
} else {
|
||||
// Initialize the byte buffer
|
||||
bb.limit(bc.capacity());
|
||||
bb.position(bc.limit());
|
||||
}
|
||||
if ((cb == null) || (cb.array() != cc.array())) {
|
||||
// Create a new char buffer if anything changed
|
||||
cb = CharBuffer.wrap(cc.array(), cc.arrayOffset() + cc.position(), cc.remaining());
|
||||
} else {
|
||||
// Initialize the char buffer
|
||||
cb.limit(cc.limit());
|
||||
cb.position(cc.position());
|
||||
}
|
||||
CoderResult result = null;
|
||||
// Parse leftover if any are present
|
||||
if (leftovers.position() > 0) {
|
||||
int pos = bb.position();
|
||||
// Loop until one char is encoded or there is a encoder error
|
||||
do {
|
||||
leftovers.put(cc.get());
|
||||
leftovers.flip();
|
||||
result = encoder.encode(leftovers, bb, false);
|
||||
leftovers.position(leftovers.limit());
|
||||
leftovers.limit(leftovers.array().length);
|
||||
} while (result.isUnderflow() && (bb.position() == pos));
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
}
|
||||
cb.position(cc.position());
|
||||
leftovers.position(0);
|
||||
}
|
||||
// Do the decoding and get the results into the byte chunk and the char
|
||||
// chunk
|
||||
result = encoder.encode(cb, bb, false);
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
} else if (result.isOverflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk
|
||||
bc.limit(bb.position());
|
||||
cc.position(cb.position());
|
||||
} else if (result.isUnderflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk
|
||||
bc.limit(bb.position());
|
||||
cc.position(cb.position());
|
||||
// Put leftovers in the leftovers char buffer
|
||||
if (cc.remaining() > 0) {
|
||||
leftovers.limit(leftovers.array().length);
|
||||
leftovers.position(cc.remaining());
|
||||
cc.get(leftovers.array(), 0, cc.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
return encoder.charset();
|
||||
}
|
||||
}
|
||||
662
java/org/apache/tomcat/util/buf/CharChunk.java
Normal file
662
java/org/apache/tomcat/util/buf/CharChunk.java
Normal file
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Utilities to manipulate char chunks. While String is the easiest way to
|
||||
* manipulate chars ( search, substrings, etc), it is known to not be the most
|
||||
* efficient solution - Strings are designed as immutable and secure objects.
|
||||
*
|
||||
* @author dac@sun.com
|
||||
* @author James Todd [gonzo@sun.com]
|
||||
* @author Costin Manolache
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public final class CharChunk extends AbstractChunk implements CharSequence {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Input interface, used when the buffer is empty.
|
||||
*/
|
||||
public static interface CharInputChannel {
|
||||
|
||||
/**
|
||||
* Read new characters.
|
||||
*
|
||||
* @return The number of characters read
|
||||
*
|
||||
* @throws IOException If an I/O error occurs during reading
|
||||
*/
|
||||
public int realReadChars() throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we need more space we'll either grow the buffer ( up to the limit )
|
||||
* or send it to a channel.
|
||||
*/
|
||||
public static interface CharOutputChannel {
|
||||
|
||||
/**
|
||||
* Send the bytes ( usually the internal conversion buffer ). Expect 8k
|
||||
* output if the buffer is full.
|
||||
*
|
||||
* @param buf characters that will be written
|
||||
* @param off offset in the characters array
|
||||
* @param len length that will be written
|
||||
* @throws IOException If an I/O occurs while writing the characters
|
||||
*/
|
||||
public void realWriteChars(char buf[], int off, int len) throws IOException;
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
// char[]
|
||||
private char[] buff;
|
||||
|
||||
// transient as serialization is primarily for values via, e.g. JMX
|
||||
private transient CharInputChannel in = null;
|
||||
private transient CharOutputChannel out = null;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized CharChunk object.
|
||||
*/
|
||||
public CharChunk() {
|
||||
}
|
||||
|
||||
|
||||
public CharChunk(int initial) {
|
||||
allocate(initial, -1);
|
||||
}
|
||||
|
||||
|
||||
// --------------------
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Setup --------------------
|
||||
|
||||
public void allocate(int initial, int limit) {
|
||||
if (buff == null || buff.length < initial) {
|
||||
buff = new char[initial];
|
||||
}
|
||||
setLimit(limit);
|
||||
start = 0;
|
||||
end = 0;
|
||||
isSet = true;
|
||||
hasHashCode = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the buffer to the specified subarray of characters.
|
||||
*
|
||||
* @param c the characters
|
||||
* @param off the start offset of the characters
|
||||
* @param len the length of the characters
|
||||
*/
|
||||
public void setChars(char[] c, int off, int len) {
|
||||
buff = c;
|
||||
start = off;
|
||||
end = start + len;
|
||||
isSet = true;
|
||||
hasHashCode = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the buffer.
|
||||
*/
|
||||
public char[] getChars() {
|
||||
return getBuffer();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the buffer.
|
||||
*/
|
||||
public char[] getBuffer() {
|
||||
return buff;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When the buffer is empty, read the data from the input channel.
|
||||
*
|
||||
* @param in The input channel
|
||||
*/
|
||||
public void setCharInputChannel(CharInputChannel in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When the buffer is full, write the data to the output channel. Also used
|
||||
* when large amount of data is appended. If not set, the buffer will grow
|
||||
* to the limit.
|
||||
*
|
||||
* @param out The output channel
|
||||
*/
|
||||
public void setCharOutputChannel(CharOutputChannel out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Adding data to the buffer --------------------
|
||||
|
||||
public void append(char c) throws IOException {
|
||||
makeSpace(1);
|
||||
int limit = getLimitInternal();
|
||||
|
||||
// couldn't make space
|
||||
if (end >= limit) {
|
||||
flushBuffer();
|
||||
}
|
||||
buff[end++] = c;
|
||||
}
|
||||
|
||||
|
||||
public void append(CharChunk src) throws IOException {
|
||||
append(src.getBuffer(), src.getOffset(), src.getLength());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add data to the buffer.
|
||||
*
|
||||
* @param src Char array
|
||||
* @param off Offset
|
||||
* @param len Length
|
||||
* @throws IOException Writing overflow data to the output channel failed
|
||||
*/
|
||||
public void append(char src[], int off, int len) throws IOException {
|
||||
// will grow, up to limit
|
||||
makeSpace(len);
|
||||
int limit = getLimitInternal();
|
||||
|
||||
// Optimize on a common case.
|
||||
// If the buffer is empty and the source is going to fill up all the
|
||||
// space in buffer, may as well write it directly to the output,
|
||||
// and avoid an extra copy
|
||||
if (len == limit && end == start && out != null) {
|
||||
out.realWriteChars(src, off, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are below the limit
|
||||
if (len <= limit - end) {
|
||||
System.arraycopy(src, off, buff, end, len);
|
||||
end += len;
|
||||
return;
|
||||
}
|
||||
|
||||
// Need more space than we can afford, need to flush buffer.
|
||||
|
||||
// The buffer is already at (or bigger than) limit.
|
||||
|
||||
// Optimization:
|
||||
// If len-avail < length (i.e. after we fill the buffer with what we
|
||||
// can, the remaining will fit in the buffer) we'll just copy the first
|
||||
// part, flush, then copy the second part - 1 write and still have some
|
||||
// space for more. We'll still have 2 writes, but we write more on the first.
|
||||
|
||||
if (len + end < 2 * limit) {
|
||||
/*
|
||||
* If the request length exceeds the size of the output buffer,
|
||||
* flush the output buffer and then write the data directly. We
|
||||
* can't avoid 2 writes, but we can write more on the second
|
||||
*/
|
||||
int avail = limit - end;
|
||||
System.arraycopy(src, off, buff, end, avail);
|
||||
end += avail;
|
||||
|
||||
flushBuffer();
|
||||
|
||||
System.arraycopy(src, off + avail, buff, end, len - avail);
|
||||
end += len - avail;
|
||||
|
||||
} else { // len > buf.length + avail
|
||||
// long write - flush the buffer and write the rest
|
||||
// directly from source
|
||||
flushBuffer();
|
||||
|
||||
out.realWriteChars(src, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append a string to the buffer.
|
||||
*
|
||||
* @param s The string
|
||||
* @throws IOException Writing overflow data to the output channel failed
|
||||
*/
|
||||
public void append(String s) throws IOException {
|
||||
append(s, 0, s.length());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append a string to the buffer.
|
||||
*
|
||||
* @param s The string
|
||||
* @param off Offset
|
||||
* @param len Length
|
||||
* @throws IOException Writing overflow data to the output channel failed
|
||||
*/
|
||||
public void append(String s, int off, int len) throws IOException {
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// will grow, up to limit
|
||||
makeSpace(len);
|
||||
int limit = getLimitInternal();
|
||||
|
||||
int sOff = off;
|
||||
int sEnd = off + len;
|
||||
while (sOff < sEnd) {
|
||||
int d = min(limit - end, sEnd - sOff);
|
||||
s.getChars(sOff, sOff + d, buff, end);
|
||||
sOff += d;
|
||||
end += d;
|
||||
if (end >= limit) {
|
||||
flushBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Removing data from the buffer --------------------
|
||||
|
||||
public int substract() throws IOException {
|
||||
if (checkEof()) {
|
||||
return -1;
|
||||
}
|
||||
return buff[start++];
|
||||
}
|
||||
|
||||
|
||||
public int substract(char dest[], int off, int len) throws IOException {
|
||||
if (checkEof()) {
|
||||
return -1;
|
||||
}
|
||||
int n = len;
|
||||
if (len > getLength()) {
|
||||
n = getLength();
|
||||
}
|
||||
System.arraycopy(buff, start, dest, off, n);
|
||||
start += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
private boolean checkEof() throws IOException {
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return true;
|
||||
}
|
||||
int n = in.realReadChars();
|
||||
if (n < 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the buffer to the sink. Called by append() when the limit is
|
||||
* reached. You can also call it explicitly to force the data to be written.
|
||||
*
|
||||
* @throws IOException Writing overflow data to the output channel failed
|
||||
*/
|
||||
public void flushBuffer() throws IOException {
|
||||
// assert out!=null
|
||||
if (out == null) {
|
||||
throw new IOException("Buffer overflow, no sink " + getLimit() + " " + buff.length);
|
||||
}
|
||||
out.realWriteChars(buff, start, end - start);
|
||||
end = start;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make space for len chars. If len is small, allocate a reserve space too.
|
||||
* Never grow bigger than the limit or {@link AbstractChunk#ARRAY_MAX_SIZE}.
|
||||
*
|
||||
* @param count The size
|
||||
*/
|
||||
public void makeSpace(int count) {
|
||||
char[] tmp = null;
|
||||
|
||||
int limit = getLimitInternal();
|
||||
|
||||
long newSize;
|
||||
long desiredSize = end + count;
|
||||
|
||||
// Can't grow above the limit
|
||||
if (desiredSize > limit) {
|
||||
desiredSize = limit;
|
||||
}
|
||||
|
||||
if (buff == null) {
|
||||
if (desiredSize < 256) {
|
||||
desiredSize = 256; // take a minimum
|
||||
}
|
||||
buff = new char[(int) desiredSize];
|
||||
}
|
||||
|
||||
// limit < buf.length (the buffer is already big)
|
||||
// or we already have space XXX
|
||||
if (desiredSize <= buff.length) {
|
||||
return;
|
||||
}
|
||||
// grow in larger chunks
|
||||
if (desiredSize < 2L * buff.length) {
|
||||
newSize = buff.length * 2L;
|
||||
} else {
|
||||
newSize = buff.length * 2L + count;
|
||||
}
|
||||
|
||||
if (newSize > limit) {
|
||||
newSize = limit;
|
||||
}
|
||||
tmp = new char[(int) newSize];
|
||||
|
||||
// Some calling code assumes buffer will not be compacted
|
||||
System.arraycopy(buff, 0, tmp, 0, end);
|
||||
buff = tmp;
|
||||
tmp = null;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Conversion and getters --------------------
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (null == buff) {
|
||||
return null;
|
||||
} else if (end - start == 0) {
|
||||
return "";
|
||||
}
|
||||
return StringCache.toString(this);
|
||||
}
|
||||
|
||||
|
||||
public String toStringInternal() {
|
||||
return new String(buff, start, end - start);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- equals --------------------
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof CharChunk) {
|
||||
return equals((CharChunk) obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
*
|
||||
* @param s the String to compare
|
||||
* @return <code>true</code> if the comparison succeeded, <code>false</code>
|
||||
* otherwise
|
||||
*/
|
||||
public boolean equals(String s) {
|
||||
char[] c = buff;
|
||||
int len = end - start;
|
||||
if (c == null || len != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int off = start;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (c[off++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
*
|
||||
* @param s the String to compare
|
||||
* @return <code>true</code> if the comparison succeeded, <code>false</code>
|
||||
* otherwise
|
||||
*/
|
||||
public boolean equalsIgnoreCase(String s) {
|
||||
char[] c = buff;
|
||||
int len = end - start;
|
||||
if (c == null || len != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int off = start;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(CharChunk cc) {
|
||||
return equals(cc.getChars(), cc.getOffset(), cc.getLength());
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(char b2[], int off2, int len2) {
|
||||
char b1[] = buff;
|
||||
if (b1 == null && b2 == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int len = end - start;
|
||||
if (len != len2 || b1 == null || b2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int off1 = start;
|
||||
|
||||
while (len-- > 0) {
|
||||
if (b1[off1++] != b2[off2++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the message bytes starts with the specified
|
||||
* string.
|
||||
* @param s The string
|
||||
*/
|
||||
public boolean startsWith(String s) {
|
||||
char[] c = buff;
|
||||
int len = s.length();
|
||||
if (c == null || len > end - start) {
|
||||
return false;
|
||||
}
|
||||
int off = start;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (c[off++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the buffer starts with the specified string.
|
||||
*
|
||||
* @param s the string
|
||||
* @param pos The position
|
||||
*
|
||||
* @return <code>true</code> if the start matches
|
||||
*/
|
||||
public boolean startsWithIgnoreCase(String s, int pos) {
|
||||
char[] c = buff;
|
||||
int len = s.length();
|
||||
if (c == null || len + pos > end - start) {
|
||||
return false;
|
||||
}
|
||||
int off = start + pos;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the message bytes end with the specified
|
||||
* string.
|
||||
* @param s The string
|
||||
*/
|
||||
public boolean endsWith(String s) {
|
||||
char[] c = buff;
|
||||
int len = s.length();
|
||||
if (c == null || len > end - start) {
|
||||
return false;
|
||||
}
|
||||
int off = end - len;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (c[off++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int getBufferElement(int index) {
|
||||
return buff[index];
|
||||
}
|
||||
|
||||
|
||||
public int indexOf(char c) {
|
||||
return indexOf(c, start);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first instance of the given character in this CharChunk
|
||||
* starting at the specified char. If the character is not found, -1 is
|
||||
* returned. <br>
|
||||
*
|
||||
* @param c The character
|
||||
* @param starting The start position
|
||||
* @return The position of the first instance of the character or -1 if the
|
||||
* character is not found.
|
||||
*/
|
||||
public int indexOf(char c, int starting) {
|
||||
int ret = indexOf(buff, start + starting, end, c);
|
||||
return (ret >= start) ? ret - start : -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first instance of the given character in the given char array
|
||||
* between the specified start and end. <br>
|
||||
*
|
||||
* @param chars The array to search
|
||||
* @param start The point to start searching from in the array
|
||||
* @param end The point to stop searching in the array
|
||||
* @param s The character to search for
|
||||
* @return The position of the first instance of the character or -1 if the
|
||||
* character is not found.
|
||||
*/
|
||||
public static int indexOf(char chars[], int start, int end, char s) {
|
||||
int offset = start;
|
||||
|
||||
while (offset < end) {
|
||||
char c = chars[offset];
|
||||
if (c == s) {
|
||||
return offset;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- utils
|
||||
private int min(int a, int b) {
|
||||
if (a < b) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
// Char sequence impl
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return buff[index + start];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
try {
|
||||
CharChunk result = (CharChunk) this.clone();
|
||||
result.setOffset(this.start + start);
|
||||
result.setEnd(this.start + end);
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
// Cannot happen
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return end - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* NO-OP.
|
||||
*
|
||||
* @param optimizedWrite Ignored
|
||||
*
|
||||
* @deprecated Unused code. This is now a NO-OP and will be removed without
|
||||
* replacement in Tomcat 10.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setOptimizedWrite(boolean optimizedWrite) {
|
||||
}
|
||||
}
|
||||
230
java/org/apache/tomcat/util/buf/CharsetCache.java
Normal file
230
java/org/apache/tomcat/util/buf/CharsetCache.java
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public class CharsetCache {
|
||||
|
||||
/* Note: Package private to enable testing without reflection */
|
||||
static final String[] INITIAL_CHARSETS = new String[] { "iso-8859-1", "utf-8" };
|
||||
|
||||
/*
|
||||
* Note: Package private to enable testing without reflection
|
||||
*/
|
||||
static final String[] LAZY_CHARSETS = new String[] {
|
||||
// Initial set from Oracle JDK 8 u192
|
||||
"037", "1006", "1025", "1026", "1046", "1047", "1089", "1097", "1098", "1112", "1122", "1123", "1124",
|
||||
"1140", "1141", "1142", "1143", "1144", "1145", "1146", "1147", "1148", "1149", "1166", "1364", "1381",
|
||||
"1383", "273", "277", "278", "280", "284", "285", "290", "297", "300", "33722", "420", "424", "437", "500",
|
||||
"5601", "646", "737", "775", "813", "834", "838", "850", "852", "855", "856", "857", "858", "860", "861",
|
||||
"862", "863", "864", "865", "866", "868", "869", "870", "871", "874", "875", "8859_13", "8859_15", "8859_2",
|
||||
"8859_3", "8859_4", "8859_5", "8859_6", "8859_7", "8859_8", "8859_9", "912", "913", "914", "915", "916",
|
||||
"918", "920", "921", "922", "923", "930", "933", "935", "937", "939", "942", "942c", "943", "943c", "948",
|
||||
"949", "949c", "950", "964", "970", "ansi-1251", "ansi_x3.4-1968", "ansi_x3.4-1986", "arabic", "ascii",
|
||||
"ascii7", "asmo-708", "big5", "big5-hkscs", "big5-hkscs", "big5-hkscs-2001", "big5-hkscs:unicode3.0",
|
||||
"big5_hkscs", "big5_hkscs_2001", "big5_solaris", "big5hk", "big5hk-2001", "big5hkscs", "big5hkscs-2001",
|
||||
"ccsid00858", "ccsid01140", "ccsid01141", "ccsid01142", "ccsid01143", "ccsid01144", "ccsid01145",
|
||||
"ccsid01146", "ccsid01147", "ccsid01148", "ccsid01149", "cesu-8", "cesu8", "cns11643", "compound_text",
|
||||
"cp-ar", "cp-gr", "cp-is", "cp00858", "cp01140", "cp01141", "cp01142", "cp01143", "cp01144", "cp01145",
|
||||
"cp01146", "cp01147", "cp01148", "cp01149", "cp037", "cp1006", "cp1025", "cp1026", "cp1046", "cp1047",
|
||||
"cp1089", "cp1097", "cp1098", "cp1112", "cp1122", "cp1123", "cp1124", "cp1140", "cp1141", "cp1142",
|
||||
"cp1143", "cp1144", "cp1145", "cp1146", "cp1147", "cp1148", "cp1149", "cp1166", "cp1250", "cp1251",
|
||||
"cp1252", "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp1364", "cp1381", "cp1383", "cp273",
|
||||
"cp277", "cp278", "cp280", "cp284", "cp285", "cp290", "cp297", "cp300", "cp33722", "cp367", "cp420",
|
||||
"cp424", "cp437", "cp500", "cp50220", "cp50221", "cp5346", "cp5347", "cp5348", "cp5349", "cp5350", "cp5353",
|
||||
"cp737", "cp775", "cp813", "cp833", "cp834", "cp838", "cp850", "cp852", "cp855", "cp856", "cp857", "cp858",
|
||||
"cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866", "cp868", "cp869", "cp870", "cp871", "cp874",
|
||||
"cp875", "cp912", "cp913", "cp914", "cp915", "cp916", "cp918", "cp920", "cp921", "cp922", "cp923", "cp930",
|
||||
"cp933", "cp935", "cp936", "cp937", "cp939", "cp942", "cp942c", "cp943", "cp943c", "cp948", "cp949",
|
||||
"cp949c", "cp950", "cp964", "cp970", "cpibm284", "cpibm285", "cpibm297", "cpibm37", "cs-ebcdic-cp-ca",
|
||||
"cs-ebcdic-cp-nl", "cs-ebcdic-cp-us", "cs-ebcdic-cp-wt", "csascii", "csbig5", "cscesu-8", "cseuckr",
|
||||
"cseucpkdfmtjapanese", "cshalfwidthkatakana", "csibm037", "csibm278", "csibm284", "csibm285", "csibm290",
|
||||
"csibm297", "csibm420", "csibm424", "csibm500", "csibm857", "csibm860", "csibm861", "csibm862", "csibm863",
|
||||
"csibm864", "csibm865", "csibm866", "csibm868", "csibm869", "csibm870", "csibm871", "csiso153gost1976874",
|
||||
"csiso159jisx02121990", "csiso2022cn", "csiso2022jp", "csiso2022jp2", "csiso2022kr", "csiso87jisx0208",
|
||||
"csisolatin0", "csisolatin2", "csisolatin3", "csisolatin4", "csisolatin5", "csisolatin9",
|
||||
"csisolatinarabic", "csisolatincyrillic", "csisolatingreek", "csisolatinhebrew", "csjisencoding", "cskoi8r",
|
||||
"cspc850multilingual", "cspc862latinhebrew", "cspc8codepage437", "cspcp852", "cspcp855", "csshiftjis",
|
||||
"cswindows31j", "cyrillic", "default", "ebcdic-cp-ar1", "ebcdic-cp-ar2", "ebcdic-cp-bh", "ebcdic-cp-ca",
|
||||
"ebcdic-cp-ch", "ebcdic-cp-fr", "ebcdic-cp-gb", "ebcdic-cp-he", "ebcdic-cp-is", "ebcdic-cp-nl",
|
||||
"ebcdic-cp-roece", "ebcdic-cp-se", "ebcdic-cp-us", "ebcdic-cp-wt", "ebcdic-cp-yu", "ebcdic-de-273+euro",
|
||||
"ebcdic-dk-277+euro", "ebcdic-es-284+euro", "ebcdic-fi-278+euro", "ebcdic-fr-277+euro", "ebcdic-gb",
|
||||
"ebcdic-gb-285+euro", "ebcdic-international-500+euro", "ebcdic-it-280+euro", "ebcdic-jp-kana",
|
||||
"ebcdic-no-277+euro", "ebcdic-s-871+euro", "ebcdic-se-278+euro", "ebcdic-sv", "ebcdic-us-037+euro",
|
||||
"ecma-114", "ecma-118", "elot_928", "euc-cn", "euc-jp", "euc-jp-linux", "euc-kr", "euc-tw", "euc_cn",
|
||||
"euc_jp", "euc_jp_linux", "euc_jp_solaris", "euc_kr", "euc_tw", "euccn", "eucjis", "eucjp", "eucjp-open",
|
||||
"euckr", "euctw", "extended_unix_code_packed_format_for_japanese", "gb18030", "gb18030-2000", "gb2312",
|
||||
"gb2312", "gb2312-1980", "gb2312-80", "gbk", "greek", "greek8", "hebrew", "ibm-037", "ibm-1006", "ibm-1025",
|
||||
"ibm-1026", "ibm-1046", "ibm-1047", "ibm-1089", "ibm-1097", "ibm-1098", "ibm-1112", "ibm-1122", "ibm-1123",
|
||||
"ibm-1124", "ibm-1166", "ibm-1364", "ibm-1381", "ibm-1383", "ibm-273", "ibm-277", "ibm-278", "ibm-280",
|
||||
"ibm-284", "ibm-285", "ibm-290", "ibm-297", "ibm-300", "ibm-33722", "ibm-33722_vascii_vpua", "ibm-37",
|
||||
"ibm-420", "ibm-424", "ibm-437", "ibm-500", "ibm-5050", "ibm-737", "ibm-775", "ibm-813", "ibm-833",
|
||||
"ibm-834", "ibm-838", "ibm-850", "ibm-852", "ibm-855", "ibm-856", "ibm-857", "ibm-860", "ibm-861",
|
||||
"ibm-862", "ibm-863", "ibm-864", "ibm-865", "ibm-866", "ibm-868", "ibm-869", "ibm-870", "ibm-871",
|
||||
"ibm-874", "ibm-875", "ibm-912", "ibm-913", "ibm-914", "ibm-915", "ibm-916", "ibm-918", "ibm-920",
|
||||
"ibm-921", "ibm-922", "ibm-923", "ibm-930", "ibm-933", "ibm-935", "ibm-937", "ibm-939", "ibm-942",
|
||||
"ibm-942c", "ibm-943", "ibm-943c", "ibm-948", "ibm-949", "ibm-949c", "ibm-950", "ibm-964", "ibm-970",
|
||||
"ibm-euckr", "ibm-thai", "ibm00858", "ibm01140", "ibm01141", "ibm01142", "ibm01143", "ibm01144", "ibm01145",
|
||||
"ibm01146", "ibm01147", "ibm01148", "ibm01149", "ibm037", "ibm037", "ibm1006", "ibm1025", "ibm1026",
|
||||
"ibm1026", "ibm1046", "ibm1047", "ibm1089", "ibm1097", "ibm1098", "ibm1112", "ibm1122", "ibm1123",
|
||||
"ibm1124", "ibm1166", "ibm1364", "ibm1381", "ibm1383", "ibm273", "ibm273", "ibm277", "ibm277", "ibm278",
|
||||
"ibm278", "ibm280", "ibm280", "ibm284", "ibm284", "ibm285", "ibm285", "ibm290", "ibm290", "ibm297",
|
||||
"ibm297", "ibm300", "ibm33722", "ibm367", "ibm420", "ibm420", "ibm424", "ibm424", "ibm437", "ibm437",
|
||||
"ibm500", "ibm500", "ibm737", "ibm775", "ibm775", "ibm813", "ibm833", "ibm834", "ibm838", "ibm850",
|
||||
"ibm850", "ibm852", "ibm852", "ibm855", "ibm855", "ibm856", "ibm857", "ibm857", "ibm860", "ibm860",
|
||||
"ibm861", "ibm861", "ibm862", "ibm862", "ibm863", "ibm863", "ibm864", "ibm864", "ibm865", "ibm865",
|
||||
"ibm866", "ibm866", "ibm868", "ibm868", "ibm869", "ibm869", "ibm870", "ibm870", "ibm871", "ibm871",
|
||||
"ibm874", "ibm875", "ibm912", "ibm913", "ibm914", "ibm915", "ibm916", "ibm918", "ibm920", "ibm921",
|
||||
"ibm922", "ibm923", "ibm930", "ibm933", "ibm935", "ibm937", "ibm939", "ibm942", "ibm942c", "ibm943",
|
||||
"ibm943c", "ibm948", "ibm949", "ibm949c", "ibm950", "ibm964", "ibm970", "iscii", "iscii91",
|
||||
"iso-10646-ucs-2", "iso-2022-cn", "iso-2022-cn-cns", "iso-2022-cn-gb", "iso-2022-jp", "iso-2022-jp-2",
|
||||
"iso-2022-kr", "iso-8859-11", "iso-8859-13", "iso-8859-15", "iso-8859-15", "iso-8859-2", "iso-8859-3",
|
||||
"iso-8859-4", "iso-8859-5", "iso-8859-6", "iso-8859-7", "iso-8859-8", "iso-8859-9", "iso-ir-101",
|
||||
"iso-ir-109", "iso-ir-110", "iso-ir-126", "iso-ir-127", "iso-ir-138", "iso-ir-144", "iso-ir-148",
|
||||
"iso-ir-153", "iso-ir-159", "iso-ir-6", "iso-ir-87", "iso2022cn", "iso2022cn_cns", "iso2022cn_gb",
|
||||
"iso2022jp", "iso2022jp2", "iso2022kr", "iso646-us", "iso8859-13", "iso8859-15", "iso8859-2", "iso8859-3",
|
||||
"iso8859-4", "iso8859-5", "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859_11", "iso8859_13",
|
||||
"iso8859_15", "iso8859_15_fdis", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5", "iso8859_6",
|
||||
"iso8859_7", "iso8859_8", "iso8859_9", "iso_646.irv:1983", "iso_646.irv:1991", "iso_8859-13", "iso_8859-15",
|
||||
"iso_8859-2", "iso_8859-2:1987", "iso_8859-3", "iso_8859-3:1988", "iso_8859-4", "iso_8859-4:1988",
|
||||
"iso_8859-5", "iso_8859-5:1988", "iso_8859-6", "iso_8859-6:1987", "iso_8859-7", "iso_8859-7:1987",
|
||||
"iso_8859-8", "iso_8859-8:1988", "iso_8859-9", "iso_8859-9:1989", "jis", "jis0201", "jis0208", "jis0212",
|
||||
"jis_c6226-1983", "jis_encoding", "jis_x0201", "jis_x0201", "jis_x0208-1983", "jis_x0212-1990",
|
||||
"jis_x0212-1990", "jisautodetect", "johab", "koi8", "koi8-r", "koi8-u", "koi8_r", "koi8_u",
|
||||
"ks_c_5601-1987", "ksc5601", "ksc5601-1987", "ksc5601-1992", "ksc5601_1987", "ksc5601_1992", "ksc_5601",
|
||||
"l2", "l3", "l4", "l5", "l9", "latin0", "latin2", "latin3", "latin4", "latin5", "latin9", "macarabic",
|
||||
"maccentraleurope", "maccroatian", "maccyrillic", "macdingbat", "macgreek", "machebrew", "maciceland",
|
||||
"macroman", "macromania", "macsymbol", "macthai", "macturkish", "macukraine", "ms-874", "ms1361", "ms50220",
|
||||
"ms50221", "ms874", "ms932", "ms936", "ms949", "ms950", "ms950_hkscs", "ms950_hkscs_xp", "ms_936", "ms_949",
|
||||
"ms_kanji", "pc-multilingual-850+euro", "pck", "shift-jis", "shift_jis", "shift_jis", "sjis",
|
||||
"st_sev_358-88", "sun_eu_greek", "tis-620", "tis620", "tis620.2533", "unicode", "unicodebig",
|
||||
"unicodebigunmarked", "unicodelittle", "unicodelittleunmarked", "us", "us-ascii", "utf-16", "utf-16be",
|
||||
"utf-16le", "utf-32", "utf-32be", "utf-32be-bom", "utf-32le", "utf-32le-bom", "utf16", "utf32", "utf_16",
|
||||
"utf_16be", "utf_16le", "utf_32", "utf_32be", "utf_32be_bom", "utf_32le", "utf_32le_bom", "windows-1250",
|
||||
"windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", "windows-1256",
|
||||
"windows-1257", "windows-1258", "windows-31j", "windows-437", "windows-874", "windows-932", "windows-936",
|
||||
"windows-949", "windows-950", "windows-iso2022jp", "windows949", "x-big5-hkscs-2001", "x-big5-solaris",
|
||||
"x-compound-text", "x-compound_text", "x-euc-cn", "x-euc-jp", "x-euc-jp-linux", "x-euc-tw", "x-eucjp",
|
||||
"x-eucjp-open", "x-ibm1006", "x-ibm1025", "x-ibm1046", "x-ibm1097", "x-ibm1098", "x-ibm1112", "x-ibm1122",
|
||||
"x-ibm1123", "x-ibm1124", "x-ibm1166", "x-ibm1364", "x-ibm1381", "x-ibm1383", "x-ibm300", "x-ibm33722",
|
||||
"x-ibm737", "x-ibm833", "x-ibm834", "x-ibm856", "x-ibm874", "x-ibm875", "x-ibm921", "x-ibm922", "x-ibm930",
|
||||
"x-ibm933", "x-ibm935", "x-ibm937", "x-ibm939", "x-ibm942", "x-ibm942c", "x-ibm943", "x-ibm943c",
|
||||
"x-ibm948", "x-ibm949", "x-ibm949c", "x-ibm950", "x-ibm964", "x-ibm970", "x-iscii91", "x-iso-2022-cn-cns",
|
||||
"x-iso-2022-cn-gb", "x-iso-8859-11", "x-jis0208", "x-jisautodetect", "x-johab", "x-macarabic",
|
||||
"x-maccentraleurope", "x-maccroatian", "x-maccyrillic", "x-macdingbat", "x-macgreek", "x-machebrew",
|
||||
"x-maciceland", "x-macroman", "x-macromania", "x-macsymbol", "x-macthai", "x-macturkish", "x-macukraine",
|
||||
"x-ms932_0213", "x-ms950-hkscs", "x-ms950-hkscs-xp", "x-mswin-936", "x-pck", "x-sjis", "x-sjis_0213",
|
||||
"x-utf-16be", "x-utf-16le", "x-utf-16le-bom", "x-utf-32be", "x-utf-32be-bom", "x-utf-32le",
|
||||
"x-utf-32le-bom", "x-windows-50220", "x-windows-50221", "x-windows-874", "x-windows-949", "x-windows-950",
|
||||
"x-windows-iso2022jp", "x0201", "x0208", "x0212", "x11-compound_text",
|
||||
// Added from Oracle JDK 10.0.2
|
||||
"csiso885915", "csiso885916", "iso-8859-16", "iso-ir-226", "iso_8859-16", "iso_8859-16:2001", "l10",
|
||||
"latin-9", "latin10", "ms932-0213", "ms932:2004", "ms932_0213", "shift_jis:2004", "shift_jis_0213:2004",
|
||||
"sjis-0213", "sjis:2004", "sjis_0213", "sjis_0213:2004", "windows-932-0213", "windows-932:2004",
|
||||
// Added from OpenJDK 11.0.1
|
||||
"932", "cp932", "cpeuccn", "ibm-1252", "ibm-932", "ibm-euccn", "ibm1252", "ibm932", "ibmeuccn", "x-ibm932",
|
||||
// Added from OpenJDK 12 ea28
|
||||
"1129", "cp1129", "ibm-1129", "ibm-euctw", "ibm1129", "x-ibm1129",
|
||||
// Added from OpenJDK 13 ea15
|
||||
"29626c", "833", "cp29626c", "ibm-1140", "ibm-1141", "ibm-1142", "ibm-1143", "ibm-1144", "ibm-1145",
|
||||
"ibm-1146", "ibm-1147", "ibm-1148", "ibm-1149", "ibm-29626c", "ibm-858", "ibm-eucjp", "ibm1140", "ibm1141",
|
||||
"ibm1142", "ibm1143", "ibm1144", "ibm1145", "ibm1146", "ibm1147", "ibm1148", "ibm1149", "ibm29626c",
|
||||
"ibm858", "x-ibm29626c",
|
||||
// Added from HPE JVM 1.8.0.17-hp-ux
|
||||
"cp1051", "cp1386", "cshproman8", "hp-roman8", "ibm-1051", "r8", "roman8", "roman9"
|
||||
};
|
||||
|
||||
private static final Charset DUMMY_CHARSET = new DummyCharset("Dummy", null);
|
||||
|
||||
private ConcurrentMap<String,Charset> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public CharsetCache() {
|
||||
// Pre-populate the cache
|
||||
for (String charsetName : INITIAL_CHARSETS) {
|
||||
Charset charset = Charset.forName(charsetName);
|
||||
addToCache(charsetName, charset);
|
||||
}
|
||||
|
||||
for (String charsetName : LAZY_CHARSETS) {
|
||||
addToCache(charsetName, DUMMY_CHARSET);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void addToCache(String name, Charset charset) {
|
||||
cache.put(name, charset);
|
||||
for (String alias : charset.aliases()) {
|
||||
cache.put(alias.toLowerCase(Locale.ENGLISH), charset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Charset getCharset(String charsetName) {
|
||||
String lcCharsetName = charsetName.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
Charset result = cache.get(lcCharsetName);
|
||||
|
||||
if (result == DUMMY_CHARSET) {
|
||||
// Name is known but the Charset is not in the cache
|
||||
Charset charset = Charset.forName(lcCharsetName);
|
||||
if (charset == null) {
|
||||
// Charset not available in this JVM - remove cache entry
|
||||
cache.remove(lcCharsetName);
|
||||
result = null;
|
||||
} else {
|
||||
// Charset is available - populate cache entry
|
||||
addToCache(lcCharsetName, charset);
|
||||
result = charset;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Placeholder Charset implementation for entries that will be loaded lazily
|
||||
* into the cache.
|
||||
*/
|
||||
private static class DummyCharset extends Charset {
|
||||
|
||||
protected DummyCharset(String canonicalName, String[] aliases) {
|
||||
super(canonicalName, aliases);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Charset cs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharsetDecoder newDecoder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharsetEncoder newEncoder() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
java/org/apache/tomcat/util/buf/Constants.java
Normal file
26
java/org/apache/tomcat/util/buf/Constants.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
/**
|
||||
* String constants for the file package.
|
||||
*/
|
||||
public final class Constants {
|
||||
|
||||
public static final String Package = "org.apache.tomcat.util.buf";
|
||||
|
||||
}
|
||||
118
java/org/apache/tomcat/util/buf/HexUtils.java
Normal file
118
java/org/apache/tomcat/util/buf/HexUtils.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Tables useful when converting byte arrays to and from strings of hexadecimal
|
||||
* digits.
|
||||
* Code from Ajp11, from Apache's JServ.
|
||||
*
|
||||
* @author Craig R. McClanahan
|
||||
*/
|
||||
public final class HexUtils {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(Constants.Package);
|
||||
|
||||
// -------------------------------------------------------------- Constants
|
||||
|
||||
/**
|
||||
* Table for HEX to DEC byte translation.
|
||||
*/
|
||||
private static final int[] DEC = {
|
||||
00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1, -1, -1, -1, -1,
|
||||
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, 10, 11, 12, 13, 14, 15,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Table for DEC to HEX byte translation.
|
||||
*/
|
||||
private static final byte[] HEX =
|
||||
{ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
|
||||
(byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
|
||||
(byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' };
|
||||
|
||||
|
||||
/**
|
||||
* Table for byte to hex string translation.
|
||||
*/
|
||||
private static final char[] hex = "0123456789abcdef".toCharArray();
|
||||
|
||||
|
||||
// --------------------------------------------------------- Static Methods
|
||||
|
||||
public static int getDec(int index) {
|
||||
// Fast for correct values, slower for incorrect ones
|
||||
try {
|
||||
return DEC[index - '0'];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static byte getHex(int index) {
|
||||
return HEX[index];
|
||||
}
|
||||
|
||||
|
||||
public static String toHexString(byte[] bytes) {
|
||||
if (null == bytes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(bytes.length << 1);
|
||||
|
||||
for(int i = 0; i < bytes.length; ++i) {
|
||||
sb.append(hex[(bytes[i] & 0xf0) >> 4])
|
||||
.append(hex[(bytes[i] & 0x0f)])
|
||||
;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] fromHexString(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((input.length() & 1) == 1) {
|
||||
// Odd number of characters
|
||||
throw new IllegalArgumentException(sm.getString("hexUtils.fromHex.oddDigits"));
|
||||
}
|
||||
|
||||
char[] inputChars = input.toCharArray();
|
||||
byte[] result = new byte[input.length() >> 1];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
int upperNibble = getDec(inputChars[2*i]);
|
||||
int lowerNibble = getDec(inputChars[2*i + 1]);
|
||||
if (upperNibble < 0 || lowerNibble < 0) {
|
||||
// Non hex character
|
||||
throw new IllegalArgumentException(sm.getString("hexUtils.fromHex.nonHex"));
|
||||
}
|
||||
result[i] = (byte) ((upperNibble << 4) + lowerNibble);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
34
java/org/apache/tomcat/util/buf/LocalStrings.properties
Normal file
34
java/org/apache/tomcat/util/buf/LocalStrings.properties
Normal file
@@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
|
||||
asn1Parser.lengthInvalid=Invalid length [{0}] bytes reported when the input data length is [{1}] bytes
|
||||
asn1Parser.tagMismatch=Expected to find value [{0}] but found value [{1}]
|
||||
|
||||
b2cConverter.unknownEncoding=The character encoding [{0}] is not supported
|
||||
|
||||
byteBufferUtils.cleaner=Cannot use direct ByteBuffer cleaner, memory leaking may occur
|
||||
|
||||
c2bConverter.recycleFailed=Failed to recycle the C2B Converter. Creating new BufferedWriter, WriteConvertor and IntermediateOutputStream.
|
||||
|
||||
hexUtils.fromHex.nonHex=The input must consist only of hex digits
|
||||
hexUtils.fromHex.oddDigits=The input must consist of an even number of hex digits
|
||||
|
||||
uDecoder.eof=End of file (EOF)
|
||||
uDecoder.noSlash=The encoded slash character is not allowed
|
||||
uDecoder.urlDecode.conversionError=Failed to decode [{0}] using character set [{1}]
|
||||
uDecoder.urlDecode.missingDigit=Failed to decode [{0}] because the % character must be followed by two hexademical digits
|
||||
uDecoder.urlDecode.uee=Unable to URL decode the specified input since the encoding [{0}] is not supported.
|
||||
|
||||
udecoder.urlDecode.iae=It is practical to %nn decode a byte array since how the %nn is encoded will vary by character set
|
||||
16
java/org/apache/tomcat/util/buf/LocalStrings_de.properties
Normal file
16
java/org/apache/tomcat/util/buf/LocalStrings_de.properties
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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.
|
||||
|
||||
uDecoder.convertHexDigit.notHex=[{0}] ist keine hexadezimale Ziffer
|
||||
16
java/org/apache/tomcat/util/buf/LocalStrings_es.properties
Normal file
16
java/org/apache/tomcat/util/buf/LocalStrings_es.properties
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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.
|
||||
|
||||
b2cConverter.unknownEncoding=La codificación de carácter [{0}] no está soportada
|
||||
29
java/org/apache/tomcat/util/buf/LocalStrings_fr.properties
Normal file
29
java/org/apache/tomcat/util/buf/LocalStrings_fr.properties
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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.
|
||||
|
||||
asn1Parser.lengthInvalid=Une longueur d''octets invalide [{0}] a été rapportée alors que la longueur de données en entrée est de [{1}] octets
|
||||
asn1Parser.tagMismatch=La valeur [{0}] était attendue mais la valeur [{1}] a été rencontrée
|
||||
|
||||
b2cConverter.unknownEncoding=L''encodage de caractères [{0}] n''est pas supporté
|
||||
|
||||
byteBufferUtils.cleaner=Impossible d'utiliser le nettoyeur de ByteBuffers directs, une fuite de mémoire peut se produire
|
||||
|
||||
hexUtils.fromHex.nonHex=L'entrée doit être uniquement des chiffres héxadécimaux
|
||||
hexUtils.fromHex.oddDigits=L'entrée doit contenir un nombre pair de chiffres héxadécimaux
|
||||
|
||||
uDecoder.eof=Fin de fichier (EOF)
|
||||
uDecoder.noSlash=Un caractère slash encodé n'est pas autorisé
|
||||
uDecoder.urlDecode.conversionError=Echec de décodage [{0}] en utilisant le jeu de caractères [{1}]
|
||||
uDecoder.urlDecode.missingDigit=Impossible de décoder [{0}] parce que le caractère % doit être suivi de deux chiffres héxadécimaux
|
||||
26
java/org/apache/tomcat/util/buf/LocalStrings_ja.properties
Normal file
26
java/org/apache/tomcat/util/buf/LocalStrings_ja.properties
Normal file
@@ -0,0 +1,26 @@
|
||||
# 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.
|
||||
|
||||
b2cConverter.unknownEncoding=文字エンコーディング [{0}] は未対応です。
|
||||
|
||||
byteBufferUtils.cleaner=直接ByteBufferクリーナーを使用することはできません、メモリリークが発生する可能性があります。
|
||||
|
||||
hexUtils.fromHex.nonHex=入力は16進数でなければなりません
|
||||
hexUtils.fromHex.oddDigits=入力は、偶数の16進数で構成する必要があります。
|
||||
|
||||
uDecoder.eof=予期せぬ場所で終端に達しました。
|
||||
uDecoder.noSlash="/" を符号化して含めることはできません。
|
||||
uDecoder.urlDecode.conversionError=文字セット[{1}]を使用して[{0}]のデコードに失敗しました
|
||||
uDecoder.urlDecode.missingDigit=%文字の後ろに2つの16進数字が続く必要があるため、[{0}]のデコードに失敗しました。
|
||||
29
java/org/apache/tomcat/util/buf/LocalStrings_ko.properties
Normal file
29
java/org/apache/tomcat/util/buf/LocalStrings_ko.properties
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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.
|
||||
|
||||
asn1Parser.lengthInvalid=입력 데이터의 바이트 길이가 [{1}]인데, 유효하지 않은 바이트 길이 [{0}](이)가 보고되었습니다.
|
||||
asn1Parser.tagMismatch=[{0}] 값이 기대 되었는데, [{1}] 값이 발견되었습니다.
|
||||
|
||||
b2cConverter.unknownEncoding=문자 인코딩 [{0}]은(는) 지원되지 않습니다.
|
||||
|
||||
byteBufferUtils.cleaner=직접적인 ByteBuffer cleaner를 사용할 수 없습니다. 메모리 누수가 발생할 수 있습니다.
|
||||
|
||||
hexUtils.fromHex.nonHex=입력은 오직 16진수 숫자로만 이루어져야 합니다.
|
||||
hexUtils.fromHex.oddDigits=입력은 반드시 짝수 개의 16진수 숫자들로 이루어져야 합니다.
|
||||
|
||||
uDecoder.eof=파일의 끝 (EOF)
|
||||
uDecoder.noSlash=인코딩된 슬래시 문자는 허용되지 않습니다.
|
||||
uDecoder.urlDecode.conversionError=문자셋 [{1}]을(를) 사용하여 [{0}]을(를) 디코드하지 못했습니다.
|
||||
uDecoder.urlDecode.missingDigit=% 문자 뒤에 두 개의 16진수 숫자들이 이어져야 하기 때문에, [{0}]을(를) 디코드하지 못했습니다.
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
|
||||
asn1Parser.lengthInvalid=无效长度 [{0}]字节报告,但是输入数据的长度是 [{1}]字节
|
||||
asn1Parser.tagMismatch=期望找到值 [{0}]但是却找到值 [{1}]
|
||||
|
||||
hexUtils.fromHex.nonHex=输入只能由十六进制数字组成
|
||||
|
||||
uDecoder.urlDecode.conversionError=使用编码[{1}]解码[{0}]失败
|
||||
578
java/org/apache/tomcat/util/buf/MessageBytes.java
Normal file
578
java/org/apache/tomcat/util/buf/MessageBytes.java
Normal file
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* This class is used to represent a subarray of bytes in an HTTP message.
|
||||
* It represents all request/response elements. The byte/char conversions are
|
||||
* delayed and cached. Everything is recyclable.
|
||||
*
|
||||
* The object can represent a byte[], a char[], or a (sub) String. All
|
||||
* operations can be made in case sensitive mode or not.
|
||||
*
|
||||
* @author dac@eng.sun.com
|
||||
* @author James Todd [gonzo@eng.sun.com]
|
||||
* @author Costin Manolache
|
||||
*/
|
||||
public final class MessageBytes implements Cloneable, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// primary type ( whatever is set as original value )
|
||||
private int type = T_NULL;
|
||||
|
||||
public static final int T_NULL = 0;
|
||||
/** getType() is T_STR if the the object used to create the MessageBytes
|
||||
was a String */
|
||||
public static final int T_STR = 1;
|
||||
/** getType() is T_BYTES if the the object used to create the MessageBytes
|
||||
was a byte[] */
|
||||
public static final int T_BYTES = 2;
|
||||
/** getType() is T_CHARS if the the object used to create the MessageBytes
|
||||
was a char[] */
|
||||
public static final int T_CHARS = 3;
|
||||
|
||||
private int hashCode=0;
|
||||
// did we compute the hashcode ?
|
||||
private boolean hasHashCode=false;
|
||||
|
||||
// Internal objects to represent array + offset, and specific methods
|
||||
private final ByteChunk byteC=new ByteChunk();
|
||||
private final CharChunk charC=new CharChunk();
|
||||
|
||||
// String
|
||||
private String strValue;
|
||||
// true if a String value was computed. Probably not needed,
|
||||
// strValue!=null is the same
|
||||
private boolean hasStrValue=false;
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized MessageBytes object.
|
||||
* Use static newInstance() in order to allow
|
||||
* future hooks.
|
||||
*/
|
||||
private MessageBytes() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new MessageBytes instance.
|
||||
* @return the instance
|
||||
*/
|
||||
public static MessageBytes newInstance() {
|
||||
return factory.newInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
return byteC.isNull() && charC.isNull() && !hasStrValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message bytes to an uninitialized (NULL) state.
|
||||
*/
|
||||
public void recycle() {
|
||||
type=T_NULL;
|
||||
byteC.recycle();
|
||||
charC.recycle();
|
||||
|
||||
strValue=null;
|
||||
|
||||
hasStrValue=false;
|
||||
hasHashCode=false;
|
||||
hasLongValue=false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the content to the specified subarray of bytes.
|
||||
*
|
||||
* @param b the bytes
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
*/
|
||||
public void setBytes(byte[] b, int off, int len) {
|
||||
byteC.setBytes( b, off, len );
|
||||
type=T_BYTES;
|
||||
hasStrValue=false;
|
||||
hasHashCode=false;
|
||||
hasLongValue=false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content to be a char[]
|
||||
*
|
||||
* @param c the chars
|
||||
* @param off the start offset of the chars
|
||||
* @param len the length of the chars
|
||||
*/
|
||||
public void setChars( char[] c, int off, int len ) {
|
||||
charC.setChars( c, off, len );
|
||||
type=T_CHARS;
|
||||
hasStrValue=false;
|
||||
hasHashCode=false;
|
||||
hasLongValue=false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content to be a string
|
||||
* @param s The string
|
||||
*/
|
||||
public void setString( String s ) {
|
||||
strValue=s;
|
||||
hasHashCode=false;
|
||||
hasLongValue=false;
|
||||
if (s == null) {
|
||||
hasStrValue=false;
|
||||
type=T_NULL;
|
||||
} else {
|
||||
hasStrValue=true;
|
||||
type=T_STR;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Conversion and getters --------------------
|
||||
|
||||
/**
|
||||
* Compute the string value.
|
||||
* @return the string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (hasStrValue) {
|
||||
return strValue;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case T_CHARS:
|
||||
strValue = charC.toString();
|
||||
hasStrValue = true;
|
||||
return strValue;
|
||||
case T_BYTES:
|
||||
strValue = byteC.toString();
|
||||
hasStrValue = true;
|
||||
return strValue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
/**
|
||||
* Return the type of the original content. Can be
|
||||
* T_STR, T_BYTES, T_CHARS or T_NULL
|
||||
* @return the type
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte chunk, representing the byte[] and offset/length.
|
||||
* Valid only if T_BYTES or after a conversion was made.
|
||||
* @return the byte chunk
|
||||
*/
|
||||
public ByteChunk getByteChunk() {
|
||||
return byteC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the char chunk, representing the char[] and offset/length.
|
||||
* Valid only if T_CHARS or after a conversion was made.
|
||||
* @return the char chunk
|
||||
*/
|
||||
public CharChunk getCharChunk() {
|
||||
return charC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string value.
|
||||
* Valid only if T_STR or after a conversion was made.
|
||||
* @return the string
|
||||
*/
|
||||
public String getString() {
|
||||
return strValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Charset used for string<->byte conversions.
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return byteC.getCharset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Charset used for string<->byte conversions.
|
||||
* @param charset The charset
|
||||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
byteC.setCharset(charset);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Do a char->byte conversion.
|
||||
*/
|
||||
public void toBytes() {
|
||||
if (isNull()) {
|
||||
return;
|
||||
}
|
||||
if (!byteC.isNull()) {
|
||||
type = T_BYTES;
|
||||
return;
|
||||
}
|
||||
toString();
|
||||
type = T_BYTES;
|
||||
Charset charset = byteC.getCharset();
|
||||
ByteBuffer result = charset.encode(strValue);
|
||||
byteC.setBytes(result.array(), result.arrayOffset(), result.limit());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert to char[] and fill the CharChunk.
|
||||
* XXX Not optimized - it converts to String first.
|
||||
*/
|
||||
public void toChars() {
|
||||
if (isNull()) {
|
||||
return;
|
||||
}
|
||||
if (!charC.isNull()) {
|
||||
type = T_CHARS;
|
||||
return;
|
||||
}
|
||||
// inefficient
|
||||
toString();
|
||||
type = T_CHARS;
|
||||
char cc[] = strValue.toCharArray();
|
||||
charC.setChars(cc, 0, cc.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the length of the original buffer.
|
||||
* Note that the length in bytes may be different from the length
|
||||
* in chars.
|
||||
* @return the length
|
||||
*/
|
||||
public int getLength() {
|
||||
if(type==T_BYTES) {
|
||||
return byteC.getLength();
|
||||
}
|
||||
if(type==T_CHARS) {
|
||||
return charC.getLength();
|
||||
}
|
||||
if(type==T_STR) {
|
||||
return strValue.length();
|
||||
}
|
||||
toString();
|
||||
if( strValue==null ) {
|
||||
return 0;
|
||||
}
|
||||
return strValue.length();
|
||||
}
|
||||
|
||||
// -------------------- equals --------------------
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return <code>true</code> if the comparison succeeded, <code>false</code> otherwise
|
||||
*/
|
||||
public boolean equals(String s) {
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
if (strValue == null) {
|
||||
return s == null;
|
||||
}
|
||||
return strValue.equals( s );
|
||||
case T_CHARS:
|
||||
return charC.equals( s );
|
||||
case T_BYTES:
|
||||
return byteC.equals( s );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return <code>true</code> if the comparison succeeded, <code>false</code> otherwise
|
||||
*/
|
||||
public boolean equalsIgnoreCase(String s) {
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
if (strValue == null) {
|
||||
return s == null;
|
||||
}
|
||||
return strValue.equalsIgnoreCase( s );
|
||||
case T_CHARS:
|
||||
return charC.equalsIgnoreCase( s );
|
||||
case T_BYTES:
|
||||
return byteC.equalsIgnoreCase( s );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof MessageBytes) {
|
||||
return equals((MessageBytes) obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean equals(MessageBytes mb) {
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
return mb.equals( strValue );
|
||||
}
|
||||
|
||||
if( mb.type != T_CHARS &&
|
||||
mb.type!= T_BYTES ) {
|
||||
// it's a string or int/date string value
|
||||
return equals( mb.toString() );
|
||||
}
|
||||
|
||||
// mb is either CHARS or BYTES.
|
||||
// this is either CHARS or BYTES
|
||||
// Deal with the 4 cases ( in fact 3, one is symmetric)
|
||||
|
||||
if( mb.type == T_CHARS && type==T_CHARS ) {
|
||||
return charC.equals( mb.charC );
|
||||
}
|
||||
if( mb.type==T_BYTES && type== T_BYTES ) {
|
||||
return byteC.equals( mb.byteC );
|
||||
}
|
||||
if( mb.type== T_CHARS && type== T_BYTES ) {
|
||||
return byteC.equals( mb.charC );
|
||||
}
|
||||
if( mb.type== T_BYTES && type== T_CHARS ) {
|
||||
return mb.byteC.equals( charC );
|
||||
}
|
||||
// can't happen
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
* @param pos The start position
|
||||
*/
|
||||
public boolean startsWithIgnoreCase(String s, int pos) {
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
if( strValue==null ) {
|
||||
return false;
|
||||
}
|
||||
if( strValue.length() < pos + s.length() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for( int i=0; i<s.length(); i++ ) {
|
||||
if( Ascii.toLower( s.charAt( i ) ) !=
|
||||
Ascii.toLower( strValue.charAt( pos + i ))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case T_CHARS:
|
||||
return charC.startsWithIgnoreCase( s, pos );
|
||||
case T_BYTES:
|
||||
return byteC.startsWithIgnoreCase( s, pos );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Hash code --------------------
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if( hasHashCode ) {
|
||||
return hashCode;
|
||||
}
|
||||
int code = 0;
|
||||
|
||||
code=hash();
|
||||
hashCode=code;
|
||||
hasHashCode=true;
|
||||
return code;
|
||||
}
|
||||
|
||||
// normal hash.
|
||||
private int hash() {
|
||||
int code=0;
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
// We need to use the same hash function
|
||||
for (int i = 0; i < strValue.length(); i++) {
|
||||
code = code * 37 + strValue.charAt( i );
|
||||
}
|
||||
return code;
|
||||
case T_CHARS:
|
||||
return charC.hash();
|
||||
case T_BYTES:
|
||||
return byteC.hash();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Inefficient initial implementation. Will be replaced on the next
|
||||
// round of tune-up
|
||||
public int indexOf(String s, int starting) {
|
||||
toString();
|
||||
return strValue.indexOf( s, starting );
|
||||
}
|
||||
|
||||
// Inefficient initial implementation. Will be replaced on the next
|
||||
// round of tune-up
|
||||
public int indexOf(String s) {
|
||||
return indexOf( s, 0 );
|
||||
}
|
||||
|
||||
public int indexOfIgnoreCase(String s, int starting) {
|
||||
toString();
|
||||
String upper=strValue.toUpperCase(Locale.ENGLISH);
|
||||
String sU=s.toUpperCase(Locale.ENGLISH);
|
||||
return upper.indexOf( sU, starting );
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the src into this MessageBytes, allocating more space if needed.
|
||||
* @param src The source
|
||||
* @throws IOException Writing overflow data to the output channel failed
|
||||
*/
|
||||
public void duplicate( MessageBytes src ) throws IOException
|
||||
{
|
||||
switch( src.getType() ) {
|
||||
case MessageBytes.T_BYTES:
|
||||
type=T_BYTES;
|
||||
ByteChunk bc=src.getByteChunk();
|
||||
byteC.allocate( 2 * bc.getLength(), -1 );
|
||||
byteC.append( bc );
|
||||
break;
|
||||
case MessageBytes.T_CHARS:
|
||||
type=T_CHARS;
|
||||
CharChunk cc=src.getCharChunk();
|
||||
charC.allocate( 2 * cc.getLength(), -1 );
|
||||
charC.append( cc );
|
||||
break;
|
||||
case MessageBytes.T_STR:
|
||||
type=T_STR;
|
||||
String sc=src.getString();
|
||||
this.setString( sc );
|
||||
break;
|
||||
}
|
||||
setCharset(src.getCharset());
|
||||
}
|
||||
|
||||
// -------------------- Deprecated code --------------------
|
||||
// efficient long
|
||||
// XXX used only for headers - shouldn't be stored here.
|
||||
private long longValue;
|
||||
private boolean hasLongValue=false;
|
||||
|
||||
/**
|
||||
* Set the buffer to the representation of a long.
|
||||
* @param l The long
|
||||
*/
|
||||
public void setLong(long l) {
|
||||
byteC.allocate(32, 64);
|
||||
long current = l;
|
||||
byte[] buf = byteC.getBuffer();
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
if (l == 0) {
|
||||
buf[end++] = (byte) '0';
|
||||
}
|
||||
if (l < 0) {
|
||||
current = -l;
|
||||
buf[end++] = (byte) '-';
|
||||
}
|
||||
while (current > 0) {
|
||||
int digit = (int) (current % 10);
|
||||
current = current / 10;
|
||||
buf[end++] = HexUtils.getHex(digit);
|
||||
}
|
||||
byteC.setOffset(0);
|
||||
byteC.setEnd(end);
|
||||
// Inverting buffer
|
||||
end--;
|
||||
if (l < 0) {
|
||||
start++;
|
||||
}
|
||||
while (end > start) {
|
||||
byte temp = buf[start];
|
||||
buf[start] = buf[end];
|
||||
buf[end] = temp;
|
||||
start++;
|
||||
end--;
|
||||
}
|
||||
longValue=l;
|
||||
hasStrValue=false;
|
||||
hasHashCode=false;
|
||||
hasLongValue=true;
|
||||
type=T_BYTES;
|
||||
}
|
||||
|
||||
// Used for headers conversion
|
||||
/**
|
||||
* Convert the buffer to an long, cache the value.
|
||||
* @return the long value
|
||||
*/
|
||||
public long getLong() {
|
||||
if( hasLongValue ) {
|
||||
return longValue;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case T_BYTES:
|
||||
longValue=byteC.getLong();
|
||||
break;
|
||||
default:
|
||||
longValue=Long.parseLong(toString());
|
||||
}
|
||||
|
||||
hasLongValue=true;
|
||||
return longValue;
|
||||
|
||||
}
|
||||
|
||||
// -------------------- Future may be different --------------------
|
||||
|
||||
private static final MessageBytesFactory factory=new MessageBytesFactory();
|
||||
|
||||
private static class MessageBytesFactory {
|
||||
protected MessageBytesFactory() {
|
||||
}
|
||||
public MessageBytes newInstance() {
|
||||
return new MessageBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
711
java/org/apache/tomcat/util/buf/StringCache.java
Normal file
711
java/org/apache/tomcat/util/buf/StringCache.java
Normal file
@@ -0,0 +1,711 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* This class implements a String cache for ByteChunk and CharChunk.
|
||||
*
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public class StringCache {
|
||||
|
||||
|
||||
private static final Log log = LogFactory.getLog(StringCache.class);
|
||||
|
||||
|
||||
// ------------------------------------------------------- Static Variables
|
||||
|
||||
|
||||
/**
|
||||
* Enabled ?
|
||||
*/
|
||||
protected static boolean byteEnabled = ("true".equals(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.byte.enabled", "false")));
|
||||
|
||||
|
||||
protected static boolean charEnabled = ("true".equals(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.char.enabled", "false")));
|
||||
|
||||
|
||||
protected static int trainThreshold = Integer.parseInt(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.trainThreshold", "20000"));
|
||||
|
||||
|
||||
protected static int cacheSize = Integer.parseInt(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.cacheSize", "200"));
|
||||
|
||||
|
||||
protected static final int maxStringSize =
|
||||
Integer.parseInt(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.maxStringSize", "128"));
|
||||
|
||||
|
||||
/**
|
||||
* Statistics hash map for byte chunk.
|
||||
*/
|
||||
protected static final HashMap<ByteEntry,int[]> bcStats =
|
||||
new HashMap<>(cacheSize);
|
||||
|
||||
|
||||
/**
|
||||
* toString count for byte chunk.
|
||||
*/
|
||||
protected static int bcCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Cache for byte chunk.
|
||||
*/
|
||||
protected static volatile ByteEntry[] bcCache = null;
|
||||
|
||||
|
||||
/**
|
||||
* Statistics hash map for char chunk.
|
||||
*/
|
||||
protected static final HashMap<CharEntry,int[]> ccStats =
|
||||
new HashMap<>(cacheSize);
|
||||
|
||||
|
||||
/**
|
||||
* toString count for char chunk.
|
||||
*/
|
||||
protected static int ccCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Cache for char chunk.
|
||||
*/
|
||||
protected static volatile CharEntry[] ccCache = null;
|
||||
|
||||
|
||||
/**
|
||||
* Access count.
|
||||
*/
|
||||
protected static int accessCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Hit count.
|
||||
*/
|
||||
protected static int hitCount = 0;
|
||||
|
||||
|
||||
// ------------------------------------------------------------ Properties
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the cacheSize.
|
||||
*/
|
||||
public int getCacheSize() {
|
||||
return cacheSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param cacheSize The cacheSize to set.
|
||||
*/
|
||||
public void setCacheSize(int cacheSize) {
|
||||
StringCache.cacheSize = cacheSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the enabled.
|
||||
*/
|
||||
public boolean getByteEnabled() {
|
||||
return byteEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param byteEnabled The enabled to set.
|
||||
*/
|
||||
public void setByteEnabled(boolean byteEnabled) {
|
||||
StringCache.byteEnabled = byteEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the enabled.
|
||||
*/
|
||||
public boolean getCharEnabled() {
|
||||
return charEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param charEnabled The enabled to set.
|
||||
*/
|
||||
public void setCharEnabled(boolean charEnabled) {
|
||||
StringCache.charEnabled = charEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the trainThreshold.
|
||||
*/
|
||||
public int getTrainThreshold() {
|
||||
return trainThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param trainThreshold The trainThreshold to set.
|
||||
*/
|
||||
public void setTrainThreshold(int trainThreshold) {
|
||||
StringCache.trainThreshold = trainThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the accessCount.
|
||||
*/
|
||||
public int getAccessCount() {
|
||||
return accessCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the hitCount.
|
||||
*/
|
||||
public int getHitCount() {
|
||||
return hitCount;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------- Public Static Methods
|
||||
|
||||
|
||||
public void reset() {
|
||||
hitCount = 0;
|
||||
accessCount = 0;
|
||||
synchronized (bcStats) {
|
||||
bcCache = null;
|
||||
bcCount = 0;
|
||||
}
|
||||
synchronized (ccStats) {
|
||||
ccCache = null;
|
||||
ccCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String toString(ByteChunk bc) {
|
||||
|
||||
// If the cache is null, then either caching is disabled, or we're
|
||||
// still training
|
||||
if (bcCache == null) {
|
||||
String value = bc.toStringInternal();
|
||||
if (byteEnabled && (value.length() < maxStringSize)) {
|
||||
// If training, everything is synced
|
||||
synchronized (bcStats) {
|
||||
// If the cache has been generated on a previous invocation
|
||||
// while waiting for the lock, just return the toString
|
||||
// value we just calculated
|
||||
if (bcCache != null) {
|
||||
return value;
|
||||
}
|
||||
// Two cases: either we just exceeded the train count, in
|
||||
// which case the cache must be created, or we just update
|
||||
// the count for the string
|
||||
if (bcCount > trainThreshold) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
// Sort the entries according to occurrence
|
||||
TreeMap<Integer,ArrayList<ByteEntry>> tempMap =
|
||||
new TreeMap<>();
|
||||
for (Entry<ByteEntry,int[]> item : bcStats.entrySet()) {
|
||||
ByteEntry entry = item.getKey();
|
||||
int[] countA = item.getValue();
|
||||
Integer count = Integer.valueOf(countA[0]);
|
||||
// Add to the list for that count
|
||||
ArrayList<ByteEntry> list = tempMap.get(count);
|
||||
if (list == null) {
|
||||
// Create list
|
||||
list = new ArrayList<>();
|
||||
tempMap.put(count, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
// Allocate array of the right size
|
||||
int size = bcStats.size();
|
||||
if (size > cacheSize) {
|
||||
size = cacheSize;
|
||||
}
|
||||
ByteEntry[] tempbcCache = new ByteEntry[size];
|
||||
// Fill it up using an alphabetical order
|
||||
// and a dumb insert sort
|
||||
ByteChunk tempChunk = new ByteChunk();
|
||||
int n = 0;
|
||||
while (n < size) {
|
||||
Object key = tempMap.lastKey();
|
||||
ArrayList<ByteEntry> list = tempMap.get(key);
|
||||
for (int i = 0; i < list.size() && n < size; i++) {
|
||||
ByteEntry entry = list.get(i);
|
||||
tempChunk.setBytes(entry.name, 0,
|
||||
entry.name.length);
|
||||
int insertPos = findClosest(tempChunk,
|
||||
tempbcCache, n);
|
||||
if (insertPos == n) {
|
||||
tempbcCache[n + 1] = entry;
|
||||
} else {
|
||||
System.arraycopy(tempbcCache, insertPos + 1,
|
||||
tempbcCache, insertPos + 2,
|
||||
n - insertPos - 1);
|
||||
tempbcCache[insertPos + 1] = entry;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
tempMap.remove(key);
|
||||
}
|
||||
bcCount = 0;
|
||||
bcStats.clear();
|
||||
bcCache = tempbcCache;
|
||||
if (log.isDebugEnabled()) {
|
||||
long t2 = System.currentTimeMillis();
|
||||
log.debug("ByteCache generation time: " +
|
||||
(t2 - t1) + "ms");
|
||||
}
|
||||
} else {
|
||||
bcCount++;
|
||||
// Allocate new ByteEntry for the lookup
|
||||
ByteEntry entry = new ByteEntry();
|
||||
entry.value = value;
|
||||
int[] count = bcStats.get(entry);
|
||||
if (count == null) {
|
||||
int end = bc.getEnd();
|
||||
int start = bc.getStart();
|
||||
// Create byte array and copy bytes
|
||||
entry.name = new byte[bc.getLength()];
|
||||
System.arraycopy(bc.getBuffer(), start, entry.name,
|
||||
0, end - start);
|
||||
// Set encoding
|
||||
entry.charset = bc.getCharset();
|
||||
// Initialize occurrence count to one
|
||||
count = new int[1];
|
||||
count[0] = 1;
|
||||
// Set in the stats hash map
|
||||
bcStats.put(entry, count);
|
||||
} else {
|
||||
count[0] = count[0] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
accessCount++;
|
||||
// Find the corresponding String
|
||||
String result = find(bc);
|
||||
if (result == null) {
|
||||
return bc.toStringInternal();
|
||||
}
|
||||
// Note: We don't care about safety for the stats
|
||||
hitCount++;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static String toString(CharChunk cc) {
|
||||
|
||||
// If the cache is null, then either caching is disabled, or we're
|
||||
// still training
|
||||
if (ccCache == null) {
|
||||
String value = cc.toStringInternal();
|
||||
if (charEnabled && (value.length() < maxStringSize)) {
|
||||
// If training, everything is synced
|
||||
synchronized (ccStats) {
|
||||
// If the cache has been generated on a previous invocation
|
||||
// while waiting for the lock, just return the toString
|
||||
// value we just calculated
|
||||
if (ccCache != null) {
|
||||
return value;
|
||||
}
|
||||
// Two cases: either we just exceeded the train count, in
|
||||
// which case the cache must be created, or we just update
|
||||
// the count for the string
|
||||
if (ccCount > trainThreshold) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
// Sort the entries according to occurrence
|
||||
TreeMap<Integer,ArrayList<CharEntry>> tempMap =
|
||||
new TreeMap<>();
|
||||
for (Entry<CharEntry,int[]> item : ccStats.entrySet()) {
|
||||
CharEntry entry = item.getKey();
|
||||
int[] countA = item.getValue();
|
||||
Integer count = Integer.valueOf(countA[0]);
|
||||
// Add to the list for that count
|
||||
ArrayList<CharEntry> list = tempMap.get(count);
|
||||
if (list == null) {
|
||||
// Create list
|
||||
list = new ArrayList<>();
|
||||
tempMap.put(count, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
// Allocate array of the right size
|
||||
int size = ccStats.size();
|
||||
if (size > cacheSize) {
|
||||
size = cacheSize;
|
||||
}
|
||||
CharEntry[] tempccCache = new CharEntry[size];
|
||||
// Fill it up using an alphabetical order
|
||||
// and a dumb insert sort
|
||||
CharChunk tempChunk = new CharChunk();
|
||||
int n = 0;
|
||||
while (n < size) {
|
||||
Object key = tempMap.lastKey();
|
||||
ArrayList<CharEntry> list = tempMap.get(key);
|
||||
for (int i = 0; i < list.size() && n < size; i++) {
|
||||
CharEntry entry = list.get(i);
|
||||
tempChunk.setChars(entry.name, 0,
|
||||
entry.name.length);
|
||||
int insertPos = findClosest(tempChunk,
|
||||
tempccCache, n);
|
||||
if (insertPos == n) {
|
||||
tempccCache[n + 1] = entry;
|
||||
} else {
|
||||
System.arraycopy(tempccCache, insertPos + 1,
|
||||
tempccCache, insertPos + 2,
|
||||
n - insertPos - 1);
|
||||
tempccCache[insertPos + 1] = entry;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
tempMap.remove(key);
|
||||
}
|
||||
ccCount = 0;
|
||||
ccStats.clear();
|
||||
ccCache = tempccCache;
|
||||
if (log.isDebugEnabled()) {
|
||||
long t2 = System.currentTimeMillis();
|
||||
log.debug("CharCache generation time: " +
|
||||
(t2 - t1) + "ms");
|
||||
}
|
||||
} else {
|
||||
ccCount++;
|
||||
// Allocate new CharEntry for the lookup
|
||||
CharEntry entry = new CharEntry();
|
||||
entry.value = value;
|
||||
int[] count = ccStats.get(entry);
|
||||
if (count == null) {
|
||||
int end = cc.getEnd();
|
||||
int start = cc.getStart();
|
||||
// Create char array and copy chars
|
||||
entry.name = new char[cc.getLength()];
|
||||
System.arraycopy(cc.getBuffer(), start, entry.name,
|
||||
0, end - start);
|
||||
// Initialize occurrence count to one
|
||||
count = new int[1];
|
||||
count[0] = 1;
|
||||
// Set in the stats hash map
|
||||
ccStats.put(entry, count);
|
||||
} else {
|
||||
count[0] = count[0] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
accessCount++;
|
||||
// Find the corresponding String
|
||||
String result = find(cc);
|
||||
if (result == null) {
|
||||
return cc.toStringInternal();
|
||||
}
|
||||
// Note: We don't care about safety for the stats
|
||||
hitCount++;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------- Protected Methods
|
||||
|
||||
|
||||
/**
|
||||
* Compare given byte chunk with byte array.
|
||||
* @param name The name to compare
|
||||
* @param compareTo The compared to data
|
||||
* @return -1, 0 or +1 if inferior, equal, or superior to the String.
|
||||
*/
|
||||
protected static final int compare(ByteChunk name, byte[] compareTo) {
|
||||
int result = 0;
|
||||
|
||||
byte[] b = name.getBuffer();
|
||||
int start = name.getStart();
|
||||
int end = name.getEnd();
|
||||
int len = compareTo.length;
|
||||
|
||||
if ((end - start) < len) {
|
||||
len = end - start;
|
||||
}
|
||||
for (int i = 0; (i < len) && (result == 0); i++) {
|
||||
if (b[i + start] > compareTo[i]) {
|
||||
result = 1;
|
||||
} else if (b[i + start] < compareTo[i]) {
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
if (result == 0) {
|
||||
if (compareTo.length > (end - start)) {
|
||||
result = -1;
|
||||
} else if (compareTo.length < (end - start)) {
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find an entry given its name in the cache and return the associated
|
||||
* String.
|
||||
* @param name The name to find
|
||||
* @return the corresponding value
|
||||
*/
|
||||
protected static final String find(ByteChunk name) {
|
||||
int pos = findClosest(name, bcCache, bcCache.length);
|
||||
if ((pos < 0) || (compare(name, bcCache[pos].name) != 0)
|
||||
|| !(name.getCharset().equals(bcCache[pos].charset))) {
|
||||
return null;
|
||||
} else {
|
||||
return bcCache[pos].value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find an entry given its name in a sorted array of map elements.
|
||||
* This will return the index for the closest inferior or equal item in the
|
||||
* given array.
|
||||
* @param name The name to find
|
||||
* @param array The array in which to look
|
||||
* @param len The effective length of the array
|
||||
* @return the position of the best match
|
||||
*/
|
||||
protected static final int findClosest(ByteChunk name, ByteEntry[] array,
|
||||
int len) {
|
||||
|
||||
int a = 0;
|
||||
int b = len - 1;
|
||||
|
||||
// Special cases: -1 and 0
|
||||
if (b == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (compare(name, array[0].name) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (b == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
i = (b + a) >>> 1;
|
||||
int result = compare(name, array[i].name);
|
||||
if (result == 1) {
|
||||
a = i;
|
||||
} else if (result == 0) {
|
||||
return i;
|
||||
} else {
|
||||
b = i;
|
||||
}
|
||||
if ((b - a) == 1) {
|
||||
int result2 = compare(name, array[b].name);
|
||||
if (result2 < 0) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare given char chunk with char array.
|
||||
* @param name The name to compare
|
||||
* @param compareTo The compared to data
|
||||
* @return -1, 0 or +1 if inferior, equal, or superior to the String.
|
||||
*/
|
||||
protected static final int compare(CharChunk name, char[] compareTo) {
|
||||
int result = 0;
|
||||
|
||||
char[] c = name.getBuffer();
|
||||
int start = name.getStart();
|
||||
int end = name.getEnd();
|
||||
int len = compareTo.length;
|
||||
|
||||
if ((end - start) < len) {
|
||||
len = end - start;
|
||||
}
|
||||
for (int i = 0; (i < len) && (result == 0); i++) {
|
||||
if (c[i + start] > compareTo[i]) {
|
||||
result = 1;
|
||||
} else if (c[i + start] < compareTo[i]) {
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
if (result == 0) {
|
||||
if (compareTo.length > (end - start)) {
|
||||
result = -1;
|
||||
} else if (compareTo.length < (end - start)) {
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find an entry given its name in the cache and return the associated
|
||||
* String.
|
||||
* @param name The name to find
|
||||
* @return the corresponding value
|
||||
*/
|
||||
protected static final String find(CharChunk name) {
|
||||
int pos = findClosest(name, ccCache, ccCache.length);
|
||||
if ((pos < 0) || (compare(name, ccCache[pos].name) != 0)) {
|
||||
return null;
|
||||
} else {
|
||||
return ccCache[pos].value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find an entry given its name in a sorted array of map elements.
|
||||
* This will return the index for the closest inferior or equal item in the
|
||||
* given array.
|
||||
* @param name The name to find
|
||||
* @param array The array in which to look
|
||||
* @param len The effective length of the array
|
||||
* @return the position of the best match
|
||||
*/
|
||||
protected static final int findClosest(CharChunk name, CharEntry[] array,
|
||||
int len) {
|
||||
|
||||
int a = 0;
|
||||
int b = len - 1;
|
||||
|
||||
// Special cases: -1 and 0
|
||||
if (b == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (compare(name, array[0].name) < 0 ) {
|
||||
return -1;
|
||||
}
|
||||
if (b == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
i = (b + a) >>> 1;
|
||||
int result = compare(name, array[i].name);
|
||||
if (result == 1) {
|
||||
a = i;
|
||||
} else if (result == 0) {
|
||||
return i;
|
||||
} else {
|
||||
b = i;
|
||||
}
|
||||
if ((b - a) == 1) {
|
||||
int result2 = compare(name, array[b].name);
|
||||
if (result2 < 0) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------- ByteEntry Inner Class
|
||||
|
||||
|
||||
private static class ByteEntry {
|
||||
|
||||
private byte[] name = null;
|
||||
private Charset charset = null;
|
||||
private String value = null;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ByteEntry) {
|
||||
return value.equals(((ByteEntry) obj).value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------- CharEntry Inner Class
|
||||
|
||||
|
||||
private static class CharEntry {
|
||||
|
||||
private char[] name = null;
|
||||
private String value = null;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof CharEntry) {
|
||||
return value.equals(((CharEntry) obj).value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
105
java/org/apache/tomcat/util/buf/StringUtils.java
Normal file
105
java/org/apache/tomcat/util/buf/StringUtils.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Utility methods to build a separated list from a given set (not
|
||||
* java.util.Set) of inputs and return that list as a string or append it to an
|
||||
* existing StringBuilder. If the given set is null or empty, an empty string
|
||||
* will be returned.
|
||||
*/
|
||||
public final class StringUtils {
|
||||
|
||||
private static final String EMPTY_STRING = "";
|
||||
|
||||
private StringUtils() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
|
||||
public static String join(String[] array) {
|
||||
if (array == null) {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
return join(Arrays.asList(array));
|
||||
}
|
||||
|
||||
|
||||
public static void join(String[] array, char separator, StringBuilder sb) {
|
||||
if (array == null) {
|
||||
return;
|
||||
}
|
||||
join(Arrays.asList(array), separator, sb);
|
||||
}
|
||||
|
||||
|
||||
public static String join(Collection<String> collection) {
|
||||
return join(collection, ',');
|
||||
}
|
||||
|
||||
|
||||
public static String join(Collection<String> collection, char separator) {
|
||||
// Shortcut
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
join(collection, separator, result);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
public static void join(Iterable<String> iterable, char separator, StringBuilder sb) {
|
||||
join(iterable, separator,
|
||||
new Function<String>() {@Override public String apply(String t) { return t; }}, sb);
|
||||
}
|
||||
|
||||
|
||||
public static <T> void join(T[] array, char separator, Function<T> function,
|
||||
StringBuilder sb) {
|
||||
if (array == null) {
|
||||
return;
|
||||
}
|
||||
join(Arrays.asList(array), separator, function, sb);
|
||||
}
|
||||
|
||||
|
||||
public static <T> void join(Iterable<T> iterable, char separator, Function<T> function,
|
||||
StringBuilder sb) {
|
||||
if (iterable == null) {
|
||||
return;
|
||||
}
|
||||
boolean first = true;
|
||||
for (T value : iterable) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append(separator);
|
||||
}
|
||||
sb.append(function.apply(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface Function<T> {
|
||||
public String apply(T t);
|
||||
}
|
||||
}
|
||||
495
java/org/apache/tomcat/util/buf/UDecoder.java
Normal file
495
java/org/apache/tomcat/util/buf/UDecoder.java
Normal file
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.CharConversionException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* All URL decoding happens here. This way we can reuse, review, optimize
|
||||
* without adding complexity to the buffers.
|
||||
*
|
||||
* The conversion will modify the original buffer.
|
||||
*
|
||||
* @author Costin Manolache
|
||||
*/
|
||||
public final class UDecoder {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(UDecoder.class);
|
||||
|
||||
private static final Log log = LogFactory.getLog(UDecoder.class);
|
||||
|
||||
public static final boolean ALLOW_ENCODED_SLASH =
|
||||
Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"));
|
||||
|
||||
private static class DecodeException extends CharConversionException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public DecodeException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
// This class does not provide a stack trace
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/** Unexpected end of data. */
|
||||
private static final IOException EXCEPTION_EOF = new DecodeException(sm.getString("uDecoder.eof"));
|
||||
|
||||
/** %xx with not-hex digit */
|
||||
private static final IOException EXCEPTION_NOT_HEX_DIGIT = new DecodeException(
|
||||
"isHexDigit");
|
||||
|
||||
/** %-encoded slash is forbidden in resource path */
|
||||
private static final IOException EXCEPTION_SLASH = new DecodeException(
|
||||
"noSlash");
|
||||
|
||||
public UDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* URLDecode, will modify the source.
|
||||
* @param mb The URL encoded bytes
|
||||
* @param query <code>true</code> if this is a query string
|
||||
* @throws IOException Invalid %xx URL encoding
|
||||
*/
|
||||
public void convert( ByteChunk mb, boolean query )
|
||||
throws IOException
|
||||
{
|
||||
int start=mb.getOffset();
|
||||
byte buff[]=mb.getBytes();
|
||||
int end=mb.getEnd();
|
||||
|
||||
int idx= ByteChunk.findByte( buff, start, end, (byte) '%' );
|
||||
int idx2=-1;
|
||||
if( query ) {
|
||||
idx2= ByteChunk.findByte( buff, start, (idx >= 0 ? idx : end), (byte) '+' );
|
||||
}
|
||||
if( idx<0 && idx2<0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// idx will be the smallest positive index ( first % or + )
|
||||
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
|
||||
idx=idx2;
|
||||
}
|
||||
|
||||
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
|
||||
|
||||
for( int j=idx; j<end; j++, idx++ ) {
|
||||
if( buff[ j ] == '+' && query) {
|
||||
buff[idx]= (byte)' ' ;
|
||||
} else if( buff[ j ] != '%' ) {
|
||||
buff[idx]= buff[j];
|
||||
} else {
|
||||
// read next 2 digits
|
||||
if( j+2 >= end ) {
|
||||
throw EXCEPTION_EOF;
|
||||
}
|
||||
byte b1= buff[j+1];
|
||||
byte b2=buff[j+2];
|
||||
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
|
||||
throw EXCEPTION_NOT_HEX_DIGIT;
|
||||
}
|
||||
|
||||
j+=2;
|
||||
int res=x2c( b1, b2 );
|
||||
if (noSlash && (res == '/')) {
|
||||
throw EXCEPTION_SLASH;
|
||||
}
|
||||
buff[idx]=(byte)res;
|
||||
}
|
||||
}
|
||||
|
||||
mb.setEnd( idx );
|
||||
|
||||
}
|
||||
|
||||
// -------------------- Additional methods --------------------
|
||||
// XXX What do we do about charset ????
|
||||
|
||||
/**
|
||||
* In-buffer processing - the buffer will be modified.
|
||||
* @param mb The URL encoded chars
|
||||
* @param query <code>true</code> if this is a query string
|
||||
* @throws IOException Invalid %xx URL encoding
|
||||
*/
|
||||
public void convert( CharChunk mb, boolean query )
|
||||
throws IOException
|
||||
{
|
||||
// log( "Converting a char chunk ");
|
||||
int start=mb.getOffset();
|
||||
char buff[]=mb.getBuffer();
|
||||
int cend=mb.getEnd();
|
||||
|
||||
int idx= CharChunk.indexOf( buff, start, cend, '%' );
|
||||
int idx2=-1;
|
||||
if( query ) {
|
||||
idx2= CharChunk.indexOf( buff, start, (idx >= 0 ? idx : cend), '+' );
|
||||
}
|
||||
if( idx<0 && idx2<0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// idx will be the smallest positive index ( first % or + )
|
||||
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
|
||||
idx=idx2;
|
||||
}
|
||||
|
||||
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
|
||||
|
||||
for( int j=idx; j<cend; j++, idx++ ) {
|
||||
if( buff[ j ] == '+' && query ) {
|
||||
buff[idx]=( ' ' );
|
||||
} else if( buff[ j ] != '%' ) {
|
||||
buff[idx]=buff[j];
|
||||
} else {
|
||||
// read next 2 digits
|
||||
if( j+2 >= cend ) {
|
||||
// invalid
|
||||
throw EXCEPTION_EOF;
|
||||
}
|
||||
char b1= buff[j+1];
|
||||
char b2=buff[j+2];
|
||||
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
|
||||
throw EXCEPTION_NOT_HEX_DIGIT;
|
||||
}
|
||||
|
||||
j+=2;
|
||||
int res=x2c( b1, b2 );
|
||||
if (noSlash && (res == '/')) {
|
||||
throw EXCEPTION_SLASH;
|
||||
}
|
||||
buff[idx]=(char)res;
|
||||
}
|
||||
}
|
||||
mb.setEnd( idx );
|
||||
}
|
||||
|
||||
/**
|
||||
* URLDecode, will modify the source
|
||||
* @param mb The URL encoded String, bytes or chars
|
||||
* @param query <code>true</code> if this is a query string
|
||||
* @throws IOException Invalid %xx URL encoding
|
||||
*/
|
||||
public void convert(MessageBytes mb, boolean query)
|
||||
throws IOException
|
||||
{
|
||||
|
||||
switch (mb.getType()) {
|
||||
case MessageBytes.T_STR:
|
||||
String strValue=mb.toString();
|
||||
if( strValue==null ) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mb.setString( convert( strValue, query ));
|
||||
} catch (RuntimeException ex) {
|
||||
throw new DecodeException(ex.getMessage());
|
||||
}
|
||||
break;
|
||||
case MessageBytes.T_CHARS:
|
||||
CharChunk charC=mb.getCharChunk();
|
||||
convert( charC, query );
|
||||
break;
|
||||
case MessageBytes.T_BYTES:
|
||||
ByteChunk bytesC=mb.getByteChunk();
|
||||
convert( bytesC, query );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* %xx decoding of a string. FIXME: this is inefficient.
|
||||
* @param str The URL encoded string
|
||||
* @param query <code>true</code> if this is a query string
|
||||
* @return the decoded string
|
||||
*/
|
||||
public final String convert(String str, boolean query)
|
||||
{
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 ) {
|
||||
return str;
|
||||
}
|
||||
|
||||
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
|
||||
|
||||
StringBuilder dec = new StringBuilder(); // decoded string output
|
||||
int strPos = 0;
|
||||
int strLen = str.length();
|
||||
|
||||
dec.ensureCapacity(str.length());
|
||||
while (strPos < strLen) {
|
||||
int laPos; // lookahead position
|
||||
|
||||
// look ahead to next URLencoded metacharacter, if any
|
||||
for (laPos = strPos; laPos < strLen; laPos++) {
|
||||
char laChar = str.charAt(laPos);
|
||||
if ((laChar == '+' && query) || (laChar == '%')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if there were non-metacharacters, copy them all as a block
|
||||
if (laPos > strPos) {
|
||||
dec.append(str.substring(strPos,laPos));
|
||||
strPos = laPos;
|
||||
}
|
||||
|
||||
// shortcut out of here if we're at the end of the string
|
||||
if (strPos >= strLen) {
|
||||
break;
|
||||
}
|
||||
|
||||
// process next metacharacter
|
||||
char metaChar = str.charAt(strPos);
|
||||
if (metaChar == '+') {
|
||||
dec.append(' ');
|
||||
strPos++;
|
||||
continue;
|
||||
} else if (metaChar == '%') {
|
||||
// We throw the original exception - the super will deal with
|
||||
// it
|
||||
// try {
|
||||
char res = (char) Integer.parseInt(
|
||||
str.substring(strPos + 1, strPos + 3), 16);
|
||||
if (noSlash && (res == '/')) {
|
||||
throw new IllegalArgumentException(sm.getString("uDecoder.noSlash"));
|
||||
}
|
||||
dec.append(res);
|
||||
strPos += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return dec.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded String.
|
||||
* When the byte array is converted to a string, ISO-885901 is used. This
|
||||
* may be different than some other servers. It is assumed the string is not
|
||||
* a query string.
|
||||
*
|
||||
* @param str The url-encoded string
|
||||
* @return the decoded string
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(String str) {
|
||||
return URLDecode(str, StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded String. It is assumed the
|
||||
* string is not a query string.
|
||||
*
|
||||
* @param str The url-encoded string
|
||||
* @param enc The encoding to use; if null, ISO-885901 is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @return the decoded string
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*
|
||||
* @deprecated This method will be removed in Tomcat 9
|
||||
*/
|
||||
@Deprecated
|
||||
public static String URLDecode(String str, String enc) {
|
||||
return URLDecode(str, enc, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded String. It is assumed the
|
||||
* string is not a query string.
|
||||
*
|
||||
* @param str The url-encoded string
|
||||
* @param charset The character encoding to use; if null, ISO-8859-1 is
|
||||
* used.
|
||||
* @return the decoded string
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(String str, Charset charset) {
|
||||
return URLDecode(str, charset, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded String.
|
||||
*
|
||||
* @param str The url-encoded string
|
||||
* @param enc The encoding to use; if null, ISO-8859-1 is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @param isQuery Is this a query string being processed
|
||||
* @return the decoded string
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*
|
||||
* @deprecated This method will be removed in Tomcat 9
|
||||
*/
|
||||
@Deprecated
|
||||
public static String URLDecode(String str, String enc, boolean isQuery) {
|
||||
Charset charset = null;
|
||||
|
||||
if (enc != null) {
|
||||
try {
|
||||
charset = B2CConverter.getCharset(enc);
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("uDecoder.urlDecode.uee", enc), uee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return URLDecode(str, charset, isQuery);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded byte array.
|
||||
*
|
||||
* @param bytes The url-encoded byte array
|
||||
* @param enc The encoding to use; if null, ISO-8859-1 is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @param isQuery Is this a query string being processed
|
||||
* @return the decoded string
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*
|
||||
* @deprecated This method will be removed in Tomcat 9
|
||||
*/
|
||||
@Deprecated
|
||||
public static String URLDecode(byte[] bytes, String enc, boolean isQuery) {
|
||||
throw new IllegalArgumentException(sm.getString("udecoder.urlDecode.iae"));
|
||||
}
|
||||
|
||||
|
||||
private static String URLDecode(String str, Charset charset, boolean isQuery) {
|
||||
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str.indexOf('%') == -1) {
|
||||
// No %nn sequences, so return string unchanged
|
||||
return str;
|
||||
}
|
||||
|
||||
if (charset == null) {
|
||||
charset = StandardCharsets.ISO_8859_1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decoding is required.
|
||||
*
|
||||
* Potential complications:
|
||||
* - The source String may be partially decoded so it is not valid to
|
||||
* assume that the source String is ASCII.
|
||||
* - Have to process as characters since there is no guarantee that the
|
||||
* byte sequence for '%' is going to be the same in all character
|
||||
* sets.
|
||||
* - We don't know how many '%nn' sequences are required for a single
|
||||
* character. It varies between character sets and some use a variable
|
||||
* length.
|
||||
*/
|
||||
|
||||
// This isn't perfect but it is a reasonable guess for the size of the
|
||||
// array required
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(str.length() * 2);
|
||||
|
||||
OutputStreamWriter osw = new OutputStreamWriter(baos, charset);
|
||||
|
||||
char[] sourceChars = str.toCharArray();
|
||||
int len = sourceChars.length;
|
||||
int ix = 0;
|
||||
|
||||
try {
|
||||
while (ix < len) {
|
||||
char c = sourceChars[ix++];
|
||||
if (c == '%') {
|
||||
osw.flush();
|
||||
if (ix + 2 > len) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("uDecoder.urlDecode.missingDigit", str));
|
||||
}
|
||||
char c1 = sourceChars[ix++];
|
||||
char c2 = sourceChars[ix++];
|
||||
if (isHexDigit(c1) && isHexDigit(c2)) {
|
||||
baos.write(x2c(c1, c2));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("uDecoder.urlDecode.missingDigit", str));
|
||||
}
|
||||
} else if (c == '+' && isQuery) {
|
||||
osw.append(' ');
|
||||
} else {
|
||||
osw.append(c);
|
||||
}
|
||||
}
|
||||
osw.flush();
|
||||
|
||||
return baos.toString(charset.name());
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("uDecoder.urlDecode.conversionError", str, charset.name()), ioe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean isHexDigit( int c ) {
|
||||
return ( ( c>='0' && c<='9' ) ||
|
||||
( c>='a' && c<='f' ) ||
|
||||
( c>='A' && c<='F' ));
|
||||
}
|
||||
|
||||
|
||||
private static int x2c( byte b1, byte b2 ) {
|
||||
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
|
||||
(b1 -'0');
|
||||
digit*=16;
|
||||
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
|
||||
(b2 -'0');
|
||||
return digit;
|
||||
}
|
||||
|
||||
|
||||
private static int x2c( char b1, char b2 ) {
|
||||
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
|
||||
(b1 -'0');
|
||||
digit*=16;
|
||||
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
|
||||
(b2 -'0');
|
||||
return digit;
|
||||
}
|
||||
}
|
||||
167
java/org/apache/tomcat/util/buf/UEncoder.java
Normal file
167
java/org/apache/tomcat/util/buf/UEncoder.java
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* Efficient implementation of an UTF-8 encoder.
|
||||
* This class is not thread safe - you need one encoder per thread.
|
||||
* The encoder will save and recycle the internal objects, avoiding
|
||||
* garbage.
|
||||
*
|
||||
* You can add extra characters that you want preserved, for example
|
||||
* while encoding a URL you can add "/".
|
||||
*
|
||||
* @author Costin Manolache
|
||||
*/
|
||||
public final class UEncoder {
|
||||
|
||||
public enum SafeCharsSet {
|
||||
WITH_SLASH("/"), DEFAULT("");
|
||||
private final BitSet safeChars;
|
||||
|
||||
private BitSet getSafeChars() {
|
||||
return this.safeChars;
|
||||
}
|
||||
|
||||
private SafeCharsSet(String additionalSafeChars) {
|
||||
safeChars = initialSafeChars();
|
||||
for (char c : additionalSafeChars.toCharArray()) {
|
||||
safeChars.set(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not static - the set may differ ( it's better than adding
|
||||
// an extra check for "/", "+", etc
|
||||
private BitSet safeChars=null;
|
||||
private C2BConverter c2b=null;
|
||||
private ByteChunk bb=null;
|
||||
private CharChunk cb=null;
|
||||
private CharChunk output=null;
|
||||
|
||||
/**
|
||||
* Create a UEncoder with an unmodifiable safe character set.
|
||||
*
|
||||
* @param safeCharsSet safe characters for this encoder
|
||||
*/
|
||||
public UEncoder(SafeCharsSet safeCharsSet) {
|
||||
this.safeChars = safeCharsSet.getSafeChars();
|
||||
}
|
||||
|
||||
/**
|
||||
* URL Encode string, using a specified encoding.
|
||||
*
|
||||
* @param s string to be encoded
|
||||
* @param start the beginning index, inclusive
|
||||
* @param end the ending index, exclusive
|
||||
*
|
||||
* @return A new CharChunk contained the URL encoded string
|
||||
*
|
||||
* @throws IOException If an I/O error occurs
|
||||
*/
|
||||
public CharChunk encodeURL(String s, int start, int end)
|
||||
throws IOException {
|
||||
if (c2b == null) {
|
||||
bb = new ByteChunk(8); // small enough.
|
||||
cb = new CharChunk(2); // small enough.
|
||||
output = new CharChunk(64); // small enough.
|
||||
c2b = new C2BConverter(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
bb.recycle();
|
||||
cb.recycle();
|
||||
output.recycle();
|
||||
}
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = s.charAt(i);
|
||||
if (safeChars.get(c)) {
|
||||
output.append(c);
|
||||
} else {
|
||||
cb.append(c);
|
||||
c2b.convert(cb, bb);
|
||||
|
||||
// "surrogate" - UTF is _not_ 16 bit, but 21 !!!!
|
||||
// ( while UCS is 31 ). Amazing...
|
||||
if (c >= 0xD800 && c <= 0xDBFF) {
|
||||
if ((i+1) < end) {
|
||||
char d = s.charAt(i+1);
|
||||
if (d >= 0xDC00 && d <= 0xDFFF) {
|
||||
cb.append(d);
|
||||
c2b.convert(cb, bb);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
urlEncode(output, bb);
|
||||
cb.recycle();
|
||||
bb.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
protected void urlEncode(CharChunk out, ByteChunk bb)
|
||||
throws IOException {
|
||||
byte[] bytes = bb.getBuffer();
|
||||
for (int j = bb.getStart(); j < bb.getEnd(); j++) {
|
||||
out.append('%');
|
||||
char ch = Character.forDigit((bytes[j] >> 4) & 0xF, 16);
|
||||
out.append(ch);
|
||||
ch = Character.forDigit(bytes[j] & 0xF, 16);
|
||||
out.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Internal implementation --------------------
|
||||
|
||||
private static BitSet initialSafeChars() {
|
||||
BitSet initialSafeChars=new BitSet(128);
|
||||
int i;
|
||||
for (i = 'a'; i <= 'z'; i++) {
|
||||
initialSafeChars.set(i);
|
||||
}
|
||||
for (i = 'A'; i <= 'Z'; i++) {
|
||||
initialSafeChars.set(i);
|
||||
}
|
||||
for (i = '0'; i <= '9'; i++) {
|
||||
initialSafeChars.set(i);
|
||||
}
|
||||
//safe
|
||||
initialSafeChars.set('$');
|
||||
initialSafeChars.set('-');
|
||||
initialSafeChars.set('_');
|
||||
initialSafeChars.set('.');
|
||||
|
||||
// Dangerous: someone may treat this as " "
|
||||
// RFC1738 does allow it, it's not reserved
|
||||
// initialSafeChars.set('+');
|
||||
//extra
|
||||
initialSafeChars.set('!');
|
||||
initialSafeChars.set('*');
|
||||
initialSafeChars.set('\'');
|
||||
initialSafeChars.set('(');
|
||||
initialSafeChars.set(')');
|
||||
initialSafeChars.set(',');
|
||||
return initialSafeChars;
|
||||
}
|
||||
}
|
||||
197
java/org/apache/tomcat/util/buf/UriUtil.java
Normal file
197
java/org/apache/tomcat/util/buf/UriUtil.java
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility class for working with URIs and URLs.
|
||||
*/
|
||||
public final class UriUtil {
|
||||
|
||||
private static final char[] HEX =
|
||||
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||
|
||||
private static final Pattern PATTERN_EXCLAMATION_MARK = Pattern.compile("!/");
|
||||
private static final Pattern PATTERN_CARET = Pattern.compile("\\^/");
|
||||
private static final Pattern PATTERN_ASTERISK = Pattern.compile("\\*/");
|
||||
private static final Pattern PATTERN_CUSTOM;
|
||||
private static final String REPLACE_CUSTOM;
|
||||
|
||||
private static final String WAR_SEPARATOR;
|
||||
|
||||
static {
|
||||
String custom = System.getProperty("org.apache.tomcat.util.buf.UriUtil.WAR_SEPARATOR");
|
||||
if (custom == null) {
|
||||
WAR_SEPARATOR = "*/";
|
||||
PATTERN_CUSTOM = null;
|
||||
REPLACE_CUSTOM = null;
|
||||
} else {
|
||||
WAR_SEPARATOR = custom + "/";
|
||||
PATTERN_CUSTOM = Pattern.compile(Pattern.quote(WAR_SEPARATOR));
|
||||
StringBuffer sb = new StringBuffer(custom.length() * 3);
|
||||
// Deliberately use the platform's default encoding
|
||||
byte[] ba = custom.getBytes();
|
||||
for (int j = 0; j < ba.length; j++) {
|
||||
// Converting each byte in the buffer
|
||||
byte toEncode = ba[j];
|
||||
sb.append('%');
|
||||
int low = toEncode & 0x0f;
|
||||
int high = (toEncode & 0xf0) >> 4;
|
||||
sb.append(HEX[high]);
|
||||
sb.append(HEX[low]);
|
||||
}
|
||||
REPLACE_CUSTOM = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private UriUtil() {
|
||||
// Utility class. Hide default constructor
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the character is allowed in the scheme of a URI.
|
||||
* See RFC 2396, Section 3.1
|
||||
*
|
||||
* @param c The character to test
|
||||
*
|
||||
* @return {@code true} if a the character is allowed, otherwise {code
|
||||
* @false}
|
||||
*/
|
||||
private static boolean isSchemeChar(char c) {
|
||||
return Character.isLetterOrDigit(c) || c == '+' || c == '-' || c == '.';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if a URI string has a <code>scheme</code> component.
|
||||
*
|
||||
* @param uri The URI to test
|
||||
*
|
||||
* @return {@code true} if a scheme is present, otherwise {code @false}
|
||||
*/
|
||||
public static boolean hasScheme(CharSequence uri) {
|
||||
int len = uri.length();
|
||||
for(int i=0; i < len ; i++) {
|
||||
char c = uri.charAt(i);
|
||||
if(c == ':') {
|
||||
return i > 0;
|
||||
} else if(!UriUtil.isSchemeChar(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static URL buildJarUrl(File jarFile) throws MalformedURLException {
|
||||
return buildJarUrl(jarFile, null);
|
||||
}
|
||||
|
||||
|
||||
public static URL buildJarUrl(File jarFile, String entryPath) throws MalformedURLException {
|
||||
return buildJarUrl(jarFile.toURI().toString(), entryPath);
|
||||
}
|
||||
|
||||
|
||||
public static URL buildJarUrl(String fileUrlString) throws MalformedURLException {
|
||||
return buildJarUrl(fileUrlString, null);
|
||||
}
|
||||
|
||||
|
||||
public static URL buildJarUrl(String fileUrlString, String entryPath) throws MalformedURLException {
|
||||
String safeString = makeSafeForJarUrl(fileUrlString);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(safeString);
|
||||
sb.append("!/");
|
||||
if (entryPath != null) {
|
||||
sb.append(makeSafeForJarUrl(entryPath));
|
||||
}
|
||||
return new URL("jar", null, -1, sb.toString());
|
||||
}
|
||||
|
||||
|
||||
public static URL buildJarSafeUrl(File file) throws MalformedURLException {
|
||||
String safe = makeSafeForJarUrl(file.toURI().toString());
|
||||
return new URL(safe);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* When testing on markt's desktop each iteration was taking ~1420ns when
|
||||
* using String.replaceAll().
|
||||
*
|
||||
* Switching the implementation to use pre-compiled patterns and
|
||||
* Pattern.matcher(input).replaceAll(replacement) reduced this by ~10%.
|
||||
*
|
||||
* Note: Given the very small absolute time of a single iteration, even for
|
||||
* a web application with 1000 JARs this is only going to add ~3ms.
|
||||
* It is therefore unlikely that further optimisation will be
|
||||
* necessary.
|
||||
*/
|
||||
/*
|
||||
* Pulled out into a separate method in case we need to handle other unusual
|
||||
* sequences in the future.
|
||||
*/
|
||||
private static String makeSafeForJarUrl(String input) {
|
||||
// Since "!/" has a special meaning in a JAR URL, make sure that the
|
||||
// sequence is properly escaped if present.
|
||||
String tmp = PATTERN_EXCLAMATION_MARK.matcher(input).replaceAll("%21/");
|
||||
// Tomcat's custom jar:war: URL handling treats */ and ^/ as special
|
||||
tmp = PATTERN_CARET.matcher(tmp).replaceAll("%5e/");
|
||||
tmp = PATTERN_ASTERISK.matcher(tmp).replaceAll("%2a/");
|
||||
if (PATTERN_CUSTOM != null) {
|
||||
tmp = PATTERN_CUSTOM.matcher(tmp).replaceAll(REPLACE_CUSTOM);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a URL of the form <code>war:file:...</code> to
|
||||
* <code>jar:file:...</code>.
|
||||
*
|
||||
* @param warUrl The WAR URL to convert
|
||||
*
|
||||
* @return The equivalent JAR URL
|
||||
*
|
||||
* @throws MalformedURLException If the conversion fails
|
||||
*/
|
||||
public static URL warToJar(URL warUrl) throws MalformedURLException {
|
||||
// Assumes that the spec is absolute and starts war:file:/...
|
||||
String file = warUrl.getFile();
|
||||
if (file.contains("*/")) {
|
||||
file = file.replaceFirst("\\*/", "!/");
|
||||
} else if (file.contains("^/")) {
|
||||
file = file.replaceFirst("\\^/", "!/");
|
||||
} else if (PATTERN_CUSTOM != null) {
|
||||
file = file.replaceFirst(PATTERN_CUSTOM.pattern(), "!/");
|
||||
}
|
||||
|
||||
return new URL("jar", warUrl.getHost(), warUrl.getPort(), file);
|
||||
}
|
||||
|
||||
|
||||
public static String getWarSeparator() {
|
||||
return WAR_SEPARATOR;
|
||||
}
|
||||
}
|
||||
299
java/org/apache/tomcat/util/buf/Utf8Decoder.java
Normal file
299
java/org/apache/tomcat/util/buf/Utf8Decoder.java
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Decodes bytes to UTF-8. Extracted from Apache Harmony and modified to reject
|
||||
* code points from U+D800 to U+DFFF as per RFC3629. The standard Java decoder
|
||||
* does not reject these. It has also been modified to reject code points
|
||||
* greater than U+10FFFF which the standard Java decoder rejects but the harmony
|
||||
* one does not.
|
||||
*/
|
||||
public class Utf8Decoder extends CharsetDecoder {
|
||||
|
||||
// The next table contains information about UTF-8 charset and
|
||||
// correspondence of 1st byte to the length of sequence
|
||||
// For information please visit http://www.ietf.org/rfc/rfc3629.txt
|
||||
//
|
||||
// Please note, o means 0, actually.
|
||||
// -------------------------------------------------------------------
|
||||
// 0 1 2 3 Value
|
||||
// -------------------------------------------------------------------
|
||||
// oxxxxxxx 00000000 00000000 0xxxxxxx
|
||||
// 11oyyyyy 1oxxxxxx 00000000 00000yyy yyxxxxxx
|
||||
// 111ozzzz 1oyyyyyy 1oxxxxxx 00000000 zzzzyyyy yyxxxxxx
|
||||
// 1111ouuu 1ouuzzzz 1oyyyyyy 1oxxxxxx 000uuuuu zzzzyyyy yyxxxxxx
|
||||
private static final int remainingBytes[] = {
|
||||
// 1owwwwww
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
// 11oyyyyy
|
||||
-1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
// 111ozzzz
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
// 1111ouuu
|
||||
3, 3, 3, 3, 3, -1, -1, -1,
|
||||
// > 11110111
|
||||
-1, -1, -1, -1, -1, -1, -1, -1};
|
||||
private static final int remainingNumbers[] = {0, // 0 1 2 3
|
||||
4224, // (01o00000b << 6)+(1o000000b)
|
||||
401536, // (011o0000b << 12)+(1o000000b << 6)+(1o000000b)
|
||||
29892736 // (0111o000b << 18)+(1o000000b << 12)+(1o000000b <<
|
||||
// 6)+(1o000000b)
|
||||
};
|
||||
private static final int lowerEncodingLimit[] = {-1, 0x80, 0x800, 0x10000};
|
||||
|
||||
|
||||
public Utf8Decoder() {
|
||||
super(StandardCharsets.UTF_8, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
|
||||
if (in.hasArray() && out.hasArray()) {
|
||||
return decodeHasArray(in, out);
|
||||
}
|
||||
return decodeNotHasArray(in, out);
|
||||
}
|
||||
|
||||
|
||||
private CoderResult decodeNotHasArray(ByteBuffer in, CharBuffer out) {
|
||||
int outRemaining = out.remaining();
|
||||
int pos = in.position();
|
||||
int limit = in.limit();
|
||||
try {
|
||||
while (pos < limit) {
|
||||
if (outRemaining == 0) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
int jchar = in.get();
|
||||
if (jchar < 0) {
|
||||
jchar = jchar & 0x7F;
|
||||
int tail = remainingBytes[jchar];
|
||||
if (tail == -1) {
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
if (limit - pos < 1 + tail) {
|
||||
// No early test for invalid sequences here as peeking
|
||||
// at the next byte is harder
|
||||
return CoderResult.UNDERFLOW;
|
||||
}
|
||||
int nextByte;
|
||||
for (int i = 0; i < tail; i++) {
|
||||
nextByte = in.get() & 0xFF;
|
||||
if ((nextByte & 0xC0) != 0x80) {
|
||||
return CoderResult.malformedForLength(1 + i);
|
||||
}
|
||||
jchar = (jchar << 6) + nextByte;
|
||||
}
|
||||
jchar -= remainingNumbers[tail];
|
||||
if (jchar < lowerEncodingLimit[tail]) {
|
||||
// Should have been encoded in a fewer octets
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
pos += tail;
|
||||
}
|
||||
// Apache Tomcat added test
|
||||
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
|
||||
return CoderResult.unmappableForLength(3);
|
||||
}
|
||||
// Apache Tomcat added test
|
||||
if (jchar > 0x10FFFF) {
|
||||
return CoderResult.unmappableForLength(4);
|
||||
}
|
||||
if (jchar <= 0xffff) {
|
||||
out.put((char) jchar);
|
||||
outRemaining--;
|
||||
} else {
|
||||
if (outRemaining < 2) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
out.put((char) ((jchar >> 0xA) + 0xD7C0));
|
||||
out.put((char) ((jchar & 0x3FF) + 0xDC00));
|
||||
outRemaining -= 2;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return CoderResult.UNDERFLOW;
|
||||
} finally {
|
||||
in.position(pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private CoderResult decodeHasArray(ByteBuffer in, CharBuffer out) {
|
||||
int outRemaining = out.remaining();
|
||||
int pos = in.position();
|
||||
int limit = in.limit();
|
||||
final byte[] bArr = in.array();
|
||||
final char[] cArr = out.array();
|
||||
final int inIndexLimit = limit + in.arrayOffset();
|
||||
int inIndex = pos + in.arrayOffset();
|
||||
int outIndex = out.position() + out.arrayOffset();
|
||||
// if someone would change the limit in process,
|
||||
// he would face consequences
|
||||
for (; inIndex < inIndexLimit && outRemaining > 0; inIndex++) {
|
||||
int jchar = bArr[inIndex];
|
||||
if (jchar < 0) {
|
||||
jchar = jchar & 0x7F;
|
||||
// If first byte is invalid, tail will be set to -1
|
||||
int tail = remainingBytes[jchar];
|
||||
if (tail == -1) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// Additional checks to detect invalid sequences ASAP
|
||||
// Checks derived from Unicode 6.2, Chapter 3, Table 3-7
|
||||
// Check 2nd byte
|
||||
int tailAvailable = inIndexLimit - inIndex - 1;
|
||||
if (tailAvailable > 0) {
|
||||
// First byte C2..DF, second byte 80..BF
|
||||
if (jchar > 0x41 && jchar < 0x60 &&
|
||||
(bArr[inIndex + 1] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte E0, second byte A0..BF
|
||||
if (jchar == 0x60 && (bArr[inIndex + 1] & 0xE0) != 0xA0) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte E1..EC, second byte 80..BF
|
||||
if (jchar > 0x60 && jchar < 0x6D &&
|
||||
(bArr[inIndex + 1] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte ED, second byte 80..9F
|
||||
if (jchar == 0x6D && (bArr[inIndex + 1] & 0xE0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte EE..EF, second byte 80..BF
|
||||
if (jchar > 0x6D && jchar < 0x70 &&
|
||||
(bArr[inIndex + 1] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte F0, second byte 90..BF
|
||||
if (jchar == 0x70 &&
|
||||
((bArr[inIndex + 1] & 0xFF) < 0x90 ||
|
||||
(bArr[inIndex + 1] & 0xFF) > 0xBF)) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte F1..F3, second byte 80..BF
|
||||
if (jchar > 0x70 && jchar < 0x74 &&
|
||||
(bArr[inIndex + 1] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte F4, second byte 80..8F
|
||||
if (jchar == 0x74 &&
|
||||
(bArr[inIndex + 1] & 0xF0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
}
|
||||
// Check third byte if present and expected
|
||||
if (tailAvailable > 1 && tail > 1) {
|
||||
if ((bArr[inIndex + 2] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(2);
|
||||
}
|
||||
}
|
||||
// Check fourth byte if present and expected
|
||||
if (tailAvailable > 2 && tail > 2) {
|
||||
if ((bArr[inIndex + 3] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(3);
|
||||
}
|
||||
}
|
||||
if (tailAvailable < tail) {
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < tail; i++) {
|
||||
int nextByte = bArr[inIndex + i + 1] & 0xFF;
|
||||
if ((nextByte & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1 + i);
|
||||
}
|
||||
jchar = (jchar << 6) + nextByte;
|
||||
}
|
||||
jchar -= remainingNumbers[tail];
|
||||
if (jchar < lowerEncodingLimit[tail]) {
|
||||
// Should have been encoded in fewer octets
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
inIndex += tail;
|
||||
}
|
||||
// Apache Tomcat added test
|
||||
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
|
||||
return CoderResult.unmappableForLength(3);
|
||||
}
|
||||
// Apache Tomcat added test
|
||||
if (jchar > 0x10FFFF) {
|
||||
return CoderResult.unmappableForLength(4);
|
||||
}
|
||||
if (jchar <= 0xffff) {
|
||||
cArr[outIndex++] = (char) jchar;
|
||||
outRemaining--;
|
||||
} else {
|
||||
if (outRemaining < 2) {
|
||||
// Encoded with 4 bytes. inIndex currently points
|
||||
// to the final byte. Move it back to first byte.
|
||||
inIndex -= 3;
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
cArr[outIndex++] = (char) ((jchar >> 0xA) + 0xD7C0);
|
||||
cArr[outIndex++] = (char) ((jchar & 0x3FF) + 0xDC00);
|
||||
outRemaining -= 2;
|
||||
}
|
||||
}
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return (outRemaining == 0 && inIndex < inIndexLimit) ?
|
||||
CoderResult.OVERFLOW :
|
||||
CoderResult.UNDERFLOW;
|
||||
}
|
||||
}
|
||||
235
java/org/apache/tomcat/util/buf/Utf8Encoder.java
Normal file
235
java/org/apache/tomcat/util/buf/Utf8Encoder.java
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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.buf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Encodes characters as bytes using UTF-8. Extracted from Apache Harmony with
|
||||
* some minor bug fixes applied.
|
||||
*/
|
||||
public class Utf8Encoder extends CharsetEncoder {
|
||||
|
||||
public Utf8Encoder() {
|
||||
super(StandardCharsets.UTF_8, 1.1f, 4.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
|
||||
if (in.hasArray() && out.hasArray()) {
|
||||
return encodeHasArray(in, out);
|
||||
}
|
||||
return encodeNotHasArray(in, out);
|
||||
}
|
||||
|
||||
private CoderResult encodeHasArray(CharBuffer in, ByteBuffer out) {
|
||||
int outRemaining = out.remaining();
|
||||
int pos = in.position();
|
||||
int limit = in.limit();
|
||||
byte[] bArr;
|
||||
char[] cArr;
|
||||
int x = pos;
|
||||
bArr = out.array();
|
||||
cArr = in.array();
|
||||
int outPos = out.position();
|
||||
int rem = in.remaining();
|
||||
for (x = pos; x < pos + rem; x++) {
|
||||
int jchar = (cArr[x] & 0xFFFF);
|
||||
|
||||
if (jchar <= 0x7F) {
|
||||
if (outRemaining < 1) {
|
||||
in.position(x);
|
||||
out.position(outPos);
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
bArr[outPos++] = (byte) (jchar & 0xFF);
|
||||
outRemaining--;
|
||||
} else if (jchar <= 0x7FF) {
|
||||
|
||||
if (outRemaining < 2) {
|
||||
in.position(x);
|
||||
out.position(outPos);
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
bArr[outPos++] = (byte) (0xC0 + ((jchar >> 6) & 0x1F));
|
||||
bArr[outPos++] = (byte) (0x80 + (jchar & 0x3F));
|
||||
outRemaining -= 2;
|
||||
|
||||
} else if (jchar >= 0xD800 && jchar <= 0xDFFF) {
|
||||
|
||||
// in has to have one byte more.
|
||||
if (limit <= x + 1) {
|
||||
in.position(x);
|
||||
out.position(outPos);
|
||||
return CoderResult.UNDERFLOW;
|
||||
}
|
||||
|
||||
if (outRemaining < 4) {
|
||||
in.position(x);
|
||||
out.position(outPos);
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
|
||||
// The surrogate pair starts with a low-surrogate.
|
||||
if (jchar >= 0xDC00) {
|
||||
in.position(x);
|
||||
out.position(outPos);
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
|
||||
int jchar2 = cArr[x + 1] & 0xFFFF;
|
||||
|
||||
// The surrogate pair ends with a high-surrogate.
|
||||
if (jchar2 < 0xDC00) {
|
||||
in.position(x);
|
||||
out.position(outPos);
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
|
||||
// Note, the Unicode scalar value n is defined
|
||||
// as follows:
|
||||
// n = (jchar-0xD800)*0x400+(jchar2-0xDC00)+0x10000
|
||||
// Where jchar is a high-surrogate,
|
||||
// jchar2 is a low-surrogate.
|
||||
int n = (jchar << 10) + jchar2 + 0xFCA02400;
|
||||
|
||||
bArr[outPos++] = (byte) (0xF0 + ((n >> 18) & 0x07));
|
||||
bArr[outPos++] = (byte) (0x80 + ((n >> 12) & 0x3F));
|
||||
bArr[outPos++] = (byte) (0x80 + ((n >> 6) & 0x3F));
|
||||
bArr[outPos++] = (byte) (0x80 + (n & 0x3F));
|
||||
outRemaining -= 4;
|
||||
x++;
|
||||
|
||||
} else {
|
||||
|
||||
if (outRemaining < 3) {
|
||||
in.position(x);
|
||||
out.position(outPos);
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
bArr[outPos++] = (byte) (0xE0 + ((jchar >> 12) & 0x0F));
|
||||
bArr[outPos++] = (byte) (0x80 + ((jchar >> 6) & 0x3F));
|
||||
bArr[outPos++] = (byte) (0x80 + (jchar & 0x3F));
|
||||
outRemaining -= 3;
|
||||
}
|
||||
if (outRemaining == 0) {
|
||||
in.position(x + 1);
|
||||
out.position(outPos);
|
||||
// If both input and output are exhausted, return UNDERFLOW
|
||||
if (x + 1 == limit) {
|
||||
return CoderResult.UNDERFLOW;
|
||||
} else {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (rem != 0) {
|
||||
in.position(x);
|
||||
out.position(outPos);
|
||||
}
|
||||
return CoderResult.UNDERFLOW;
|
||||
}
|
||||
|
||||
private CoderResult encodeNotHasArray(CharBuffer in, ByteBuffer out) {
|
||||
int outRemaining = out.remaining();
|
||||
int pos = in.position();
|
||||
int limit = in.limit();
|
||||
try {
|
||||
while (pos < limit) {
|
||||
if (outRemaining == 0) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
|
||||
int jchar = (in.get() & 0xFFFF);
|
||||
|
||||
if (jchar <= 0x7F) {
|
||||
|
||||
if (outRemaining < 1) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
out.put((byte) jchar);
|
||||
outRemaining--;
|
||||
|
||||
} else if (jchar <= 0x7FF) {
|
||||
|
||||
if (outRemaining < 2) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
out.put((byte) (0xC0 + ((jchar >> 6) & 0x1F)));
|
||||
out.put((byte) (0x80 + (jchar & 0x3F)));
|
||||
outRemaining -= 2;
|
||||
|
||||
} else if (jchar >= 0xD800 && jchar <= 0xDFFF) {
|
||||
|
||||
// in has to have one byte more.
|
||||
if (limit <= pos + 1) {
|
||||
return CoderResult.UNDERFLOW;
|
||||
}
|
||||
|
||||
if (outRemaining < 4) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
|
||||
// The surrogate pair starts with a low-surrogate.
|
||||
if (jchar >= 0xDC00) {
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
|
||||
int jchar2 = (in.get() & 0xFFFF);
|
||||
|
||||
// The surrogate pair ends with a high-surrogate.
|
||||
if (jchar2 < 0xDC00) {
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
|
||||
// Note, the Unicode scalar value n is defined
|
||||
// as follows:
|
||||
// n = (jchar-0xD800)*0x400+(jchar2-0xDC00)+0x10000
|
||||
// Where jchar is a high-surrogate,
|
||||
// jchar2 is a low-surrogate.
|
||||
int n = (jchar << 10) + jchar2 + 0xFCA02400;
|
||||
|
||||
out.put((byte) (0xF0 + ((n >> 18) & 0x07)));
|
||||
out.put((byte) (0x80 + ((n >> 12) & 0x3F)));
|
||||
out.put((byte) (0x80 + ((n >> 6) & 0x3F)));
|
||||
out.put((byte) (0x80 + (n & 0x3F)));
|
||||
outRemaining -= 4;
|
||||
pos++;
|
||||
|
||||
} else {
|
||||
|
||||
if (outRemaining < 3) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
out.put((byte) (0xE0 + ((jchar >> 12) & 0x0F)));
|
||||
out.put((byte) (0x80 + ((jchar >> 6) & 0x3F)));
|
||||
out.put((byte) (0x80 + (jchar & 0x3F)));
|
||||
outRemaining -= 3;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
} finally {
|
||||
in.position(pos);
|
||||
}
|
||||
return CoderResult.UNDERFLOW;
|
||||
}
|
||||
}
|
||||
37
java/org/apache/tomcat/util/buf/package.html
Normal file
37
java/org/apache/tomcat/util/buf/package.html
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.
|
||||
-->
|
||||
<html><body>
|
||||
<H2>Buffers and Encodings</h2>
|
||||
|
||||
This package contains buffers and utils to perform encoding/decoding of buffers. That includes byte to char
|
||||
conversions, URL encodings, etc.
|
||||
|
||||
<p>
|
||||
Encoding is a critical operation for performance. There are few tricks in this package - the C2B and
|
||||
B2C converters are caching an ISReader/OSWriter and keep everything allocated to do the conversions
|
||||
in any VM without any garbage.
|
||||
|
||||
<p>
|
||||
This package must accommodate future extensions and additional converters ( most important: the nio.charset,
|
||||
which should be detected and used if available ). Also, we do have one hand-written UTF8Decoder, and
|
||||
other tuned encoders could be added.
|
||||
|
||||
<p>
|
||||
My benchmarks ( I'm costin :-) show only small differences between C2B, B2C and hand-written codders/decoders,
|
||||
so UTF8Decoder may be disabled.
|
||||
|
||||
</body></html>
|
||||
Reference in New Issue
Block a user