JBossWeb SVN: r563 - trunk/java/org/apache/tomcat/util/http.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-27 21:43:04 -0400 (Thu, 27 Mar 2008)
New Revision: 563
Modified:
trunk/java/org/apache/tomcat/util/http/Parameters.java
Log:
- Remove useless old code (the only way to handle chars is to cast them to bytes).
Modified: trunk/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- trunk/java/org/apache/tomcat/util/http/Parameters.java 2008-03-28 00:54:21 UTC (rev 562)
+++ trunk/java/org/apache/tomcat/util/http/Parameters.java 2008-03-28 01:43:04 UTC (rev 563)
@@ -175,6 +175,21 @@
protected CharChunk tmpNameC = new CharChunk(32);
protected CharChunk tmpValueC = new CharChunk(128);
+ public void processParameters( MessageBytes data ) {
+ processParameters(data, encoding);
+ }
+
+ public void processParameters( MessageBytes data, String encoding ) {
+ if( data==null || data.isNull() || data.getLength() <= 0 ) return;
+
+ if (data.getType() != MessageBytes.T_BYTES) {
+ data.toBytes();
+ }
+ ByteChunk bc=data.getByteChunk();
+ processParameters( bc.getBytes(), bc.getOffset(),
+ bc.getLength(), encoding);
+ }
+
public void processParameters( byte bytes[], int start, int len ) {
processParameters(bytes, start, len, encoding);
}
@@ -237,7 +252,7 @@
} while( pos<end );
}
- private String urlDecode(ByteChunk bc, String enc)
+ protected String urlDecode(ByteChunk bc, String enc)
throws IOException {
if( urlDec==null ) {
urlDec=new UDecoder();
@@ -265,87 +280,6 @@
return result;
}
- public void processParameters( char chars[], int start, int len ) {
- int end=start+len;
- int pos=start;
-
- if( debug>0 )
- log( "Chars: " + new String( chars, start, len ));
- do {
- boolean noEq=false;
- int nameStart=pos;
- int valStart=-1;
- int valEnd=-1;
-
- int nameEnd=CharChunk.indexOf(chars, nameStart, end, '=' );
- int nameEnd2=CharChunk.indexOf(chars, nameStart, end, '&' );
- if( (nameEnd2!=-1 ) &&
- ( nameEnd==-1 || nameEnd > nameEnd2) ) {
- nameEnd=nameEnd2;
- noEq=true;
- valStart=nameEnd;
- valEnd=nameEnd;
- if( debug>0) log("no equal " + nameStart + " " + nameEnd + " " + new String(chars, nameStart, nameEnd-nameStart) );
- }
- if( nameEnd== -1 ) nameEnd=end;
-
- if( ! noEq ) {
- valStart= (nameEnd < end) ? nameEnd+1 : end;
- valEnd=CharChunk.indexOf(chars, valStart, end, '&');
- if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
- }
-
- pos=valEnd+1;
-
- if( nameEnd<=nameStart ) {
- continue;
- // invalid chunk - no name, it's better to ignore
- // XXX log it ?
- }
-
- try {
- tmpNameC.append( chars, nameStart, nameEnd-nameStart );
- tmpValueC.append( chars, valStart, valEnd-valStart );
-
- if( debug > 0 )
- log( tmpNameC + "= " + tmpValueC);
-
- if( urlDec==null ) {
- urlDec=new UDecoder();
- }
-
- urlDec.convert( tmpNameC );
- urlDec.convert( tmpValueC );
-
- if( debug > 0 )
- log( tmpNameC + "= " + tmpValueC);
-
- addParam( tmpNameC.toString(), tmpValueC.toString() );
- } catch( IOException ex ) {
- ex.printStackTrace();
- }
-
- tmpNameC.recycle();
- tmpValueC.recycle();
-
- } while( pos<end );
- }
-
- public void processParameters( MessageBytes data ) {
- processParameters(data, encoding);
- }
-
- public void processParameters( MessageBytes data, String encoding ) {
- if( data==null || data.isNull() || data.getLength() <= 0 ) return;
-
- if (data.getType() != MessageBytes.T_BYTES) {
- data.toBytes();
- }
- ByteChunk bc=data.getByteChunk();
- processParameters( bc.getBytes(), bc.getOffset(),
- bc.getLength(), encoding);
- }
-
/** Debug purpose
*/
public String paramsAsString() {
@@ -368,78 +302,4 @@
log.debug("Parameters: " + s );
}
- // -------------------- Old code, needs rewrite --------------------
-
- /** Used by RequestDispatcher
- */
- public void processParameters( String str ) {
- int end=str.length();
- int pos=0;
- if( debug > 0)
- log("String: " + str );
-
- do {
- boolean noEq=false;
- int valStart=-1;
- int valEnd=-1;
-
- int nameStart=pos;
- int nameEnd=str.indexOf('=', nameStart );
- int nameEnd2=str.indexOf('&', nameStart );
- if( nameEnd2== -1 ) nameEnd2=end;
- if( (nameEnd2!=-1 ) &&
- ( nameEnd==-1 || nameEnd > nameEnd2) ) {
- nameEnd=nameEnd2;
- noEq=true;
- valStart=nameEnd;
- valEnd=nameEnd;
- if( debug>0) log("no equal " + nameStart + " " + nameEnd + " " + str.substring(nameStart, nameEnd) );
- }
-
- if( nameEnd== -1 ) nameEnd=end;
-
- if( ! noEq ) {
- valStart=nameEnd+1;
- valEnd=str.indexOf('&', valStart);
- if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
- }
-
- pos=valEnd+1;
-
- if( nameEnd<=nameStart ) {
- continue;
- }
- if( debug>0)
- log( "XXX " + nameStart + " " + nameEnd + " "
- + valStart + " " + valEnd );
-
- try {
- tmpNameC.append(str, nameStart, nameEnd-nameStart );
- tmpValueC.append(str, valStart, valEnd-valStart );
-
- if( debug > 0 )
- log( tmpNameC + "= " + tmpValueC);
-
- if( urlDec==null ) {
- urlDec=new UDecoder();
- }
-
- urlDec.convert( tmpNameC );
- urlDec.convert( tmpValueC );
-
- if( debug > 0 )
- log( tmpNameC + "= " + tmpValueC);
-
- addParam( tmpNameC.toString(), tmpValueC.toString() );
- } catch( IOException ex ) {
- ex.printStackTrace();
- }
-
- tmpNameC.recycle();
- tmpValueC.recycle();
-
- } while( pos<end );
- }
-
-
}
16 years, 11 months
JBossWeb SVN: r562 - in trunk: webapps/docs and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-27 20:54:21 -0400 (Thu, 27 Mar 2008)
New Revision: 562
Modified:
trunk/java/javax/servlet/http/HttpServlet.java
trunk/webapps/docs/changelog.xml
Log:
- Port fix for 4562: HEAD requests cannot use includes. Patch provided by David Jencks.
- Will revert if it causes TCK signature problems.
Modified: trunk/java/javax/servlet/http/HttpServlet.java
===================================================================
--- trunk/java/javax/servlet/http/HttpServlet.java 2008-03-28 00:52:58 UTC (rev 561)
+++ trunk/java/javax/servlet/http/HttpServlet.java 2008-03-28 00:54:21 UTC (rev 562)
@@ -1,19 +1,19 @@
/*
-* 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.
-*/
+ * 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 javax.servlet.http;
import java.io.IOException;
@@ -23,7 +23,6 @@
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
-import java.util.Locale;
import java.util.ResourceBundle;
import javax.servlet.GenericServlet;
@@ -34,7 +33,6 @@
/**
- *
* Provides an abstract class to be subclassed to create
* an HTTP servlet suitable for a Web site. A subclass of
* <code>HttpServlet</code> must override at least
@@ -72,16 +70,12 @@
* Java Tutorial on Multithreaded Programming</a> for more
* information on handling multiple threads in a Java program.
*
- * @author Various
- * @version $Version$
- *
+ * @author Various
+ * @version $Version$
*/
-
-
-
public abstract class HttpServlet extends GenericServlet
- implements java.io.Serializable
-{
+ implements java.io.Serializable {
+
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
@@ -94,24 +88,18 @@
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE =
- "javax.servlet.http.LocalStrings";
+ "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings =
- ResourceBundle.getBundle(LSTRING_FILE);
+ ResourceBundle.getBundle(LSTRING_FILE);
-
-
/**
* Does nothing, because this is an abstract class.
- *
*/
-
public HttpServlet() { }
-
/**
- *
* Called by the server (via the <code>service</code> method) to
* allow a servlet to handle a GET request.
*
@@ -157,46 +145,38 @@
*
* <p>If the request is incorrectly formatted, <code>doGet</code>
* returns an HTTP "Bad Request" message.
- *
*
- * @param req an {@link HttpServletRequest} object that
- * contains the request the client has made
- * of the servlet
+ * @param req an {@link HttpServletRequest} object that
+ * contains the request the client has made
+ * of the servlet
*
- * @param resp an {@link HttpServletResponse} object that
- * contains the response the servlet sends
- * to the client
+ * @param resp an {@link HttpServletResponse} object that
+ * contains the response the servlet sends
+ * to the client
*
- * @exception IOException if an input or output error is
- * detected when the servlet handles
- * the GET request
+ * @exception IOException if an input or output error is
+ * detected when the servlet handles
+ * the GET request
*
- * @exception ServletException if the request for the GET
- * could not be handled
- *
+ * @exception ServletException if the request for the GET
+ * could not be handled
*
* @see javax.servlet.ServletResponse#setContentType
- *
*/
-
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
+ throws ServletException, IOException
{
- String protocol = req.getProtocol();
- String msg = lStrings.getString("http.method_get_not_supported");
- if (protocol.endsWith("1.1")) {
- resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
- } else {
- resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
- }
+ String protocol = req.getProtocol();
+ String msg = lStrings.getString("http.method_get_not_supported");
+ if (protocol.endsWith("1.1")) {
+ resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+ } else {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+ }
}
-
-
-
/**
- *
* Returns the time the <code>HttpServletRequest</code>
* object was last modified,
* in milliseconds since midnight January 1, 1970 GMT.
@@ -208,28 +188,21 @@
* This makes browser and proxy caches work more effectively,
* reducing the load on server and network resources.
*
+ * @param req the <code>HttpServletRequest</code>
+ * object that is sent to the servlet
*
- * @param req the <code>HttpServletRequest</code>
- * object that is sent to the servlet
- *
- * @return a <code>long</code> integer specifying
- * the time the <code>HttpServletRequest</code>
- * object was last modified, in milliseconds
- * since midnight, January 1, 1970 GMT, or
- * -1 if the time is not known
- *
+ * @return a <code>long</code> integer specifying
+ * the time the <code>HttpServletRequest</code>
+ * object was last modified, in milliseconds
+ * since midnight, January 1, 1970 GMT, or
+ * -1 if the time is not known
*/
-
protected long getLastModified(HttpServletRequest req) {
- return -1;
+ return -1;
}
-
-
/**
- *
- *
* <p>Receives an HTTP HEAD request from the protected
* <code>service</code> method and handles the
* request.
@@ -250,34 +223,27 @@
* <code>doHead</code> returns an HTTP "Bad Request"
* message.
*
+ * @param req the request object that is passed to the servlet
*
- * @param req the request object that is passed
- * to the servlet
- *
- * @param resp the response object that the servlet
- * uses to return the headers to the clien
+ * @param resp the response object that the servlet
+ * uses to return the headers to the clien
*
- * @exception IOException if an input or output error occurs
+ * @exception IOException if an input or output error occurs
*
- * @exception ServletException if the request for the HEAD
- * could not be handled
+ * @exception ServletException if the request for the HEAD
+ * could not be handled
*/
-
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- NoBodyResponse response = new NoBodyResponse(resp);
-
- doGet(req, response);
- response.setContentLength();
- }
-
+ throws ServletException, IOException {
+ NoBodyResponse response = new NoBodyResponse(resp);
+ doGet(req, response);
+ response.setContentLength();
+ }
/**
- *
* Called by the server (via the <code>service</code> method)
* to allow a servlet to handle a POST request.
*
@@ -316,43 +282,37 @@
* <code>doPost</code> returns an HTTP "Bad Request" message.
*
*
- * @param req an {@link HttpServletRequest} object that
- * contains the request the client has made
- * of the servlet
+ * @param req an {@link HttpServletRequest} object that
+ * contains the request the client has made
+ * of the servlet
*
- * @param resp an {@link HttpServletResponse} object that
- * contains the response the servlet sends
- * to the client
+ * @param resp an {@link HttpServletResponse} object that
+ * contains the response the servlet sends
+ * to the client
*
- * @exception IOException if an input or output error is
- * detected when the servlet handles
- * the request
+ * @exception IOException if an input or output error is
+ * detected when the servlet handles
+ * the request
*
- * @exception ServletException if the request for the POST
- * could not be handled
+ * @exception ServletException if the request for the POST
+ * could not be handled
*
- *
* @see javax.servlet.ServletOutputStream
* @see javax.servlet.ServletResponse#setContentType
- *
- *
*/
-
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- String protocol = req.getProtocol();
- String msg = lStrings.getString("http.method_post_not_supported");
- if (protocol.endsWith("1.1")) {
- resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
- } else {
- resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
- }
+ throws ServletException, IOException {
+
+ String protocol = req.getProtocol();
+ String msg = lStrings.getString("http.method_post_not_supported");
+ if (protocol.endsWith("1.1")) {
+ resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+ } else {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+ }
}
-
-
/**
* Called by the server (via the <code>service</code> method)
* to allow a servlet to handle a PUT request.
@@ -380,41 +340,35 @@
* <p>If the HTTP PUT request is incorrectly formatted,
* <code>doPut</code> returns an HTTP "Bad Request" message.
*
+ * @param req the {@link HttpServletRequest} object that
+ * contains the request the client made of
+ * the servlet
*
- * @param req the {@link HttpServletRequest} object that
- * contains the request the client made of
- * the servlet
+ * @param resp the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client
*
- * @param resp the {@link HttpServletResponse} object that
- * contains the response the servlet returns
- * to the client
+ * @exception IOException if an input or output error occurs
+ * while the servlet is handling the
+ * PUT request
*
- * @exception IOException if an input or output error occurs
- * while the servlet is handling the
- * PUT request
- *
- * @exception ServletException if the request for the PUT
- * cannot be handled
- *
+ * @exception ServletException if the request for the PUT
+ * cannot be handled
*/
-
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- String protocol = req.getProtocol();
- String msg = lStrings.getString("http.method_put_not_supported");
- if (protocol.endsWith("1.1")) {
- resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
- } else {
- resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
- }
+ throws ServletException, IOException {
+
+ String protocol = req.getProtocol();
+ String msg = lStrings.getString("http.method_put_not_supported");
+ if (protocol.endsWith("1.1")) {
+ resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+ } else {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+ }
}
-
-
/**
- *
* Called by the server (via the <code>service</code> method)
* to allow a servlet to handle a DELETE request.
*
@@ -432,37 +386,33 @@
* <code>doDelete</code> returns an HTTP "Bad Request"
* message.
*
+ * @param req the {@link HttpServletRequest} object that
+ * contains the request the client made of
+ * the servlet
*
- * @param req the {@link HttpServletRequest} object that
- * contains the request the client made of
- * the servlet
*
+ * @param resp the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client
*
- * @param resp the {@link HttpServletResponse} object that
- * contains the response the servlet returns
- * to the client
+ * @exception IOException if an input or output error occurs
+ * while the servlet is handling the
+ * DELETE request
*
- *
- * @exception IOException if an input or output error occurs
- * while the servlet is handling the
- * DELETE request
- *
- * @exception ServletException if the request for the
- * DELETE cannot be handled
- *
+ * @exception ServletException if the request for the
+ * DELETE cannot be handled
*/
-
protected void doDelete(HttpServletRequest req,
- HttpServletResponse resp)
- throws ServletException, IOException
- {
- String protocol = req.getProtocol();
- String msg = lStrings.getString("http.method_delete_not_supported");
- if (protocol.endsWith("1.1")) {
- resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
- } else {
- resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
- }
+ HttpServletResponse resp)
+ throws ServletException, IOException {
+
+ String protocol = req.getProtocol();
+ String msg = lStrings.getString("http.method_delete_not_supported");
+ if (protocol.endsWith("1.1")) {
+ resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+ } else {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+ }
}
@@ -474,19 +424,19 @@
Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
Method[] thisMethods = c.getDeclaredMethods();
-
+
if ((parentMethods != null) && (parentMethods.length > 0)) {
Method[] allMethods =
new Method[parentMethods.length + thisMethods.length];
- System.arraycopy(parentMethods, 0, allMethods, 0,
+ System.arraycopy(parentMethods, 0, allMethods, 0,
parentMethods.length);
- System.arraycopy(thisMethods, 0, allMethods, parentMethods.length,
+ System.arraycopy(thisMethods, 0, allMethods, parentMethods.length,
thisMethods.length);
- thisMethods = allMethods;
- }
+ thisMethods = allMethods;
+ }
- return thisMethods;
+ return thisMethods;
}
@@ -506,82 +456,75 @@
* servlet implements new HTTP methods, beyond those
* implemented by HTTP 1.1.
*
- * @param req the {@link HttpServletRequest} object that
- * contains the request the client made of
- * the servlet
+ * @param req the {@link HttpServletRequest} object that
+ * contains the request the client made of
+ * the servlet
*
+ * @param resp the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client
*
- * @param resp the {@link HttpServletResponse} object that
- * contains the response the servlet returns
- * to the client
+ * @exception IOException if an input or output error occurs
+ * while the servlet is handling the
+ * OPTIONS request
*
- *
- * @exception IOException if an input or output error occurs
- * while the servlet is handling the
- * OPTIONS request
- *
- * @exception ServletException if the request for the
- * OPTIONS cannot be handled
- *
+ * @exception ServletException if the request for the
+ * OPTIONS cannot be handled
*/
-
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- Method[] methods = getAllDeclaredMethods(this.getClass());
-
- boolean ALLOW_GET = false;
- boolean ALLOW_HEAD = false;
- boolean ALLOW_POST = false;
- boolean ALLOW_PUT = false;
- boolean ALLOW_DELETE = false;
- boolean ALLOW_TRACE = true;
- boolean ALLOW_OPTIONS = true;
-
- for (int i=0; i<methods.length; i++) {
- Method m = methods[i];
-
- if (m.getName().equals("doGet")) {
- ALLOW_GET = true;
- ALLOW_HEAD = true;
- }
- if (m.getName().equals("doPost"))
- ALLOW_POST = true;
- if (m.getName().equals("doPut"))
- ALLOW_PUT = true;
- if (m.getName().equals("doDelete"))
- ALLOW_DELETE = true;
-
- }
-
- String allow = null;
- if (ALLOW_GET)
- if (allow==null) allow=METHOD_GET;
- if (ALLOW_HEAD)
- if (allow==null) allow=METHOD_HEAD;
- else allow += ", " + METHOD_HEAD;
- if (ALLOW_POST)
- if (allow==null) allow=METHOD_POST;
- else allow += ", " + METHOD_POST;
- if (ALLOW_PUT)
- if (allow==null) allow=METHOD_PUT;
- else allow += ", " + METHOD_PUT;
- if (ALLOW_DELETE)
- if (allow==null) allow=METHOD_DELETE;
- else allow += ", " + METHOD_DELETE;
- if (ALLOW_TRACE)
- if (allow==null) allow=METHOD_TRACE;
- else allow += ", " + METHOD_TRACE;
- if (ALLOW_OPTIONS)
- if (allow==null) allow=METHOD_OPTIONS;
- else allow += ", " + METHOD_OPTIONS;
-
- resp.setHeader("Allow", allow);
+ throws ServletException, IOException {
+
+ Method[] methods = getAllDeclaredMethods(this.getClass());
+
+ boolean ALLOW_GET = false;
+ boolean ALLOW_HEAD = false;
+ boolean ALLOW_POST = false;
+ boolean ALLOW_PUT = false;
+ boolean ALLOW_DELETE = false;
+ boolean ALLOW_TRACE = true;
+ boolean ALLOW_OPTIONS = true;
+
+ for (int i=0; i<methods.length; i++) {
+ Method m = methods[i];
+
+ if (m.getName().equals("doGet")) {
+ ALLOW_GET = true;
+ ALLOW_HEAD = true;
+ }
+ if (m.getName().equals("doPost"))
+ ALLOW_POST = true;
+ if (m.getName().equals("doPut"))
+ ALLOW_PUT = true;
+ if (m.getName().equals("doDelete"))
+ ALLOW_DELETE = true;
+ }
+
+ String allow = null;
+ if (ALLOW_GET)
+ if (allow==null) allow=METHOD_GET;
+ if (ALLOW_HEAD)
+ if (allow==null) allow=METHOD_HEAD;
+ else allow += ", " + METHOD_HEAD;
+ if (ALLOW_POST)
+ if (allow==null) allow=METHOD_POST;
+ else allow += ", " + METHOD_POST;
+ if (ALLOW_PUT)
+ if (allow==null) allow=METHOD_PUT;
+ else allow += ", " + METHOD_PUT;
+ if (ALLOW_DELETE)
+ if (allow==null) allow=METHOD_DELETE;
+ else allow += ", " + METHOD_DELETE;
+ if (ALLOW_TRACE)
+ if (allow==null) allow=METHOD_TRACE;
+ else allow += ", " + METHOD_TRACE;
+ if (ALLOW_OPTIONS)
+ if (allow==null) allow=METHOD_OPTIONS;
+ else allow += ", " + METHOD_OPTIONS;
+
+ resp.setHeader("Allow", allow);
}
-
-
/**
* Called by the server (via the <code>service</code> method)
* to allow a servlet to handle a TRACE request.
@@ -590,63 +533,53 @@
* request to the client, so that they can be used in
* debugging. There's no need to override this method.
*
+ * @param req the {@link HttpServletRequest} object that
+ * contains the request the client made of
+ * the servlet
*
+ * @param resp the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client
*
- * @param req the {@link HttpServletRequest} object that
- * contains the request the client made of
- * the servlet
+ * @exception IOException if an input or output error occurs
+ * while the servlet is handling the
+ * TRACE request
*
- *
- * @param resp the {@link HttpServletResponse} object that
- * contains the response the servlet returns
- * to the client
- *
- *
- * @exception IOException if an input or output error occurs
- * while the servlet is handling the
- * TRACE request
- *
- * @exception ServletException if the request for the
- * TRACE cannot be handled
- *
+ * @exception ServletException if the request for the
+ * TRACE cannot be handled
*/
-
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
+ throws ServletException, IOException
{
-
- int responseLength;
-
- String CRLF = "\r\n";
- String responseString = "TRACE "+ req.getRequestURI()+
- " " + req.getProtocol();
-
- Enumeration reqHeaderEnum = req.getHeaderNames();
-
- while( reqHeaderEnum.hasMoreElements() ) {
- String headerName = (String)reqHeaderEnum.nextElement();
- responseString += CRLF + headerName + ": " +
- req.getHeader(headerName);
- }
-
- responseString += CRLF;
-
- responseLength = responseString.length();
-
- resp.setContentType("message/http");
- resp.setContentLength(responseLength);
- ServletOutputStream out = resp.getOutputStream();
- out.print(responseString);
- out.close();
- return;
- }
+
+ int responseLength;
+
+ String CRLF = "\r\n";
+ String responseString = "TRACE "+ req.getRequestURI()+
+ " " + req.getProtocol();
+
+ Enumeration reqHeaderEnum = req.getHeaderNames();
+
+ while( reqHeaderEnum.hasMoreElements() ) {
+ String headerName = (String)reqHeaderEnum.nextElement();
+ responseString += CRLF + headerName + ": " +
+ req.getHeader(headerName);
+ }
+
+ responseString += CRLF;
+
+ responseLength = responseString.length();
+
+ resp.setContentType("message/http");
+ resp.setContentLength(responseLength);
+ ServletOutputStream out = resp.getOutputStream();
+ out.print(responseString);
+ out.close();
+ return;
+ }
-
-
-
/**
- *
* Receives standard HTTP requests from the public
* <code>service</code> method and dispatches
* them to the <code>do</code><i>XXX</i> methods defined in
@@ -654,92 +587,83 @@
* {@link javax.servlet.Servlet#service} method. There's no
* need to override this method.
*
+ * @param req the {@link HttpServletRequest} object that
+ * contains the request the client made of
+ * the servlet
*
+ * @param resp the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client
*
- * @param req the {@link HttpServletRequest} object that
- * contains the request the client made of
- * the servlet
+ * @exception IOException if an input or output error occurs
+ * while the servlet is handling the
+ * HTTP request
*
- *
- * @param resp the {@link HttpServletResponse} object that
- * contains the response the servlet returns
- * to the client
- *
- *
- * @exception IOException if an input or output error occurs
- * while the servlet is handling the
- * HTTP request
- *
- * @exception ServletException if the HTTP request
- * cannot be handled
+ * @exception ServletException if the HTTP request
+ * cannot be handled
*
- * @see javax.servlet.Servlet#service
- *
+ * @see javax.servlet.Servlet#service
*/
-
protected void service(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- String method = req.getMethod();
+ throws ServletException, IOException {
- if (method.equals(METHOD_GET)) {
- long lastModified = getLastModified(req);
- if (lastModified == -1) {
- // servlet doesn't support if-modified-since, no reason
- // to go through further expensive logic
- doGet(req, resp);
- } else {
- long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
- if (ifModifiedSince < (lastModified / 1000 * 1000)) {
- // If the servlet mod time is later, call doGet()
+ String method = req.getMethod();
+
+ if (method.equals(METHOD_GET)) {
+ long lastModified = getLastModified(req);
+ if (lastModified == -1) {
+ // servlet doesn't support if-modified-since, no reason
+ // to go through further expensive logic
+ doGet(req, resp);
+ } else {
+ long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
+ if (ifModifiedSince < (lastModified / 1000 * 1000)) {
+ // If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
- maybeSetLastModified(resp, lastModified);
- doGet(req, resp);
- } else {
- resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
- }
- }
+ maybeSetLastModified(resp, lastModified);
+ doGet(req, resp);
+ } else {
+ resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ }
+ }
- } else if (method.equals(METHOD_HEAD)) {
- long lastModified = getLastModified(req);
- maybeSetLastModified(resp, lastModified);
- doHead(req, resp);
+ } else if (method.equals(METHOD_HEAD)) {
+ long lastModified = getLastModified(req);
+ maybeSetLastModified(resp, lastModified);
+ doHead(req, resp);
- } else if (method.equals(METHOD_POST)) {
- doPost(req, resp);
-
- } else if (method.equals(METHOD_PUT)) {
- doPut(req, resp);
-
- } else if (method.equals(METHOD_DELETE)) {
- doDelete(req, resp);
-
- } else if (method.equals(METHOD_OPTIONS)) {
- doOptions(req,resp);
-
- } else if (method.equals(METHOD_TRACE)) {
- doTrace(req,resp);
-
- } else {
- //
- // Note that this means NO servlet supports whatever
- // method was requested, anywhere on this server.
- //
+ } else if (method.equals(METHOD_POST)) {
+ doPost(req, resp);
+
+ } else if (method.equals(METHOD_PUT)) {
+ doPut(req, resp);
+
+ } else if (method.equals(METHOD_DELETE)) {
+ doDelete(req, resp);
+
+ } else if (method.equals(METHOD_OPTIONS)) {
+ doOptions(req,resp);
+
+ } else if (method.equals(METHOD_TRACE)) {
+ doTrace(req,resp);
+
+ } else {
+ //
+ // Note that this means NO servlet supports whatever
+ // method was requested, anywhere on this server.
+ //
- String errMsg = lStrings.getString("http.method_not_implemented");
- Object[] errArgs = new Object[1];
- errArgs[0] = method;
- errMsg = MessageFormat.format(errMsg, errArgs);
-
- resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
- }
+ String errMsg = lStrings.getString("http.method_not_implemented");
+ Object[] errArgs = new Object[1];
+ errArgs[0] = method;
+ errMsg = MessageFormat.format(errMsg, errArgs);
+
+ resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
+ }
}
-
-
-
/*
* Sets the Last-Modified entity header field, if it has not
* already been set and if the value is meaningful. Called before
@@ -747,236 +671,103 @@
* written. A subclass might have set this header already, so we
* check.
*/
-
private void maybeSetLastModified(HttpServletResponse resp,
- long lastModified) {
- if (resp.containsHeader(HEADER_LASTMOD))
- return;
- if (lastModified >= 0)
- resp.setDateHeader(HEADER_LASTMOD, lastModified);
+ long lastModified) {
+ if (resp.containsHeader(HEADER_LASTMOD))
+ return;
+ if (lastModified >= 0)
+ resp.setDateHeader(HEADER_LASTMOD, lastModified);
}
-
-
/**
- *
* Dispatches client requests to the protected
* <code>service</code> method. There's no need to
* override this method.
- *
*
- * @param req the {@link HttpServletRequest} object that
- * contains the request the client made of
- * the servlet
+ * @param req the {@link HttpServletRequest} object that
+ * contains the request the client made of
+ * the servlet
*
+ * @param res the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client
*
- * @param res the {@link HttpServletResponse} object that
- * contains the response the servlet returns
- * to the client
+ * @exception IOException if an input or output error occurs
+ * while the servlet is handling the
+ * HTTP request
*
- *
- * @exception IOException if an input or output error occurs
- * while the servlet is handling the
- * HTTP request
- *
- * @exception ServletException if the HTTP request cannot
- * be handled
- *
+ * @exception ServletException if the HTTP request cannot
+ * be handled
*
* @see javax.servlet.Servlet#service
- *
*/
-
public void service(ServletRequest req, ServletResponse res)
- throws ServletException, IOException
- {
- HttpServletRequest request;
- HttpServletResponse response;
-
- try {
- request = (HttpServletRequest) req;
- response = (HttpServletResponse) res;
- } catch (ClassCastException e) {
- throw new ServletException("non-HTTP request or response");
- }
- service(request, response);
+ throws ServletException, IOException {
+
+ HttpServletRequest request;
+ HttpServletResponse response;
+
+ try {
+ request = (HttpServletRequest) req;
+ response = (HttpServletResponse) res;
+ } catch (ClassCastException e) {
+ throw new ServletException("non-HTTP request or response");
+ }
+ service(request, response);
}
}
-
-
/*
- * A response that includes no body, for use in (dumb) "HEAD" support.
+ * A response wrapper for use in (dumb) "HEAD" support.
* This just swallows that body, counting the bytes in order to set
- * the content length appropriately. All other methods delegate directly
- * to the HTTP Servlet Response object used to construct this one.
+ * the content length appropriately. All other methods delegate to the
+ * wrapped HTTP Servlet Response object.
*/
// file private
-class NoBodyResponse implements HttpServletResponse {
- private HttpServletResponse resp;
- private NoBodyOutputStream noBody;
- private PrintWriter writer;
- private boolean didSetContentLength;
+class NoBodyResponse extends HttpServletResponseWrapper {
+ private NoBodyOutputStream noBody;
+ private PrintWriter writer;
+ private boolean didSetContentLength;
// file private
NoBodyResponse(HttpServletResponse r) {
- resp = r;
- noBody = new NoBodyOutputStream();
+ super(r);
+ noBody = new NoBodyOutputStream();
}
// file private
void setContentLength() {
- if (!didSetContentLength)
- resp.setContentLength(noBody.getContentLength());
+ if (!didSetContentLength)
+ super.setContentLength(noBody.getContentLength());
}
// SERVLET RESPONSE interface methods
public void setContentLength(int len) {
- resp.setContentLength(len);
- didSetContentLength = true;
+ super.setContentLength(len);
+ didSetContentLength = true;
}
- public void setCharacterEncoding(String charset)
- { resp.setCharacterEncoding(charset); }
-
- public void setContentType(String type)
- { resp.setContentType(type); }
-
- public String getContentType()
- { return resp.getContentType(); }
-
- public ServletOutputStream getOutputStream() throws IOException
- { return noBody; }
-
- public String getCharacterEncoding()
- { return resp.getCharacterEncoding(); }
-
- public PrintWriter getWriter() throws UnsupportedEncodingException
- {
- if (writer == null) {
- OutputStreamWriter w;
-
- w = new OutputStreamWriter(noBody, getCharacterEncoding());
- writer = new PrintWriter(w);
- }
- return writer;
+ public ServletOutputStream getOutputStream() throws IOException {
+ return noBody;
}
- public void setBufferSize(int size) throws IllegalStateException
- { resp.setBufferSize(size); }
+ public PrintWriter getWriter() throws UnsupportedEncodingException {
- public int getBufferSize()
- { return resp.getBufferSize(); }
+ if (writer == null) {
+ OutputStreamWriter w;
- public void reset() throws IllegalStateException
- { resp.reset(); }
-
- public void resetBuffer() throws IllegalStateException
- { resp.resetBuffer(); }
-
- public boolean isCommitted()
- { return resp.isCommitted(); }
-
- public void flushBuffer() throws IOException
- { resp.flushBuffer(); }
-
- public void setLocale(Locale loc)
- { resp.setLocale(loc); }
-
- public Locale getLocale()
- { return resp.getLocale(); }
-
-
- // HTTP SERVLET RESPONSE interface methods
-
- public void addCookie(Cookie cookie)
- { resp.addCookie(cookie); }
-
- public boolean containsHeader(String name)
- { return resp.containsHeader(name); }
-
- /** @deprecated */
- public void setStatus(int sc, String sm)
- { resp.setStatus(sc, sm); }
-
- public void setStatus(int sc)
- { resp.setStatus(sc); }
-
- public void setHeader(String name, String value)
- { resp.setHeader(name, value); }
-
- public void setIntHeader(String name, int value)
- { resp.setIntHeader(name, value); }
-
- public void setDateHeader(String name, long date)
- { resp.setDateHeader(name, date); }
-
- public void sendError(int sc, String msg) throws IOException
- { resp.sendError(sc, msg); }
-
- public void sendError(int sc) throws IOException
- { resp.sendError(sc); }
-
- public void sendRedirect(String location) throws IOException
- { resp.sendRedirect(location); }
-
- public String encodeURL(String url)
- { return resp.encodeURL(url); }
-
- public String encodeRedirectURL(String url)
- { return resp.encodeRedirectURL(url); }
-
- public void addHeader(String name, String value)
- { resp.addHeader(name, value); }
-
- public void addDateHeader(String name, long value)
- { resp.addDateHeader(name, value); }
-
- public void addIntHeader(String name, int value)
- { resp.addIntHeader(name, value); }
-
-
-
-
- /**
- * @deprecated As of Version 2.1, replaced by
- * {@link HttpServletResponse#encodeURL}.
- *
- */
-
-
- public String encodeUrl(String url)
- { return this.encodeURL(url); }
-
-
-
-
-
-
-
-
- /**
- * @deprecated As of Version 2.1, replaced by
- * {@link HttpServletResponse#encodeRedirectURL}.
- *
- */
-
-
- public String encodeRedirectUrl(String url)
- { return this.encodeRedirectURL(url); }
-
+ w = new OutputStreamWriter(noBody, getCharacterEncoding());
+ writer = new PrintWriter(w);
+ }
+ return writer;
+ }
}
-
-
-
-
-
/*
* Servlet output stream that gobbles up all its data.
*/
@@ -985,35 +776,35 @@
class NoBodyOutputStream extends ServletOutputStream {
private static final String LSTRING_FILE =
- "javax.servlet.http.LocalStrings";
+ "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings =
- ResourceBundle.getBundle(LSTRING_FILE);
+ ResourceBundle.getBundle(LSTRING_FILE);
- private int contentLength = 0;
+ private int contentLength = 0;
// file private
NoBodyOutputStream() {}
// file private
int getContentLength() {
- return contentLength;
+ return contentLength;
}
public void write(int b) {
- contentLength++;
+ contentLength++;
}
public void write(byte buf[], int offset, int len)
- throws IOException
+ throws IOException
{
- if (len >= 0) {
- contentLength += len;
- } else {
- // XXX
- // isn't this really an IllegalArgumentException?
-
- String msg = lStrings.getString("err.io.negativelength");
- throw new IOException("negative length");
- }
+ if (len >= 0) {
+ contentLength += len;
+ } else {
+ // XXX
+ // isn't this really an IllegalArgumentException?
+
+ String msg = lStrings.getString("err.io.negativelength");
+ throw new IOException(msg);
+ }
}
}
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2008-03-28 00:52:58 UTC (rev 561)
+++ trunk/webapps/docs/changelog.xml 2008-03-28 00:54:21 UTC (rev 562)
@@ -24,6 +24,10 @@
<fix>
Remove tomcat-native.tar.gz from the distribution (JBoss Native should be used instead). (remm)
</fix>
+ <fix>
+ <bug>44562</bug>: HEAD requests cannot use includes. Patch provided by
+ David Jencks. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Catalina">
16 years, 11 months
JBossWeb SVN: r561 - in trunk/java/org/apache/tomcat/util: http and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-27 20:52:58 -0400 (Thu, 27 Mar 2008)
New Revision: 561
Modified:
trunk/java/org/apache/tomcat/util/collections/MultiMap.java
trunk/java/org/apache/tomcat/util/http/Parameters.java
Log:
- Fix problems in the MultiMap algorithm.
Modified: trunk/java/org/apache/tomcat/util/collections/MultiMap.java
===================================================================
--- trunk/java/org/apache/tomcat/util/collections/MultiMap.java 2008-03-27 21:13:09 UTC (rev 560)
+++ trunk/java/org/apache/tomcat/util/collections/MultiMap.java 2008-03-28 00:52:58 UTC (rev 561)
@@ -195,21 +195,21 @@
}
public int findNext( int startPos ) {
- int next= fields[startPos].nextPos;
- if( next != MultiMap.NEED_NEXT ) {
- return next;
- }
+ int next= fields[startPos].nextPos;
+ if( next != MultiMap.NEED_NEXT ) {
+ return next;
+ }
- // next==NEED_NEXT, we never searched for this header
- MessageBytes name=fields[startPos].name;
- for (int i = startPos; i < count; i++) {
- if (fields[i].name.equals(name)) {
- // cache the search result
- fields[startPos].nextPos=i;
- return i;
- }
- }
- fields[startPos].nextPos= MultiMap.LAST;
+ // next==NEED_NEXT, we never searched for this header
+ MessageBytes name=fields[startPos].name;
+ for (int i = (startPos + 1); i < count; i++) {
+ if (fields[i].name.equals(name)) {
+ // cache the search result
+ fields[startPos].nextPos=i;
+ return i;
+ }
+ }
+ fields[startPos].nextPos= MultiMap.LAST;
return -1;
}
@@ -219,8 +219,8 @@
// -------------------- Internal representation --------------------
final class Field {
- MessageBytes name;
- MessageBytes value;
+ MessageBytes name = MessageBytes.newInstance();
+ MessageBytes value = MessageBytes.newInstance();
// Extra info for speed
Modified: trunk/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- trunk/java/org/apache/tomcat/util/http/Parameters.java 2008-03-27 21:13:09 UTC (rev 560)
+++ trunk/java/org/apache/tomcat/util/http/Parameters.java 2008-03-28 00:52:58 UTC (rev 561)
@@ -107,9 +107,9 @@
public String[] getParameterValues(String name) {
handleQueryParameters();
int pos = findFirst(name);
- if (pos > 0) {
+ if (pos >= 0) {
ArrayList<String> result = new ArrayList<String>();
- while (pos > 0) {
+ while (pos >= 0) {
result.add(getValue(pos).toString());
pos = findNext(pos);
}
@@ -128,7 +128,7 @@
public String getParameter(String name) {
handleQueryParameters();
int pos = findFirst(name);
- if (pos > 0) {
+ if (pos >= 0) {
return getValue(pos).toString();
} else {
return null;
16 years, 11 months
JBossWeb SVN: r560 - trunk/test/java/org/apache/jboss/web/comet.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-27 17:13:09 -0400 (Thu, 27 Mar 2008)
New Revision: 560
Added:
trunk/test/java/org/apache/jboss/web/comet/CometServletTest2.java
Log:
- Reader and writer seem to work fine with Comet [but using a really large char[] could cause problems
with the current code; I don't know if it's really legitimate with Comet as a huge amount of data has
to be buffered, which is very costly]. Tested with chunked input.
Added: trunk/test/java/org/apache/jboss/web/comet/CometServletTest2.java
===================================================================
--- trunk/test/java/org/apache/jboss/web/comet/CometServletTest2.java (rev 0)
+++ trunk/test/java/org/apache/jboss/web/comet/CometServletTest2.java 2008-03-27 21:13:09 UTC (rev 560)
@@ -0,0 +1,91 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2008, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+
+package org.jboss.web.comet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+public class CometServletTest2 extends HttpServlet implements CometProcessor {
+
+ int count = 0;
+
+ public void event(CometEvent event) throws IOException, ServletException {
+ System.out.println("[" + event.getHttpServletRequest().getSession(true).getId() + "] " + event.getType());
+ switch (event.getType()) {
+ case BEGIN:
+ //event.suspend();
+ break;
+ case END:
+ break;
+ case ERROR:
+ event.close();
+ break;
+ case EVENT:
+ Writer writer = event.getHttpServletResponse().getWriter();
+ // Using while (true): Not checking if the connection is available to writing immediately
+ // will cause the write to be performed in blocking mode.
+ // boolean b = true;
+ // while (b) {
+ while (event.ready()) {
+ if (count % 100 == 0) {
+ writer.write((count++) + " \r\n");
+ } else {
+ writer.write((count++) + " ");
+ }
+ }
+ //if (event.ready())
+ // os.flush();
+ break;
+ case READ:
+ BufferedReader reader = event.getHttpServletRequest().getReader();
+ // Using while (true): Not checking if input is available will trigger a blocking
+ // read. No other event should be triggered (the current READ event will be in progress
+ // until the read timeouts, which will trigger an ERROR event due to an IOException).
+ // while (true) {
+ while (reader.ready()) {
+ int c = reader.read();
+ if (c > 0) {
+ System.out.print((char) c);
+ } else {
+ System.out.print(c);
+ break;
+ }
+ }
+ System.out.println();
+ break;
+ case TIMEOUT:
+ // This will cause a generic event to be sent to the servlet every time the connection is idle for
+ // a while.
+ event.resume();
+ break;
+ case WRITE:
+ break;
+ }
+ }
+
+}
16 years, 11 months
JBossWeb SVN: r559 - in trunk: java/org/apache/tomcat/util/http and 1 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-27 13:49:20 -0400 (Thu, 27 Mar 2008)
New Revision: 559
Added:
trunk/java/org/apache/tomcat/util/http/Parameters2.java
Modified:
trunk/java/org/apache/tomcat/util/collections/MultiMap.java
trunk/java/org/apache/tomcat/util/http/Parameters.java
trunk/webapps/docs/changelog.xml
Log:
- Move to MultiMap backend (I never reviewed this class and did not realize it was still based on a Hashtable).
- The old class stays there just in case for now as Parameters2.
Modified: trunk/java/org/apache/tomcat/util/collections/MultiMap.java
===================================================================
--- trunk/java/org/apache/tomcat/util/collections/MultiMap.java 2008-03-27 14:22:16 UTC (rev 558)
+++ trunk/java/org/apache/tomcat/util/collections/MultiMap.java 2008-03-27 17:49:20 UTC (rev 559)
@@ -17,6 +17,8 @@
package org.apache.tomcat.util.collections;
+import java.util.Enumeration;
+
import org.apache.tomcat.util.buf.MessageBytes;
// Originally MimeHeaders
@@ -74,6 +76,13 @@
}
/**
+ * Return names enumeration.
+ */
+ public Enumeration names() {
+ return new MultiMapNamesEnumeration(this, true, true);
+ }
+
+ /**
* Returns the Nth header name
* This may be used to iterate through all header fields.
*
Modified: trunk/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- trunk/java/org/apache/tomcat/util/http/Parameters.java 2008-03-27 14:22:16 UTC (rev 558)
+++ trunk/java/org/apache/tomcat/util/http/Parameters.java 2008-03-27 17:49:20 UTC (rev 559)
@@ -18,8 +18,8 @@
package org.apache.tomcat.util.http;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Enumeration;
-import java.util.Hashtable;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.CharChunk;
@@ -30,215 +30,106 @@
/**
*
* @author Costin Manolache
+ * @author Remy Maucherat
*/
public final class Parameters extends MultiMap {
-
- private static org.jboss.logging.Logger log=
- org.jboss.logging.Logger.getLogger(Parameters.class );
-
- // Transition: we'll use the same Hashtable( String->String[] )
- // for the beginning. When we are sure all accesses happen through
- // this class - we can switch to MultiMap
- private Hashtable paramHashStringArray=new Hashtable();
- private boolean didQueryParameters=false;
- private boolean didMerge=false;
-
- MessageBytes queryMB;
- MimeHeaders headers;
+ protected static org.jboss.logging.Logger log =
+ org.jboss.logging.Logger.getLogger(Parameters.class);
- UDecoder urlDec;
- MessageBytes decodedQuery=MessageBytes.newInstance();
+ public static final int INITIAL_SIZE = 8;
+ protected static final String[] ARRAY_TYPE = new String[0];
+
+ protected boolean didQueryParameters = false;
+ protected boolean didMerge = false;
- public static final int INITIAL_SIZE=4;
+ protected MessageBytes queryMB;
- // Garbage-less parameter merging.
- // In a sub-request with parameters, the new parameters
- // will be stored in child. When a getParameter happens,
- // the 2 are merged togheter. The child will be altered
- // to contain the merged values - the parent is allways the
- // original request.
- private Parameters child=null;
- private Parameters parent=null;
- private Parameters currentChild=null;
+ protected UDecoder urlDec;
+ protected MessageBytes decodedQuery = MessageBytes.newInstance();
- String encoding=null;
- String queryStringEncoding=null;
+ protected String encoding = null;
+ protected String queryStringEncoding = null;
/**
*
*/
public Parameters() {
- super( INITIAL_SIZE );
+ super(INITIAL_SIZE);
}
- public void setQuery( MessageBytes queryMB ) {
- this.queryMB=queryMB;
+ public void setQuery(MessageBytes queryMB) {
+ this.queryMB = queryMB;
}
- public void setHeaders( MimeHeaders headers ) {
- this.headers=headers;
+ public void setHeaders(MimeHeaders headers) {
+ // Not used anymore at the moment
}
- public void setEncoding( String s ) {
- encoding=s;
- if(debug>0) log( "Set encoding to " + s );
+ public void setEncoding(String s) {
+ encoding = s;
}
- public void setQueryStringEncoding( String s ) {
- queryStringEncoding=s;
- if(debug>0) log( "Set query string encoding to " + s );
+ public void setURLDecoder(UDecoder u) {
+ urlDec = u;
}
+ public void setQueryStringEncoding(String s) {
+ queryStringEncoding = s;
+ }
+
public void recycle() {
super.recycle();
- paramHashStringArray.clear();
- didQueryParameters=false;
- currentChild=null;
- didMerge=false;
- encoding=null;
+ didQueryParameters = false;
+ didMerge = false;
+ encoding = null;
decodedQuery.recycle();
}
- // -------------------- Sub-request support --------------------
-
- public Parameters getCurrentSet() {
- if( currentChild==null )
- return this;
- return currentChild;
- }
-
- /** Create ( or reuse ) a child that will be used during a sub-request.
- All future changes ( setting query string, adding parameters )
- will affect the child ( the parent request is never changed ).
- Both setters and getters will return the data from the deepest
- child, merged with data from parents.
- */
- public void push() {
- // We maintain a linked list, that will grow to the size of the
- // longest include chain.
- // The list has 2 points of interest:
- // - request.parameters() is the original request and head,
- // - request.parameters().currentChild() is the current set.
- // The ->child and parent<- links are preserved ( currentChild is not
- // the last in the list )
-
- // create a new element in the linked list
- // note that we reuse the child, if any - pop will not
- // set child to null !
- if( currentChild==null ) {
- currentChild=new Parameters();
- currentChild.setURLDecoder( urlDec );
- currentChild.parent=this;
- return;
- }
- if( currentChild.child==null ) {
- currentChild.child=new Parameters();
- currentChild.setURLDecoder( urlDec );
- currentChild.child.parent=currentChild;
- } // it is not null if this object already had a child
- // i.e. a deeper include() ( we keep it )
-
- // the head will be the new element.
- currentChild=currentChild.child;
- currentChild.setEncoding( encoding );
- }
-
- /** Discard the last child. This happens when we return from a
- sub-request and the parameters are locally modified.
- */
- public void pop() {
- if( currentChild==null ) {
- throw new RuntimeException( "Attempt to pop without a push" );
- }
- currentChild.recycle();
- currentChild=currentChild.parent;
- // don't remove the top.
- }
-
// -------------------- Data access --------------------
// Access to the current name/values, no side effect ( processing ).
// You must explicitely call handleQueryParameters and the post methods.
// This is the original data representation ( hash of String->String[])
- public void addParameterValues( String key, String[] newValues) {
- if ( key==null ) return;
- String values[];
- if (paramHashStringArray.containsKey(key)) {
- String oldValues[] = (String[])paramHashStringArray.get(key);
- values = new String[oldValues.length + newValues.length];
- for (int i = 0; i < oldValues.length; i++) {
- values[i] = oldValues[i];
- }
- for (int i = 0; i < newValues.length; i++) {
- values[i+ oldValues.length] = newValues[i];
- }
- } else {
- values = newValues;
+ public void addParameterValues(String name, String[] values) {
+ if (name == null || values == null) {
+ return;
}
-
- paramHashStringArray.put(key, values);
+ for (int i = 0; i < values.length; i++) {
+ String value = values[i];
+ int pos = addField();
+ getName(pos).setString(name);
+ getValue(pos).setString(value);
+ }
}
public String[] getParameterValues(String name) {
handleQueryParameters();
- // sub-request
- if( currentChild!=null ) {
- currentChild.merge();
- return (String[])currentChild.paramHashStringArray.get(name);
+ int pos = findFirst(name);
+ if (pos > 0) {
+ ArrayList<String> result = new ArrayList<String>();
+ while (pos > 0) {
+ result.add(getValue(pos).toString());
+ pos = findNext(pos);
+ }
+ return result.toArray(ARRAY_TYPE);
+ } else {
+ return null;
}
-
- // no "facade"
- String values[]=(String[])paramHashStringArray.get(name);
- return values;
}
public Enumeration getParameterNames() {
handleQueryParameters();
- // Slow - the original code
- if( currentChild!=null ) {
- currentChild.merge();
- return currentChild.paramHashStringArray.keys();
- }
-
- // merge in child
- return paramHashStringArray.keys();
+ return names();
}
- /** Combine the parameters from parent with our local ones
- */
- private void merge() {
- // recursive
- if( debug > 0 ) {
- log("Before merging " + this + " " + parent + " " + didMerge );
- log( paramsAsString());
- }
- // Local parameters first - they take precedence as in spec.
- handleQueryParameters();
-
- // we already merged with the parent
- if( didMerge ) return;
-
- // we are the top level
- if( parent==null ) return;
-
- // Add the parent props to the child ( lower precedence )
- parent.merge();
- Hashtable parentProps=parent.paramHashStringArray;
- merge2( paramHashStringArray , parentProps);
- didMerge=true;
- if(debug > 0 )
- log("After " + paramsAsString());
- }
-
-
// Shortcut.
- public String getParameter(String name ) {
- String[] values = getParameterValues(name);
- if (values != null) {
- if( values.length==0 ) return "";
- return values[0];
+ public String getParameter(String name) {
+ handleQueryParameters();
+ int pos = findFirst(name);
+ if (pos > 0) {
+ return getValue(pos).toString();
} else {
return null;
}
@@ -266,80 +157,24 @@
processParameters( decodedQuery, queryStringEncoding );
}
- // --------------------
-
- /** Combine 2 hashtables into a new one.
- * ( two will be added to one ).
- * Used to combine child parameters ( RequestDispatcher's query )
- * with parent parameters ( original query or parent dispatcher )
- */
- private static void merge2(Hashtable one, Hashtable two ) {
- Enumeration e = two.keys();
-
- while (e.hasMoreElements()) {
- String name = (String) e.nextElement();
- String[] oneValue = (String[]) one.get(name);
- String[] twoValue = (String[]) two.get(name);
- String[] combinedValue;
-
- if (twoValue == null) {
- continue;
- } else {
- if( oneValue==null ) {
- combinedValue = new String[twoValue.length];
- System.arraycopy(twoValue, 0, combinedValue,
- 0, twoValue.length);
- } else {
- combinedValue = new String[oneValue.length +
- twoValue.length];
- System.arraycopy(oneValue, 0, combinedValue, 0,
- oneValue.length);
- System.arraycopy(twoValue, 0, combinedValue,
- oneValue.length, twoValue.length);
- }
- one.put(name, combinedValue);
- }
+ protected void addParam( String name, String value ) {
+ if (name == null) {
+ return;
}
+ int pos = addField();
+ getName(pos).setString(name);
+ getValue(pos).setString(value);
}
- // incredibly inefficient data representation for parameters,
- // until we test the new one
- private void addParam( String key, String value ) {
- if( key==null ) return;
- String values[];
- if (paramHashStringArray.containsKey(key)) {
- String oldValues[] = (String[])paramHashStringArray.
- get(key);
- values = new String[oldValues.length + 1];
- for (int i = 0; i < oldValues.length; i++) {
- values[i] = oldValues[i];
- }
- values[oldValues.length] = value;
- } else {
- values = new String[1];
- values[0] = value;
- }
-
-
- paramHashStringArray.put(key, values);
- }
-
- public void setURLDecoder( UDecoder u ) {
- urlDec=u;
- }
-
// -------------------- Parameter parsing --------------------
- // This code is not used right now - it's the optimized version
- // of the above.
-
// we are called from a single thread - we can do it the hard way
// if needed
- ByteChunk tmpName=new ByteChunk();
- ByteChunk tmpValue=new ByteChunk();
- CharChunk tmpNameC=new CharChunk(1024);
- CharChunk tmpValueC=new CharChunk(1024);
-
+ protected ByteChunk tmpName = new ByteChunk();
+ protected ByteChunk tmpValue = new ByteChunk();
+ protected CharChunk tmpNameC = new CharChunk(32);
+ protected CharChunk tmpValueC = new CharChunk(128);
+
public void processParameters( byte bytes[], int start, int len ) {
processParameters(bytes, start, len, encoding);
}
@@ -515,11 +350,11 @@
*/
public String paramsAsString() {
StringBuffer sb=new StringBuffer();
- Enumeration en= paramHashStringArray.keys();
+ Enumeration en= names();
while( en.hasMoreElements() ) {
String k=(String)en.nextElement();
sb.append( k ).append("=");
- String v[]=(String[])paramHashStringArray.get( k );
+ String v[]=(String[])getParameterValues(k);
for( int i=0; i<v.length; i++ )
sb.append( v[i] ).append(",");
sb.append("\n");
Added: trunk/java/org/apache/tomcat/util/http/Parameters2.java
===================================================================
--- trunk/java/org/apache/tomcat/util/http/Parameters2.java (rev 0)
+++ trunk/java/org/apache/tomcat/util/http/Parameters2.java 2008-03-27 17:49:20 UTC (rev 559)
@@ -0,0 +1,611 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.util.http;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.buf.UDecoder;
+import org.apache.tomcat.util.collections.MultiMap;
+
+/**
+ *
+ * @author Costin Manolache
+ * @deprecated
+ */
+public final class Parameters2 extends MultiMap {
+
+
+ private static org.jboss.logging.Logger log=
+ org.jboss.logging.Logger.getLogger(Parameters2.class );
+
+ // Transition: we'll use the same Hashtable( String->String[] )
+ // for the beginning. When we are sure all accesses happen through
+ // this class - we can switch to MultiMap
+ private Hashtable paramHashStringArray=new Hashtable();
+ private boolean didQueryParameters=false;
+ private boolean didMerge=false;
+
+ MessageBytes queryMB;
+ MimeHeaders headers;
+
+ UDecoder urlDec;
+ MessageBytes decodedQuery=MessageBytes.newInstance();
+
+ public static final int INITIAL_SIZE=4;
+
+ // Garbage-less parameter merging.
+ // In a sub-request with parameters, the new parameters
+ // will be stored in child. When a getParameter happens,
+ // the 2 are merged togheter. The child will be altered
+ // to contain the merged values - the parent is allways the
+ // original request.
+ private Parameters2 child=null;
+ private Parameters2 parent=null;
+ private Parameters2 currentChild=null;
+
+ String encoding=null;
+ String queryStringEncoding=null;
+
+ /**
+ *
+ */
+ public Parameters2() {
+ super( INITIAL_SIZE );
+ }
+
+ public void setQuery( MessageBytes queryMB ) {
+ this.queryMB=queryMB;
+ }
+
+ public void setHeaders( MimeHeaders headers ) {
+ this.headers=headers;
+ }
+
+ public void setEncoding( String s ) {
+ encoding=s;
+ if(debug>0) log( "Set encoding to " + s );
+ }
+
+ public void setQueryStringEncoding( String s ) {
+ queryStringEncoding=s;
+ if(debug>0) log( "Set query string encoding to " + s );
+ }
+
+ public void recycle() {
+ super.recycle();
+ paramHashStringArray.clear();
+ didQueryParameters=false;
+ currentChild=null;
+ didMerge=false;
+ encoding=null;
+ decodedQuery.recycle();
+ }
+
+ // -------------------- Sub-request support --------------------
+
+ public Parameters2 getCurrentSet() {
+ if( currentChild==null )
+ return this;
+ return currentChild;
+ }
+
+ /** Create ( or reuse ) a child that will be used during a sub-request.
+ All future changes ( setting query string, adding parameters )
+ will affect the child ( the parent request is never changed ).
+ Both setters and getters will return the data from the deepest
+ child, merged with data from parents.
+ */
+ public void push() {
+ // We maintain a linked list, that will grow to the size of the
+ // longest include chain.
+ // The list has 2 points of interest:
+ // - request.parameters() is the original request and head,
+ // - request.parameters().currentChild() is the current set.
+ // The ->child and parent<- links are preserved ( currentChild is not
+ // the last in the list )
+
+ // create a new element in the linked list
+ // note that we reuse the child, if any - pop will not
+ // set child to null !
+ if( currentChild==null ) {
+ currentChild=new Parameters2();
+ currentChild.setURLDecoder( urlDec );
+ currentChild.parent=this;
+ return;
+ }
+ if( currentChild.child==null ) {
+ currentChild.child=new Parameters2();
+ currentChild.setURLDecoder( urlDec );
+ currentChild.child.parent=currentChild;
+ } // it is not null if this object already had a child
+ // i.e. a deeper include() ( we keep it )
+
+ // the head will be the new element.
+ currentChild=currentChild.child;
+ currentChild.setEncoding( encoding );
+ }
+
+ /** Discard the last child. This happens when we return from a
+ sub-request and the parameters are locally modified.
+ */
+ public void pop() {
+ if( currentChild==null ) {
+ throw new RuntimeException( "Attempt to pop without a push" );
+ }
+ currentChild.recycle();
+ currentChild=currentChild.parent;
+ // don't remove the top.
+ }
+
+ // -------------------- Data access --------------------
+ // Access to the current name/values, no side effect ( processing ).
+ // You must explicitely call handleQueryParameters and the post methods.
+
+ // This is the original data representation ( hash of String->String[])
+
+ public void addParameterValues( String key, String[] newValues) {
+ if ( key==null ) return;
+ String values[];
+ if (paramHashStringArray.containsKey(key)) {
+ String oldValues[] = (String[])paramHashStringArray.get(key);
+ values = new String[oldValues.length + newValues.length];
+ for (int i = 0; i < oldValues.length; i++) {
+ values[i] = oldValues[i];
+ }
+ for (int i = 0; i < newValues.length; i++) {
+ values[i+ oldValues.length] = newValues[i];
+ }
+ } else {
+ values = newValues;
+ }
+
+ paramHashStringArray.put(key, values);
+ }
+
+ public String[] getParameterValues(String name) {
+ handleQueryParameters();
+ // sub-request
+ if( currentChild!=null ) {
+ currentChild.merge();
+ return (String[])currentChild.paramHashStringArray.get(name);
+ }
+
+ // no "facade"
+ String values[]=(String[])paramHashStringArray.get(name);
+ return values;
+ }
+
+ public Enumeration getParameterNames() {
+ handleQueryParameters();
+ // Slow - the original code
+ if( currentChild!=null ) {
+ currentChild.merge();
+ return currentChild.paramHashStringArray.keys();
+ }
+
+ // merge in child
+ return paramHashStringArray.keys();
+ }
+
+ /** Combine the parameters from parent with our local ones
+ */
+ private void merge() {
+ // recursive
+ if( debug > 0 ) {
+ log("Before merging " + this + " " + parent + " " + didMerge );
+ log( paramsAsString());
+ }
+ // Local parameters first - they take precedence as in spec.
+ handleQueryParameters();
+
+ // we already merged with the parent
+ if( didMerge ) return;
+
+ // we are the top level
+ if( parent==null ) return;
+
+ // Add the parent props to the child ( lower precedence )
+ parent.merge();
+ Hashtable parentProps=parent.paramHashStringArray;
+ merge2( paramHashStringArray , parentProps);
+ didMerge=true;
+ if(debug > 0 )
+ log("After " + paramsAsString());
+ }
+
+
+ // Shortcut.
+ public String getParameter(String name ) {
+ String[] values = getParameterValues(name);
+ if (values != null) {
+ if( values.length==0 ) return "";
+ return values[0];
+ } else {
+ return null;
+ }
+ }
+ // -------------------- Processing --------------------
+ /** Process the query string into parameters
+ */
+ public void handleQueryParameters() {
+ if( didQueryParameters ) return;
+
+ didQueryParameters=true;
+
+ if( queryMB==null || queryMB.isNull() )
+ return;
+
+ if( debug > 0 )
+ log( "Decoding query " + decodedQuery + " " + queryStringEncoding);
+
+ try {
+ decodedQuery.duplicate( queryMB );
+ } catch (IOException e) {
+ // Can't happen, as decodedQuery can't overflow
+ e.printStackTrace();
+ }
+ processParameters( decodedQuery, queryStringEncoding );
+ }
+
+ // --------------------
+
+ /** Combine 2 hashtables into a new one.
+ * ( two will be added to one ).
+ * Used to combine child parameters ( RequestDispatcher's query )
+ * with parent parameters ( original query or parent dispatcher )
+ */
+ private static void merge2(Hashtable one, Hashtable two ) {
+ Enumeration e = two.keys();
+
+ while (e.hasMoreElements()) {
+ String name = (String) e.nextElement();
+ String[] oneValue = (String[]) one.get(name);
+ String[] twoValue = (String[]) two.get(name);
+ String[] combinedValue;
+
+ if (twoValue == null) {
+ continue;
+ } else {
+ if( oneValue==null ) {
+ combinedValue = new String[twoValue.length];
+ System.arraycopy(twoValue, 0, combinedValue,
+ 0, twoValue.length);
+ } else {
+ combinedValue = new String[oneValue.length +
+ twoValue.length];
+ System.arraycopy(oneValue, 0, combinedValue, 0,
+ oneValue.length);
+ System.arraycopy(twoValue, 0, combinedValue,
+ oneValue.length, twoValue.length);
+ }
+ one.put(name, combinedValue);
+ }
+ }
+ }
+
+ // incredibly inefficient data representation for parameters,
+ // until we test the new one
+ private void addParam( String key, String value ) {
+ if( key==null ) return;
+ String values[];
+ if (paramHashStringArray.containsKey(key)) {
+ String oldValues[] = (String[])paramHashStringArray.
+ get(key);
+ values = new String[oldValues.length + 1];
+ for (int i = 0; i < oldValues.length; i++) {
+ values[i] = oldValues[i];
+ }
+ values[oldValues.length] = value;
+ } else {
+ values = new String[1];
+ values[0] = value;
+ }
+
+
+ paramHashStringArray.put(key, values);
+ }
+
+ public void setURLDecoder( UDecoder u ) {
+ urlDec=u;
+ }
+
+ // -------------------- Parameter parsing --------------------
+
+ // This code is not used right now - it's the optimized version
+ // of the above.
+
+ // we are called from a single thread - we can do it the hard way
+ // if needed
+ ByteChunk tmpName=new ByteChunk();
+ ByteChunk tmpValue=new ByteChunk();
+ CharChunk tmpNameC=new CharChunk(1024);
+ CharChunk tmpValueC=new CharChunk(1024);
+
+ public void processParameters( byte bytes[], int start, int len ) {
+ processParameters(bytes, start, len, encoding);
+ }
+
+ public void processParameters( byte bytes[], int start, int len,
+ String enc ) {
+ int end=start+len;
+ int pos=start;
+
+ if( debug>0 )
+ log( "Bytes: " + new String( bytes, start, len ));
+
+ do {
+ boolean noEq=false;
+ int valStart=-1;
+ int valEnd=-1;
+
+ int nameStart=pos;
+ int nameEnd=ByteChunk.indexOf(bytes, nameStart, end, '=' );
+ // Workaround for a&b&c encoding
+ int nameEnd2=ByteChunk.indexOf(bytes, nameStart, end, '&' );
+ if( (nameEnd2!=-1 ) &&
+ ( nameEnd==-1 || nameEnd > nameEnd2) ) {
+ nameEnd=nameEnd2;
+ noEq=true;
+ valStart=nameEnd;
+ valEnd=nameEnd;
+ if( debug>0) log("no equal " + nameStart + " " + nameEnd + " " + new String(bytes, nameStart, nameEnd-nameStart) );
+ }
+ if( nameEnd== -1 )
+ nameEnd=end;
+
+ if( ! noEq ) {
+ valStart= (nameEnd < end) ? nameEnd+1 : end;
+ valEnd=ByteChunk.indexOf(bytes, valStart, end, '&');
+ if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+ }
+
+ pos=valEnd+1;
+
+ if( nameEnd<=nameStart ) {
+ log.warn("Parameters: Invalid chunk ignored.");
+ continue;
+ // invalid chunk - it's better to ignore
+ }
+ tmpName.setBytes( bytes, nameStart, nameEnd-nameStart );
+ tmpValue.setBytes( bytes, valStart, valEnd-valStart );
+
+ try {
+ addParam( urlDecode(tmpName, enc), urlDecode(tmpValue, enc) );
+ } catch (IOException e) {
+ // Exception during character decoding: skip parameter
+ log.warn("Parameters: Character decoding failed. " +
+ "Parameter skipped.", e);
+ }
+
+ tmpName.recycle();
+ tmpValue.recycle();
+
+ } while( pos<end );
+ }
+
+ private String urlDecode(ByteChunk bc, String enc)
+ throws IOException {
+ if( urlDec==null ) {
+ urlDec=new UDecoder();
+ }
+ urlDec.convert(bc);
+ String result = null;
+ if (enc != null) {
+ bc.setEncoding(enc);
+ result = bc.toString();
+ } else {
+ CharChunk cc = tmpNameC;
+ int length = bc.getLength();
+ cc.allocate(length, -1);
+ // Default encoding: fast conversion
+ byte[] bbuf = bc.getBuffer();
+ char[] cbuf = cc.getBuffer();
+ int start = bc.getStart();
+ for (int i = 0; i < length; i++) {
+ cbuf[i] = (char) (bbuf[i + start] & 0xff);
+ }
+ cc.setChars(cbuf, 0, length);
+ result = cc.toString();
+ cc.recycle();
+ }
+ return result;
+ }
+
+ public void processParameters( char chars[], int start, int len ) {
+ int end=start+len;
+ int pos=start;
+
+ if( debug>0 )
+ log( "Chars: " + new String( chars, start, len ));
+ do {
+ boolean noEq=false;
+ int nameStart=pos;
+ int valStart=-1;
+ int valEnd=-1;
+
+ int nameEnd=CharChunk.indexOf(chars, nameStart, end, '=' );
+ int nameEnd2=CharChunk.indexOf(chars, nameStart, end, '&' );
+ if( (nameEnd2!=-1 ) &&
+ ( nameEnd==-1 || nameEnd > nameEnd2) ) {
+ nameEnd=nameEnd2;
+ noEq=true;
+ valStart=nameEnd;
+ valEnd=nameEnd;
+ if( debug>0) log("no equal " + nameStart + " " + nameEnd + " " + new String(chars, nameStart, nameEnd-nameStart) );
+ }
+ if( nameEnd== -1 ) nameEnd=end;
+
+ if( ! noEq ) {
+ valStart= (nameEnd < end) ? nameEnd+1 : end;
+ valEnd=CharChunk.indexOf(chars, valStart, end, '&');
+ if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+ }
+
+ pos=valEnd+1;
+
+ if( nameEnd<=nameStart ) {
+ continue;
+ // invalid chunk - no name, it's better to ignore
+ // XXX log it ?
+ }
+
+ try {
+ tmpNameC.append( chars, nameStart, nameEnd-nameStart );
+ tmpValueC.append( chars, valStart, valEnd-valStart );
+
+ if( debug > 0 )
+ log( tmpNameC + "= " + tmpValueC);
+
+ if( urlDec==null ) {
+ urlDec=new UDecoder();
+ }
+
+ urlDec.convert( tmpNameC );
+ urlDec.convert( tmpValueC );
+
+ if( debug > 0 )
+ log( tmpNameC + "= " + tmpValueC);
+
+ addParam( tmpNameC.toString(), tmpValueC.toString() );
+ } catch( IOException ex ) {
+ ex.printStackTrace();
+ }
+
+ tmpNameC.recycle();
+ tmpValueC.recycle();
+
+ } while( pos<end );
+ }
+
+ public void processParameters( MessageBytes data ) {
+ processParameters(data, encoding);
+ }
+
+ public void processParameters( MessageBytes data, String encoding ) {
+ if( data==null || data.isNull() || data.getLength() <= 0 ) return;
+
+ if (data.getType() != MessageBytes.T_BYTES) {
+ data.toBytes();
+ }
+ ByteChunk bc=data.getByteChunk();
+ processParameters( bc.getBytes(), bc.getOffset(),
+ bc.getLength(), encoding);
+ }
+
+ /** Debug purpose
+ */
+ public String paramsAsString() {
+ StringBuffer sb=new StringBuffer();
+ Enumeration en= paramHashStringArray.keys();
+ while( en.hasMoreElements() ) {
+ String k=(String)en.nextElement();
+ sb.append( k ).append("=");
+ String v[]=(String[])paramHashStringArray.get( k );
+ for( int i=0; i<v.length; i++ )
+ sb.append( v[i] ).append(",");
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ private static int debug=0;
+ private void log(String s ) {
+ if (log.isDebugEnabled())
+ log.debug("Parameters: " + s );
+ }
+
+ // -------------------- Old code, needs rewrite --------------------
+
+ /** Used by RequestDispatcher
+ */
+ public void processParameters( String str ) {
+ int end=str.length();
+ int pos=0;
+ if( debug > 0)
+ log("String: " + str );
+
+ do {
+ boolean noEq=false;
+ int valStart=-1;
+ int valEnd=-1;
+
+ int nameStart=pos;
+ int nameEnd=str.indexOf('=', nameStart );
+ int nameEnd2=str.indexOf('&', nameStart );
+ if( nameEnd2== -1 ) nameEnd2=end;
+ if( (nameEnd2!=-1 ) &&
+ ( nameEnd==-1 || nameEnd > nameEnd2) ) {
+ nameEnd=nameEnd2;
+ noEq=true;
+ valStart=nameEnd;
+ valEnd=nameEnd;
+ if( debug>0) log("no equal " + nameStart + " " + nameEnd + " " + str.substring(nameStart, nameEnd) );
+ }
+
+ if( nameEnd== -1 ) nameEnd=end;
+
+ if( ! noEq ) {
+ valStart=nameEnd+1;
+ valEnd=str.indexOf('&', valStart);
+ if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+ }
+
+ pos=valEnd+1;
+
+ if( nameEnd<=nameStart ) {
+ continue;
+ }
+ if( debug>0)
+ log( "XXX " + nameStart + " " + nameEnd + " "
+ + valStart + " " + valEnd );
+
+ try {
+ tmpNameC.append(str, nameStart, nameEnd-nameStart );
+ tmpValueC.append(str, valStart, valEnd-valStart );
+
+ if( debug > 0 )
+ log( tmpNameC + "= " + tmpValueC);
+
+ if( urlDec==null ) {
+ urlDec=new UDecoder();
+ }
+
+ urlDec.convert( tmpNameC );
+ urlDec.convert( tmpValueC );
+
+ if( debug > 0 )
+ log( tmpNameC + "= " + tmpValueC);
+
+ addParam( tmpNameC.toString(), tmpValueC.toString() );
+ } catch( IOException ex ) {
+ ex.printStackTrace();
+ }
+
+ tmpNameC.recycle();
+ tmpValueC.recycle();
+
+ } while( pos<end );
+ }
+
+
+}
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2008-03-27 14:22:16 UTC (rev 558)
+++ trunk/webapps/docs/changelog.xml 2008-03-27 17:49:20 UTC (rev 559)
@@ -64,6 +64,10 @@
<update>
For consistency, refactor character output using the NIO character decoders. (remm)
</update>
+ <update>
+ Move the parameters backend to the probably more efficient MultiMap from Hashtable,
+ and remove the nesting capabilities inherited from Tomcat 3. (remm)
+ </update>
</changelog>
</subsection>
</section>
16 years, 11 months
JBossWeb SVN: r558 - in trunk: java/org/apache/tomcat/util/buf and 1 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-27 10:22:16 -0400 (Thu, 27 Mar 2008)
New Revision: 558
Modified:
trunk/java/org/apache/catalina/connector/OutputBuffer.java
trunk/java/org/apache/catalina/connector/Response.java
trunk/java/org/apache/tomcat/util/buf/C2BConverter.java
trunk/java/org/apache/tomcat/util/buf/UEncoder.java
trunk/webapps/docs/changelog.xml
Log:
- For consistency, refactor output using NIO. This makes input and output very similar, but the performance
improvements and memory savings should be minimal since output was already relatively sane, unlike input
(except sendRedirects should be much faster; woohoo, party).
Modified: trunk/java/org/apache/catalina/connector/OutputBuffer.java
===================================================================
--- trunk/java/org/apache/catalina/connector/OutputBuffer.java 2008-03-27 03:04:12 UTC (rev 557)
+++ trunk/java/org/apache/catalina/connector/OutputBuffer.java 2008-03-27 14:22:16 UTC (rev 558)
@@ -30,6 +30,7 @@
import org.apache.catalina.Globals;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.C2BConverter;
+import org.apache.tomcat.util.buf.CharChunk;
/**
@@ -41,7 +42,7 @@
* @author Remy Maucherat
*/
public class OutputBuffer extends Writer
- implements ByteChunk.ByteOutputChannel {
+ implements ByteChunk.ByteOutputChannel, CharChunk.CharOutputChannel {
// -------------------------------------------------------------- Constants
@@ -62,6 +63,12 @@
/**
+ * The chunk buffer.
+ */
+ private CharChunk cb;
+
+
+ /**
* State of the output buffer.
*/
private boolean initial = true;
@@ -98,6 +105,12 @@
/**
+ * Char chunk used to output chars.
+ */
+ private CharChunk outputCharChunk = new CharChunk();
+
+
+ /**
* Encoding to use.
*/
private String enc;
@@ -112,7 +125,8 @@
/**
* List of encoders.
*/
- protected HashMap encoders = new HashMap();
+ protected HashMap<String, C2BConverter> encoders =
+ new HashMap<String, C2BConverter>();
/**
@@ -156,6 +170,10 @@
bb = new ByteChunk(size);
bb.setLimit(size);
bb.setByteOutputChannel(this);
+ cb = new CharChunk(size);
+ cb.setLimit(size);
+ cb.setOptimizedWrite(false);
+ cb.setCharOutputChannel(this);
}
@@ -169,7 +187,7 @@
* @param coyoteResponse Associated Coyote response
*/
public void setResponse(Response coyoteResponse) {
- this.coyoteResponse = coyoteResponse;
+ this.coyoteResponse = coyoteResponse;
}
@@ -225,7 +243,9 @@
bytesWritten = 0;
charsWritten = 0;
- bb.recycle();
+ bb.recycle();
+ cb.recycle();
+ outputCharChunk.setChars(null, 0, 0);
closed = false;
suspended = false;
@@ -261,6 +281,12 @@
if (suspended)
return;
+ // If there are chars, flush all of them to the byte buffer now as bytes are used to
+ // calculate the content-length (if everything fits into the byte buffer, of course).
+ if (cb.getLength() > 0) {
+ cb.flushBuffer();
+ }
+
if ((!coyoteResponse.isCommitted())
&& (coyoteResponse.getContentLengthLong() == -1)) {
// If this didn't cause a commit of the response, the final content
@@ -305,6 +331,9 @@
coyoteResponse.sendHeaders();
initial = false;
}
+ if (cb.getLength() > 0) {
+ cb.flushBuffer();
+ }
if (bb.getLength() > 0) {
bb.flushBuffer();
}
@@ -350,7 +379,7 @@
* @throws IOException An underlying IOException occurred
*/
public void realWriteBytes(byte buf[], int off, int cnt)
- throws IOException {
+ throws IOException {
if (closed)
return;
@@ -417,25 +446,43 @@
// ------------------------------------------------- Chars Handling Methods
+ /**
+ * Convert the chars to bytes, then send the data to the client.
+ *
+ * @param buf Char buffer to be written to the response
+ * @param off Offset
+ * @param len Length
+ *
+ * @throws IOException An underlying IOException occurred
+ */
+ public void realWriteChars(char buf[], int off, int len)
+ throws IOException {
+
+ charsWritten += len;
+ outputCharChunk.setChars(buf, off, len);
+ while (outputCharChunk.getLength() > 0) {
+ conv.convert(outputCharChunk, bb);
+ if (outputCharChunk.getLength() > 0) {
+ bb.flushBuffer();
+ }
+ }
+
+ }
+
public void write(int c)
throws IOException {
if (suspended)
return;
- conv.convert((char) c);
- conv.flushBuffer();
- charsWritten++;
-
+ cb.append((char) c);
+
}
public void write(char c[])
throws IOException {
- if (suspended)
- return;
-
write(c, 0, c.length);
}
@@ -447,9 +494,7 @@
if (suspended)
return;
- conv.convert(c, off, len);
- conv.flushBuffer();
- charsWritten += len;
+ cb.append(c, off, len);
}
@@ -463,11 +508,9 @@
if (suspended)
return;
- charsWritten += len;
if (s == null)
s = "null";
- conv.convert(s, off, len);
- conv.flushBuffer();
+ cb.append(s, off, len);
}
@@ -480,8 +523,7 @@
if (s == null)
s = "null";
- conv.convert(s);
- conv.flushBuffer();
+ cb.append(s);
}
@@ -518,7 +560,7 @@
new PrivilegedExceptionAction(){
public Object run() throws IOException{
- return new C2BConverter(bb, enc);
+ return new C2BConverter(enc);
}
}
@@ -529,7 +571,7 @@
throw (IOException)e;
}
} else {
- conv = new C2BConverter(bb, enc);
+ conv = new C2BConverter(enc);
}
encoders.put(enc, conv);
@@ -596,6 +638,7 @@
public void reset() {
bb.recycle();
+ cb.recycle();
bytesWritten = 0;
charsWritten = 0;
gotEnc = false;
Modified: trunk/java/org/apache/catalina/connector/Response.java
===================================================================
--- trunk/java/org/apache/catalina/connector/Response.java 2008-03-27 03:04:12 UTC (rev 557)
+++ trunk/java/org/apache/catalina/connector/Response.java 2008-03-27 14:22:16 UTC (rev 558)
@@ -1502,16 +1502,16 @@
if (!leadingSlash) {
String relativePath = request.getDecodedRequestURI();
int pos = relativePath.lastIndexOf('/');
- relativePath = relativePath.substring(0, pos);
- String encodedURI = null;
- final String frelativePath = relativePath;
+ CharChunk encodedURI = null;
if (SecurityUtil.isPackageProtectionEnabled() ){
+ final String frelativePath = relativePath;
+ final int fend = pos;
try{
- encodedURI = (String)AccessController.doPrivileged(
+ encodedURI = (CharChunk)AccessController.doPrivileged(
new PrivilegedExceptionAction(){
public Object run() throws IOException{
- return urlEncoder.encodeURL(frelativePath);
+ return urlEncoder.encodeURL(frelativePath, 0, fend);
}
});
} catch (PrivilegedActionException pae){
@@ -1521,9 +1521,10 @@
throw iae;
}
} else {
- encodedURI = urlEncoder.encodeURL(relativePath);
+ encodedURI = urlEncoder.encodeURL(relativePath, 0, pos);
}
- redirectURLCC.append(encodedURI, 0, encodedURI.length());
+ redirectURLCC.append(encodedURI);
+ encodedURI.recycle();
redirectURLCC.append('/');
}
redirectURLCC.append(location, 0, location.length());
Modified: trunk/java/org/apache/tomcat/util/buf/C2BConverter.java
===================================================================
--- trunk/java/org/apache/tomcat/util/buf/C2BConverter.java 2008-03-27 03:04:12 UTC (rev 557)
+++ trunk/java/org/apache/tomcat/util/buf/C2BConverter.java 2008-03-27 14:22:16 UTC (rev 558)
@@ -18,251 +18,76 @@
package org.apache.tomcat.util.buf;
import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
-/** Efficient conversion of character to bytes.
- *
- * This uses the standard JDK mechansim - a writer - but provides mechanisms
- * to recycle all the objects that are used. It is compatible with JDK1.1 and up,
- * ( nio is better, but it's not available even in 1.2 or 1.3 )
- *
+/**
+ * NIO based character encoder.
*/
public final class C2BConverter {
- private static org.jboss.logging.Logger log=
- org.jboss.logging.Logger.getLogger(C2BConverter.class );
-
- private IntermediateOutputStream ios;
- private WriteConvertor conv;
- private ByteChunk bb;
- private String enc;
-
- /** Create a converter, with bytes going to a byte buffer
- */
- public C2BConverter(ByteChunk output, String encoding) throws IOException {
- this.bb=output;
- ios=new IntermediateOutputStream( output );
- conv=new WriteConvertor( ios, encoding );
- this.enc=encoding;
- }
+ protected static org.jboss.logging.Logger log =
+ org.jboss.logging.Logger.getLogger(C2BConverter.class);
- /** Create a converter
- */
- public C2BConverter(String encoding) throws IOException {
- this( new ByteChunk(1024), encoding );
- }
+ protected CharsetEncoder encoder = null;
+ protected ByteBuffer bb = null;
+ protected CharBuffer cb = null;
- public ByteChunk getByteChunk() {
- return bb;
- }
-
- public String getEncoding() {
- return enc;
- }
-
- public void setByteChunk(ByteChunk bb) {
- this.bb=bb;
- ios.setByteChunk( bb );
- }
-
- /** Reset the internal state, empty the buffers.
- * The encoding remain in effect, the internal buffers remain allocated.
+ /**
+ * Create an encoder for the specified charset.
*/
- public final void recycle() {
- conv.recycle();
- bb.recycle();
+ public C2BConverter(String charset) {
+ encoder = Charset.forName(charset).newEncoder();
}
- /** Generate the bytes using the specified encoding
+ /**
+ * The encoding remain in effect, the encoder remains allocated.
*/
- public final void convert(char c[], int off, int len ) throws IOException {
- conv.write( c, off, len );
+ public void recycle() {
+ encoder.reset();
}
- /** Generate the bytes using the specified encoding
+ /**
+ * Convert the given charaters to bytes.
*/
- public final void convert(String s, int off, int len ) throws IOException {
- conv.write( s, off, len );
- }
-
- /** Generate the bytes using the specified encoding
- */
- public final void convert(String s ) throws IOException {
- conv.write( s );
- }
-
- /** Generate the bytes using the specified encoding
- */
- public final void convert(char c ) throws IOException {
- conv.write( c );
- }
-
- /** Convert a message bytes chars to bytes
- */
- public final void convert(MessageBytes mb ) throws IOException {
- int type=mb.getType();
- if( type==MessageBytes.T_BYTES )
- return;
- ByteChunk orig=bb;
- setByteChunk( mb.getByteChunk());
- bb.recycle();
- bb.allocate( 32, -1 );
-
- if( type==MessageBytes.T_STR ) {
- convert( mb.getString() );
- // System.out.println("XXX Converting " + mb.getString() );
- } else if( type==MessageBytes.T_CHARS ) {
- CharChunk charC=mb.getCharChunk();
- convert( charC.getBuffer(),
- charC.getOffset(), charC.getLength());
- //System.out.println("XXX Converting " + mb.getCharChunk() );
+ public void convert(CharChunk cc, ByteChunk bc)
+ throws IOException {
+ if ((bb == null) || (bb.array() != bc.getBuffer())) {
+ // Create a new byte buffer if anything changed
+ bb = ByteBuffer.wrap(bc.getBuffer(), bc.getEnd(),
+ bc.getBuffer().length - bc.getEnd());
} else {
- if (log.isDebugEnabled())
- log.debug("XXX unknowon type " + type );
+ // Initialize the byte buffer
+ bb.position(bc.getEnd());
+ bb.limit(bc.getBuffer().length);
}
- flushBuffer();
- //System.out.println("C2B: XXX " + bb.getBuffer() + bb.getLength());
- setByteChunk(orig);
+ if ((cb == null) || (cb.array() != cc.getBuffer())) {
+ // Create a new char buffer if anything changed
+ cb = CharBuffer.wrap(cc.getBuffer(), cc.getStart(),
+ cc.getLength());
+ } else {
+ // Initialize the char buffer
+ cb.position(cc.getStart());
+ cb.limit(cc.getEnd());
+ }
+ // Parse leftover if any are present
+ CoderResult result = null;
+ // Do the decoding and get the results into the byte chunk and the char chunk
+ result = encoder.encode(cb, bb, false);
+ if (result.isError() || result.isMalformed()) {
+ result.throwException();
+ } else if (result.isOverflow()) {
+ // Propagate current positions to the byte chunk and char chunk
+ bc.setEnd(bb.position());
+ cc.setOffset(cb.position());
+ } else if (result.isUnderflow()) {
+ // Propagate current positions to the byte chunk and char chunk
+ bc.setEnd(bb.position());
+ cc.setOffset(cb.position());
+ }
}
-
- /** Flush any internal buffers into the ByteOutput or the internal
- * byte[]
- */
- public final void flushBuffer() throws IOException {
- conv.flush();
- }
-
-}
-
-// -------------------- Private implementation --------------------
-
-
-
-/**
- * Special writer class, where close() is overritten. The default implementation
- * would set byteOutputter to null, and the writter can't be recycled.
- *
- * Note that the flush method will empty the internal buffers _and_ call
- * flush on the output stream - that's why we use an intermediary output stream
- * that overrides flush(). The idea is to have full control: flushing the
- * char->byte converter should be independent of flushing the OutputStream.
- *
- * When a WriteConverter is created, it'll allocate one or 2 byte buffers,
- * with a 8k size that can't be changed ( at least in JDK1.1 -> 1.4 ). It would
- * also allocate a ByteOutputter or equivalent - again some internal buffers.
- *
- * It is essential to keep this object around and reuse it. You can use either
- * pools or per thread data - but given that in most cases a converter will be
- * needed for every thread and most of the time only 1 ( or 2 ) encodings will
- * be used, it is far better to keep it per thread and eliminate the pool
- * overhead too.
- *
- */
- final class WriteConvertor extends OutputStreamWriter {
- // stream with flush() and close(). overriden.
- private IntermediateOutputStream ios;
- // Has a private, internal byte[8192]
-
- /** Create a converter.
- */
- public WriteConvertor( IntermediateOutputStream out, String enc )
- throws UnsupportedEncodingException
- {
- super( out, enc );
- ios=out;
- }
-
- /** Overriden - will do nothing but reset internal state.
- */
- public final void close() throws IOException {
- // NOTHING
- // Calling super.close() would reset out and cb.
- }
-
- /**
- * Flush the characters only
- */
- public final void flush() throws IOException {
- // Will flushBuffer and out()
- // flushBuffer put any remaining chars in the byte[]
- super.flush();
- }
-
- public final void write(char cbuf[], int off, int len) throws IOException {
- // will do the conversion and call write on the output stream
- super.write( cbuf, off, len );
- }
-
- /** Reset the buffer
- */
- public final void recycle() {
- ios.disable();
- try {
- // System.out.println("Reseting writer");
- flush();
- } catch( Exception ex ) {
- ex.printStackTrace();
- }
- ios.enable();
- }
-
}
-
-
-/** Special output stream where close() is overriden, so super.close()
- is never called.
-
- This allows recycling. It can also be disabled, so callbacks will
- not be called if recycling the converter and if data was not flushed.
-*/
-final class IntermediateOutputStream extends OutputStream {
- private ByteChunk tbuff;
- private boolean enabled=true;
-
- public IntermediateOutputStream(ByteChunk tbuff) {
- this.tbuff=tbuff;
- }
-
- public final void close() throws IOException {
- // shouldn't be called - we filter it out in writer
- throw new IOException("close() called - shouldn't happen ");
- }
-
- public final void flush() throws IOException {
- // nothing - write will go directly to the buffer,
- // we don't keep any state
- }
-
- public final void write(byte cbuf[], int off, int len) throws IOException {
- // will do the conversion and call write on the output stream
- if( enabled ) {
- tbuff.append( cbuf, off, len );
- }
- }
-
- public final void write( int i ) throws IOException {
- throw new IOException("write( int ) called - shouldn't happen ");
- }
-
- // -------------------- Internal methods --------------------
-
- void setByteChunk( ByteChunk bb ) {
- tbuff=bb;
- }
-
- /** Temporary disable - this is used to recycle the converter without
- * generating an output if the buffers were not flushed
- */
- final void disable() {
- enabled=false;
- }
-
- /** Reenable - used to recycle the converter
- */
- final void enable() {
- enabled=true;
- }
-}
Modified: trunk/java/org/apache/tomcat/util/buf/UEncoder.java
===================================================================
--- trunk/java/org/apache/tomcat/util/buf/UEncoder.java 2008-03-27 03:04:12 UTC (rev 557)
+++ trunk/java/org/apache/tomcat/util/buf/UEncoder.java 2008-03-27 14:22:16 UTC (rev 558)
@@ -31,113 +31,99 @@
* while encoding a URL you can add "/".
*
* @author Costin Manolache
+ * @author Remy Maucherat
*/
public final class UEncoder {
- private static org.jboss.logging.Logger log=
- org.jboss.logging.Logger.getLogger(UEncoder.class );
+ private static org.jboss.logging.Logger log =
+ org.jboss.logging.Logger.getLogger(UEncoder.class);
// Not static - the set may differ ( it's better than adding
// an extra check for "/", "+", etc
private BitSet safeChars=null;
private C2BConverter c2b=null;
private ByteChunk bb=null;
+ private CharChunk cb=null;
+ private CharChunk output=null;
private String encoding="UTF8";
private static final int debug=0;
public UEncoder() {
- initSafeChars();
+ initSafeChars();
}
public void setEncoding( String s ) {
- encoding=s;
+ encoding=s;
}
public void addSafeCharacter( char c ) {
- safeChars.set( c );
+ safeChars.set( c );
}
-
/** URL Encode string, using a specified encoding.
*
* @param buf The writer
* @param s string to be encoded
* @throws IOException If an I/O error occurs
*/
- public void urlEncode( Writer buf, String s )
- throws IOException
- {
- if( c2b==null ) {
- bb=new ByteChunk(16); // small enough.
- c2b=new C2BConverter( bb, encoding );
- }
+ public CharChunk encodeURL(String s, int start, int end)
+ throws IOException {
+ if (c2b == null) {
+ bb = new ByteChunk(8); // small enough.
+ cb = new CharChunk(2); // small enough.
+ output = new CharChunk(64); // small enough.
+ c2b = new C2BConverter(encoding);
+ } else {
+ bb.recycle();
+ cb.recycle();
+ }
- for (int i = 0; i < s.length(); i++) {
- int c = (int) s.charAt(i);
- if( safeChars.get( c ) ) {
- if( debug > 0 ) log("Safe: " + (char)c);
- buf.write((char)c);
- } else {
- if( debug > 0 ) log("Unsafe: " + (char)c);
- c2b.convert( (char)c );
-
- // "surrogate" - UTF is _not_ 16 bit, but 21 !!!!
- // ( while UCS is 31 ). Amazing...
- if (c >= 0xD800 && c <= 0xDBFF) {
- if ( (i+1) < s.length()) {
- int d = (int) s.charAt(i+1);
- if (d >= 0xDC00 && d <= 0xDFFF) {
- if( debug > 0 ) log("Unsafe: " + c);
- c2b.convert( (char)d);
- i++;
- }
- }
- }
+ for (int i = start; i < end; i++) {
+ char c = s.charAt(i);
+ if (safeChars.get(c)) {
+ if( debug > 0 ) log("Safe: " + (char)c);
+ output.append(c);
+ } else {
+ if( debug > 0 ) log("Unsafe: " + (char)c);
+ cb.append(c);
+ c2b.convert(cb, bb);
- c2b.flushBuffer();
-
- urlEncode( buf, bb.getBuffer(), bb.getOffset(),
- bb.getLength() );
- bb.recycle();
- }
- }
+ // "surrogate" - UTF is _not_ 16 bit, but 21 !!!!
+ // ( while UCS is 31 ). Amazing...
+ if (c >= 0xD800 && c <= 0xDBFF) {
+ if ((i+1) < end) {
+ char d = s.charAt(i+1);
+ if (d >= 0xDC00 && d <= 0xDFFF) {
+ if( debug > 0 ) log("Unsafe: " + d);
+ cb.append(d);
+ c2b.convert(cb, bb);
+ i++;
+ }
+ }
+ }
+
+ urlEncode(output, bb);
+ cb.recycle();
+ bb.recycle();
+ }
+ }
+
+ return output;
}
- /**
- */
- public void urlEncode( Writer buf, byte bytes[], int off, int len)
- throws IOException
- {
- for( int j=off; j< len; j++ ) {
- buf.write( '%' );
- char ch = Character.forDigit((bytes[j] >> 4) & 0xF, 16);
- if( debug > 0 ) log("Encode: " + ch);
- buf.write(ch);
- ch = Character.forDigit(bytes[j] & 0xF, 16);
- if( debug > 0 ) log("Encode: " + ch);
- buf.write(ch);
- }
+ protected void urlEncode(CharChunk out, ByteChunk bb)
+ throws IOException {
+ byte[] bytes = bb.getBuffer();
+ for (int j = bb.getStart(); j < bb.getEnd(); j++) {
+ out.append('%');
+ char ch = Character.forDigit((bytes[j] >> 4) & 0xF, 16);
+ out.append(ch);
+ ch = Character.forDigit(bytes[j] & 0xF, 16);
+ out.append(ch);
+ }
}
- /**
- * Utility funtion to re-encode the URL.
- * Still has problems with charset, since UEncoder mostly
- * ignores it.
- */
- public String encodeURL(String uri) {
- String outUri=null;
- try {
- // XXX optimize - recycle, etc
- CharArrayWriter out = new CharArrayWriter();
- urlEncode(out, uri);
- outUri=out.toString();
- } catch (IOException iex) {
- }
- return outUri;
- }
-
-
// -------------------- Internal implementation --------------------
//
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2008-03-27 03:04:12 UTC (rev 557)
+++ trunk/webapps/docs/changelog.xml 2008-03-27 14:22:16 UTC (rev 558)
@@ -61,6 +61,9 @@
<bug>44494</bug>: Fix incorrect reads with multibyte charsets by moving the byte to char
converter to the NIO character decoders. (remm)
</update>
+ <update>
+ For consistency, refactor character output using the NIO character decoders. (remm)
+ </update>
</changelog>
</subsection>
</section>
16 years, 11 months
JBossWeb SVN: r557 - trunk/java/org/apache/tomcat/util/buf.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-26 23:04:12 -0400 (Wed, 26 Mar 2008)
New Revision: 557
Modified:
trunk/java/org/apache/tomcat/util/buf/B2CConverter.java
Log:
- Trivial code cleanup.
Modified: trunk/java/org/apache/tomcat/util/buf/B2CConverter.java
===================================================================
--- trunk/java/org/apache/tomcat/util/buf/B2CConverter.java 2008-03-27 03:03:37 UTC (rev 556)
+++ trunk/java/org/apache/tomcat/util/buf/B2CConverter.java 2008-03-27 03:04:12 UTC (rev 557)
@@ -32,10 +32,9 @@
*/
public class B2CConverter {
- private static org.jboss.logging.Logger log =
+ protected static org.jboss.logging.Logger log =
org.jboss.logging.Logger.getLogger(B2CConverter.class);
- protected String charset = null;
protected CharsetDecoder decoder = null;
protected ByteBuffer bb = null;
protected CharBuffer cb = null;
@@ -50,7 +49,6 @@
*/
public B2CConverter(String charset) {
decoder = Charset.forName(charset).newDecoder();
- this.charset = charset;
byte[] left = new byte[4];
leftovers = ByteBuffer.wrap(left);
}
16 years, 11 months
JBossWeb SVN: r556 - trunk/java/org/apache/catalina/connector.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-26 23:03:37 -0400 (Wed, 26 Mar 2008)
New Revision: 556
Modified:
trunk/java/org/apache/catalina/connector/InputBuffer.java
Log:
- IB no longer uses the overflow mechanism, so cleanup the code.
Modified: trunk/java/org/apache/catalina/connector/InputBuffer.java
===================================================================
--- trunk/java/org/apache/catalina/connector/InputBuffer.java 2008-03-27 00:42:29 UTC (rev 555)
+++ trunk/java/org/apache/catalina/connector/InputBuffer.java 2008-03-27 03:03:37 UTC (rev 556)
@@ -41,8 +41,7 @@
* @author Remy Maucherat
*/
public class InputBuffer extends Reader
- implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel,
- CharChunk.CharOutputChannel {
+ implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel {
// -------------------------------------------------------------- Constants
@@ -81,30 +80,12 @@
/**
- * Number of bytes read.
- */
- private int bytesRead = 0;
-
-
- /**
- * Number of chars read.
- */
- private int charsRead = 0;
-
-
- /**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false;
/**
- * Byte chunk used to input bytes.
- */
- private ByteChunk inputChunk = new ByteChunk();
-
-
- /**
* Encoding to use.
*/
private String enc;
@@ -119,7 +100,8 @@
/**
* List of encoders.
*/
- protected HashMap encoders = new HashMap();
+ protected HashMap<String, B2CConverter> encoders =
+ new HashMap<String, B2CConverter>();
/**
@@ -181,7 +163,6 @@
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
- cb.setCharOutputChannel(this);
}
@@ -218,8 +199,6 @@
public void recycle() {
state = INITIAL_STATE;
- bytesRead = 0;
- charsRead = 0;
// If usage of mark made the buffer too big, reallocate it
if (cb.getChars().length > size) {
@@ -227,7 +206,6 @@
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
- cb.setCharOutputChannel(this);
} else {
cb.recycle();
}
@@ -356,20 +334,6 @@
// ------------------------------------------------- Chars Handling Methods
- /**
- * Since the converter will use append, it is possible to get chars to
- * be removed from the buffer for "writing". Since the chars have already
- * been read before, they are ignored. If a mark was set, then the
- * mark is lost.
- */
- public void realWriteChars(char c[], int off, int len)
- throws IOException {
- markPos = -1;
- cb.setOffset(0);
- cb.setEnd(0);
- }
-
-
public void setEncoding(String s) {
enc = s;
}
@@ -527,7 +491,7 @@
gotEnc = true;
if (enc == null)
enc = DEFAULT_ENCODING;
- conv = (B2CConverter) encoders.get(enc);
+ conv = encoders.get(enc);
if (conv == null) {
if (SecurityUtil.isPackageProtectionEnabled()){
try{
16 years, 11 months
JBossWeb SVN: r555 - in trunk: java/org/apache/tomcat/util/buf and 1 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-26 20:42:29 -0400 (Wed, 26 Mar 2008)
New Revision: 555
Modified:
trunk/java/org/apache/catalina/connector/CoyoteAdapter.java
trunk/java/org/apache/catalina/connector/InputBuffer.java
trunk/java/org/apache/tomcat/util/buf/B2CConverter.java
trunk/java/org/apache/tomcat/util/buf/ByteChunk.java
trunk/java/org/apache/tomcat/util/buf/CharChunk.java
trunk/java/org/apache/tomcat/util/buf/UTF8Decoder.java
trunk/webapps/docs/changelog.xml
Log:
- Move the B2C converter to use a NIO CharsetDecoder backend. The main issue with the previous code
was the unknown state of the various buffers used in the JDK objects. This change will also
save a significant amount of memory (the NIO decoder does not have any internal buffer).
Modified: trunk/java/org/apache/catalina/connector/CoyoteAdapter.java
===================================================================
--- trunk/java/org/apache/catalina/connector/CoyoteAdapter.java 2008-03-27 00:26:25 UTC (rev 554)
+++ trunk/java/org/apache/catalina/connector/CoyoteAdapter.java 2008-03-27 00:42:29 UTC (rev 555)
@@ -651,7 +651,7 @@
} else {
conv.recycle();
}
- } catch (IOException e) {
+ } catch (Exception e) {
// Ignore
log.error("Invalid URI encoding; using HTTP default");
connector.setURIEncoding(null);
Modified: trunk/java/org/apache/catalina/connector/InputBuffer.java
===================================================================
--- trunk/java/org/apache/catalina/connector/InputBuffer.java 2008-03-27 00:26:25 UTC (rev 554)
+++ trunk/java/org/apache/catalina/connector/InputBuffer.java 2008-03-27 00:42:29 UTC (rev 555)
@@ -297,10 +297,6 @@
}
}
}
- /*if (available == 0) {
- coyoteRequest.action(ActionCode.ACTION_AVAILABLE, null);
- available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
- }*/
return available;
}
@@ -386,8 +382,7 @@
setConverter();
if (bb.getLength() <= 0) {
- int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
- if (nRead < 0) {
+ if (realReadBytes(bb.getBytes(), 0, bb.getBytes().length) < 0) {
return -1;
}
}
@@ -395,10 +390,19 @@
if (markPos == -1) {
cb.setOffset(0);
cb.setEnd(0);
+ } else {
+ // Make sure there's enough space in the worst case
+ cb.makeSpace(bb.getLength());
+ if ((cb.getBuffer().length - cb.getEnd()) == 0) {
+ // We went over the limit
+ cb.setOffset(0);
+ cb.setEnd(0);
+ markPos = -1;
+ }
}
state = CHAR_STATE;
- conv.convert(bb, cb, bb.getLength());
+ conv.convert(bb, cb);
return cb.getLength();
@@ -484,7 +488,9 @@
if (offset < size) {
offset = size;
}
- cb.setLimit(cb.getStart() + offset);
+ if (cb.getStart() + offset > cb.getLimit()) {
+ cb.setLimit(cb.getStart() + offset);
+ }
markPos = cb.getStart();
}
@@ -493,8 +499,6 @@
throws IOException {
if (state == CHAR_STATE) {
if (markPos < 0) {
- cb.recycle();
- markPos = -1;
throw new IOException();
} else {
cb.setOffset(markPos);
Modified: trunk/java/org/apache/tomcat/util/buf/B2CConverter.java
===================================================================
--- trunk/java/org/apache/tomcat/util/buf/B2CConverter.java 2008-03-27 00:26:25 UTC (rev 554)
+++ trunk/java/org/apache/tomcat/util/buf/B2CConverter.java 2008-03-27 00:42:29 UTC (rev 555)
@@ -19,242 +19,113 @@
package org.apache.tomcat.util.buf;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-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;
-/** Efficient conversion of bytes to character .
- *
- * This uses the standard JDK mechansim - a reader - but provides mechanisms
- * to recycle all the objects that are used. It is compatible with JDK1.1
- * and up,
- * ( nio is better, but it's not available even in 1.2 or 1.3 )
- *
- * Not used in the current code, the performance gain is not very big
- * in the current case ( since String is created anyway ), but it will
- * be used in a later version or after the remaining optimizations.
+/**
+ * Helper class to handle byte to char conversion using NIO.
+ *
+ * @author Remy Maucherat
*/
public class B2CConverter {
-
-
- private static org.jboss.logging.Logger log=
- org.jboss.logging.Logger.getLogger( B2CConverter.class );
-
- private IntermediateInputStream iis;
- private ReadConvertor conv;
- private String encoding;
- protected B2CConverter() {
- }
-
- /** Create a converter, with bytes going to a byte buffer
- */
- public B2CConverter(String encoding)
- throws IOException
- {
- this.encoding=encoding;
- reset();
- }
+ private static org.jboss.logging.Logger log =
+ org.jboss.logging.Logger.getLogger(B2CConverter.class);
-
- /** Reset the internal state, empty the buffers.
- * The encoding remain in effect, the internal buffers remain allocated.
+ protected String charset = null;
+ protected CharsetDecoder decoder = null;
+ protected ByteBuffer bb = null;
+ protected CharBuffer cb = null;
+
+ /**
+ * Leftover buffer used for incomplete characters.
*/
- public void recycle() {
- conv.recycle();
- }
+ protected ByteBuffer leftovers = null;
- static final int BUFFER_SIZE=8192;
- char result[]=new char[BUFFER_SIZE];
-
- /** Convert a buffer of bytes into a chars
- * @deprecated
+ /**
+ * Create a decoder for the specified charset.
*/
- public void convert( ByteChunk bb, CharChunk cb )
- throws IOException
- {
- // Set the ByteChunk as input to the Intermediate reader
- convert(bb, cb, bb.getLength());
+ public B2CConverter(String charset) {
+ decoder = Charset.forName(charset).newDecoder();
+ this.charset = charset;
+ byte[] left = new byte[4];
+ leftovers = ByteBuffer.wrap(left);
}
- public void convert(ByteChunk bb, CharChunk cb, int limit)
- throws IOException {
- iis.setByteChunk(bb);
- try {
- // read from the reader
- int l = 0;
- while( limit > 0 ) { // conv.ready() ) {
- int size = limit < BUFFER_SIZE ? limit : BUFFER_SIZE;
- l = bb.getLength();
- int cnt=conv.read( result, 0, size );
- if( cnt <= 0 ) {
- // End of stream ! - we may be in a bad state
- if( debug>0)
- log( "EOF" );
- // reset();
- return;
- }
- if( debug > 1 )
- log("Converted: " + new String( result, 0, cnt ));
- // Make sure there's enough space to append the characters which
- // have been converted
- if (cb.getEnd() + cnt > cb.getLimit()) {
- cb.setLimit(cb.getEnd() + cnt);
- }
- cb.append( result, 0, cnt );
- limit = limit - (l - bb.getLength());
+ /**
+ * Convert the given bytes to chars.
+ *
+ * @param bc
+ * @param cc
+ */
+ public void convert(ByteChunk bc, CharChunk cc)
+ 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.position(bc.getStart());
+ bb.limit(bc.getEnd());
+ }
+ 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.position(cc.getEnd());
+ cb.limit(cc.getBuffer().length);
+ }
+ // Parse leftover if any are present
+ CoderResult result = null;
+ if (leftovers.position() > 0) {
+ int pos = cb.position();
+ do {
+ leftovers.put(bc.substractB());
+ leftovers.flip();
+ result = decoder.decode(leftovers, cb, false);
+ leftovers.position(leftovers.limit());
+ leftovers.limit(leftovers.array().length);
+ } while (result.isUnderflow() && (cb.position() == pos));
+ if (result.isError() || result.isMalformed()) {
+ result.throwException();
}
- } catch( IOException ex) {
- if( debug>0)
- log( "Reseting the converter " + ex.toString() );
- reset();
- throw ex;
+ 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, false);
+ 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.position(bc.getLength());
+ leftovers.limit(leftovers.array().length);
+ bc.substract(leftovers.array(), 0, bc.getLength());
+ }
+ }
}
- public void reset()
- throws IOException
- {
- // destroy the reader/iis
- iis=new IntermediateInputStream();
- conv=new ReadConvertor( iis, encoding );
- }
-
- private final int debug=0;
- void log( String s ) {
- if (log.isDebugEnabled())
- log.debug("B2CConverter: " + s );
- }
-
- // -------------------- Not used - the speed improvemnt is quite small
-
- /*
- private Hashtable decoders;
- public static final boolean useNewString=false;
- public static final boolean useSpecialDecoders=true;
- private UTF8Decoder utfD;
- // private char[] conversionBuff;
- CharChunk conversionBuf;
-
-
- private static String decodeString(ByteChunk mb, String enc)
- throws IOException
- {
- byte buff=mb.getBuffer();
- int start=mb.getStart();
- int end=mb.getEnd();
- if( useNewString ) {
- if( enc==null) enc="UTF8";
- return new String( buff, start, end-start, enc );
- }
- B2CConverter b2c=null;
- if( useSpecialDecoders &&
- (enc==null || "UTF8".equalsIgnoreCase(enc))) {
- if( utfD==null ) utfD=new UTF8Decoder();
- b2c=utfD;
- }
- if(decoders == null ) decoders=new Hashtable();
- if( enc==null ) enc="UTF8";
- b2c=(B2CConverter)decoders.get( enc );
- if( b2c==null ) {
- if( useSpecialDecoders ) {
- if( "UTF8".equalsIgnoreCase( enc ) ) {
- b2c=new UTF8Decoder();
- }
- }
- if( b2c==null )
- b2c=new B2CConverter( enc );
- decoders.put( enc, b2c );
- }
- if( conversionBuf==null ) conversionBuf=new CharChunk(1024);
-
- try {
- conversionBuf.recycle();
- b2c.convert( this, conversionBuf );
- //System.out.println("XXX 1 " + conversionBuf );
- return conversionBuf.toString();
- } catch( IOException ex ) {
- ex.printStackTrace();
- return null;
- }
- }
-
- */
-}
-
-// -------------------- Private implementation --------------------
-
-
-
-/**
- *
- */
-final class ReadConvertor extends InputStreamReader {
- // stream with flush() and close(). overriden.
- private IntermediateInputStream iis;
-
- // Has a private, internal byte[8192]
-
- /** Create a converter.
+ /**
+ * Reset the internal state, empty the buffers.
+ * The encoding remain in effect, the internal buffers remain allocated.
*/
- public ReadConvertor( IntermediateInputStream in, String enc )
- throws UnsupportedEncodingException
- {
- super( in, enc );
- iis=in;
+ public void recycle() {
+ decoder.reset();
+ leftovers.position(0);
}
-
- /** Overriden - will do nothing but reset internal state.
- */
- public final void close() throws IOException {
- // NOTHING
- // Calling super.close() would reset out and cb.
- }
-
- public final int read(char cbuf[], int off, int len)
- throws IOException
- {
- // will do the conversion and call write on the output stream
- return super.read( cbuf, off, len );
- }
-
- /** Reset the buffer
- */
- public final void recycle() {
- }
-}
-
-/** Special output stream where close() is overriden, so super.close()
- is never called.
-
- This allows recycling. It can also be disabled, so callbacks will
- not be called if recycling the converter and if data was not flushed.
-*/
-final class IntermediateInputStream extends InputStream {
- ByteChunk bc = null;
-
- public IntermediateInputStream() {
- }
-
- public final void close() throws IOException {
- // shouldn't be called - we filter it out in writer
- throw new IOException("close() called - shouldn't happen ");
- }
-
- public final int read(byte cbuf[], int off, int len) throws IOException {
- return bc.substract(cbuf, off, len);
- }
-
- public final int read() throws IOException {
- return bc.substract();
- }
-
- // -------------------- Internal methods --------------------
-
-
- void setByteChunk( ByteChunk mb ) {
- bc = mb;
- }
-
}
Modified: trunk/java/org/apache/tomcat/util/buf/ByteChunk.java
===================================================================
--- trunk/java/org/apache/tomcat/util/buf/ByteChunk.java 2008-03-27 00:26:25 UTC (rev 554)
+++ trunk/java/org/apache/tomcat/util/buf/ByteChunk.java 2008-03-27 00:42:29 UTC (rev 555)
@@ -378,6 +378,21 @@
}
+ 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(ByteChunk src)
throws IOException {
Modified: trunk/java/org/apache/tomcat/util/buf/CharChunk.java
===================================================================
--- trunk/java/org/apache/tomcat/util/buf/CharChunk.java 2008-03-27 00:26:25 UTC (rev 554)
+++ trunk/java/org/apache/tomcat/util/buf/CharChunk.java 2008-03-27 00:42:29 UTC (rev 555)
@@ -444,7 +444,7 @@
/** Make space for len chars. If len is small, allocate
* a reserve space too. Never grow bigger than limit.
*/
- private void makeSpace(int count)
+ public void makeSpace(int count)
{
char[] tmp = null;
Modified: trunk/java/org/apache/tomcat/util/buf/UTF8Decoder.java
===================================================================
--- trunk/java/org/apache/tomcat/util/buf/UTF8Decoder.java 2008-03-27 00:26:25 UTC (rev 554)
+++ trunk/java/org/apache/tomcat/util/buf/UTF8Decoder.java 2008-03-27 00:42:29 UTC (rev 555)
@@ -40,7 +40,7 @@
// may have state !!
public UTF8Decoder() {
-
+ super("UTF-8");
}
public void recycle() {
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2008-03-27 00:26:25 UTC (rev 554)
+++ trunk/webapps/docs/changelog.xml 2008-03-27 00:42:29 UTC (rev 555)
@@ -57,9 +57,10 @@
<update>
Add support for specifying defaults for properties (format is ${property:default}). (remm)
</update>
- <fix>
- <bug>44494</bug>: Fix incorrect reads with multibyte charsets. (remm)
- </fix>
+ <update>
+ <bug>44494</bug>: Fix incorrect reads with multibyte charsets by moving the byte to char
+ converter to the NIO character decoders. (remm)
+ </update>
</changelog>
</subsection>
</section>
16 years, 11 months
JBossWeb SVN: r554 - trunk/test/webapps/reader.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2008-03-26 20:26:25 -0400 (Wed, 26 Mar 2008)
New Revision: 554
Added:
trunk/test/webapps/reader/garbage.jsp
trunk/test/webapps/reader/mark.jsp
Modified:
trunk/test/webapps/reader/index.html
trunk/test/webapps/reader/read.jsp
trunk/test/webapps/reader/readCharB.jsp
trunk/test/webapps/reader/readLine.jsp
trunk/test/webapps/reader/test.jsp
Log:
- New input test update. Submitted by Suzuki Yuichiro.
Added: trunk/test/webapps/reader/garbage.jsp
===================================================================
--- trunk/test/webapps/reader/garbage.jsp (rev 0)
+++ trunk/test/webapps/reader/garbage.jsp 2008-03-27 00:26:25 UTC (rev 554)
@@ -0,0 +1,65 @@
+<%@ page pageEncoding="UTF-8"%>
+<%@ page import="java.io.*"%>
+<HTML>
+<HEAD>
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<TITLE>request#getReader test.</TITLE>
+</HEAD>
+<BODY>
+garbage.jsp is called.
+<HR>
+
+<%
+ String expected = (String) session.getAttribute("expected");
+ String formName = (String) session.getAttribute("formName");
+ String readSize = (String) session.getAttribute("readForGarbage");
+
+ int size = Integer.parseInt(readSize);
+ request.setCharacterEncoding("UTF-8");
+ response.setContentType("text/html; charset=UTF-8");
+ BufferedReader reader = request.getReader();
+ StringBuffer sb = new StringBuffer();
+
+ readCharB(reader, sb, 1, size);
+
+ //outln(out,sb.toString());
+
+ String boundary = null;
+ String contentType = request.getContentType();
+ if (contentType != null) {
+ int delim = contentType.indexOf("boundary=");
+ boundary = contentType.substring(delim + 9).trim();
+ }
+ expected = "--" + boundary
+ + "\r\nContent-Disposition: form-data; name=\"" + formName
+ + "\"\r\n\r\n" + expected + "\r\n--" + boundary + "--\r\n";
+
+ outln(out, "Content-Type:" + request.getContentType());
+ outln(out, "Character Encoding:" + request.getCharacterEncoding());
+ outln(out, "Content-Length:" + request.getContentLength());
+ outln(out, "read:" + sb.length());
+ outln(out, "correct:"
+ + (sb.toString().equals(expected.substring(0, sb.length()))));
+ if (sb.length() == expected.length()) {
+ outln(out, "The buffer would be empty.");
+ }else{
+ outln(out, "The garbage data may be in the byte buffer or converter buffer.");
+ outln(out, "Do reload. If the result is \"correct:false\", the garbage was effected to the request.");
+ }
+%>
+
+</BODY>
+</HTML>
+<%!void readCharB(BufferedReader br, StringBuffer sb, int bufferSize, int size)
+ throws IOException {
+ char[] buf = new char[bufferSize];
+ int read = 0;
+ while (sb.length() < size && ((read = br.read(buf)) != -1)) {
+ sb.append(buf, 0, read);
+ }
+ }
+
+ void outln(JspWriter out, String str) throws IOException {
+ out.println(str + "<BR>");
+ System.out.println(str);
+ }%>
Modified: trunk/test/webapps/reader/index.html
===================================================================
--- trunk/test/webapps/reader/index.html 2008-03-26 19:44:56 UTC (rev 553)
+++ trunk/test/webapps/reader/index.html 2008-03-27 00:26:25 UTC (rev 554)
@@ -4,20 +4,40 @@
<TITLE>request#getReader test.</TITLE>
</HEAD>
<BODY>
-<FORM method="GET" action="test.jsp" >
-Characters size:<input type="text" name="size" value="8192" /><BR>
-Use MultiByte Character:
-<input value="false" type="radio" checked name="ascii"/>yes
-<input value="true" type="radio" name="ascii"/>no
+<FORM method="GET" action="test.jsp">Characters size:<input
+ type="text" name="size" value="8192" /><BR>
+Use MultiByte Character: <input value="false" type="radio" checked
+ name="ascii" />yes <input value="true" type="radio" name="ascii" />no
+<HR>
+[test case:]<BR>
+<input value="read" type="radio" checked name="kind" /><B>only read
+[readLine()/read()/read(char[1])]</B><BR>
+<input value="garbage" type="radio" name="kind" /><B>garbage in
+buffer</B><BR>
+<ul>
+ <li>real read size:<input type="text" name="readForGarbage"
+ value="4096"><BR>
+</ul>
+<input value="mark/reset" type="radio" name="kind" /><B>mark/reset</B><BR>
+<ul>
+ <li>read size before mark():<input type="text"
+ name="readBeforeMark" value="0"><BR>
+ <li>readAheadLimit size:<input type="text" name="readAheadLimit"
+ value="8192"><BR>
+ <li>read size after mark()/before reset():<input type="text"
+ name="readAfterMark" value="8192"><BR>
+</ul>
+
<BR>
-<input type="submit" value="send" /></FORM>
<HR>
-Note:
- The real post body will be added followings:<BR>
+<input type="submit" value="submit" /></FORM>
+<HR>
+Note: The real post body will be added followings:
+<BR>
<ul>
-<li>multipart boundary.
-<li>Content-Disposition: form-data; name="[text form area name]"
-<li>Some CR/LFs.
+ <li>multipart boundary.
+ <li>Content-Disposition: form-data; name="[text form area name]"
+ <li>Some CR/LFs.
</ul>
</BODY>
</HTML>
Added: trunk/test/webapps/reader/mark.jsp
===================================================================
--- trunk/test/webapps/reader/mark.jsp (rev 0)
+++ trunk/test/webapps/reader/mark.jsp 2008-03-27 00:26:25 UTC (rev 554)
@@ -0,0 +1,111 @@
+<%@ page pageEncoding="UTF-8"%>
+<%@ page import="java.io.*"%>
+<HTML>
+<HEAD>
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<TITLE>request#getReader test.</TITLE>
+</HEAD>
+<BODY>
+mark.jsp is called.
+<HR>
+
+<%
+ request.setCharacterEncoding("UTF-8");
+ response.setContentType("text/html; charset=UTF-8");
+
+ String expected = (String) session.getAttribute("expected");
+ String formName = (String) session.getAttribute("formName");
+ String readBeforeMarkS = (String) session
+ .getAttribute("readBeforeMark");
+ int readBeforeMark = Integer.parseInt(readBeforeMarkS);
+ String readAheadLimitS = (String) session
+ .getAttribute("readAheadLimit");
+ int readAheadLimit = Integer.parseInt(readAheadLimitS);
+ String readAfterMarkS = (String) session
+ .getAttribute("readAfterMark");
+ int readAfterMark = Integer.parseInt(readAfterMarkS);
+
+ BufferedReader reader = request.getReader();
+
+ String boundary = null;
+ String contentType = request.getContentType();
+ if (contentType != null) {
+ int delim = contentType.indexOf("boundary=");
+ boundary = contentType.substring(delim + 9).trim();
+ }
+ expected = "--" + boundary
+ + "\r\nContent-Disposition: form-data; name=\"" + formName
+ + "\"\r\n\r\n" + expected + "\r\n--" + boundary + "--\r\n";
+
+ if (expected.length() < readBeforeMark) {
+ readBeforeMark = expected.length();
+ }
+ if (expected.length() < readBeforeMark + readAfterMark) {
+ readAfterMark = expected.length() - readBeforeMark;
+ }
+
+ String expectedBeforeM = expected.substring(0, readBeforeMark);
+ StringBuffer beforeMSB = new StringBuffer();
+ String expectedAfterM = expected.substring(readBeforeMark,
+ readAfterMark + readBeforeMark);
+ StringBuffer afterMSB = new StringBuffer();
+ String expectedAfterR = null;
+ String expectedAfterRex = expected.substring(readBeforeMark + readAfterMark);
+ String expectedAfterRok = expected.substring(readBeforeMark);
+ StringBuffer afterRSB = new StringBuffer();
+ boolean isResetFailExpected = (readAfterMark > readAheadLimit);
+
+ readCharB(reader, beforeMSB, 1, readBeforeMark);
+ reader.mark(readAheadLimit);
+ readCharB(reader, afterMSB, 1, readAfterMark);
+ String resetMessage = null;
+ boolean isExOccur = false;
+ try {
+ reader.reset();
+ resetMessage = "<I>no throw</I>";
+ expectedAfterR = expectedAfterRok;
+ } catch (Exception e) {
+ resetMessage = ((e instanceof IOException) && (isResetFailExpected)) ? ""
+ : "<B><I>N.G.</I></B>:";
+ resetMessage += "<I>" + e.toString() + "</I>";
+ isExOccur = true;
+ expectedAfterR = expectedAfterRex;
+ }
+ readCharB(reader, afterRSB, 1, -1);
+
+ outln(out, "Content-Type:" + request.getContentType());
+ outln(out, "Character Encoding:" + request.getCharacterEncoding());
+ outln(out, "Content-Length:" + request.getContentLength());
+%>
+<HR>
+<%
+ outln(out, "read before mark expected:" + readBeforeMark
+ + " result:" + beforeMSB.length() + " correct:<B><I>"
+ + expectedBeforeM.equals(beforeMSB.toString())+"</I></B>");
+ outln(out, "mark(" + readAheadLimit + ")");
+ outln(out, "read after mark expected:" + expectedAfterM.length() + " result:"
+ + afterMSB.length() + " correct:<B><I>"
+ + expectedAfterM.equals(afterMSB.toString())+"</I></B>");
+ outln(out, "reset() is <B><I>"+((isResetFailExpected)?"":"not ")+" allowed</I></B> to throw IOException, result:" + resetMessage);
+
+ outln(out, "read after reset expected:" + expectedAfterR.length()
+ + " result:" + afterRSB.length() + " correct:<B><I>"
+ + expectedAfterR.equals(afterRSB.toString())+"</I></B>");
+%>
+
+</BODY>
+</HTML>
+<%!void readCharB(BufferedReader br, StringBuffer sb, int bufferSize, int size)
+ throws IOException {
+ char[] buf = new char[bufferSize];
+ int read = 0;
+ while (((size == -1) || (sb.length() < size))
+ && ((read = br.read(buf)) != -1)) {
+ sb.append(buf, 0, read);
+ }
+ }
+
+ void outln(JspWriter out, String str) throws IOException {
+ out.println(str + "<BR>");
+ System.out.println(str);
+ }%>
Modified: trunk/test/webapps/reader/read.jsp
===================================================================
--- trunk/test/webapps/reader/read.jsp 2008-03-26 19:44:56 UTC (rev 553)
+++ trunk/test/webapps/reader/read.jsp 2008-03-27 00:26:25 UTC (rev 554)
@@ -10,25 +10,27 @@
<HR>
<%
-String expected = (String) session.getAttribute("expected");
-String formName = (String) session.getAttribute("formName");
-request.setCharacterEncoding("UTF-8");
-response.setContentType("text/html; charset=UTF-8");
-BufferedReader reader = request.getReader();
-StringBuffer sb = new StringBuffer();
+ String expected = (String) session.getAttribute("expected");
+ String formName = (String) session.getAttribute("formName");
+ request.setCharacterEncoding("UTF-8");
+ response.setContentType("text/html; charset=UTF-8");
+ BufferedReader reader = request.getReader();
+ StringBuffer sb = new StringBuffer();
read(reader, sb);
//outln(out,sb.toString());
-
+
String boundary = null;
String contentType = request.getContentType();
- if(contentType != null){
+ if (contentType != null) {
int delim = contentType.indexOf("boundary=");
- boundary = contentType.substring(delim+9).trim();
+ boundary = contentType.substring(delim + 9).trim();
}
- expected = "--"+boundary+"\r\nContent-Disposition: form-data; name=\""+formName+"\"\r\n\r\n"+expected+"\r\n--"+boundary+"--\r\n";
-
+ expected = "--" + boundary
+ + "\r\nContent-Disposition: form-data; name=\"" + formName
+ + "\"\r\n\r\n" + expected + "\r\n--" + boundary + "--\r\n";
+
outln(out, "Content-Type:" + request.getContentType());
outln(out, "Character Encoding:" + request.getCharacterEncoding());
outln(out, "Content-Length:" + request.getContentLength());
Modified: trunk/test/webapps/reader/readCharB.jsp
===================================================================
--- trunk/test/webapps/reader/readCharB.jsp 2008-03-26 19:44:56 UTC (rev 553)
+++ trunk/test/webapps/reader/readCharB.jsp 2008-03-27 00:26:25 UTC (rev 554)
@@ -10,25 +10,27 @@
<HR>
<%
-String expected = (String) session.getAttribute("expected");
-String formName = (String) session.getAttribute("formName");
-request.setCharacterEncoding("UTF-8");
-response.setContentType("text/html; charset=UTF-8");
-BufferedReader reader = request.getReader();
-StringBuffer sb = new StringBuffer();
+ String expected = (String) session.getAttribute("expected");
+ String formName = (String) session.getAttribute("formName");
+ request.setCharacterEncoding("UTF-8");
+ response.setContentType("text/html; charset=UTF-8");
+ BufferedReader reader = request.getReader();
+ StringBuffer sb = new StringBuffer();
readCharB(reader, sb, 1);
//outln(out,sb.toString());
-
+
String boundary = null;
String contentType = request.getContentType();
- if(contentType != null){
+ if (contentType != null) {
int delim = contentType.indexOf("boundary=");
- boundary = contentType.substring(delim+9).trim();
+ boundary = contentType.substring(delim + 9).trim();
}
- expected = "--"+boundary+"\r\nContent-Disposition: form-data; name=\""+formName+"\"\r\n\r\n"+expected+"\r\n--"+boundary+"--\r\n";
-
+ expected = "--" + boundary
+ + "\r\nContent-Disposition: form-data; name=\"" + formName
+ + "\"\r\n\r\n" + expected + "\r\n--" + boundary + "--\r\n";
+
outln(out, "Content-Type:" + request.getContentType());
outln(out, "Character Encoding:" + request.getCharacterEncoding());
outln(out, "Content-Length:" + request.getContentLength());
Modified: trunk/test/webapps/reader/readLine.jsp
===================================================================
--- trunk/test/webapps/reader/readLine.jsp 2008-03-26 19:44:56 UTC (rev 553)
+++ trunk/test/webapps/reader/readLine.jsp 2008-03-27 00:26:25 UTC (rev 554)
@@ -20,15 +20,17 @@
readLine(reader, sb);
//outln(out,sb.toString());
-
+
String boundary = null;
String contentType = request.getContentType();
- if(contentType != null){
+ if (contentType != null) {
int delim = contentType.indexOf("boundary=");
- boundary = contentType.substring(delim+9).trim();
+ boundary = contentType.substring(delim + 9).trim();
}
- expected = "--"+boundary+"\r\nContent-Disposition: form-data; name=\""+formName+"\"\r\n\r\n"+expected+"\r\n--"+boundary+"--\r\n";
-
+ expected = "--" + boundary
+ + "\r\nContent-Disposition: form-data; name=\"" + formName
+ + "\"\r\n\r\n" + expected + "\r\n--" + boundary + "--\r\n";
+
outln(out, "Content-Type:" + request.getContentType());
outln(out, "Character Encoding:" + request.getCharacterEncoding());
outln(out, "Content-Length:" + request.getContentLength());
@@ -42,7 +44,7 @@
<%!void readLine(BufferedReader br, StringBuffer sb) throws IOException {
String read = null;
while ((read = br.readLine()) != null) {
- sb.append(read+"\r\n");
+ sb.append(read + "\r\n");
}
}
Modified: trunk/test/webapps/reader/test.jsp
===================================================================
--- trunk/test/webapps/reader/test.jsp 2008-03-26 19:44:56 UTC (rev 553)
+++ trunk/test/webapps/reader/test.jsp 2008-03-27 00:26:25 UTC (rev 554)
@@ -39,26 +39,52 @@
if (sb.length() > size) {
sb.delete(size, sb.length());
}
+
+ session.setAttribute("expected", sb.toString());
+ session.setAttribute("formName", formName);
+
+ String kind = request.getParameter("kind");
+
+ if ("read".equals(kind)) {
%>
-<FORM method="POST" action="<%= response.encodeURL("readLine.jsp") %>" enctype="multipart/form-data">
-request#getReader()#readLine test<BR>
+<FORM method="POST" action="<%= response.encodeURL("readLine.jsp") %>"
+ enctype="multipart/form-data">request#getReader()#readLine test<BR>
<input type="text" name="<%= formName %>" value="<%= sb.toString() %>" />
<input type="submit" value="send" /></FORM>
-<FORM method="POST" action="<%= response.encodeURL("read.jsp") %>" enctype="multipart/form-data">
-request#getReader()#read() test<BR>
+<FORM method="POST" action="<%= response.encodeURL("read.jsp") %>"
+ enctype="multipart/form-data">request#getReader()#read() test<BR>
<input type="text" name="<%= formName %>" value="<%= sb.toString() %>" />
<input type="submit" value="send" /></FORM>
-<FORM method="POST" action="<%= response.encodeURL("readCharB.jsp") %>" enctype="multipart/form-data" >
-request#getReader()#read(char[1]) test<BR>
+<FORM method="POST" action="<%= response.encodeURL("readCharB.jsp") %>"
+ enctype="multipart/form-data">request#getReader()#read(char[1])
+test<BR>
<input type="text" name="<%= formName %>" value="<%= sb.toString() %>" />
<input type="submit" value="send" /></FORM>
-
<%
- session.setAttribute("expected", sb.toString());
- session.setAttribute("formName", formName);
+} else if ("garbage".equals(kind)) {
+ session.setAttribute("readForGarbage",request.getParameter("readForGarbage"));
%>
+<FORM method="POST"
+ action="<%= response.encodeURL("garbage.jsp")%>"
+ enctype="multipart/form-data">garbage in bufffer test<BR>
+<input type="text" name="<%= formName %>" value="<%= sb.toString() %>" />
+<input type="submit" value="send" /></FORM>
+<%
+} else if ("mark/reset".equals(kind)) {
+ session.setAttribute("readBeforeMark",request.getParameter("readBeforeMark"));
+ session.setAttribute("readAheadLimit",request.getParameter("readAheadLimit"));
+ session.setAttribute("readAfterMark",request.getParameter("readAfterMark"));
+%>
+<FORM method="POST"
+ action="<%= response.encodeURL("mark.jsp") %>"
+ enctype="multipart/form-data">mark/reset test<BR>
+<input type="text" name="<%= formName %>" value="<%= sb.toString() %>" />
+<input type="submit" value="send" /></FORM>
+<%
+}
+%>
</BODY>
</HTML>
16 years, 11 months