You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by rg...@apache.org on 2016/01/14 15:16:26 UTC
[2/2] struts git commit: WW-4507 - clone Tomcat UDecoder and use it
for in query string handling
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 <rg...@apache.org>
Authored: Thu Jan 14 14:52:03 2016 +0100
Committer: Rene Gielen <rg...@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 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;
+ }
+
+}
\ 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 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;
+ }
+}
\ 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 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;
+ }
+
+}
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;
+ }
+}