mirror of
https://github.com/apache/struts.git
synced 2026-08-07 15:46:57 +00:00
Merge branch 'support-2-3' of https://git-wip-us.apache.org/repos/asf/struts into support-2-3
Conflicts: plugins/rest/src/test/java/org/apache/struts2/rest/RestActionMapperTest.java
This commit is contained in:
@@ -26,9 +26,9 @@ import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
import org.apache.struts2.util.URLDecoderUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
@@ -133,10 +133,10 @@ public class Restful2ActionMapper extends DefaultActionMapper {
|
||||
|
||||
while (st.hasMoreTokens()) {
|
||||
if (isNameTok) {
|
||||
paramName = URLDecoder.decode(st.nextToken(), "UTF-8");
|
||||
paramName = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
|
||||
isNameTok = false;
|
||||
} else {
|
||||
paramValue = URLDecoder.decode(st.nextToken(), "UTF-8");
|
||||
paramValue = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
|
||||
|
||||
if ((paramName != null) && (paramName.length() > 0)) {
|
||||
parameters.put(paramName, paramValue);
|
||||
|
||||
@@ -25,9 +25,9 @@ import com.opensymphony.xwork2.config.ConfigurationManager;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import org.apache.struts2.RequestUtils;
|
||||
import org.apache.struts2.util.URLDecoderUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
@@ -67,10 +67,10 @@ public class RestfulActionMapper implements ActionMapper {
|
||||
|
||||
while (st.hasMoreTokens()) {
|
||||
if (isNameTok) {
|
||||
paramName = URLDecoder.decode(st.nextToken(), "UTF-8");
|
||||
paramName = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
|
||||
isNameTok = false;
|
||||
} else {
|
||||
paramValue = URLDecoder.decode(st.nextToken(), "UTF-8");
|
||||
paramValue = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
|
||||
|
||||
if ((paramName != null) && (paramName.length() > 0)) {
|
||||
parameters.put(paramName, paramValue);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.apache.struts2.util;
|
||||
|
||||
import org.apache.struts2.util.tomcat.buf.UDecoder;
|
||||
|
||||
/**
|
||||
* URLDecoderUtil serves as a facade for a correct URL decoding implementation.
|
||||
* As of Struts 2.3.25 it uses Tomcat URLDecoder functionality rather than the one found in java.io.
|
||||
*/
|
||||
public class URLDecoderUtil {
|
||||
|
||||
/**
|
||||
* Decodes a <code>x-www-form-urlencoded</code> string.
|
||||
* @param sequence the String to decode
|
||||
* @param charset The name of a supported character encoding.
|
||||
* @return the newly decoded <code>String</code>
|
||||
* @exception IllegalArgumentException If the encoding is not valid
|
||||
*/
|
||||
public static String decode(String sequence, String charset) {
|
||||
return UDecoder.URLDecode(sequence, charset);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* 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.struts2.util.tomcat.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[] toUpper = new byte[256];
|
||||
private static final byte[] toLower = new byte[256];
|
||||
|
||||
/*
|
||||
* Character type tables.
|
||||
*/
|
||||
|
||||
private static final boolean[] isAlpha = new boolean[256];
|
||||
private static final boolean[] isUpper = new boolean[256];
|
||||
private static final boolean[] isLower = new boolean[256];
|
||||
private static final boolean[] isWhite = new boolean[256];
|
||||
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++) {
|
||||
toUpper[i] = (byte)i;
|
||||
toLower[i] = (byte)i;
|
||||
}
|
||||
|
||||
for (int lc = 'a'; lc <= 'z'; lc++) {
|
||||
int uc = lc + 'A' - 'a';
|
||||
|
||||
toUpper[lc] = (byte)uc;
|
||||
toLower[uc] = (byte)lc;
|
||||
isAlpha[lc] = true;
|
||||
isAlpha[uc] = true;
|
||||
isLower[lc] = true;
|
||||
isUpper[uc] = true;
|
||||
}
|
||||
|
||||
isWhite[ ' '] = true;
|
||||
isWhite['\t'] = true;
|
||||
isWhite['\r'] = true;
|
||||
isWhite['\n'] = true;
|
||||
isWhite['\f'] = true;
|
||||
isWhite['\b'] = true;
|
||||
|
||||
for (int d = '0'; d <= '9'; d++) {
|
||||
isDigit[d] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the upper case equivalent of the specified ASCII character.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int toUpper(int c) {
|
||||
return toUpper[c & 0xff] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lower case equivalent of the specified ASCII character.
|
||||
*/
|
||||
|
||||
public static int toLower(int c) {
|
||||
return toLower[c & 0xff] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is upper or lower case.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isAlpha(int c) {
|
||||
return isAlpha[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is upper case.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isUpper(int c) {
|
||||
return isUpper[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is lower case.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isLower(int c) {
|
||||
return isLower[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is white space.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isWhite(int c) {
|
||||
return isWhite[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is a digit.
|
||||
*/
|
||||
|
||||
public static boolean isDigit(int c) {
|
||||
return isDigit[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an unsigned integer 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
|
||||
* @exception NumberFormatException if the integer format was invalid
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int parseInt(byte[] b, int off, int len)
|
||||
throws NumberFormatException
|
||||
{
|
||||
int c;
|
||||
|
||||
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
|
||||
int n = c - '0';
|
||||
|
||||
while (--len > 0) {
|
||||
if (!isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
n = n * 10 + c - '0';
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int parseInt(char[] b, int off, int len)
|
||||
throws NumberFormatException
|
||||
{
|
||||
int c;
|
||||
|
||||
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
|
||||
int n = c - '0';
|
||||
|
||||
while (--len > 0) {
|
||||
if (!isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
n = n * 10 + c - '0';
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static long parseLong(char[] 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';
|
||||
long m;
|
||||
|
||||
while (--len > 0) {
|
||||
if (!isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
m = n * 10 + c - '0';
|
||||
|
||||
if (m < n) {
|
||||
// Overflow
|
||||
throw new NumberFormatException();
|
||||
} else {
|
||||
n = m;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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.struts2.util.tomcat.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.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* NIO based character decoder.
|
||||
*/
|
||||
public class B2CConverter {
|
||||
|
||||
private static final Map<String, Charset> encodingToCharsetCache =
|
||||
new HashMap<String, Charset>();
|
||||
|
||||
public static final Charset ISO_8859_1;
|
||||
public static final Charset UTF_8;
|
||||
|
||||
// Protected so unit tests can use it
|
||||
protected static final int LEFTOVER_SIZE = 9;
|
||||
|
||||
static {
|
||||
for (Charset charset: Charset.availableCharsets().values()) {
|
||||
encodingToCharsetCache.put(
|
||||
charset.name().toLowerCase(Locale.ENGLISH), charset);
|
||||
for (String alias : charset.aliases()) {
|
||||
encodingToCharsetCache.put(
|
||||
alias.toLowerCase(Locale.ENGLISH), charset);
|
||||
}
|
||||
}
|
||||
Charset iso88591 = null;
|
||||
Charset utf8 = null;
|
||||
try {
|
||||
iso88591 = getCharset("ISO-8859-1");
|
||||
utf8 = getCharset("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Impossible. All JVMs must support these.
|
||||
e.printStackTrace();
|
||||
}
|
||||
ISO_8859_1 = iso88591;
|
||||
UTF_8 = utf8;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public static Charset getCharsetLower(String lowerCaseEnc)
|
||||
throws UnsupportedEncodingException {
|
||||
|
||||
Charset charset = encodingToCharsetCache.get(lowerCaseEnc);
|
||||
|
||||
if (charset == null) {
|
||||
// Pre-population of the cache means this must be invalid
|
||||
throw new UnsupportedEncodingException("The character encoding " + lowerCaseEnc + " is not supported");
|
||||
}
|
||||
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(String encoding) throws IOException {
|
||||
this(encoding, false);
|
||||
}
|
||||
|
||||
public B2CConverter(String encoding, boolean replaceOnError)
|
||||
throws IOException {
|
||||
byte[] left = new byte[LEFTOVER_SIZE];
|
||||
leftovers = ByteBuffer.wrap(left);
|
||||
CodingErrorAction action;
|
||||
if (replaceOnError) {
|
||||
action = CodingErrorAction.REPLACE;
|
||||
} else {
|
||||
action = CodingErrorAction.REPORT;
|
||||
}
|
||||
Charset charset = getCharset(encoding);
|
||||
// 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(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
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,935 @@
|
||||
/*
|
||||
* 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.struts2.util.tomcat.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/*
|
||||
* 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 implements Cloneable, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Input interface, used when the buffer is empty
|
||||
*
|
||||
* Same as java.nio.channel.ReadableByteChannel
|
||||
*/
|
||||
public static interface ByteInputChannel {
|
||||
/**
|
||||
* Read new bytes ( usually the internal conversion buffer ).
|
||||
* The implementation is allowed to ignore the parameters,
|
||||
* and mutate the chunk if it wishes to implement its own buffering.
|
||||
*/
|
||||
public int realReadBytes(byte cbuf[], int off, int len)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
/** Same as java.nio.channel.WrittableByteChannel.
|
||||
*/
|
||||
public static interface ByteOutputChannel {
|
||||
/**
|
||||
* Send the bytes ( usually the internal conversion buffer ).
|
||||
* Expect 8k output if the buffer is full.
|
||||
*/
|
||||
public void realWriteBytes(byte cbuf[], int off, int len)
|
||||
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 = B2CConverter.ISO_8859_1;
|
||||
|
||||
// byte[]
|
||||
private byte[] buff;
|
||||
|
||||
private int start=0;
|
||||
private int end;
|
||||
|
||||
private Charset charset;
|
||||
|
||||
private boolean isSet=false; // XXX
|
||||
|
||||
// How much can it grow, when data is added
|
||||
private int limit=-1;
|
||||
|
||||
private ByteInputChannel in = null;
|
||||
private ByteOutputChannel out = null;
|
||||
|
||||
private boolean optimizedWrite=true;
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized ByteChunk object.
|
||||
*/
|
||||
public ByteChunk() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
public ByteChunk( int initial ) {
|
||||
allocate( initial, -1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public ByteChunk getClone() {
|
||||
try {
|
||||
return (ByteChunk)this.clone();
|
||||
} catch( Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
return ! isSet; // buff==null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message buff to an uninitialized state.
|
||||
*/
|
||||
public void recycle() {
|
||||
// buff = null;
|
||||
charset=null;
|
||||
start=0;
|
||||
end=0;
|
||||
isSet=false;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
buff=null;
|
||||
}
|
||||
|
||||
// -------------------- Setup --------------------
|
||||
|
||||
public void allocate( int initial, int limit ) {
|
||||
if( buff==null || buff.length < initial ) {
|
||||
buff=new byte[initial];
|
||||
}
|
||||
this.limit=limit;
|
||||
start=0;
|
||||
end=0;
|
||||
isSet=true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message bytes 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setOptimizedWrite(boolean optimizedWrite) {
|
||||
this.optimizedWrite = optimizedWrite;
|
||||
}
|
||||
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
if (charset == null) {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message bytes.
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return getBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message bytes.
|
||||
*/
|
||||
public byte[] getBuffer() {
|
||||
return buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset of the bytes.
|
||||
* For output this is the end of the buffer.
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setOffset(int off) {
|
||||
if (end < off ) {
|
||||
end=off;
|
||||
}
|
||||
start=off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the bytes.
|
||||
* XXX need to clean this up
|
||||
*/
|
||||
public int getLength() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
/** Maximum amount of data in this buffer.
|
||||
*
|
||||
* If -1 or not set, the buffer will grow indefinitely.
|
||||
* 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.
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit=limit;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the buffer is empty, read the data from 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.
|
||||
*/
|
||||
public void setByteOutputChannel(ByteOutputChannel out) {
|
||||
this.out=out;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd( int i ) {
|
||||
end=i;
|
||||
}
|
||||
|
||||
// -------------------- Adding data to the buffer --------------------
|
||||
/** Append a char, by casting it to byte. This IS NOT intended for unicode.
|
||||
*
|
||||
* @param c
|
||||
* @throws IOException
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public void append( char c )
|
||||
throws IOException
|
||||
{
|
||||
append( (byte)c);
|
||||
}
|
||||
|
||||
public void append( byte b )
|
||||
throws IOException
|
||||
{
|
||||
makeSpace( 1 );
|
||||
|
||||
// couldn't make space
|
||||
if( limit >0 && 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
|
||||
*/
|
||||
public void append( byte src[], int off, int len )
|
||||
throws IOException
|
||||
{
|
||||
// will grow, up to limit
|
||||
makeSpace( len );
|
||||
|
||||
// if we don't have limit: makeSpace can grow as it wants
|
||||
if( limit < 0 ) {
|
||||
// assert: makeSpace made enough space
|
||||
System.arraycopy( src, off, buff, end, len );
|
||||
end+=len;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 ( optimizedWrite && len == limit && end == start && out != null ) {
|
||||
out.realWriteBytes( src, off, len );
|
||||
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
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Removing data from the buffer --------------------
|
||||
|
||||
public int substract()
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadBytes( buff, 0, buff.length );
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return (buff[start++] & 0xFF);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public int substract(ByteChunk src)
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadBytes( buff, 0, buff.length );
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int len = getLength();
|
||||
src.append(buff, start, len);
|
||||
start = end;
|
||||
return len;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public byte substractB()
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null)
|
||||
return -1;
|
||||
int n = in.realReadBytes( buff, 0, buff.length );
|
||||
if (n < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (buff[start++]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public int substract( byte src[], int off, int len )
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadBytes( buff, 0, buff.length );
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int n = len;
|
||||
if (len > getLength()) {
|
||||
n = getLength();
|
||||
}
|
||||
System.arraycopy(buff, start, src, off, n);
|
||||
start += n;
|
||||
return n;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public void flushBuffer()
|
||||
throws IOException
|
||||
{
|
||||
//assert out!=null
|
||||
if( out==null ) {
|
||||
throw new IOException( "Buffer overflow, no sink " + limit + " " +
|
||||
buff.length );
|
||||
}
|
||||
out.realWriteBytes( buff, start, end-start );
|
||||
end=start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make space for len chars. If len is small, allocate a reserve space too.
|
||||
* Never grow bigger than limit.
|
||||
*/
|
||||
public void makeSpace(int count) {
|
||||
byte[] tmp = null;
|
||||
|
||||
int newSize;
|
||||
int desiredSize=end + count;
|
||||
|
||||
// Can't grow above the limit
|
||||
if( limit > 0 &&
|
||||
desiredSize > limit) {
|
||||
desiredSize=limit;
|
||||
}
|
||||
|
||||
if( buff==null ) {
|
||||
if( desiredSize < 256 )
|
||||
{
|
||||
desiredSize=256; // take a minimum
|
||||
}
|
||||
buff=new byte[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 < 2 * buff.length ) {
|
||||
newSize= buff.length * 2;
|
||||
if( limit >0 &&
|
||||
newSize > limit ) {
|
||||
newSize=limit;
|
||||
}
|
||||
tmp=new byte[newSize];
|
||||
} else {
|
||||
newSize= buff.length * 2 + count ;
|
||||
if( limit > 0 &&
|
||||
newSize > limit ) {
|
||||
newSize=limit;
|
||||
}
|
||||
tmp=new byte[newSize];
|
||||
}
|
||||
|
||||
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;
|
||||
cb = charset.decode(ByteBuffer.wrap(buff, start, end-start));
|
||||
return new String(cb.array(), cb.arrayOffset(), cb.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getInt()
|
||||
{
|
||||
return Ascii.parseInt(buff, start,end-start);
|
||||
}
|
||||
|
||||
public long getLong() {
|
||||
return Ascii.parseLong(buff, start,end-start);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- equals --------------------
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return true if the comparison succeeded, false 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 blen = end-start;
|
||||
if (b == null || blen != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int boff = start;
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (b[boff++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return true if the comparison succeeded, false otherwise
|
||||
*/
|
||||
public boolean equalsIgnoreCase(String s) {
|
||||
byte[] b = buff;
|
||||
int blen = end-start;
|
||||
if (b == null || blen != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int boff = start;
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (Ascii.toLower(b[boff++]) != 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 ( len2 != len || 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 message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean startsWith(String s) {
|
||||
// Works only if enc==UTF
|
||||
byte[] b = buff;
|
||||
int blen = s.length();
|
||||
if (b == null || blen > end-start) {
|
||||
return false;
|
||||
}
|
||||
int boff = start;
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (b[boff++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes start with the specified byte array.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean startsWith(byte[] b2) {
|
||||
byte[] b1 = buff;
|
||||
if (b1 == null && b2 == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int len = end - start;
|
||||
if (b1 == null || b2 == null || b2.length > len) {
|
||||
return false;
|
||||
}
|
||||
for (int i = start, j = 0; i < end && j < b2.length;) {
|
||||
if (b1[i++] != b2[j++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
* @param pos The position
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
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( buff[i] != first ) {
|
||||
continue;
|
||||
}
|
||||
// found first char, now look for a match
|
||||
int myPos=i+1;
|
||||
for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
|
||||
if( buff[myPos++] != src.charAt( srcPos++ )) {
|
||||
continue mainLoop;
|
||||
}
|
||||
}
|
||||
return i-start; // found it
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// -------------------- Hash code --------------------
|
||||
|
||||
// normal hash.
|
||||
public int hash() {
|
||||
return hashBytes( buff, start, end-start);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public int hashIgnoreCase() {
|
||||
return hashBytesIC( buff, start, end-start );
|
||||
}
|
||||
|
||||
private static int hashBytes( byte buff[], int start, int bytesLen ) {
|
||||
int max=start+bytesLen;
|
||||
byte bb[]=buff;
|
||||
int code=0;
|
||||
for (int i = start; i < max ; i++) {
|
||||
code = code * 37 + bb[i];
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
private static int hashBytesIC( byte bytes[], int start,
|
||||
int bytesLen )
|
||||
{
|
||||
int max=start+bytesLen;
|
||||
byte bb[]=bytes;
|
||||
int code=0;
|
||||
for (int i = start; i < max ; i++) {
|
||||
code = code * 37 + Ascii.toLower(bb[i]);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 c 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 c) {
|
||||
int offset = start;
|
||||
|
||||
while (offset < end) {
|
||||
byte b=bytes[offset];
|
||||
if (b == c) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first instance of any byte that is not one 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 list of bytes to search for
|
||||
* @return The position of the first instance a byte that is not
|
||||
* in the list of bytes to search for or -1 if no such byte
|
||||
* is found.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int findNotBytes(byte bytes[], int start, int end, byte b[]) {
|
||||
int blen = b.length;
|
||||
int offset = start;
|
||||
boolean found;
|
||||
|
||||
while (offset < end) {
|
||||
found = true;
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (bytes[offset] == b[i]) {
|
||||
found=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,700 @@
|
||||
/*
|
||||
* 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.struts2.util.tomcat.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 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 implements Cloneable, Serializable, CharSequence {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Input interface, used when the buffer is emptied.
|
||||
public static interface CharInputChannel {
|
||||
/**
|
||||
* Read new bytes ( usually the internal conversion buffer ).
|
||||
* The implementation is allowed to ignore the parameters,
|
||||
* and mutate the chunk if it wishes to implement its own buffering.
|
||||
*/
|
||||
public int realReadChars(char cbuf[], int off, int len)
|
||||
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.
|
||||
*/
|
||||
public void realWriteChars(char cbuf[], int off, int len)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
private int hashCode = 0;
|
||||
// did we compute the hashcode ?
|
||||
private boolean hasHashCode = false;
|
||||
|
||||
// char[]
|
||||
private char buff[];
|
||||
|
||||
private int start;
|
||||
private int end;
|
||||
|
||||
private boolean isSet=false; // XXX
|
||||
|
||||
// -1: grow indefinitely
|
||||
// maximum amount to be cached
|
||||
private int limit=-1;
|
||||
|
||||
private CharInputChannel in = null;
|
||||
private CharOutputChannel out = null;
|
||||
|
||||
private boolean optimizedWrite=true;
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized CharChunk object.
|
||||
*/
|
||||
public CharChunk() {
|
||||
}
|
||||
|
||||
public CharChunk(int size) {
|
||||
allocate( size, -1 );
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
public boolean isNull() {
|
||||
if( end > 0 ) {
|
||||
return false;
|
||||
}
|
||||
return !isSet; //XXX
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message bytes to an uninitialized state.
|
||||
*/
|
||||
public void recycle() {
|
||||
// buff=null;
|
||||
isSet=false; // XXX
|
||||
hasHashCode = false;
|
||||
start=0;
|
||||
end=0;
|
||||
}
|
||||
|
||||
// -------------------- Setup --------------------
|
||||
|
||||
public void allocate( int initial, int limit ) {
|
||||
if( buff==null || buff.length < initial ) {
|
||||
buff=new char[initial];
|
||||
}
|
||||
this.limit=limit;
|
||||
start=0;
|
||||
end=0;
|
||||
isSet=true;
|
||||
hasHashCode = false;
|
||||
}
|
||||
|
||||
|
||||
public void setOptimizedWrite(boolean optimizedWrite) {
|
||||
this.optimizedWrite = optimizedWrite;
|
||||
}
|
||||
|
||||
public void setChars( char[] c, int off, int len ) {
|
||||
buff=c;
|
||||
start=off;
|
||||
end=start + len;
|
||||
isSet=true;
|
||||
hasHashCode = false;
|
||||
}
|
||||
|
||||
/** Maximum amount of data in this buffer.
|
||||
*
|
||||
* If -1 or not set, the buffer will grow indefinitely.
|
||||
* 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.
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit=limit;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the buffer is empty, read the data from 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.
|
||||
*/
|
||||
public void setCharOutputChannel(CharOutputChannel out) {
|
||||
this.out=out;
|
||||
}
|
||||
|
||||
// compat
|
||||
public char[] getChars()
|
||||
{
|
||||
return getBuffer();
|
||||
}
|
||||
|
||||
public char[] getBuffer()
|
||||
{
|
||||
return buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset of the bytes.
|
||||
* For output this is the end of the buffer.
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset of the bytes.
|
||||
*/
|
||||
public void setOffset(int off) {
|
||||
start=off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the bytes.
|
||||
*/
|
||||
public int getLength() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd( int i ) {
|
||||
end=i;
|
||||
}
|
||||
|
||||
// -------------------- Adding data --------------------
|
||||
|
||||
public void append( char b )
|
||||
throws IOException
|
||||
{
|
||||
makeSpace( 1 );
|
||||
|
||||
// couldn't make space
|
||||
if( limit >0 && end >= limit ) {
|
||||
flushBuffer();
|
||||
}
|
||||
buff[end++]=b;
|
||||
}
|
||||
|
||||
public void append( CharChunk src )
|
||||
throws IOException
|
||||
{
|
||||
append( src.getBuffer(), src.getOffset(), src.getLength());
|
||||
}
|
||||
|
||||
/** Add data to the buffer
|
||||
*/
|
||||
public void append( char src[], int off, int len )
|
||||
throws IOException
|
||||
{
|
||||
// will grow, up to limit
|
||||
makeSpace( len );
|
||||
|
||||
// if we don't have limit: makeSpace can grow as it wants
|
||||
if( limit < 0 ) {
|
||||
// assert: makeSpace made enough space
|
||||
System.arraycopy( src, off, buff, end, len );
|
||||
end+=len;
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimize on a common case.
|
||||
// If 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 ( optimizedWrite && len == limit && end == start && out != null ) {
|
||||
out.realWriteChars( src, off, len );
|
||||
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
|
||||
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
|
||||
*/
|
||||
public void append(String s) throws IOException {
|
||||
append(s, 0, s.length());
|
||||
}
|
||||
|
||||
/** Append a string to the buffer
|
||||
*/
|
||||
public void append(String s, int off, int len) throws IOException {
|
||||
if (s==null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// will grow, up to limit
|
||||
makeSpace( len );
|
||||
|
||||
// if we don't have limit: makeSpace can grow as it wants
|
||||
if( limit < 0 ) {
|
||||
// assert: makeSpace made enough space
|
||||
s.getChars(off, off+len, buff, end );
|
||||
end+=len;
|
||||
return;
|
||||
}
|
||||
|
||||
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 ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadChars(buff, end, buff.length - end);
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return (buff[start++]);
|
||||
|
||||
}
|
||||
|
||||
public int substract( char src[], int off, int len )
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadChars( buff, end, buff.length - end);
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int n = len;
|
||||
if (len > getLength()) {
|
||||
n = getLength();
|
||||
}
|
||||
System.arraycopy(buff, start, src, off, n);
|
||||
start += n;
|
||||
return n;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void flushBuffer()
|
||||
throws IOException
|
||||
{
|
||||
//assert out!=null
|
||||
if( out==null ) {
|
||||
throw new IOException( "Buffer overflow, no sink " + limit + " " +
|
||||
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 limit.
|
||||
*/
|
||||
public void makeSpace(int count)
|
||||
{
|
||||
char[] tmp = null;
|
||||
|
||||
int newSize;
|
||||
int desiredSize=end + count;
|
||||
|
||||
// Can't grow above the limit
|
||||
if( limit > 0 &&
|
||||
desiredSize > limit) {
|
||||
desiredSize=limit;
|
||||
}
|
||||
|
||||
if( buff==null ) {
|
||||
if( desiredSize < 256 )
|
||||
{
|
||||
desiredSize=256; // take a minimum
|
||||
}
|
||||
buff=new char[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 < 2 * buff.length ) {
|
||||
newSize= buff.length * 2;
|
||||
if( limit >0 &&
|
||||
newSize > limit ) {
|
||||
newSize=limit;
|
||||
}
|
||||
tmp=new char[newSize];
|
||||
} else {
|
||||
newSize= buff.length * 2 + count ;
|
||||
if( limit > 0 &&
|
||||
newSize > limit ) {
|
||||
newSize=limit;
|
||||
}
|
||||
tmp=new char[newSize];
|
||||
}
|
||||
|
||||
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 true if the comparison succeeded, false 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 true if the comparison succeeded, false 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;
|
||||
}
|
||||
|
||||
if (b1== null || b2==null || end-start != len2) {
|
||||
return false;
|
||||
}
|
||||
int off1 = start;
|
||||
int len=end-start;
|
||||
while ( len-- > 0) {
|
||||
if (b1[off1++] != b2[off2++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true 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 message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true 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;
|
||||
}
|
||||
|
||||
// -------------------- Hash code --------------------
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (hasHashCode) {
|
||||
return hashCode;
|
||||
}
|
||||
int code = 0;
|
||||
|
||||
code = hash();
|
||||
hashCode = code;
|
||||
hasHashCode = true;
|
||||
return code;
|
||||
}
|
||||
|
||||
// normal hash.
|
||||
public int hash() {
|
||||
int code=0;
|
||||
for (int i = start; i < start + end-start; i++) {
|
||||
code = code * 37 + buff[i];
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
public int indexOf(char c) {
|
||||
return indexOf( c, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes starts with the specified string.
|
||||
* @param c the character
|
||||
*/
|
||||
public int indexOf(char c, int starting) {
|
||||
int ret = indexOf( buff, start+starting, end, c );
|
||||
return (ret >= start) ? ret - start : -1;
|
||||
}
|
||||
|
||||
public static int indexOf( char chars[], int off, int cend, char qq )
|
||||
{
|
||||
while( off < cend ) {
|
||||
char b=chars[off];
|
||||
if( b==qq ) {
|
||||
return off;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
public int indexOf(String src, int srcOff, int srcLen, int myOff ) {
|
||||
char first=src.charAt( srcOff );
|
||||
|
||||
// Look for first char
|
||||
int srcEnd = srcOff + srcLen;
|
||||
|
||||
for( int i=myOff+start; i <= (end - srcLen); i++ ) {
|
||||
if( buff[i] != first ) {
|
||||
continue;
|
||||
}
|
||||
// found first char, now look for a match
|
||||
int myPos=i+1;
|
||||
for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
|
||||
if( buff[myPos++] != src.charAt( srcPos++ )) {
|
||||
break;
|
||||
}
|
||||
if( srcPos==srcEnd )
|
||||
{
|
||||
return i-start; // found it
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// -------------------- utils
|
||||
private int min(int a, int b) {
|
||||
if (a < b) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Char sequence impl
|
||||
|
||||
public char charAt(int index) {
|
||||
return buff[index + start];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return end - start;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
// -------------------------------------------------------------- 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("The input must consist of an even number of hex digits");
|
||||
}
|
||||
|
||||
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("The input must consist only of hex digits");
|
||||
}
|
||||
result[i] = (byte) ((upperNibble << 4) + lowerNibble);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
/*
|
||||
* 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.struts2.util.tomcat.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_STR if the the object used to create the MessageBytes
|
||||
was a byte[] */
|
||||
public static final int T_BYTES = 2;
|
||||
/** getType() is T_STR 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
|
||||
*/
|
||||
public static MessageBytes newInstance() {
|
||||
return factory.newInstance();
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
// should we check also hasStrValue ???
|
||||
return byteC.isNull() && charC.isNull() && ! hasStrValue;
|
||||
// bytes==null && strValue==null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 bytes
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public CharChunk getCharChunk() {
|
||||
return charC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string value.
|
||||
* Valid only if T_STR or after a conversion was made.
|
||||
*/
|
||||
public String getString() {
|
||||
return strValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Charset used for string<->byte conversions.
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return byteC.getCharset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Charset used for string<->byte conversions.
|
||||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
byteC.setCharset(charset);
|
||||
}
|
||||
|
||||
/** Do a char->byte conversion.
|
||||
*/
|
||||
public void toBytes() {
|
||||
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( ! 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.
|
||||
*/
|
||||
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 true if the comparison succeeded, false 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 true if the comparison succeeded, false 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true 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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- 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 an 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
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
* 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.struts2.util.tomcat.buf;
|
||||
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* This class implements a String cache for ByteChunk and CharChunk.
|
||||
*
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public class StringCache {
|
||||
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(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<ByteEntry, int[]>(cacheSize);
|
||||
|
||||
|
||||
/**
|
||||
* toString count for byte chunk.
|
||||
*/
|
||||
protected static int bcCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Cache for byte chunk.
|
||||
*/
|
||||
protected static ByteEntry[] bcCache = null;
|
||||
|
||||
|
||||
/**
|
||||
* Statistics hash map for char chunk.
|
||||
*/
|
||||
protected static final HashMap<CharEntry,int[]> ccStats =
|
||||
new HashMap<CharEntry, int[]>(cacheSize);
|
||||
|
||||
|
||||
/**
|
||||
* toString count for char chunk.
|
||||
*/
|
||||
protected static int ccCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Cache for char chunk.
|
||||
*/
|
||||
protected static 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<Integer, ArrayList<ByteEntry>>();
|
||||
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<ByteEntry>();
|
||||
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<Integer, ArrayList<CharEntry>>();
|
||||
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<CharEntry>();
|
||||
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.
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* 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.struts2.util.tomcat.buf;
|
||||
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import java.io.CharConversionException;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* 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 Logger log = LoggerFactory.getLogger(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("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.
|
||||
*/
|
||||
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 );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// -------------------- Additional methods --------------------
|
||||
// XXX What do we do about charset ????
|
||||
|
||||
/** In-buffer processing - the buffer will be modified
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// XXX Old code, needs to be replaced !!!!
|
||||
//
|
||||
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("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, the system default
|
||||
* character encoding 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
|
||||
*
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(String str) {
|
||||
return URLDecode(str, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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, the default encoding is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(String str, String enc) {
|
||||
return URLDecode(str, enc, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded String.
|
||||
*
|
||||
* @param str The url-encoded string
|
||||
* @param enc The encoding to use; if null, the default encoding is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @param isQuery Is this a query string being processed
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(String str, String enc, boolean isQuery) {
|
||||
if (str == null)
|
||||
return (null);
|
||||
|
||||
// use the specified encoding to extract bytes out of the
|
||||
// given string so that the encoding is not lost. If an
|
||||
// encoding is not specified, use ISO-8859-1
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
if (enc == null) {
|
||||
bytes = str.getBytes("ISO-8859-1");
|
||||
} else {
|
||||
bytes = str.getBytes(B2CConverter.getCharset(enc));
|
||||
}
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Unable to URL decode the specified input since the encoding "+ enc + " is not supported.", uee);
|
||||
}
|
||||
}
|
||||
|
||||
return URLDecode(bytes, enc, 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, the default encoding is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @param isQuery Is this a query string being processed
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(byte[] bytes, String enc, boolean isQuery) {
|
||||
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
int len = bytes.length;
|
||||
int ix = 0;
|
||||
int ox = 0;
|
||||
while (ix < len) {
|
||||
byte b = bytes[ix++]; // Get byte to test
|
||||
if (b == '+' && isQuery) {
|
||||
b = (byte)' ';
|
||||
} else if (b == '%') {
|
||||
if (ix + 2 > len) {
|
||||
throw new IllegalArgumentException(
|
||||
"The % character must be followed by two hexademical digits");
|
||||
}
|
||||
b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
|
||||
+ convertHexDigit(bytes[ix++]));
|
||||
}
|
||||
bytes[ox++] = b;
|
||||
}
|
||||
if (enc != null) {
|
||||
try {
|
||||
return new String(bytes, 0, ox, B2CConverter.getCharset(enc));
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Unable to URL decode the specified input since the encoding " + enc + " is not supported.", uee);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return new String(bytes, 0, ox);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static byte convertHexDigit( byte b ) {
|
||||
if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
|
||||
if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
|
||||
if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
|
||||
throw new IllegalArgumentException(((char) b) + " is not a hexadecimal digit");
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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.struts2.util.tomcat.buf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
|
||||
/**
|
||||
* 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(B2CConverter.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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,11 @@ import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
import org.apache.struts2.util.URLDecoderUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -276,15 +276,15 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the URL using {@link java.net.URLDecoder#decode(String, String)} with the encoding specified in the configuration.
|
||||
* Decodes the URL using {@link URLDecoderUtil#decode(String, String)} with the encoding specified in the configuration.
|
||||
*
|
||||
* @param input the input to decode
|
||||
* @return the encoded string
|
||||
*/
|
||||
public String decode( String input ) {
|
||||
try {
|
||||
return URLDecoder.decode(input, encoding);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return URLDecoderUtil.decode(input, encoding);
|
||||
} catch (Exception e) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Could not decode URL parameter '#0', returning value un-decoded", input);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.apache.struts2.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class URLDecoderUtilTest {
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringInvalid() {
|
||||
// %n rather than %nn should throw an IAE according to the Javadoc
|
||||
Exception exception = null;
|
||||
try {
|
||||
URLDecoderUtil.decode("%5xxxxx", "ISO-8859-1");
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
}
|
||||
assertTrue(exception instanceof IllegalArgumentException);
|
||||
|
||||
// Edge case trying to trigger ArrayIndexOutOfBoundsException
|
||||
exception = null;
|
||||
try {
|
||||
URLDecoderUtil.decode("%5", "ISO-8859-1");
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
}
|
||||
assertTrue(exception instanceof IllegalArgumentException);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidIso88591Start() {
|
||||
|
||||
String result = URLDecoderUtil.decode("%41xxxx", "ISO-8859-1");
|
||||
assertEquals("Axxxx", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidIso88591Middle() {
|
||||
|
||||
String result = URLDecoderUtil.decode("xx%41xx", "ISO-8859-1");
|
||||
assertEquals("xxAxx", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidIso88591End() {
|
||||
|
||||
String result = URLDecoderUtil.decode("xxxx%41", "ISO-8859-1");
|
||||
assertEquals("xxxxA", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidUtf8Start() {
|
||||
String result = URLDecoderUtil.decode("%c3%aaxxxx", "UTF-8");
|
||||
assertEquals("\u00eaxxxx", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidUtf8Middle() {
|
||||
|
||||
String result = URLDecoderUtil.decode("xx%c3%aaxx", "UTF-8");
|
||||
assertEquals("xx\u00eaxx", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidUtf8End() {
|
||||
|
||||
String result = URLDecoderUtil.decode("xxxx%c3%aa", "UTF-8");
|
||||
assertEquals("xxxx\u00ea", result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -300,9 +300,20 @@ public class RestActionMapper extends DefaultActionMapper {
|
||||
private void handleDynamicMethodInvocation(ActionMapping mapping, String name) {
|
||||
int exclamation = name.lastIndexOf("!");
|
||||
if (exclamation != -1) {
|
||||
mapping.setName(name.substring(0, exclamation));
|
||||
String actionName = name.substring(0, exclamation);
|
||||
String actionMethod = name.substring(exclamation + 1);
|
||||
|
||||
// WW-4585
|
||||
// add any ; appendix to name, it will be handled later in getMapping method
|
||||
int scPos = actionMethod.indexOf(';');
|
||||
if (scPos != -1) {
|
||||
actionName = actionName + actionMethod.substring(scPos);
|
||||
actionMethod = actionMethod.substring(0, scPos);
|
||||
}
|
||||
|
||||
mapping.setName(actionName);
|
||||
if (allowDynamicMethodCalls) {
|
||||
mapping.setMethod(name.substring(exclamation + 1));
|
||||
mapping.setMethod(actionMethod);
|
||||
} else {
|
||||
mapping.setMethod(null);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
|
||||
@@ -133,7 +135,7 @@ public class DefaultHttpHeadersTest extends TestCase {
|
||||
Date now = new Date();
|
||||
DefaultHttpHeaders headers = new DefaultHttpHeaders()
|
||||
.lastModified(now);
|
||||
mockRequest.addHeader("If-Modified-Since", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(now));
|
||||
mockRequest.addHeader("If-Modified-Since", getGMTDateFormat().format(now));
|
||||
headers.apply(mockRequest, mockResponse, new Object());
|
||||
|
||||
assertEquals(SC_NOT_MODIFIED, mockResponse.getStatus());
|
||||
@@ -152,7 +154,7 @@ public class DefaultHttpHeadersTest extends TestCase {
|
||||
public void testLastModifiedSince() {
|
||||
Date now = new Date();
|
||||
DefaultHttpHeaders headers = new DefaultHttpHeaders().lastModified(now);
|
||||
mockRequest.addHeader("If-Modified-Since", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(now));
|
||||
mockRequest.addHeader("If-Modified-Since", getGMTDateFormat().format(now));
|
||||
headers.apply(mockRequest, mockResponse, new Object());
|
||||
|
||||
assertEquals(SC_NOT_MODIFIED, mockResponse.getStatus());
|
||||
@@ -161,7 +163,7 @@ public class DefaultHttpHeadersTest extends TestCase {
|
||||
public void testLastModifiedSinceIsOlder() {
|
||||
Date now = new Date();
|
||||
DefaultHttpHeaders headers = new DefaultHttpHeaders().lastModified(now);
|
||||
mockRequest.addHeader("If-Modified-Since", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date(now.getTime() - 1000 * 60 * 60)));
|
||||
mockRequest.addHeader("If-Modified-Since", getGMTDateFormat().format(new Date(now.getTime() - 1000 * 60 * 60)));
|
||||
headers.apply(mockRequest, mockResponse, new Object());
|
||||
|
||||
assertEquals(SC_NOT_MODIFIED, mockResponse.getStatus());
|
||||
@@ -173,7 +175,7 @@ public class DefaultHttpHeadersTest extends TestCase {
|
||||
.lastModified(now)
|
||||
.withETag("asdf");
|
||||
mockRequest.addHeader("If-None-Match", "asdf");
|
||||
mockRequest.addHeader("If-Modified-Since", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(now));
|
||||
mockRequest.addHeader("If-Modified-Since", getGMTDateFormat().format(now));
|
||||
headers.apply(mockRequest, mockResponse, new Object());
|
||||
|
||||
assertEquals(SC_NOT_MODIFIED, mockResponse.getStatus());
|
||||
@@ -219,4 +221,10 @@ public class DefaultHttpHeadersTest extends TestCase {
|
||||
assertEquals(SC_OK, mockResponse.getStatus());
|
||||
|
||||
}
|
||||
|
||||
private SimpleDateFormat getGMTDateFormat() {
|
||||
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
||||
format.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ public class RestActionMapperTest extends TestCase {
|
||||
req.setRequestURI("/myapp/animals/dog/fido!update;jsessionid=29fefpv23do1g");
|
||||
req.setServletPath("/animals/dog/fido");
|
||||
req.setMethod("GET");
|
||||
|
||||
|
||||
ActionMapping mapping = mapper.getMapping(req, configManager);
|
||||
|
||||
assertEquals("/animals", mapping.getNamespace());
|
||||
@@ -196,10 +196,10 @@ public class RestActionMapperTest extends TestCase {
|
||||
req.setRequestURI("/myapp/animals/dog/fido!update;jsessionid=29fefpv23do1g");
|
||||
req.setServletPath("/animals/dog/fido");
|
||||
req.setMethod("GET");
|
||||
|
||||
|
||||
// allow DMI
|
||||
mapper.setAllowDynamicMethodCalls("true");
|
||||
|
||||
|
||||
ActionMapping mapping = mapper.getMapping(req, configManager);
|
||||
|
||||
assertEquals("/animals", mapping.getNamespace());
|
||||
|
||||
@@ -118,6 +118,25 @@
|
||||
<module>../maven/struts-annotations</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jdk8</id>
|
||||
<activation>
|
||||
<jdk>1.8</jdk>
|
||||
</activation>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user