WW-4507 - clone Tomcat UDecoder and use it for in query string handling
Project: http://git-wip-us.apache.org/repos/asf/struts/repo Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/5421930b Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/5421930b Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/5421930b Branch: refs/heads/support-2-3 Commit: 5421930b49822606792f36653b17d3d95ef106f9 Parents: c6750c1 Author: Rene Gielen <rgie...@apache.org> Authored: Thu Jan 14 14:52:03 2016 +0100 Committer: Rene Gielen <rgie...@apache.org> Committed: Thu Jan 14 14:52:03 2016 +0100 ---------------------------------------------------------------------- .../dispatcher/mapper/Restful2ActionMapper.java | 6 +- .../dispatcher/mapper/RestfulActionMapper.java | 6 +- .../org/apache/struts2/util/URLDecoderUtil.java | 22 + .../apache/struts2/util/tomcat/buf/Ascii.java | 255 +++++ .../struts2/util/tomcat/buf/B2CConverter.java | 201 ++++ .../struts2/util/tomcat/buf/ByteChunk.java | 935 +++++++++++++++++++ .../struts2/util/tomcat/buf/CharChunk.java | 700 ++++++++++++++ .../struts2/util/tomcat/buf/HexUtils.java | 113 +++ .../struts2/util/tomcat/buf/MessageBytes.java | 546 +++++++++++ .../struts2/util/tomcat/buf/StringCache.java | 695 ++++++++++++++ .../struts2/util/tomcat/buf/UDecoder.java | 421 +++++++++ .../struts2/util/tomcat/buf/Utf8Decoder.java | 293 ++++++ .../struts2/views/util/DefaultUrlHelper.java | 8 +- .../apache/struts2/util/URLDecoderUtilTest.java | 71 ++ 14 files changed, 4262 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/struts/blob/5421930b/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java index 3f08e84..0d93711 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java @@ -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); http://git-wip-us.apache.org/repos/asf/struts/blob/5421930b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java index b2378f4..4b98409 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java @@ -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); http://git-wip-us.apache.org/repos/asf/struts/blob/5421930b/core/src/main/java/org/apache/struts2/util/URLDecoderUtil.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/struts2/util/URLDecoderUtil.java b/core/src/main/java/org/apache/struts2/util/URLDecoderUtil.java new file mode 100644 index 0000000..10f2a78 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/util/URLDecoderUtil.java @@ -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); + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/5421930b/core/src/main/java/org/apache/struts2/util/tomcat/buf/Ascii.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/Ascii.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/Ascii.java new file mode 100644 index 0000000..1b0ccb6 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/util/tomcat/buf/Ascii.java @@ -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 d...@eng.sun.com + * @author James Todd [go...@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; + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/struts/blob/5421930b/core/src/main/java/org/apache/struts2/util/tomcat/buf/B2CConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/B2CConverter.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/B2CConverter.java new file mode 100644 index 0000000..a3fc6d1 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/util/tomcat/buf/B2CConverter.java @@ -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()); + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/struts/blob/5421930b/core/src/main/java/org/apache/struts2/util/tomcat/buf/ByteChunk.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/ByteChunk.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/ByteChunk.java new file mode 100644 index 0000000..aff247f --- /dev/null +++ b/core/src/main/java/org/apache/struts2/util/tomcat/buf/ByteChunk.java @@ -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 d...@sun.com + * @author James Todd [go...@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; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/struts/blob/5421930b/core/src/main/java/org/apache/struts2/util/tomcat/buf/CharChunk.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/CharChunk.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/CharChunk.java new file mode 100644 index 0000000..527707a --- /dev/null +++ b/core/src/main/java/org/apache/struts2/util/tomcat/buf/CharChunk.java @@ -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 d...@sun.com + * @author James Todd [go...@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; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/5421930b/core/src/main/java/org/apache/struts2/util/tomcat/buf/HexUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/HexUtils.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/HexUtils.java new file mode 100644 index 0000000..0b9a116 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/util/tomcat/buf/HexUtils.java @@ -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; + } +}