Return-Path: Delivered-To: apmail-tomcat-dev-archive@www.apache.org Received: (qmail 71552 invoked from network); 2 May 2007 02:23:34 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 2 May 2007 02:23:33 -0000 Received: (qmail 7082 invoked by uid 500); 2 May 2007 02:23:30 -0000 Delivered-To: apmail-tomcat-dev-archive@tomcat.apache.org Received: (qmail 6845 invoked by uid 500); 2 May 2007 02:23:28 -0000 Mailing-List: contact dev-help@tomcat.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: "Tomcat Developers List" Delivered-To: mailing list dev@tomcat.apache.org Received: (qmail 6825 invoked by uid 500); 2 May 2007 02:23:27 -0000 Delivered-To: apmail-jakarta-tomcat-dev@jakarta.apache.org Received: (qmail 6819 invoked by uid 99); 2 May 2007 02:23:27 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 01 May 2007 19:23:27 -0700 X-ASF-Spam-Status: No, hits=-99.5 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 01 May 2007 19:23:14 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id DE5791A984A; Tue, 1 May 2007 19:22:53 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r534293 [4/11] - in /tomcat/sandbox/tomcat-lite: ./ bin/ external/ java/ java/org/apache/commons/logging/ java/org/apache/tomcat/lite/ java/org/apache/tomcat/lite/ctxmap/ java/org/apache/tomcat/lite/http/ java/org/apache/tomcat/lite/http11/... Date: Wed, 02 May 2007 02:22:50 -0000 To: tomcat-dev@jakarta.apache.org From: costin@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20070502022253.DE5791A984A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/WebappServletMapper.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/WebappServletMapper.java?view=auto&rev=534293 ============================================================================== --- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/WebappServletMapper.java (added) +++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/WebappServletMapper.java Tue May 1 19:22:45 2007 @@ -0,0 +1,856 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation + * + * Licensed 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.lite; + +import java.io.File; + +import org.apache.tomcat.lite.util.MappingData; +import org.apache.tomcat.util.buf.Ascii; +import org.apache.tomcat.util.buf.CharChunk; +import org.apache.tomcat.util.buf.MessageBytes; + +/** + * Mapper, which implements the servlet API mapping rules (which are derived + * from the HTTP rules). + * + * Based on catalina mapper - but simplified. All host and context mappings + * is done in HostMapper - this is just dealing with web.xml. + * + * For corner cases ( very large number of rules, dynamic rules, etc ) you + * can override the mapper for a context with a class extending this. + */ +public class WebappServletMapper { + + /** + * Context associated with this wrapper, used for wrapper mapping. + */ + public ContextMapElement contextMapElement = new ContextMapElement(); + + + // --------------------------------------------------------- Public Methods + + public WebappServletMapper(ServletContextImpl impl) { + contextMapElement.object = impl; + contextMapElement.name = impl.getContextPath(); + } + + + /** Set context, used for wrapper mapping (request dispatcher). + * + * @param welcomeResources Welcome files defined for this context + * @param resources Static resources of the context + */ + public void setContext(String path, String[] welcomeResources, + File resources) { + contextMapElement.name = path; + contextMapElement.welcomeResources = welcomeResources; + contextMapElement.resources = resources; + } + + + /** + * Add a wrapper to the context associated with this wrapper. + * + * @param path Wrapper mapping + * @param wrapper The Wrapper object + */ + public void addWrapper(String path, Object wrapper) { + addWrapper(contextMapElement, path, wrapper); + } + + + public void addWrapper(String path, Object wrapper, boolean jspWildCard) { + addWrapper(contextMapElement, path, wrapper, jspWildCard); + } + + + public void addWrapper(ContextMapElement context, String path, Object wrapper) { + addWrapper(context, path, wrapper, false); + } + + + /** + * Adds a wrapper to the given context. + * + * @param context The context to which to add the wrapper + * @param path Wrapper mapping + * @param wrapper The Wrapper object + * @param jspWildCard true if the wrapper corresponds to the JspServlet + * and the mapping path contains a wildcard; false otherwise + */ + protected void addWrapper(ContextMapElement context, String path, Object wrapper, + boolean jspWildCard) { + + synchronized (context) { + WrapperMapElement newWrapper = new WrapperMapElement(); + newWrapper.object = wrapper; + newWrapper.jspWildCard = jspWildCard; + if (path.endsWith("/*")) { + // Wildcard wrapper + newWrapper.name = path.substring(0, path.length() - 2); + WrapperMapElement[] oldWrappers = context.wildcardWrappers; + WrapperMapElement[] newWrappers = + new WrapperMapElement[oldWrappers.length + 1]; + if (insertMap(oldWrappers, newWrappers, newWrapper)) { + context.wildcardWrappers = newWrappers; + int slashCount = slashCount(newWrapper.name); + if (slashCount > context.nesting) { + context.nesting = slashCount; + } + } + } else if (path.startsWith("*.")) { + // Extension wrapper + newWrapper.name = path.substring(2); + WrapperMapElement[] oldWrappers = context.extensionWrappers; + WrapperMapElement[] newWrappers = + new WrapperMapElement[oldWrappers.length + 1]; + if (insertMap(oldWrappers, newWrappers, newWrapper)) { + context.extensionWrappers = newWrappers; + } + } else if (path.equals("/")) { + // Default wrapper + newWrapper.name = ""; + context.defaultWrapper = newWrapper; + } else { + // Exact wrapper + newWrapper.name = path; + WrapperMapElement[] oldWrappers = context.exactWrappers; + WrapperMapElement[] newWrappers = + new WrapperMapElement[oldWrappers.length + 1]; + if (insertMap(oldWrappers, newWrappers, newWrapper)) { + context.exactWrappers = newWrappers; + } + } + } + } + + + /** + * Remove a wrapper from the context associated with this wrapper. + * + * @param path Wrapper mapping + */ + public void removeWrapper(String path) { + removeWrapper(contextMapElement, path); + } + + + protected void removeWrapper(ContextMapElement context, String path) { + synchronized (context) { + if (path.endsWith("/*")) { + // Wildcard wrapper + String name = path.substring(0, path.length() - 2); + WrapperMapElement[] oldWrappers = context.wildcardWrappers; + WrapperMapElement[] newWrappers = + new WrapperMapElement[oldWrappers.length - 1]; + if (removeMap(oldWrappers, newWrappers, name)) { + // Recalculate nesting + context.nesting = 0; + for (int i = 0; i < newWrappers.length; i++) { + int slashCount = slashCount(newWrappers[i].name); + if (slashCount > context.nesting) { + context.nesting = slashCount; + } + } + context.wildcardWrappers = newWrappers; + } + } else if (path.startsWith("*.")) { + // Extension wrapper + String name = path.substring(2); + WrapperMapElement[] oldWrappers = context.extensionWrappers; + WrapperMapElement[] newWrappers = + new WrapperMapElement[oldWrappers.length - 1]; + if (removeMap(oldWrappers, newWrappers, name)) { + context.extensionWrappers = newWrappers; + } + } else if (path.equals("/")) { + // Default wrapper + context.defaultWrapper = null; + } else { + // Exact wrapper + String name = path; + WrapperMapElement[] oldWrappers = context.exactWrappers; + WrapperMapElement[] newWrappers = + new WrapperMapElement[oldWrappers.length - 1]; + if (removeMap(oldWrappers, newWrappers, name)) { + context.exactWrappers = newWrappers; + } + } + } + } + + /** + * Map the specified URI relative to the context, + * mutating the given mapping data. + * + * @param uri URI + * @param mappingData This structure will contain the result of the mapping + * operation + */ + public void map(MessageBytes uri, MappingData mappingData) + throws Exception { + + uri.toChars(); + CharChunk uricc = uri.getCharChunk(); + //uricc.setLimit(-1); + internalMapWrapper(contextMapElement, uricc, mappingData); + + } + + + // -------------------------------------------------------- Private Methods + + + /** + * Wrapper mapping. + */ + private final void internalMapWrapper(ContextMapElement context, + CharChunk path, + MappingData mappingData) + throws Exception { + + int pathOffset = path.getOffset(); + int pathEnd = path.getEnd(); + int servletPath = pathOffset; + boolean noServletPath = false; + + int length = context.name.length(); + if (length == 1) length--; + if (length != (pathEnd - pathOffset)) { + servletPath = pathOffset + length; + } else { + noServletPath = true; + // What is this doing ??? + path.append('/'); + pathOffset = path.getOffset(); + pathEnd = path.getEnd(); + servletPath = pathOffset+length; + } + + path.setOffset(servletPath); + + // Rule 1 -- Exact Match + WrapperMapElement[] exactWrappers = context.exactWrappers; + internalMapExactWrapper(exactWrappers, path, mappingData); + + // Rule 2 -- Prefix Match + boolean checkJspWelcomeFiles = false; + WrapperMapElement[] wildcardWrappers = context.wildcardWrappers; + if (mappingData.wrapper == null) { + internalMapWildcardWrapper(wildcardWrappers, context.nesting, + path, mappingData); + if (mappingData.wrapper != null && mappingData.jspWildCard) { + char[] buf = path.getBuffer(); + if (buf[pathEnd - 1] == '/') { + /* + * Path ending in '/' was mapped to JSP servlet based on + * wildcard match (e.g., as specified in url-pattern of a + * jsp-property-group. + * Force the context's welcome files, which are interpreted + * as JSP files (since they match the url-pattern), to be + * considered. See Bugzilla 27664. + */ + mappingData.wrapper = null; + checkJspWelcomeFiles = true; + } else { + // See Bugzilla 27704 + mappingData.wrapperPath.setChars(buf, path.getStart(), + path.getLength()); + mappingData.pathInfo.recycle(); + } + } + } + + if(mappingData.wrapper == null && noServletPath) { + // The path is empty, redirect to "/" + mappingData.redirectPath.setChars + (path.getBuffer(), pathOffset, pathEnd); + path.setEnd(pathEnd - 1); + //return; + } + + // Rule 3 -- Extension Match + WrapperMapElement[] extensionWrappers = context.extensionWrappers; + if (mappingData.wrapper == null && !checkJspWelcomeFiles) { + internalMapExtensionWrapper(extensionWrappers, path, mappingData); + } + + File file = null; + // Rule 4 -- Welcome resources processing for servlets + if (mappingData.wrapper == null) { + boolean checkWelcomeFiles = checkJspWelcomeFiles; + if (!checkWelcomeFiles) { + char[] buf = path.getBuffer(); + checkWelcomeFiles = (buf[pathEnd - 1] == '/'); + } + if (checkWelcomeFiles) { + for (int i = 0; (i < context.welcomeResources.length) + && (mappingData.wrapper == null); i++) { + path.setOffset(pathOffset); + path.setEnd(pathEnd); + path.append(context.welcomeResources[i], 0, + context.welcomeResources[i].length()); + path.setOffset(servletPath); + + // Rule 4a -- Welcome resources processing for exact macth + internalMapExactWrapper(exactWrappers, path, mappingData); + + // Rule 4b -- Welcome resources processing for prefix match + if (mappingData.wrapper == null) { + internalMapWildcardWrapper + (wildcardWrappers, context.nesting, + path, mappingData); + } + + // Rule 4c -- Welcome resources processing + // for physical folder + if (mappingData.wrapper == null + && context.resources != null) { + // Default servlet: check if it's file or dir to apply + // welcome files rules. + // TODO: Save the File in attributes, + // to avoid duplication in DefaultServlet. + + String pathStr = path.toString(); + file = new File(context.resources, pathStr); + if (file.exists() && !(file.isDirectory()) ) { + + internalMapExtensionWrapper(extensionWrappers, + path, mappingData); + if (mappingData.wrapper == null + && context.defaultWrapper != null) { + mappingData.wrapper = + context.defaultWrapper.object; + mappingData.requestPath.setChars + (path.getBuffer(), path.getStart(), + path.getLength()); + mappingData.wrapperPath.setChars + (path.getBuffer(), path.getStart(), + path.getLength()); + mappingData.requestPath.setString(pathStr); + mappingData.wrapperPath.setString(pathStr); + } + } + } + } + + path.setOffset(servletPath); + path.setEnd(pathEnd); + } + + } + + + // Rule 7 -- Default servlet + if (mappingData.wrapper == null && !checkJspWelcomeFiles) { + if (context.defaultWrapper != null) { + mappingData.wrapper = context.defaultWrapper.object; + mappingData.requestPath.setChars + (path.getBuffer(), path.getStart(), path.getLength()); + mappingData.wrapperPath.setChars + (path.getBuffer(), path.getStart(), path.getLength()); + } + // Redirection to a folder + char[] buf = path.getBuffer(); + if (context.resources != null && buf[pathEnd -1 ] != '/') { + String pathStr = path.toString(); + file = new File( context.resources, pathStr); + if (file.exists() && file.isDirectory()) { + // Note: this mutates the path: do not do any processing + // after this (since we set the redirectPath, there + // shouldn't be any) + path.setOffset(pathOffset); + path.append('/'); + mappingData.redirectPath.setChars + (path.getBuffer(), path.getStart(), path.getLength()); + } else { + mappingData.requestPath.setString(pathStr); + mappingData.wrapperPath.setString(pathStr); + } + } + } + + path.setOffset(pathOffset); + path.setEnd(pathEnd); + } + + + /** + * Exact mapping. + */ + private final void internalMapExactWrapper + (WrapperMapElement[] wrappers, CharChunk path, MappingData mappingData) { + int pos = find(wrappers, path); + if ((pos != -1) && (path.equals(wrappers[pos].name))) { + mappingData.requestPath.setString(wrappers[pos].name); + mappingData.wrapperPath.setString(wrappers[pos].name); + mappingData.wrapper = wrappers[pos].object; + } + } + + + /** + * Wildcard mapping. + */ + private final void internalMapWildcardWrapper + (WrapperMapElement[] wrappers, int nesting, CharChunk path, + MappingData mappingData) { + + int pathEnd = path.getEnd(); + int pathOffset = path.getOffset(); + + int lastSlash = -1; + int length = -1; + int pos = find(wrappers, path); + if (pos != -1) { + boolean found = false; + while (pos >= 0) { + if (path.startsWith(wrappers[pos].name)) { + length = wrappers[pos].name.length(); + if (path.getLength() == length) { + found = true; + break; + } else if (path.startsWithIgnoreCase("/", length)) { + found = true; + break; + } + } + if (lastSlash == -1) { + lastSlash = nthSlash(path, nesting + 1); + } else { + lastSlash = lastSlash(path); + } + path.setEnd(lastSlash); + pos = find(wrappers, path); + } + path.setEnd(pathEnd); + if (found) { + mappingData.wrapperPath.setString(wrappers[pos].name); + if (path.getLength() > length) { + mappingData.pathInfo.setChars + (path.getBuffer(), + path.getOffset() + length, + path.getLength() - length); + } + mappingData.requestPath.setChars + (path.getBuffer(), path.getOffset(), path.getLength()); + mappingData.wrapper = wrappers[pos].object; + mappingData.jspWildCard = wrappers[pos].jspWildCard; + } + } + } + + + /** + * Extension mappings. + */ + private final void internalMapExtensionWrapper + (WrapperMapElement[] wrappers, CharChunk path, MappingData mappingData) { + char[] buf = path.getBuffer(); + int pathEnd = path.getEnd(); + int servletPath = path.getOffset(); + int slash = -1; + for (int i = pathEnd - 1; i >= servletPath; i--) { + if (buf[i] == '/') { + slash = i; + break; + } + } + if (slash == -1 ) slash = 0; + if (slash >= 0) { + int period = -1; + for (int i = pathEnd - 1; i > slash; i--) { + if (buf[i] == '.') { + period = i; + break; + } + } + if (period >= 0) { + path.setOffset(period + 1); + path.setEnd(pathEnd); + int pos = find(wrappers, path); + if ((pos != -1) + && (path.equals(wrappers[pos].name))) { + mappingData.wrapperPath.setChars + (buf, servletPath, pathEnd - servletPath); + mappingData.requestPath.setChars + (buf, servletPath, pathEnd - servletPath); + mappingData.wrapper = wrappers[pos].object; + } + path.setOffset(servletPath); + path.setEnd(pathEnd); + } + } + } + + + /** + * Find a map elemnt given its name in a sorted array of map elements. + * This will return the index for the closest inferior or equal item in the + * given array. + */ + public static final int find(MapElement[] map, CharChunk name) { + return find(map, name, name.getStart(), name.getEnd()); + } + + + /** + * Find a map elemnt given its name in a sorted array of map elements. + * This will return the index for the closest inferior or equal item in the + * given array. + */ + private static final int find(MapElement[] map, CharChunk name, + int start, int end) { + + int a = 0; + int b = map.length - 1; + + // Special cases: -1 and 0 + if (b == -1) { + return -1; + } + + if (compare(name, start, end, map[0].name) < 0 ) { + return -1; + } + if (b == 0) { + return 0; + } + + int i = 0; + while (true) { + i = (b + a) / 2; + int result = compare(name, start, end, map[i].name); + if (result == 1) { + a = i; + } else if (result == 0) { + return i; + } else { + b = i; + } + if ((b - a) == 1) { + int result2 = compare(name, start, end, map[b].name); + if (result2 < 0) { + return a; + } else { + return b; + } + } + } + + } + + /** + * Find a map elemnt given its name in a sorted array of map elements. + * This will return the index for the closest inferior or equal item in the + * given array. + */ + private static final int findIgnoreCase(MapElement[] map, CharChunk name) { + return findIgnoreCase(map, name, name.getStart(), name.getEnd()); + } + + + /** + * Find a map elemnt given its name in a sorted array of map elements. + * This will return the index for the closest inferior or equal item in the + * given array. + */ + private static final int findIgnoreCase(MapElement[] map, CharChunk name, + int start, int end) { + + int a = 0; + int b = map.length - 1; + + // Special cases: -1 and 0 + if (b == -1) { + return -1; + } + if (compareIgnoreCase(name, start, end, map[0].name) < 0 ) { + return -1; + } + if (b == 0) { + return 0; + } + + int i = 0; + while (true) { + i = (b + a) / 2; + int result = compareIgnoreCase(name, start, end, map[i].name); + if (result == 1) { + a = i; + } else if (result == 0) { + return i; + } else { + b = i; + } + if ((b - a) == 1) { + int result2 = compareIgnoreCase(name, start, end, map[b].name); + if (result2 < 0) { + return a; + } else { + return b; + } + } + } + + } + + + /** + * Find a map elemnt given its name in a sorted array of map elements. + * This will return the index for the closest inferior or equal item in the + * given array. + */ + public static final int find(MapElement[] map, String name) { + + int a = 0; + int b = map.length - 1; + + // Special cases: -1 and 0 + if (b == -1) { + return -1; + } + + if (name.compareTo(map[0].name) < 0) { + return -1; + } + if (b == 0) { + return 0; + } + + int i = 0; + while (true) { + i = (b + a) / 2; + int result = name.compareTo(map[i].name); + if (result > 0) { + a = i; + } else if (result == 0) { + return i; + } else { + b = i; + } + if ((b - a) == 1) { + int result2 = name.compareTo(map[b].name); + if (result2 < 0) { + return a; + } else { + return b; + } + } + } + + } + + + /** + * Compare given char chunk with String. + * Return -1, 0 or +1 if inferior, equal, or superior to the String. + */ + private static final int compare(CharChunk name, int start, int end, + String compareTo) { + int result = 0; + char[] c = name.getBuffer(); + int len = compareTo.length(); + if ((end - start) < len) { + len = end - start; + } + for (int i = 0; (i < len) && (result == 0); i++) { + if (c[i + start] > compareTo.charAt(i)) { + result = 1; + } else if (c[i + start] < compareTo.charAt(i)) { + result = -1; + } + } + if (result == 0) { + if (compareTo.length() > (end - start)) { + result = -1; + } else if (compareTo.length() < (end - start)) { + result = 1; + } + } + return result; + } + + + /** + * Compare given char chunk with String ignoring case. + * Return -1, 0 or +1 if inferior, equal, or superior to the String. + */ + private static final int compareIgnoreCase(CharChunk name, int start, int end, + String compareTo) { + int result = 0; + char[] c = name.getBuffer(); + int len = compareTo.length(); + if ((end - start) < len) { + len = end - start; + } + for (int i = 0; (i < len) && (result == 0); i++) { + if (Ascii.toLower(c[i + start]) > Ascii.toLower(compareTo.charAt(i))) { + result = 1; + } else if (Ascii.toLower(c[i + start]) < Ascii.toLower(compareTo.charAt(i))) { + result = -1; + } + } + if (result == 0) { + if (compareTo.length() > (end - start)) { + result = -1; + } else if (compareTo.length() < (end - start)) { + result = 1; + } + } + return result; + } + + + /** + * Find the position of the last slash in the given char chunk. + */ + public static final int lastSlash(CharChunk name) { + + char[] c = name.getBuffer(); + int end = name.getEnd(); + int start = name.getStart(); + int pos = end; + + while (pos > start) { + if (c[--pos] == '/') { + break; + } + } + + return (pos); + + } + + + /** + * Find the position of the nth slash, in the given char chunk. + */ + public static final int nthSlash(CharChunk name, int n) { + + char[] c = name.getBuffer(); + int end = name.getEnd(); + int start = name.getStart(); + int pos = start; + int count = 0; + + while (pos < end) { + if ((c[pos++] == '/') && ((++count) == n)) { + pos--; + break; + } + } + + return (pos); + + } + + + /** + * Return the slash count in a given string. + */ + public static final int slashCount(String name) { + int pos = -1; + int count = 0; + while ((pos = name.indexOf('/', pos + 1)) != -1) { + count++; + } + return count; + } + + + /** + * Insert into the right place in a sorted MapElement array, and prevent + * duplicates. + */ + public static final boolean insertMap + (MapElement[] oldMap, MapElement[] newMap, MapElement newElement) { + int pos = find(oldMap, newElement.name); + if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) { + return false; + } + System.arraycopy(oldMap, 0, newMap, 0, pos + 1); + newMap[pos + 1] = newElement; + System.arraycopy + (oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1); + return true; + } + + + /** + * Insert into the right place in a sorted MapElement array. + */ + public static final boolean removeMap + (MapElement[] oldMap, MapElement[] newMap, String name) { + int pos = find(oldMap, name); + if ((pos != -1) && (name.equals(oldMap[pos].name))) { + System.arraycopy(oldMap, 0, newMap, 0, pos); + System.arraycopy(oldMap, pos + 1, newMap, pos, + oldMap.length - pos - 1); + return true; + } + return false; + } + + + // ------------------------------------------------- MapElement Inner Class + + + protected static abstract class MapElement { + /** hostname or path + */ + public String name = null; + public Object object = null; + + public String toString() { + return "MapElement: \"" + name +"\""; + } + } + + + // ---------------------------------------------------- Context Inner Class + + + public static final class ContextMapElement + extends MapElement { + + public String[] welcomeResources = new String[0]; + public File resources = null; + public WrapperMapElement defaultWrapper = null; + public WrapperMapElement[] exactWrappers = new WrapperMapElement[0]; + public WrapperMapElement[] wildcardWrappers = new WrapperMapElement[0]; + public WrapperMapElement[] extensionWrappers = new WrapperMapElement[0]; + public int nesting = 0; + + public String toString() { + return "ContextMapElement {" + + "name: \"" + name + + "\"\nnesting: \"" + nesting + + "\"\n}"; + } + } + + + // ---------------------------------------------------- Wrapper Inner Class + + + public static class WrapperMapElement + extends MapElement { + public boolean jspWildCard = false; + } + +} Added: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11Buffer.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11Buffer.java?view=auto&rev=534293 ============================================================================== --- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11Buffer.java (added) +++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11Buffer.java Tue May 1 19:22:45 2007 @@ -0,0 +1,1384 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation + * + * Licensed 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.lite.http11; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; + +import org.apache.tomcat.lite.util.MessageWriter; +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.http.ContentType; +import org.apache.tomcat.util.http.Cookies; +import org.apache.tomcat.util.http.HttpMessages; +import org.apache.tomcat.util.http.MimeHeaders; +import org.apache.tomcat.util.http.Parameters; + +/** + * Non-blocking parser for request line and headers. + * + * + * Implementation of InputBuffer which provides HTTP request header parsing as + * well as transfer decoding. + * + * Will also hold/cache headers. + * + * There is one Http11Buffer per connection. It doesn't hold a Request + * reference - Requests are per/thread and represent active operations. + * + * @author Remy Maucherat + * @author costin + */ +public class Http11Buffer { + + /** + * CRLF. + */ + public static final String CRLF = "\r\n"; + + + /** + * Server string. + */ + public static final byte[] SERVER_BYTES = + "Server: Apache-Coyote/1.1\r\n".getBytes(); + + + /** + * CR. + */ + public static final byte CR = (byte) '\r'; + + + /** + * LF. + */ + public static final byte LF = (byte) '\n'; + + + /** + * SP. + */ + public static final byte SP = (byte) ' '; + + + /** + * HT. + */ + public static final byte HT = (byte) '\t'; + + + /** + * COLON. + */ + public static final byte COLON = (byte) ':'; + + /** + * SEMI_COLON. + */ + public static final byte SEMI_COLON = (byte) ';'; + + /** + * 'A'. + */ + public static final byte A = (byte) 'A'; + + + /** + * 'a'. + */ + public static final byte a = (byte) 'a'; + + + /** + * 'Z'. + */ + public static final byte Z = (byte) 'Z'; + + + /** + * Lower case offset. + */ + public static final byte LC_OFFSET = A - a; + + + + /** + * '?'. + */ + public static final byte QUESTION = (byte) '?'; + + + + /** + * HTTP/1.0. + */ + public static final String HTTP_10 = "HTTP/1.0"; + + public static final byte[] _200_BYTES = {'2', '0', '0'}; + + public static final byte[] _400_BYTES = {'4', '0' , '0'}; + + public static final byte[] _404_BYTES = { '4', '0', '4' }; + + + /** + * HTTP/1.1. + */ + public static final String HTTP_11 = "HTTP/1.1"; + public static final byte[] HTTP_11_BYTES = HTTP_11.getBytes(); + + public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1"; + + /** + * Default locale as mandated by the spec. + */ + private static final Locale DEFAULT_LOCALE = Locale.getDefault(); + + + /** + * Post data buffer. + */ + public final static int CACHED_POST_LEN = 8192; + + // coyote uses 48K - not sure how arbitrary it is. + // should be <= output buffer size + private final static int HEAD_SIZE = 32 * 1026; + + // Fields. + + // Wrappers around the input buffer. + public MessageBytes methodMB = MessageBytes.newInstance(); + public MessageBytes unparsedURIMB = MessageBytes.newInstance(); + public MessageBytes uriMB = MessageBytes.newInstance(); + public MessageBytes decodedUriMB = MessageBytes.newInstance(); + + public MessageBytes queryMB = MessageBytes.newInstance(); + public MessageBytes protoMB = MessageBytes.newInstance(); + + public MimeHeaders headers = new MimeHeaders(); + + // Buffer + + /** Last valid byte in the buf[] + */ + public int lastValid; + + /** Position in the buffer. + */ + public int pos; + + /** + * Pointer to the current read buffer. + */ + public byte[] buf; + + public byte[] postData = null; + + // for output + public MessageWriter messageWriter = new MessageWriter(8192); + + + // Options + /** + * Swallow input ? (in the case of an expectation) + */ + protected boolean swallowInput; + /** + * Pointer to the US-ASCII header buffer. + */ + protected char[] ascbuf; + // res + protected String contentType = null; + protected String contentLanguage = null; + protected String characterEncoding = DEFAULT_CHARACTER_ENCODING; + + /** + * Has the charset been explicitly set. + */ + protected boolean charsetSet = false; + // ---------------- Res + /** + * Holds request error exception. + */ + protected Throwable errorException = null; + //Request req; + // Protocol components ( both req and response ) + // TODO: move the relevant buffers here. Now using it as a struct{} + // status code + int status; + + boolean commited; + String message; + + + int resStatus = 200; + + // restart from this position + int lastParsed = 0; + + // 0: parsing request line + // 1: parsing headers + // 2: request done + int state = 0; + + static int STATE_REQUEST_LINE = 0; + static int STATE_HEADERS = 1; + static int STATE_BODY = 2; + + // remote address/host + private int serverPort = -1; + + private MessageBytes serverNameMB = MessageBytes.newInstance(); + + + private int remotePort; + private int localPort; + + + + private MessageBytes schemeMB = MessageBytes.newInstance(); + + private MessageBytes instanceId = MessageBytes.newInstance(); + + + /** + * URL decoder. + */ + private UDecoder urlDecoder = new UDecoder(); + + + /** + * HTTP specific fields. (remove them ?) + */ + private long contentLength = -1; + + private MessageBytes contentTypeMB = null; + + + private String charEncoding = null; + private Cookies cookies = new Cookies(headers); + private Parameters parameters = new Parameters(); + private MessageBytes remoteUser=MessageBytes.newInstance(); + + private MessageBytes authType=MessageBytes.newInstance(); + + private HashMap attributes=new HashMap(); + + + // --------------------------------------------------------- Public Methods + + private int bytesRead=0; + + // Time of the request - usefull to avoid repeated calls to System.currentTime + private long startTime = 0L; + + // General informations + private long bytesWritten=0; + + + private Locale locale = DEFAULT_LOCALE; + + public Http11Buffer() { + this(HEAD_SIZE); + } + + public Http11Buffer(int headerBufferSize) { + //this.req = req; + buf = new byte[headerBufferSize]; + // TODO: do we really need this ? How does it interact with MessageBytes ? + ascbuf = new char[headerBufferSize]; + + state = STATE_REQUEST_LINE; + swallowInput = true; + } + + + public void addHeader(String name, String value) { + char cc=name.charAt(0); + if( cc=='C' || cc=='c' ) { + if( checkSpecialHeader(name, value) ) + return; + } + headers.addValue(name).setString( value ); + } + + /** + * Warning: This method always returns false for Content-Type + * and Content-Length. + */ + public boolean containsHeader(String name) { + return headers.getHeader(name) != null; + } + + public MessageBytes decodedURI() { + return decodedUriMB; + } + + + /** + * End the header block. + */ + public void endHeaders() { + + buf[pos++] = CR; + buf[pos++] = LF; + + } + + /** + * End request (consumes leftover bytes). + * + * @throws IOException an undelying I/O error occured + */ + public void endRequest() + throws IOException { + +// if (swallowInput && (lastActiveFilter != -1)) { +// int extraBytes = (int) activeFilters[lastActiveFilter].end(); +// pos = pos - extraBytes; +// } + + } + + /** + * Get the character encoding used for this request. + */ + public String getCharacterEncoding() { + + if (charEncoding != null) + return charEncoding; + + charEncoding = ContentType.getCharsetFromContentType(getContentType()); + return charEncoding; + + } + + /** + * Return the content language. + */ + public String getContentLanguage() { + return contentLanguage; + } + + + + + + + public int getContentLength() { + long length = getContentLengthLong(); + + if (length < Integer.MAX_VALUE) { + return (int) length; + } + return -1; + } + public long getContentLengthLong() { + return contentLength; + } + public String getContentType() { + + String ret = contentType; + + if (ret != null + && characterEncoding != null + && charsetSet) { + ret = ret + ";charset=" + characterEncoding; + } + + return ret; + } + public Cookies getCookies() { + return cookies; + } + + /** + * Get the Exception that occurred during request + * processing. + */ + public Throwable getErrorException() { + return errorException; + } + + + // ---------------- Buffers + public int getFreeSpace() { + return buf.length - lastValid; + } + + + public int getFreeSpace(int sz) { + int remain = getFreeSpace(); + return (sz > remain) ? remain :sz; + } + + + public String getHeader(String name) { + return headers.getHeader(name); + } + + public Locale getLocale() { + return locale; + } + + /** + * Get the status message. + */ + public String getMessage() { + return message; + } + + public MimeHeaders getMimeHeaders() { + return headers; + } + + public Parameters getParameters() { + return parameters; + } + + public int getStatus() { + return resStatus; + } + + public UDecoder getURLDecoder() { + return urlDecoder; + } + + + + public boolean isCommitted() { + return commited; + } + public boolean isExceptionPresent() { + return ( errorException != null ); + } + + + public MessageBytes method() { + return methodMB; + } + + + + /** + * End processing of current HTTP request. + * Note: All bytes of the current request should have been already + * consumed. This method only resets all the pointers so that we are ready + * to parse the next HTTP request. + */ + public void nextRequest() { + swallowInput = true; + if (pos < lastValid) { + System.arraycopy(buf, pos, buf, 0, lastValid - pos); + lastValid -= pos; + } else { + lastValid = 0; + } + pos = 0; + state = STATE_REQUEST_LINE; + swallowInput = true; + } + + + /** + * Parse HTTP headers. + * + * @return false when not done, true when all headers have been parsed. + */ + public boolean parseHeaders() + throws IOException { + + // lastParsed is the end of the previous line ( request line or header ) + pos = lastParsed; + + while( state == STATE_HEADERS) { + // Check for blank line + byte chr = 0; + + // End of headers or ? + while (true) { + // Read new bytes if needed + if (pos >= lastValid) { + return false; + } + chr = buf[pos]; + if ((chr == CR) || (chr == LF)) { + if (chr == LF) { + pos++; + // Done with the headers + state = STATE_BODY; + lastParsed = pos; + return true; + } // else: CR - keep going + } else { + break; // real char + } + pos++; + } + + // Mark the current buffer position + int start = pos; + int headerStart = pos; + // Reading the header name. Header name is always US-ASCII + boolean colon = false; + int colonPos = pos; + + while (!colon) { + // Read new bytes if needed + if (pos >= lastValid) { + return false; + } + if (buf[pos] == COLON) { + colon = true; + colonPos = pos; + } + chr = buf[pos]; + // Convert header to-lower case ??? Modify the buffer ? + // TODO: FIX THIS + if ((chr >= A) && (chr <= Z)) { + buf[pos] = (byte) (chr - LC_OFFSET); + } + ascbuf[pos] = (char) buf[pos]; + pos++; + } + + // Mark the current buffer position + start = pos; + int realPos = pos; + + // Reading the header value (which can be spanned on multiple lines) + boolean eol = false; + boolean validLine = true; + + while (validLine) { + boolean space = true; + // Skipping spaces + while (space) { + if ((buf[pos] == SP) || + (buf[pos] == HT)) { + if (pos >= lastValid) return false; + pos++; + } else { + space = false; + } + } + + // Reading bytes until the end of the line. Also move the + // bytes to normalize the value + int lastSignificantChar = realPos; + // TODO: remove normalization, do it at the end or never + while (!eol) { + if (buf[pos] == CR) { + } else if (buf[pos] == LF) { + eol = true; + } else if (buf[pos] == SP) { + buf[realPos] = buf[pos]; + realPos++; + } else { + buf[realPos] = buf[pos]; + buf[pos] = SP; // so next time we skip it. + realPos++; + lastSignificantChar = realPos; + } + + if (pos >= lastValid) return false; + pos++; + } + + realPos = lastSignificantChar; + + // Checking the first character of the new line. If the character + // is a LWS, then it's a multiline header + // Read new bytes if needed + if (pos >= lastValid) return false; + + chr = buf[pos]; + if ((chr != SP) && (chr != HT)) { + validLine = false; + } else { + eol = false; + // Copying one extra space in the buffer (since there must + // be at least one space inserted between the lines) + buf[realPos] = chr; + realPos++; + } + } + + // Set the header value + MessageBytes headerValue = + headers.addValue(ascbuf, headerStart, + colonPos - headerStart); + headerValue.setBytes(buf, start, realPos - start); + + lastParsed = pos; + } + + state = STATE_BODY; + lastParsed = pos; + return true; + } + + + /** + * Read the request line. This function is meant to be used during the + * HTTP request header parsing. Do NOT attempt to read the request body + * using it. + * + * @throws IOException If an exception occurs during the underlying socket + * read operations, or if the given buffer is not big enough to accomodate + * the whole line. + */ + public boolean parseRequestLine() + throws IOException { + + int start = 0; + + lastParsed = pos; + state = STATE_REQUEST_LINE; + + // Skipping blank lines + byte chr = 0; + do { + + // Read new bytes if needed + if (pos >= lastValid) { + return false; + } + + chr = buf[pos++]; + + } while ((chr == CR) || (chr == LF)); + + pos--; + + // Mark the current buffer position + start = pos; + + // + // Reading the method name + // Method name is always US-ASCII + // + boolean space = false; + + while (!space) { + // Read new bytes if needed + if (pos >= lastValid) { + return false; + } + + ascbuf[pos] = (char) buf[pos]; + + if (buf[pos] == SP) { + space = true; + methodMB.setChars(ascbuf, start, pos - start); + } + pos++; + } + + // Mark the current buffer position + start = pos; + int end = 0; + int questionPos = -1; + + // Reading the URI + space = false; + boolean eol = false; + while (!space) { + // Read new bytes if needed + if (pos >= lastValid) { + return false; + } + if (buf[pos] == SP) { + space = true; + end = pos; + } else if ((buf[pos] == CR) + || (buf[pos] == LF)) { + // HTTP/0.9 style request + eol = true; + space = true; + end = pos; + } else if ((buf[pos] == QUESTION) + && (questionPos == -1)) { + questionPos = pos; + } + pos++; + } + + unparsedURIMB.setBytes(buf, start, end - start); + if (questionPos >= 0) { + queryMB.setBytes(buf, questionPos + 1, + end - questionPos - 1); + uriMB.setBytes(buf, start, questionPos - start); + } else { + uriMB.setBytes(buf, start, end - start); + } + + // Mark the current buffer position + start = pos; + end = 0; + + // Reading the protocol. Protocol is always US-ASCII + while (!eol) { + // Read new bytes if needed + if (pos >= lastValid) { + return false; + } + ascbuf[pos] = (char) buf[pos]; + + if (buf[pos] == CR) { + end = pos; + } else if (buf[pos] == LF) { + if (end == 0) + end = pos; + eol = true; + } + pos++; + } + + if ((end - start) > 0) { + protoMB.setChars(ascbuf, start, end - start); + } else { + protoMB.setString(""); + } + + state = STATE_HEADERS; + lastParsed = pos; + return true; + } + + public boolean parseResponseLine() + throws IOException { + int start = 0; + lastParsed = pos; + state = STATE_REQUEST_LINE; + + // Mark the current buffer position + start = pos; + + + // Protocol is always US-ASCII + boolean space = false; + + while (!space) { + ascbuf[pos] = (char) buf[pos]; + if (buf[pos] == SP) { + space = true; + methodMB.setChars(ascbuf, start, pos - start); + } + if (pos >= lastValid) return false; + pos++; + } + + // Mark the current buffer position + start = pos; + int end = 0; + + // Read status code + space = false; + boolean eol = false; + while (!space) { + if (pos >= lastValid) return false; + + if (buf[pos] == SP) { + space = true; + end = pos; + } else if ((buf[pos] == CR) + || (buf[pos] == LF)) { + // HTTP/0.9 style request + eol = true; + space = true; + end = pos; + } + pos++; + } + + // Mark the current buffer position + start = pos; + end = 0; + + // Status message + while (!eol) { + // Read new bytes if needed + if (pos >= lastValid) { + return false; + } + ascbuf[pos] = (char) buf[pos]; + + if (buf[pos] == CR) { + end = pos; + } else if (buf[pos] == LF) { + if (end == 0) + end = pos; + eol = true; + } + pos++; + } + + if ((end - start) > 0) { + protoMB.setChars(ascbuf, start, end - start); + } else { + protoMB.setString(""); + } + + state = STATE_HEADERS; + lastParsed = pos; + return true; + } + + public MessageBytes protocol() { + return protoMB; + } + + public MessageBytes queryString() { + return queryMB; + } + + /** + * Recycle the input buffer. This should be called when closing the + * connection. + */ + public void recycle() { + lastValid = 0; + pos = 0; + swallowInput = true; + + messageWriter.recycle(); + + resStatus = 200; + contentType = null; + contentLanguage = null; + locale = DEFAULT_LOCALE; + characterEncoding = DEFAULT_CHARACTER_ENCODING; + charsetSet = false; + contentLength = -1; + message = null; + commited = false; + errorException = null; + headers.clear(); + + + // update counters + bytesWritten=0; + + bytesRead=0; + + contentLength = -1; + contentTypeMB = null; + charEncoding = null; + headers.recycle(); + serverNameMB.recycle(); + serverPort=-1; + localPort = -1; + remotePort = -1; + + cookies.recycle(); + parameters.recycle(); + + unparsedURIMB.recycle(); + uriMB.recycle(); + decodedUriMB.recycle(); + queryMB.recycle(); + methodMB.recycle(); + protoMB.recycle(); + + schemeMB.recycle(); + + instanceId.recycle(); + remoteUser.recycle(); + authType.recycle(); + attributes.clear(); + + + } + public MessageBytes requestURI() { + return uriMB; + } + + + public void reset() + throws IllegalStateException { + + // Reset the headers only if this is the main request, + // not for included + contentType = null; + locale = DEFAULT_LOCALE; + contentLanguage = null; + characterEncoding = DEFAULT_CHARACTER_ENCODING; + contentLength = -1; + charsetSet = false; + + status = 200; + message = null; + headers.clear(); + + // Force the PrintWriter to flush its data to the output + // stream before resetting the output stream + // + // Reset the stream + if (commited) { + //String msg = sm.getString("servletOutputStreamImpl.reset.ise"); + throw new IllegalStateException(); + } + } + public MessageBytes scheme() { + return schemeMB; + } + + + /** + * Send a header. + * + * @param name Header name + * @param value Header value + */ + public void sendHeader(ByteChunk name, ByteChunk value) { + + write(name); + buf[pos++] = COLON; + buf[pos++] = SP; + write(value); + buf[pos++] = CR; + buf[pos++] = LF; + + } + + /** + * Send a header. + * + * @param name Header name + * @param value Header value + */ + public void sendHeader(MessageBytes name, MessageBytes value) { + + write(name); + buf[pos++] = COLON; + buf[pos++] = SP; + write(value); + buf[pos++] = CR; + buf[pos++] = LF; + + } + + /** + * Send a header. + * + * @param name Header name + * @param value Header value + */ + public void sendHeader(String name, String value) { + + write(name); + buf[pos++] = COLON; + buf[pos++] = SP; + write(value); + buf[pos++] = CR; + buf[pos++] = LF; + + } + + /** + * Send the response status line. + */ + public void sendStatus(int status, String message) { + + // Write protocol name + write(HTTP_11_BYTES); + buf[pos++] = SP; + + // Write status code + switch (status) { + case 200: + write(_200_BYTES); + break; + case 400: + write(_400_BYTES); + break; + case 404: + write(_404_BYTES); + break; + default: + write(status); + } + + buf[pos++] = SP; + + // Write message + if (message == null) { + write(getMessage(status)); + } else { + write(message); + } + + buf[pos++] = CR; + buf[pos++] = LF; + } + + public void setCharacterEncoding(String enc) { + this.charEncoding = enc; + } + + public void setCommitted(boolean v) { + this.commited = v; + } + + public void setContentLength(int contentLength) { + this.contentLength = contentLength; + } + public void setContentLength(long contentLength) { + this.contentLength = contentLength; + } + + /** + * Sets the content type. + * + * This method must preserve any response charset that may already have + * been set via a call to response.setContentType(), response.setLocale(), + * or response.setCharacterEncoding(). + * + * @param type the content type + */ + public void setContentType(String type) { + + int semicolonIndex = -1; + + if (type == null) { + this.contentType = null; + return; + } + + /* + * Remove the charset param (if any) from the Content-Type, and use it + * to set the response encoding. + * The most recent response encoding setting will be appended to the + * response's Content-Type (as its charset param) by getContentType(); + */ + boolean hasCharset = false; + int len = type.length(); + int index = type.indexOf(';'); + while (index != -1) { + semicolonIndex = index; + index++; + while (index < len && Character.isSpace(type.charAt(index))) { + index++; + } + if (index+8 < len + && type.charAt(index) == 'c' + && type.charAt(index+1) == 'h' + && type.charAt(index+2) == 'a' + && type.charAt(index+3) == 'r' + && type.charAt(index+4) == 's' + && type.charAt(index+5) == 'e' + && type.charAt(index+6) == 't' + && type.charAt(index+7) == '=') { + hasCharset = true; + break; + } + index = type.indexOf(';', index); + } + + if (!hasCharset) { + this.contentType = type; + return; + } + + this.contentType = type.substring(0, semicolonIndex); + String tail = type.substring(index+8); + int nextParam = tail.indexOf(';'); + String charsetValue = null; + if (nextParam != -1) { + this.contentType += tail.substring(nextParam); + charsetValue = tail.substring(0, nextParam); + } else { + charsetValue = tail; + } + + // The charset value may be quoted, but must not contain any quotes. + if (charsetValue != null && charsetValue.length() > 0) { + charsetSet=true; + charsetValue = charsetValue.replace('"', ' '); + this.characterEncoding = charsetValue.trim(); + } + } + + /** + * Set the error Exception that occurred during + * request processing. + */ + public void setErrorException(Exception ex) { + errorException = ex; + } + + + + public void setHeader(String name, String value) { + char cc=name.charAt(0); + if( cc=='C' || cc=='c' ) { + if( checkSpecialHeader(name, value) ) + return; + } + headers.setValue(name).setString( value); + } + + + // -------------------------- Output methods ---------------------- + // Used for response and new request generation + + /** + * Called explicitely by user to set the Content-Language and + * the default encoding + */ + public void setLocale(Locale locale) { + + if (locale == null) { + return; // throw an exception? + } + + // Save the locale for use by getLocale() + this.locale = locale; + + // Set the contentLanguage for header output + contentLanguage = locale.getLanguage(); + if ((contentLanguage != null) && (contentLanguage.length() > 0)) { + String country = locale.getCountry(); + StringBuffer value = new StringBuffer(contentLanguage); + if ((country != null) && (country.length() > 0)) { + value.append('-'); + value.append(country); + } + contentLanguage = value.toString(); + } + + } + + + /** + * Set the status message. + */ + public void setMessage(String message) { + this.message = message; + } + + + /** + * Set the response status + */ + public void setStatus( int status ) { + this.resStatus = status; + } + + + /** + * Set the swallow input flag. + */ + public void setSwallowInput(boolean swallowInput) { + this.swallowInput = swallowInput; + } + + + public String toString() { + return new String(buf, 0, lastValid); + } + + + /** + * This method will write the contents of the specyfied byte + * buffer to the output stream, without filtering. This method is meant to + * be used to write the response header. + * + * @param b data to be written + */ + public void write(byte[] b) { + + // Writing the byte chunk to the output buffer + System.arraycopy(b, 0, buf, pos, b.length); + pos = pos + b.length; + + } + + /** + * This method will write the contents of the specyfied message bytes + * buffer to the output stream, without filtering. This method is meant to + * be used to write the response header. + * + * @param bc data to be written + */ + protected void write(ByteChunk bc) { + + // Writing the byte chunk to the output buffer + System.arraycopy(bc.getBytes(), bc.getStart(), buf, pos, + bc.getLength()); + pos = pos + bc.getLength(); + + } + + protected void write(CharSequence cc) { + for (int i = 0; i < cc.length(); i++) { + char c = cc.charAt(i); + // Note: This is clearly incorrect for many strings, + // but is the only consistent approach within the current + // servlet framework. It must suffice until servlet output + // streams properly encode their output. + if ((c <= 31) && (c != 9)) { + c = ' '; + } else if (c == 127) { + c = ' '; + } + buf[pos++] = (byte) c; + } + } + + /** + * This method will print the specified integer to the output stream, + * without filtering. This method is meant to be used to write the + * response header. + * + * @param i data to be written + */ + protected void write(int i) { + + write(String.valueOf(i)); + + } + + + /** + * This method will write the contents of the specyfied message bytes + * buffer to the output stream, without filtering. This method is meant to + * be used to write the response header. + * + * @param mb data to be written + */ + protected void write(MessageBytes mb) { + + if (mb.isBytes()) { + ByteChunk bc = mb.getByteChunk(); + write(bc); + } else { + write(mb.getCharSequence()); + } + + } + + /** + * Set internal fields for special header names. + * Called from set/addHeader. + * Return true if the header is special, no need to set the header. + */ + private boolean checkSpecialHeader( String name, String value) { + // XXX Eliminate redundant fields !!! + // ( both header and in special fields ) + if( name.equalsIgnoreCase( "Content-Type" ) ) { + setContentType( value ); + return true; + } + if( name.equalsIgnoreCase( "Content-Length" ) ) { + try { + long cL=Long.parseLong( value ); + setContentLength( cL ); + return true; + } catch( NumberFormatException ex ) { + // Do nothing - the spec doesn't have any "throws" + // and the user might know what he's doing + return false; + } + } + if( name.equalsIgnoreCase( "Content-Language" ) ) { + // XXX XXX Need to construct Locale or something else + } + return false; + } + + // ---------------------------------------------------- InputBuffer Methods + + +// /** +// * Read some bytes. +// */ +// public int doRead(ByteChunk chunk, Request req) +// throws IOException { +// +// if (lastActiveFilter == -1) +// return inputStreamInputBuffer.doRead(chunk, req); +// else +// return activeFilters[lastActiveFilter].doRead(chunk,req); +// +// } + + + // ------------------------------------------------------ Protected Methods + + + +// /** +// * Fill the internal buffer using data from the undelying input stream. +// * +// * @return false if at end of stream +// */ +// protected boolean fill() +// throws IOException { +// +// int nRead = 0; +// +// if (parsingHeader) { +// +// if (lastValid == buf.length) { +// throw new IOException +// (); +// } +// +// nRead = inputStream.read(buf, pos, buf.length - lastValid); +// if (nRead > 0) { +// lastValid = pos + nRead; +// } +// +// } else { +// +// buf = bodyBuffer; +// pos = 0; +// lastValid = 0; +// nRead = inputStream.read(buf, 0, buf.length); +// if (nRead > 0) { +// lastValid = nRead; +// } +// +// } +// +// return (nRead > 0); +// +// } + +// public int doRead(ByteChunk chunk, Request req ) +// throws IOException { +// +// if (pos >= lastValid) { +// if (!fill()) +// return -1; +// } +// +// int length = lastValid - pos; +// chunk.setBytes(buf, pos, length); +// pos = lastValid; + + private String getMessage(final int message){ + return HttpMessages.getMessage(message); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For additional commands, e-mail: dev-help@tomcat.apache.org