From: costin Date: Wed, 24 Feb 2010 18:03:00 +0000 (+0000) Subject: Moved utils to connector ( the servlet part will either go away or be a separate... X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=6cf88dddb35b64dc922dd95c604e9278d36ec958;p=tomcat7.0 Moved utils to connector ( the servlet part will either go away or be a separate package ). Make it compile again on android. Few sync issues found while load testing. SSL fixes. Separate build target for connector. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@915902 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/tomcat-lite/build.xml b/modules/tomcat-lite/build.xml index 36974b008..9941f37d4 100644 --- a/modules/tomcat-lite/build.xml +++ b/modules/tomcat-lite/build.xml @@ -14,9 +14,11 @@ + + @@ -31,9 +33,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + description="Build all classes against tomcat head."> + + encoding="ISO-8859-1"> - diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java index 45bec43fb..02b449073 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java @@ -28,7 +28,7 @@ import org.apache.tomcat.util.modeler.Registry; * * All objects of interest are registered automatically. */ -public class JmxObjectManagerSpi extends ObjectManager { +public class JmxObjectManagerSpi extends ObjectManager implements Runnable { Registry registry; Logger log = Logger.getLogger("JmxObjectManager"); @@ -54,5 +54,15 @@ public class JmxObjectManagerSpi extends ObjectManager { public Object get(String key) { return null; } + + ObjectManager om; + + public void setObjectManager(ObjectManager om) { + this.om = om; + } + public void run() { + om.register(this); + // TODO: register existing objects in JMX + } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java index c5a2e761b..660131a27 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/UJmxHandler.java @@ -21,14 +21,11 @@ package org.apache.tomcat.integration.jmx; import java.io.IOException; import java.io.PrintWriter; -import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import java.util.Set; import java.util.logging.Logger; import org.apache.tomcat.integration.DynamicObject; -import org.apache.tomcat.integration.ObjectManager; import org.apache.tomcat.lite.http.HttpRequest; import org.apache.tomcat.lite.http.HttpResponse; import org.apache.tomcat.lite.http.HttpWriter; @@ -47,16 +44,18 @@ import org.apache.tomcat.lite.http.HttpChannel.HttpService; */ public class UJmxHandler implements HttpService { - private static Logger log = Logger.getLogger(UJmxHandler.class.getName()); + protected static Logger log = Logger.getLogger(UJmxHandler.class.getName()); private UJmxObjectManagerSpi jmx; + public UJmxHandler() { + } + public UJmxHandler(UJmxObjectManagerSpi jmx) { this.jmx = jmx; } public void getAttribute(PrintWriter writer, String onameStr, String att) { try { - Object bean = jmx.objects.get(onameStr); Class beanClass = bean.getClass(); DynamicObject ci = jmx.getClassInfo(beanClass); diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java index 993566c13..c98276cfa 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java @@ -72,7 +72,7 @@ public class ContentType { int index = type.indexOf(';'); while (index != -1) { index++; - while (index < len && Character.isSpace(type.charAt(index))) { + while (index < len && Character.isWhitespace(type.charAt(index))) { index++; } if (index+8 < len diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java index 2571a85ad..9529a1946 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java @@ -4,6 +4,8 @@ package org.apache.tomcat.lite.http; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -144,6 +146,8 @@ public class Http11Connection extends HttpConnection return closeInHead(); } } + + return true; } @@ -209,6 +213,11 @@ public class Http11Connection extends HttpConnection if (!headersReceived) { headRecvBuf.wrapTo(headW); parseMessage(activeHttp, headW); + // Part of parseMessage we can switch the protocol + if (switchedProtocol != null) { + return; + } + if (serverMode && activeHttp.httpReq.decodedUri.remaining() == 0) { abort(activeHttp, "Invalid url"); } @@ -552,6 +561,8 @@ public class Http11Connection extends HttpConnection return statusCode.remaining() > 0; } + List connectionHeaders = new ArrayList(); + private void parseHeaders(HttpChannel http, HttpMessageBytes msgBytes, BBuffer head) throws IOException { @@ -559,6 +570,9 @@ public class Http11Connection extends HttpConnection head.readLine(line); int idx = 0; + + BBuffer upgrade = null; + while(line.remaining() > 0) { // not empty.. idx = msgBytes.addHeader(); @@ -567,7 +581,23 @@ public class Http11Connection extends HttpConnection parseHeader(http, head, line, nameBuf, valBuf); // TODO: process 'interesting' headers here. + if (nameBuf.equalsIgnoreCase("connection")) { + // TODO: save and remove if not recognized + } + if (nameBuf.equalsIgnoreCase("upgrade")) { + upgrade = valBuf; + } + } + + if (upgrade != null) { + if (upgrade.equalsIgnoreCase("WebSocket")) { + + } else if (upgrade.equalsIgnoreCase("SPDY/1.0")) { + + } } + + // TODO: process connection headers } /** @@ -1406,8 +1436,10 @@ public class Http11Connection extends HttpConnection HttpChannel httpCh = activeHttp; boolean ssl = httpCh.getRequest().isSecure(); if (ssl) { - SslChannel ch1 = new SslChannel(); - ch1.setSslContext(httpConnector.sslConnector.getSSLContext()); + String[] hostPort = httpCh.getTarget().split(":"); + + SslChannel ch1 = httpConnector.sslConnector.channel( + hostPort[0], Integer.parseInt(hostPort[1])); ch1.setSink(net); net.addFilterAfter(ch1); net = ch1; @@ -1419,7 +1451,7 @@ public class Http11Connection extends HttpConnection } if (!net.isOpen()) { - httpCh.abort("Can't connect"); + httpCh.abort(net.lastException()); return; } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java index 363ac59bd..696a47089 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java @@ -282,14 +282,14 @@ public class HttpConnectionPool { if (con.isOpen()) { hits.incrementAndGet(); - if (debug) { - httpCh.trace("HTTP_CONNECT: Reuse connection " + target + " " + this); - } +// if (debug) { +// log.info("HTTP_CONNECT: Reuse connection " + target + " " + this); +// } con.sendRequest(httpCh); } else { misses.incrementAndGet(); if (debug) { - httpCh.trace("HTTP_CONNECT: Start connection " + target + " " + this); + log.info("HTTP_CONNECT: Start connection " + target + " " + this); } httpConnect(httpCh, target, ssl, (Http11Connection) con); @@ -300,7 +300,7 @@ public class HttpConnectionPool { boolean ssl, IOConnector.ConnectedCallback cb) throws IOException { if (debug) { - httpCh.trace("HTTP_CONNECT: New connection " + target); + log.info("HTTP_CONNECT: New connection " + target); } String[] hostPort = target.split(":"); @@ -341,9 +341,6 @@ public class HttpConnectionPool { // again. if (remoteServer.pending.size() == 0) { con.activeHttp = null; - if (debug) { - log.info("After request: no pending"); - } return; } req = remoteServer.pending.remove(); diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java index 63a657275..cc272e99c 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java @@ -115,6 +115,10 @@ public class HttpConnector { return dispatcher; } + public HttpConnectionPool getConnectionPool() { + return cpool; + } + public HttpConnector withIOConnector(IOConnector selectors) { ioConnector = selectors; return this; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java index 867ca2ea8..3bd8f0112 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java @@ -4,16 +4,23 @@ package org.apache.tomcat.lite.http; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted; import org.apache.tomcat.lite.http.HttpConnector.HttpConnection; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.BufferedIOReader; import org.apache.tomcat.lite.io.CBuffer; +import org.apache.tomcat.lite.io.FastHttpDateFormat; import org.apache.tomcat.lite.io.IOBuffer; import org.apache.tomcat.lite.io.IOInputStream; import org.apache.tomcat.lite.io.IOOutputStream; @@ -113,7 +120,9 @@ public abstract class HttpMessage { } } } - + + protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT"); + private HttpMessageBytes msgBytes = new HttpMessageBytes(); protected HttpMessage.State state = HttpMessage.State.HEAD; @@ -149,6 +158,15 @@ public abstract class HttpMessage { long contentLength = -2; boolean chunked; + /** + * The set of SimpleDateFormat formats to use in getDateHeader(). + * + * Notice that because SimpleDateFormat is not thread-safe, we can't + * declare formats[] as a static variable. + */ + protected SimpleDateFormat formats[] = null; + + BBuffer clBuffer = BBuffer.allocate(64); public HttpMessage(HttpChannel httpCh) { @@ -189,6 +207,41 @@ public abstract class HttpMessage { return headers; } + /** + * Return the value of the specified date header, if any; otherwise + * return -1. + * + * @param name Name of the requested date header + * + * @exception IllegalArgumentException if the specified header value + * cannot be converted to a date + */ + public long getDateHeader(String name) { + + String value = getHeader(name); + if (value == null) + return (-1L); + if (formats == null) { + formats = new SimpleDateFormat[] { + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) + }; + formats[0].setTimeZone(GMT_ZONE); + formats[1].setTimeZone(GMT_ZONE); + formats[2].setTimeZone(GMT_ZONE); + } + + // Attempt to convert the date header in a variety of formats + long result = FastHttpDateFormat.parseDate(value, formats); + if (result != (-1L)) { + return result; + } + throw new IllegalArgumentException(value); + + } + + public Collection getHeaderNames() { MultiMap headers = getMimeHeaders(); @@ -373,6 +426,14 @@ public abstract class HttpMessage { return in; } + public InputStream getInputStream() { + return in; + } + + public IOOutputStream getOutputStream() { + return out; + } + public IOOutputStream getBodyOutputStream() { return out; } @@ -410,6 +471,10 @@ public abstract class HttpMessage { reader.setEncoding(getCharacterEncoding()); return bufferedReader; } + + public PrintWriter getWriter() { + return new PrintWriter(getBodyWriter()); + } public HttpWriter getBodyWriter() { conv.setEncoding(getCharacterEncoding()); diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java index bbc670786..c43594633 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java @@ -175,6 +175,30 @@ public class HttpRequest extends HttpMessage { return (mappingData); } + /** + * Return the portion of the request URI used to select the Context + * of the Request. + */ + public String getContextPath() { + return (getMappingData().contextPath.toString()); + } + + public String getPathInfo() { + CBuffer pathInfo = getMappingData().pathInfo; + if (pathInfo.length() == 0) { + return null; + } + return (getMappingData().pathInfo.toString()); + } + + /** + * Return the portion of the request URI used to select the servlet + * that will process this request. + */ + public String getServletPath() { + return (getMappingData().wrapperPath.toString()); + } + /** * Parse query parameters - but not POST body. * @@ -843,6 +867,9 @@ public class HttpRequest extends HttpMessage { */ protected void processReceivedHeaders() throws IOException { BBuffer url = getMsgBytes().url(); + if (url.remaining() == 0) { + System.err.println("No input"); + } if (url.get(0) == 'h') { int firstSlash = url.indexOf('/', 0); schemeMB.appendAscii(url.array(), diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java index fd38abf28..39f2ffa57 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java @@ -8,9 +8,336 @@ import java.util.HashMap; import org.apache.tomcat.lite.io.BBucket; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.CBuffer; -import org.apache.tomcat.lite.io.IOBuffer; public class HttpResponse extends HttpMessage { + + /* + * Server status codes; see RFC 2068. + */ + + /** + * Status code (100) indicating the client can continue. + */ + + public static final int SC_CONTINUE = 100; + + + /** + * Status code (101) indicating the server is switching protocols + * according to Upgrade header. + */ + + public static final int SC_SWITCHING_PROTOCOLS = 101; + + /** + * Status code (200) indicating the request succeeded normally. + */ + + public static final int SC_OK = 200; + + /** + * Status code (201) indicating the request succeeded and created + * a new resource on the server. + */ + + public static final int SC_CREATED = 201; + + /** + * Status code (202) indicating that a request was accepted for + * processing, but was not completed. + */ + + public static final int SC_ACCEPTED = 202; + + /** + * Status code (203) indicating that the meta information presented + * by the client did not originate from the server. + */ + + public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203; + + /** + * Status code (204) indicating that the request succeeded but that + * there was no new information to return. + */ + + public static final int SC_NO_CONTENT = 204; + + /** + * Status code (205) indicating that the agent SHOULD reset + * the document view which caused the request to be sent. + */ + + public static final int SC_RESET_CONTENT = 205; + + /** + * Status code (206) indicating that the server has fulfilled + * the partial GET request for the resource. + */ + + public static final int SC_PARTIAL_CONTENT = 206; + + /** + * Used by Webdav. + */ + public static final int SC_MULTI_STATUS = 207; + // This one collides with HTTP 1.1 + // "207 Partial Update OK" + + /** + * Status code (300) indicating that the requested resource + * corresponds to any one of a set of representations, each with + * its own specific location. + */ + + public static final int SC_MULTIPLE_CHOICES = 300; + + /** + * Status code (301) indicating that the resource has permanently + * moved to a new location, and that future references should use a + * new URI with their requests. + */ + + public static final int SC_MOVED_PERMANENTLY = 301; + + /** + * Status code (302) indicating that the resource has temporarily + * moved to another location, but that future references should + * still use the original URI to access the resource. + * + * This definition is being retained for backwards compatibility. + * SC_FOUND is now the preferred definition. + */ + + public static final int SC_MOVED_TEMPORARILY = 302; + + /** + * Status code (302) indicating that the resource reside + * temporarily under a different URI. Since the redirection might + * be altered on occasion, the client should continue to use the + * Request-URI for future requests.(HTTP/1.1) To represent the + * status code (302), it is recommended to use this variable. + */ + + public static final int SC_FOUND = 302; + + /** + * Status code (303) indicating that the response to the request + * can be found under a different URI. + */ + + public static final int SC_SEE_OTHER = 303; + + /** + * Status code (304) indicating that a conditional GET operation + * found that the resource was available and not modified. + */ + + public static final int SC_NOT_MODIFIED = 304; + + /** + * Status code (305) indicating that the requested resource + * MUST be accessed through the proxy given by the + * Location field. + */ + + public static final int SC_USE_PROXY = 305; + + /** + * Status code (307) indicating that the requested resource + * resides temporarily under a different URI. The temporary URI + * SHOULD be given by the Location + * field in the response. + */ + + public static final int SC_TEMPORARY_REDIRECT = 307; + + /** + * Status code (400) indicating the request sent by the client was + * syntactically incorrect. + */ + + public static final int SC_BAD_REQUEST = 400; + + /** + * Status code (401) indicating that the request requires HTTP + * authentication. + */ + + public static final int SC_UNAUTHORIZED = 401; + + /** + * Status code (402) reserved for future use. + */ + + public static final int SC_PAYMENT_REQUIRED = 402; + + /** + * Status code (403) indicating the server understood the request + * but refused to fulfill it. + */ + + public static final int SC_FORBIDDEN = 403; + + /** + * Status code (404) indicating that the requested resource is not + * available. + */ + + public static final int SC_NOT_FOUND = 404; + + /** + * Status code (405) indicating that the method specified in the + * Request-Line is not allowed for the resource + * identified by the Request-URI. + */ + + public static final int SC_METHOD_NOT_ALLOWED = 405; + + /** + * Status code (406) indicating that the resource identified by the + * request is only capable of generating response entities which have + * content characteristics not acceptable according to the accept + * headers sent in the request. + */ + + public static final int SC_NOT_ACCEPTABLE = 406; + + /** + * Status code (407) indicating that the client MUST first + * authenticate itself with the proxy. + */ + + public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407; + + /** + * Status code (408) indicating that the client did not produce a + * request within the time that the server was prepared to wait. + */ + + public static final int SC_REQUEST_TIMEOUT = 408; + + /** + * Status code (409) indicating that the request could not be + * completed due to a conflict with the current state of the + * resource. + */ + + public static final int SC_CONFLICT = 409; + + /** + * Status code (410) indicating that the resource is no longer + * available at the server and no forwarding address is known. + * This condition SHOULD be considered permanent. + */ + + public static final int SC_GONE = 410; + + /** + * Status code (411) indicating that the request cannot be handled + * without a defined Content-Length. + */ + + public static final int SC_LENGTH_REQUIRED = 411; + + /** + * Status code (412) indicating that the precondition given in one + * or more of the request-header fields evaluated to false when it + * was tested on the server. + */ + + public static final int SC_PRECONDITION_FAILED = 412; + + /** + * Status code (413) indicating that the server is refusing to process + * the request because the request entity is larger than the server is + * willing or able to process. + */ + + public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413; + + /** + * Status code (414) indicating that the server is refusing to service + * the request because the Request-URI is longer + * than the server is willing to interpret. + */ + + public static final int SC_REQUEST_URI_TOO_LONG = 414; + + /** + * Status code (415) indicating that the server is refusing to service + * the request because the entity of the request is in a format not + * supported by the requested resource for the requested method. + */ + + public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415; + + /** + * Status code (416) indicating that the server cannot serve the + * requested byte range. + */ + + public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + + /** + * Status code (417) indicating that the server could not meet the + * expectation given in the Expect request header. + */ + + public static final int SC_EXPECTATION_FAILED = 417; + + /** + * Status code (423) indicating the destination resource of a + * method is locked, and either the request did not contain a + * valid Lock-Info header, or the Lock-Info header identifies + * a lock held by another principal. + */ + public static final int SC_LOCKED = 423; + + /** + * Status code (500) indicating an error inside the HTTP server + * which prevented it from fulfilling the request. + */ + + public static final int SC_INTERNAL_SERVER_ERROR = 500; + + /** + * Status code (501) indicating the HTTP server does not support + * the functionality needed to fulfill the request. + */ + + public static final int SC_NOT_IMPLEMENTED = 501; + + /** + * Status code (502) indicating that the HTTP server received an + * invalid response from a server it consulted when acting as a + * proxy or gateway. + */ + + public static final int SC_BAD_GATEWAY = 502; + + /** + * Status code (503) indicating that the HTTP server is + * temporarily overloaded, and unable to handle the request. + */ + + public static final int SC_SERVICE_UNAVAILABLE = 503; + + /** + * Status code (504) indicating that the server did not receive + * a timely response from the upstream server while acting as + * a gateway or proxy. + */ + + public static final int SC_GATEWAY_TIMEOUT = 504; + + /** + * Status code (505) indicating that the server does not support + * or refuses to support the HTTP protocol version that was used + * in the request message. + */ + + public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505; // will not be recycled public Object nativeResponse; @@ -47,6 +374,14 @@ public class HttpResponse extends HttpMessage { status = i; } + public void sendError(int status) { + this.status = status; + } + + public void sendError(int status, String msg) { + message.set(msg); + } + public int getStatus() { if (status >= 0) { return status; @@ -112,7 +447,7 @@ public class HttpResponse extends HttpMessage { * Common messages are cached. * */ - BBucket getMessage( int status ) { + static BBucket getMessage( int status ) { // method from Response. // Does HTTP requires/allow international messages or @@ -134,6 +469,9 @@ public class HttpResponse extends HttpMessage { return bb; } + public static String getStatusText(int code) { + return getMessage(code).toString(); + } static BBucket st_unknown = BBuffer.wrapper("No Message"); static BBucket st_200 = BBuffer.wrapper("OK"); @@ -192,6 +530,8 @@ public class HttpResponse extends HttpMessage { addStatus(504, "Gateway Timeout"); addStatus(505, "HTTP Version Not Supported"); addStatus(507, "Insufficient Storage"); + addStatus(SC_LOCKED, "Locked"); + } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java index e4c2a77dd..ccba678a0 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java @@ -95,9 +95,12 @@ public class SpdyConnection extends HttpConnector.HttpConnection CompressFilter headCompressIn = new CompressFilter() .setDictionary(SPDY_DICT, DICT_ID); + CompressFilter headCompressOut = new CompressFilter() .setDictionary(SPDY_DICT, DICT_ID); + IOBuffer headerCompressBuffer = new IOBuffer(); + IOBuffer headerDeCompressBuffer = new IOBuffer(); AtomicInteger inFrames = new AtomicInteger(); AtomicInteger inDataFrames = new AtomicInteger(); @@ -120,32 +123,35 @@ public class SpdyConnection extends HttpConnector.HttpConnection } @Override - public synchronized void dataReceived(IOBuffer iob) throws IOException { - while (true) { - int avail = iob.available(); - if (avail == 0) { - return; - } - if (currentInFrame == null) { - if (inFrameBuffer.remaining() + avail < 8) { + public void dataReceived(IOBuffer iob) throws IOException { + // Only one thread doing receive at a time. + synchronized (inFrameBuffer) { + while (true) { + int avail = iob.available(); + if (avail == 0) { return; } - if (inFrameBuffer.remaining() < 8) { - int headRest = 8 - inFrameBuffer.remaining(); - int rd = iob.read(inFrameBuffer, headRest); + if (currentInFrame == null) { + if (inFrameBuffer.remaining() + avail < 8) { + return; + } + if (inFrameBuffer.remaining() < 8) { + int headRest = 8 - inFrameBuffer.remaining(); + int rd = iob.read(inFrameBuffer, headRest); + } + currentInFrame = new SpdyConnection.Frame(); // TODO: reuse + currentInFrame.parse(this, inFrameBuffer); } - currentInFrame = new SpdyConnection.Frame(); // TODO: reuse - currentInFrame.parse(this, inFrameBuffer); - } - if (iob.available() < currentInFrame.length) { - return; - } - // We have a full frame. Process it. - onFrame(iob); + if (iob.available() < currentInFrame.length) { + return; + } + // We have a full frame. Process it. + onFrame(iob); - // TODO: extra checks, make sure the frame is correct and - // it consumed all data. - currentInFrame = null; + // TODO: extra checks, make sure the frame is correct and + // it consumed all data. + currentInFrame = null; + } } } @@ -179,7 +185,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection ch.setHttpService(this.httpConnector.defaultService); } - synchronized (this) { + synchronized (channels) { channels.put(ch.channelId, ch); } @@ -206,7 +212,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection } else if (currentInFrame.type == SpdyConnection.Frame.TYPE_SYN_REPLY) { int chId = SpdyConnection.readInt(iob); HttpChannel ch; - synchronized (this) { + synchronized (channels) { ch = channels.get(chId); if (ch == null) { abort("Channel not found"); @@ -239,7 +245,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection inDataFrames.incrementAndGet(); // data frame - part of an existing stream HttpChannel ch; - synchronized (this) { + synchronized (channels) { ch = channels.get(currentInFrame.streamId); } if (ch == null) { @@ -280,8 +286,10 @@ public class SpdyConnection extends HttpConnector.HttpConnection */ private void abort(String msg) throws IOException { streamErrors.incrementAndGet(); - for (HttpChannel ch : channels.values()) { - ch.abort(msg); + synchronized(channels) { + for (HttpChannel ch : channels.values()) { + ch.abort(msg); + } } close(); } @@ -299,13 +307,13 @@ public class SpdyConnection extends HttpConnector.HttpConnection if (headerCompression) { // 0x800 headers seems a bit too much - assume compressed. // I wish this was a flag... - headerCompressBuffer.recycle(); + headerDeCompressBuffer.recycle(); // stream id ( 4 ) + unused ( 2 ) // nvCount is compressed in impl - spec is different - headCompressIn.decompress(iob, headerCompressBuffer, + headCompressIn.decompress(iob, headerDeCompressBuffer, currentInFrame.length - 6); - headerCompressBuffer.copyAll(headRecvBuf); - headerCompressBuffer.recycle(); + headerDeCompressBuffer.copyAll(headRecvBuf); + headerDeCompressBuffer.recycle(); nvCount = readShort(headRecvBuf); } else { nvCount = readShort(iob); @@ -424,7 +432,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection http.setConnection(this); - synchronized (this) { + synchronized (channels) { channels.put(http.channelId, http); } @@ -446,7 +454,9 @@ public class SpdyConnection extends HttpConnector.HttpConnection public synchronized Collection getActives() { - return channels.values(); + synchronized(channels) { + return channels.values(); + } } @Override @@ -711,7 +721,7 @@ public class SpdyConnection extends HttpConnector.HttpConnection @Override protected void endSendReceive(HttpChannel http) throws IOException { - synchronized (this) { + synchronized (channels) { HttpChannel doneHttp = channels.remove(http.channelId); if (doneHttp != http) { log.severe("Error removing " + doneHttp + " " + http); @@ -775,8 +785,15 @@ public class SpdyConnection extends HttpConnector.HttpConnection } secure = httpCh.getRequest().isSecure(); if (secure) { - SslChannel ch1 = new SslChannel(); - ch1.setSslContext(httpConnector.sslConnector.getSSLContext()); + if (httpConnector.debugHttp) { + IOChannel ch1 = new DumpChannel("NET-IN"); + net.addFilterAfter(ch1); + net = ch1; + } + String[] hostPort = httpCh.getTarget().split(":"); + + SslChannel ch1 = httpConnector.sslConnector.channel( + hostPort[0], Integer.parseInt(hostPort[1])); ch1.setSink(net); net.addFilterAfter(ch1); net = ch1; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java index 1319b271d..47ea5025b 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java @@ -46,7 +46,9 @@ public class DumpChannel extends IOChannel { } any = true; out("IN", first, false); - in.queue(first); + if (!in.isAppendClosed()) { + in.queue(first); + } } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java index 2553d62fa..f3a2cabe4 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java @@ -342,8 +342,12 @@ public class IOBuffer { public int write(ByteBuffer bb) throws IOException { int len = bb.remaining(); + int pos = bb.position(); + if (len == 0) { + return 0; + } append(bb); - bb.position(bb.position() + len); + bb.position(pos + len); return len; } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java index 9893cf6cd..99dbc8812 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java @@ -21,7 +21,7 @@ import java.nio.channels.ByteChannel; * @author Costin Manolache */ public abstract class IOChannel implements ByteChannel, IOConnector.DataReceivedCallback, - IOConnector.DataFlushedCallback { //, IOConnector.ClosedCallback { + IOConnector.DataFlushedCallback { protected IOChannel net; protected IOChannel app; @@ -39,6 +39,8 @@ public abstract class IOChannel implements ByteChannel, IOConnector.DataReceived // TODO: update, etc public long ts; + protected Throwable lastException; + public void setConnectedCallback(IOConnector.ConnectedCallback connectedCallback) { this.connectedCallback = connectedCallback; } @@ -131,7 +133,18 @@ public abstract class IOChannel implements ByteChannel, IOConnector.DataReceived } } } - + + /** + * Return last IO exception. + * + * The channel is async, exceptions can happen at any time. + * The normal callback will be called ( connected, received ), it + * should check if the channel is closed and the exception. + */ + public Throwable lastException() { + return lastException; + } + public void close() throws IOException { shutdownOutput(); // Should it read the buffers ? diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java index 62eb0da1b..89468594b 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java @@ -21,6 +21,12 @@ public abstract class IOConnector { public void handleReceived(IOChannel ch) throws IOException; } + /** + * Callback for accept and connect. + * + * Will also be called if an error happens while connecting, in + * which case the connection will be closed. + */ public static interface ConnectedCallback { public void handleConnected(IOChannel ch) throws IOException; } @@ -35,6 +41,10 @@ public abstract class IOConnector { return timer; } + public IOConnector getNet() { + return null; + } + public abstract void acceptor(IOConnector.ConnectedCallback sc, CharSequence port, Object extra) throws IOException; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java index ad8f9520c..0c84f6841 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java @@ -10,7 +10,6 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; -import java.util.BitSet; import java.util.HashMap; import java.util.Map; @@ -155,6 +154,40 @@ public class IOWriter extends Writer { } } + // TODO: use it for utf-8 + public static int char2utf8(byte[] ba, int off, char c, char c1) { + int i = 0; + if (c < 0x80) { + ba[off++] = (byte) (c & 0xFF); + return 1; + } else if (c < 0x800) + { + ba[off++] = (byte) (0xC0 | c >> 6); + ba[off++] = (byte) (0x80 | c & 0x3F); + return 2; + } + else if (c < 0x10000) + { + ba[off++] = (byte) ((0xE0 | c >> 12)); + ba[off++] = (byte) ((0x80 | c >> 6 & 0x3F)); + ba[off++] = (byte) ((0x80 | c & 0x3F)); + return 3; + } + else if (c < 0x200000) + { + ba[off++] = (byte) ((0xF0 | c >> 18)); + ba[off++] = (byte) ((0x80 | c >> 12 & 0x3F)); + ba[off++] = (byte) ((0x80 | c >> 6 & 0x3F)); + ba[off++] = (byte) ((0x80 | c & 0x3F)); + return 4; + } + + + return i; + } + + + /** * Just send the chars to the byte[], without flushing down. * diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java index c8a136b1f..559a9e541 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java @@ -5,7 +5,6 @@ import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.Channel; -import java.nio.channels.SelectionKey; /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java index d782ae3b2..f10d37a00 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java @@ -410,23 +410,17 @@ public class NioThread implements Runnable { } readInterest(ch, true); - - if (ch.callback != null) { - ch.callback.handleConnected(ch); - } } catch (Throwable t) { - log.warning("Error in connect, closing " + t); close(ch, t); - try { - if (ch.callback != null) { - ch.callback.handleConnected(ch); - } - } catch(Throwable t1) { - log.warning("Double error in connect, callback broken too"); - t1.printStackTrace(); + } + try { + if (ch.callback != null) { + ch.callback.handleConnected(ch); } - + } catch(Throwable t1) { + log.log(Level.WARNING, "Error in connect callback", t1); } + } private void handleAccept(NioChannel ch, SelectionKey sk) @@ -729,17 +723,17 @@ public class NioThread implements Runnable { */ public int close(NioChannel selectorData, Throwable exception) throws IOException { synchronized (closeInterest) { + if (exception != null) { + selectorData.lastException = exception; + } + selectorData.readInterest = false; if (isSelectorThread()) { closeIOThread(selectorData, true); return 0; } - if (exception != null) { - selectorData.lastException = exception; - } if (!selectorData.inClosed) { closeInterest.add(selectorData); } - selectorData.readInterest = false; } selector.wakeup(); return 0; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java index 6c75d4818..1fa662baa 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java @@ -239,6 +239,7 @@ public class SocketIOChannel extends IOChannel implements NioChannelCallback { @Override public void handleClosed(NioChannel ch) throws IOException { + lastException = ch.lastException; closed(); // our callback. } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java index 163228ac6..e2ae08a29 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java @@ -5,12 +5,14 @@ package org.apache.tomcat.lite.io; import java.io.IOException; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.Status; @@ -30,9 +32,13 @@ public class SslChannel extends IOChannel implements Runnable { IOBuffer in = new IOBuffer(this); IOBuffer out = new IOBuffer(this); + + long handshakeTimeout = 10000; + // Used for session reuse + String host; + int port; public SslChannel() { - } ByteBuffer myAppOutData; @@ -52,13 +58,30 @@ public class SslChannel extends IOChannel implements Runnable { private boolean closeHandshake = false; - private void initSsl() throws GeneralSecurityException { + /** + * Setting the host/port enables clients to reuse SSL session - + * less traffic and encryption overhead at startup, assuming the + * server caches the session ( i.e. single server or distributed cache ). + * + * SSL ticket extension is another possibility. + */ + public SslChannel setTarget(String host, int port) { + this.host = host; + this.port = port; + return this; + } + + private synchronized void initSsl() throws GeneralSecurityException { if (sslEngine != null) { return; } if (client) { - sslEngine = sslCtx.createSSLEngine(); + if (port > 0) { + sslEngine = sslCtx.createSSLEngine(host, port); + } else { + sslEngine = sslCtx.createSSLEngine(); + } sslEngine.setUseClientMode(client); } else { sslEngine = sslCtx.createSSLEngine(); @@ -89,7 +112,7 @@ public class SslChannel extends IOChannel implements Runnable { @Override - public void setSink(IOChannel net) throws IOException { + public synchronized void setSink(IOChannel net) throws IOException { try { initSsl(); super.setSink(net); @@ -97,7 +120,7 @@ public class SslChannel extends IOChannel implements Runnable { log.log(Level.SEVERE, "Error initializing ", e); } } - + @Override public IOBuffer getIn() { return in; @@ -118,7 +141,7 @@ public class SslChannel extends IOChannel implements Runnable { */ public int processInput(IOBuffer netIn, IOBuffer appIn) throws IOException { if (log.isLoggable(Level.FINEST)) { - log.finest("JSSE: processInput " + handshakeInProgress + " " + netIn.getBufferCount()); + log.info("JSSE: processInput " + handshakeInProgress + " " + netIn.getBufferCount()); } if (!handshakeDone && !handshakeInProgress) { handshakeInProgress = true; @@ -134,6 +157,7 @@ public class SslChannel extends IOChannel implements Runnable { private synchronized int processRealInput(IOBuffer netIn, IOBuffer appIn) throws IOException { int rd = 0; boolean needsMore = true; + boolean notEnough = false; while (needsMore) { if (netIn.isClosedAndEmpty()) { @@ -142,9 +166,14 @@ public class SslChannel extends IOChannel implements Runnable { return -1; } myNetInData.compact(); - int rdNow = netIn.read(myNetInData); - myNetInData.flip(); - if (rdNow == 0) { + int rdNow; + try { + rdNow = netIn.read(myNetInData); + } finally { + myNetInData.flip(); + } + if (rdNow == 0 && (myNetInData.remaining() == 0 || + notEnough)) { return rd; } if (rdNow == -1) { @@ -153,10 +182,18 @@ public class SslChannel extends IOChannel implements Runnable { return rd; } + notEnough = true; // next read of 0 while (myNetInData.remaining() > 0) { myAppInData.compact(); - unwrapR = sslEngine.unwrap(myNetInData, myAppInData); - myAppInData.flip(); + try { + unwrapR = sslEngine.unwrap(myNetInData, myAppInData); + } catch (SSLException ex) { + log.warning("Read error: " + ex); + close(); + return -1; + } finally { + myAppInData.flip(); + } if (myAppInData.remaining() > 0) { in.write(myAppInData); // all will be written } @@ -216,50 +253,78 @@ public class SslChannel extends IOChannel implements Runnable { } public void close() throws IOException { + if (net.getOut().isAppendClosed()) { + return; + } sslEngine.closeOutbound(); // mark as closed - myNetOutData.compact(); - SSLEngineResult wrap = sslEngine.wrap(EMPTY, - myNetOutData); - myNetOutData.flip(); - if (wrap.getStatus() != Status.CLOSED) { - System.err.println("Unexpected status " + wrap); + synchronized(myNetOutData) { + myNetOutData.compact(); + + SSLEngineResult wrap; + try { + wrap = sslEngine.wrap(EMPTY, myNetOutData); + if (wrap.getStatus() != Status.CLOSED) { + log.warning("Unexpected close status " + wrap); + } + } catch (Throwable t ) { + log.info("Error wrapping " + myNetOutData); + } finally { + myNetOutData.flip(); + } + if (myNetOutData.remaining() > 0) { + net.getOut().write(myNetOutData); + } } - net.getOut().write(myNetOutData); // TODO: timer to close socket if we don't get // clean close handshake + super.close(); } - private synchronized void startRealSending() throws IOException { - while (true) { - - myAppOutData.compact(); - int rd = out.read(myAppOutData); - myAppOutData.flip(); - if (rd == 0) { - break; - } - if (rd < 0) { - close(); - break; - } + private Object sendLock = new Object(); - myNetOutData.compact(); - SSLEngineResult wrap = sslEngine.wrap(myAppOutData, - myNetOutData); - myNetOutData.flip(); - net.getOut().write(myNetOutData); - - if (wrap != null) { - switch (wrap.getStatus()) { - case BUFFER_UNDERFLOW: { + private void startRealSending() throws IOException { + // Only one thread at a time + synchronized (sendLock) { + while (true) { + + myAppOutData.compact(); + int rd; + try { + rd = out.read(myAppOutData); + } finally { + myAppOutData.flip(); + } + if (rd == 0) { break; } - case OK: { + if (rd < 0) { + close(); break; } - case BUFFER_OVERFLOW: { - throw new IOException("Overflow"); + + SSLEngineResult wrap; + synchronized(myNetOutData) { + myNetOutData.compact(); + try { + wrap = sslEngine.wrap(myAppOutData, + myNetOutData); + } finally { + myNetOutData.flip(); + } + net.getOut().write(myNetOutData); } + if (wrap != null) { + switch (wrap.getStatus()) { + case BUFFER_UNDERFLOW: { + break; + } + case OK: { + break; + } + case BUFFER_OVERFLOW: { + throw new IOException("Overflow"); + } + } } } } @@ -275,16 +340,16 @@ public class SslChannel extends IOChannel implements Runnable { // We'll need to unregister and register again from the selector. private void handleHandshking() { if (log.isLoggable(Level.FINEST)) { - log.finest("Starting handshake"); + log.info("Starting handshake"); } handshakeInProgress = true; - new Thread(this).start(); + ((SslConnector) connector).handshakeExecutor.execute(this); } private void endHandshake() throws IOException { if (log.isLoggable(Level.FINEST)) { - log.finest("Handshake done"); + log.info("Handshake done " + net.getIn().available()); } handshakeDone = true; handshakeInProgress = false; @@ -292,6 +357,10 @@ public class SslChannel extends IOChannel implements Runnable { flushing = false; startSending(); } + if (myNetInData.remaining() > 0 || net.getIn().available() > 0) { + // Last SSL packet also includes data. + handleReceived(net); + } } /** @@ -311,26 +380,41 @@ public class SslChannel extends IOChannel implements Runnable { long t0 = System.currentTimeMillis(); - while (hstatus != HandshakeStatus.FINISHED) { + while (hstatus != HandshakeStatus.NOT_HANDSHAKING + && hstatus != HandshakeStatus.FINISHED + && !net.getIn().isAppendClosed()) { + if (System.currentTimeMillis() - t0 > handshakeTimeout) { + throw new TimeoutException(); + } if (wrap != null && wrap.getStatus() == Status.CLOSED) { break; } if (log.isLoggable(Level.FINEST)) { - log.finest("-->doHandshake() loop: status = " + hstatus + " " + + log.info("-->doHandshake() loop: status = " + hstatus + " " + sslEngine.getHandshakeStatus()); } if (hstatus == HandshakeStatus.NEED_WRAP) { // || initial - for client initial = false; - myNetOutData.compact(); - - wrap = sslEngine.wrap(myAppOutData, myNetOutData); - myNetOutData.flip(); - - hstatus = wrap.getHandshakeStatus(); + synchronized(myNetOutData) { + myNetOutData.compact(); - net.getOut().write(myNetOutData); + try { + wrap = sslEngine.wrap(myAppOutData, myNetOutData); + } catch (Throwable t) { + t.printStackTrace(); + close(); + return; + } finally { + myNetOutData.flip(); + } + + hstatus = wrap.getHandshakeStatus(); + if (myNetOutData.remaining() > 0) { + net.getOut().write(myNetOutData); + } + } net.startSending(); @@ -356,11 +440,17 @@ public class SslChannel extends IOChannel implements Runnable { || wrap.getStatus() == Status.BUFFER_UNDERFLOW || (hstatus == HandshakeStatus.NEED_UNWRAP && myNetInData.remaining() == 0)) { myNetInData.compact(); - int rd = net.getIn().read(myNetInData); - myNetInData.flip(); + // non-blocking + int rd; + try { + rd = net.getIn().read(myNetInData); + } finally { + myNetInData.flip(); + } if (rd == 0) { - net.getIn().waitData(10000); - continue; + net.getIn().waitData(handshakeTimeout - + (System.currentTimeMillis() - t0)); + rd = net.getIn().read(myNetInData); } if (rd < 0) { // in closed @@ -368,7 +458,7 @@ public class SslChannel extends IOChannel implements Runnable { } } if (log.isLoggable(Level.FINEST)) { - log.finest("Unwrap chunk done " + hstatus + " " + wrap + log.info("Unwrap chunk done " + hstatus + " " + wrap + " " + sslEngine.getHandshakeStatus()); } @@ -384,7 +474,7 @@ public class SslChannel extends IOChannel implements Runnable { long t1task = System.currentTimeMillis(); hstatus = sslEngine.getHandshakeStatus(); if (log.isLoggable(Level.FINEST)) { - log.finest("Tasks done in " + (t1task - t0task) + " new status " + + log.info("Tasks done in " + (t1task - t0task) + " new status " + hstatus); } @@ -401,6 +491,7 @@ public class SslChannel extends IOChannel implements Runnable { try { close(); net.close(); + sendHandleReceivedCallback(); } catch (IOException ex) { log.log(Level.SEVERE, "Error closing", ex); } @@ -419,4 +510,9 @@ public class SslChannel extends IOChannel implements Runnable { this.sslCtx = sslCtx; return this; } + + public SslChannel setSslConnector(SslConnector con) { + this.connector = con; + return this; + } } diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java index 04acbeefe..cdb4acef9 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java @@ -19,7 +19,6 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; -import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; @@ -28,8 +27,9 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAKeyGenParameterSpec; -import java.security.spec.X509EncodedKeySpec; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -42,6 +42,12 @@ import javax.net.ssl.X509TrustManager; public class SslConnector extends IOConnector { + /** + * TODO: option to require validation. + * TODO: remember cert signature. This is needed to support self-signed + * certs, like those used by the test. + * + */ public static class BasicTrustManager implements X509TrustManager { private X509Certificate[] chain; @@ -64,20 +70,21 @@ public class SslConnector extends IOConnector { public static TrustManager[] trustAllCerts = new TrustManager[] { new BasicTrustManager() }; - static final boolean debug = false; - static { - if (debug) { - System.setProperty("javax.net.debug", "ssl"); - } - } + static final boolean debug = false; + IOConnector net; private KeyManager[] keyManager; SSLContext sslCtx; boolean server; private TrustManager[] trustManagers; - Executor handshakeExecutor; + public AtomicInteger handshakeCount = new AtomicInteger(); + public AtomicInteger handshakeOk = new AtomicInteger(); + public AtomicInteger handshakeErr = new AtomicInteger(); + public AtomicInteger handshakeTime = new AtomicInteger(); + + Executor handshakeExecutor = Executors.newCachedThreadPool(); static int id = 0; public SslConnector() { @@ -92,7 +99,7 @@ public class SslConnector extends IOConnector { try { sslCtx = SSLContext.getInstance("TLS"); if (trustManagers == null) { - trustManagers = + trustManagers = new TrustManager[] {new BasicTrustManager()}; } @@ -115,6 +122,18 @@ public class SslConnector extends IOConnector { return net; } + public SslChannel channel(String host, int port) { + return new SslChannel() + .setTarget(host, port) + .setSslContext(getSSLContext()) + .setSslConnector(this); + } + + public SslChannel serverChannel() { + return new SslChannel() + .setSslContext(getSSLContext()) + .setSslConnector(this).withServer(); + } @Override public void acceptor(final ConnectedCallback sc, CharSequence port, Object extra) @@ -129,9 +148,7 @@ public class SslConnector extends IOConnector { first = dch; } - IOChannel sslch = new SslChannel() - .setSslContext(sslCtx) - .withServer(); + IOChannel sslch = serverChannel(); sslch.setSink(first); first.addFilterAfter(sslch); @@ -148,7 +165,7 @@ public class SslConnector extends IOConnector { } @Override - public void connect(String host, int port, final ConnectedCallback sc) + public void connect(final String host, final int port, final ConnectedCallback sc) throws IOException { getNet().connect(host, port, new ConnectedCallback() { @@ -161,8 +178,7 @@ public class SslConnector extends IOConnector { first = dch; } - IOChannel sslch = new SslChannel() - .setSslContext(sslCtx); + IOChannel sslch = channel(host, port); sslch.setSink(first); first.addFilterAfter(sslch); @@ -318,7 +334,7 @@ public class SslConnector extends IOConnector { public static void fixUrlConnection() { try { - SSLContext sc = SSLContext.getInstance("SSL"); + SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, SslConnector.trustAllCerts, null); javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory( sc.getSocketFactory()); diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java index 684b85c7e..d641cb66b 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java @@ -33,147 +33,33 @@ import org.apache.tomcat.lite.http.HttpRequest; public class ServletApi30 extends ServletApi { public ServletContextImpl newContext() { - return new ServletContextImpl() { - protected void initEngineDefaults() throws ServletException { - super.initEngineDefaults(); - setAttribute(InstanceManager.class.getName(), - new LiteInstanceManager(getObjectManager())); - } - @Override - public Dynamic addFilter(String filterName, String className) { - FilterConfigImpl fc = new FilterConfigImpl(this); - fc.setData(filterName, null, new HashMap()); - fc.setData(filterName, className, new HashMap()); - filters.put(filterName, fc); - return new DynamicFilterRegistration(fc); - } - - @Override - public Dynamic addFilter(String filterName, Filter filter) { - FilterConfigImpl fc = new FilterConfigImpl(this); - fc.setData(filterName, null, new HashMap()); - fc.setFilter(filter); - filters.put(filterName, fc); - return new DynamicFilterRegistration(fc); - } - - @Override - public Dynamic addFilter(String filterName, - Class filterClass) { - FilterConfigImpl fc = new FilterConfigImpl(this); - fc.setData(filterName, null, new HashMap()); - fc.setFilterClass(filterClass); - filters.put(filterName, fc); - return new DynamicFilterRegistration(fc); - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( - String servletName, String className) { - return null; - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( - String servletName, Servlet servlet) { - return null; - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( - String servletName, Class servletClass) { - return null; - } - - - @Override - public Set getDefaultSessionTrackingModes() { - return null; - } - - - @Override - public Set getEffectiveSessionTrackingModes() { - return null; - } - - @Override - public FilterRegistration getFilterRegistration(String filterName) { - return null; - } - - @Override - public Map getFilterRegistrations() { - return null; - } - - @Override - public JspConfigDescriptor getJspConfigDescriptor() { - return null; - } - - @Override - public ServletRegistration getServletRegistration(String servletName) { - return null; - } - - @Override - public Map getServletRegistrations() { - return null; - } - - @Override - public SessionCookieConfig getSessionCookieConfig() { - return null; - } - - @Override - public void setSessionTrackingModes( - EnumSet sessionTrackingModes) - throws IllegalStateException, IllegalArgumentException { - } - - public int getMajorVersion() { - return 3; - } - - public int getMinorVersion() { - return 0; - } - - }; + return new Servlet30ContextImpl(); } public ServletRequestImpl newRequest(HttpRequest req) { return new ServletRequestImpl(req) { - @Override public Part getPart(String name) { return null; } - @Override public Collection getParts() throws IOException, ServletException { return null; } - @Override public AsyncContext getAsyncContext() { return null; } - @Override public DispatcherType getDispatcherType() { return null; } - @Override public AsyncContext startAsync() { return null; } - @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) { return null; @@ -182,6 +68,99 @@ public class ServletApi30 extends ServletApi { }; } + public final class Servlet30ContextImpl extends ServletContextImpl { + protected void initEngineDefaults() throws ServletException { + super.initEngineDefaults(); + setAttribute(InstanceManager.class.getName(), + new LiteInstanceManager(getObjectManager())); + } + + public Dynamic addFilter(String filterName, String className) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setData(filterName, className, new HashMap()); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + public Dynamic addFilter(String filterName, Filter filter) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setFilter(filter); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + public Dynamic addFilter(String filterName, + Class filterClass) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setFilterClass(filterClass); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, String className) { + return null; + } + + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, Servlet servlet) { + return null; + } + + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, Class servletClass) { + return null; + } + + public Set getDefaultSessionTrackingModes() { + return null; + } + + public Set getEffectiveSessionTrackingModes() { + return null; + } + + public FilterRegistration getFilterRegistration(String filterName) { + return null; + } + + public Map getFilterRegistrations() { + return null; + } + + public JspConfigDescriptor getJspConfigDescriptor() { + return null; + } + + public ServletRegistration getServletRegistration(String servletName) { + return null; + } + + public Map getServletRegistrations() { + return null; + } + + public SessionCookieConfig getSessionCookieConfig() { + return null; + } + + public int getMajorVersion() { + return 3; + } + + public int getMinorVersion() { + return 0; + } + + public void setSessionTrackingModes( + Set sessionTrackingModes) + throws IllegalStateException, IllegalArgumentException { + } + } + private final class LiteInstanceManager implements InstanceManager { private ObjectManager om; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java index 78e96cce0..002b19281 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java @@ -53,6 +53,8 @@ import javax.servlet.ServletException; import org.apache.tomcat.integration.ObjectManager; import org.apache.tomcat.lite.http.BaseMapper; import org.apache.tomcat.lite.io.FileConnectorJavaIo; +import org.apache.tomcat.lite.util.MimeMap; +import org.apache.tomcat.lite.util.UrlUtils; import org.apache.tomcat.servlets.config.ConfigLoader; import org.apache.tomcat.servlets.config.ServletContextConfig; import org.apache.tomcat.servlets.config.ServletContextConfig.FilterData; @@ -60,9 +62,7 @@ import org.apache.tomcat.servlets.config.ServletContextConfig.FilterMappingData; import org.apache.tomcat.servlets.config.ServletContextConfig.ServletData; import org.apache.tomcat.servlets.session.UserSessionManager; import org.apache.tomcat.servlets.util.Enumerator; -import org.apache.tomcat.servlets.util.MimeMap; import org.apache.tomcat.servlets.util.RequestUtil; -import org.apache.tomcat.servlets.util.UrlUtils; /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java index a2e430c21..8b9baa769 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java @@ -54,9 +54,9 @@ import org.apache.tomcat.lite.http.MultiMap.Entry; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.CBuffer; import org.apache.tomcat.lite.io.FastHttpDateFormat; +import org.apache.tomcat.lite.util.LocaleParser; import org.apache.tomcat.servlets.session.UserSessionManager; import org.apache.tomcat.servlets.util.Enumerator; -import org.apache.tomcat.servlets.util.LocaleParser; import org.apache.tomcat.servlets.util.RequestUtil; @@ -206,8 +206,6 @@ public abstract class ServletRequestImpl implements HttpServletRequest { public static final String WORK_DIR_ATTR = "javax.servlet.context.tempdir"; - protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT"); - /** * The default Locale if none are specified. @@ -228,13 +226,6 @@ public abstract class ServletRequestImpl implements HttpServletRequest { protected Cookie[] cookies = null; - /** - * The set of SimpleDateFormat formats to use in getDateHeader(). - * - * Notice that because SimpleDateFormat is not thread-safe, we can't - * declare formats[] as a static variable. - */ - protected SimpleDateFormat formats[] = null; /** @@ -683,28 +674,7 @@ public abstract class ServletRequestImpl implements HttpServletRequest { * cannot be converted to a date */ public long getDateHeader(String name) { - - String value = getHeader(name); - if (value == null) - return (-1L); - if (formats == null) { - formats = new SimpleDateFormat[] { - new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), - new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), - new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) - }; - formats[0].setTimeZone(GMT_ZONE); - formats[1].setTimeZone(GMT_ZONE); - formats[2].setTimeZone(GMT_ZONE); - } - - // Attempt to convert the date header in a variety of formats - long result = FastHttpDateFormat.parseDate(value, formats); - if (result != (-1L)) { - return result; - } - throw new IllegalArgumentException(value); - + return httpRequest.getDateHeader(name); } /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java index 791759c40..9fcfc7314 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java @@ -32,6 +32,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import org.apache.tomcat.lite.util.URLEncoder; import org.apache.tomcat.servlets.util.RequestUtil; /** @@ -430,7 +431,7 @@ public class WebappFilterMapper implements Filter { } public void setURLPattern(String urlPattern) { - this.urlPattern = RequestUtil.URLDecode(urlPattern); + this.urlPattern = URLEncoder.URLDecode(urlPattern); } /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Base64.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Base64.java new file mode 100644 index 000000000..848c99d3e --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Base64.java @@ -0,0 +1,272 @@ +/* + * 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.lite.util; + +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * This class provides encode/decode for RFC 2045 Base64 as + * defined by RFC 2045, N. Freed and N. Borenstein. + * RFC 2045: Multipurpose Internet Mail Extensions (MIME) + * Part One: Format of Internet Message Bodies. Reference + * 1996 Available at: http://www.ietf.org/rfc/rfc2045.txt + * This class is used by XML Schema binary format validation + * + * @author Jeffrey Rodriguez + * @version $Revision: 467222 $ $Date: 2006-10-23 20:17:11 -0700 (Mon, 23 Oct 2006) $ + */ + +public final class Base64 { + + + private static Logger log= + Logger.getLogger( Base64.class.getName() ); + + static private final int BASELENGTH = 255; + static private final int LOOKUPLENGTH = 63; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int SIXBIT = 6; + static private final int FOURBYTE = 4; + + + static private final byte PAD = ( byte ) '='; + static private byte [] base64Alphabet = new byte[BASELENGTH]; + static private byte [] lookUpBase64Alphabet = new byte[LOOKUPLENGTH + 1]; + + static { + + for (int i = 0; i= 'A'; i-- ) { + base64Alphabet[i] = (byte) (i-'A'); + } + for ( int i = 'z'; i>= 'a'; i--) { + base64Alphabet[i] = (byte) ( i-'a' + 26); + } + + for ( int i = '9'; i >= '0'; i--) { + base64Alphabet[i] = (byte) (i-'0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + String lookupString = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + "0123456789+/"; + + for (int i = 0; i < lookupString.length(); i++ ) { + lookUpBase64Alphabet[i] = (byte) lookupString.charAt(i); + } + } + + + static boolean isBase64( byte octect ) { + //shall we ignore white space? JEFF?? + return(octect == PAD || base64Alphabet[octect] != -1 ); + } + + + static boolean isArrayByteBase64( byte[] arrayOctect ) { + int length = arrayOctect.length; + if ( length == 0 ) + return false; + for ( int i=0; i < length; i++ ) { + if ( Base64.isBase64( arrayOctect[i] ) == false) + return false; + } + return true; + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static byte[] encode( byte[] binaryData ) { + int lengthDataBits = binaryData.length*EIGHTBIT; + int fewerThan24bits = lengthDataBits%TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits/TWENTYFOURBITGROUP; + byte encodedData[] = null; + + + if ( fewerThan24bits != 0 ) //data not divisible by 24 bit + encodedData = new byte[ (numberTriplets + 1 )*4 ]; + else // 16 or 8 bit + encodedData = new byte[ numberTriplets*4 ]; + + int k=0, l=0, b1=0,b2=0,b3=0; + + int encodedIndex = 0; + int dataIndex = 0; + int i = 0; + for ( i = 0; i>2 ]; + encodedData[encodedIndex+1] = lookUpBase64Alphabet[(b2 >>4 ) | +( k<<4 )]; + encodedData[encodedIndex+2] = lookUpBase64Alphabet[ (l <<2 ) | +( b3>>6)]; + encodedData[encodedIndex+3] = lookUpBase64Alphabet[ b3 & 0x3f ]; + } + + // form integral number of 6-bit groups + dataIndex = i*3; + encodedIndex = i*4; + if (fewerThan24bits == EIGHTBIT ) { + b1 = binaryData[dataIndex]; + k = (byte) ( b1 &0x03 ); + encodedData[encodedIndex] = lookUpBase64Alphabet[ b1 >>2 ]; + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[ k<<4 ]; + encodedData[encodedIndex + 2] = PAD; + encodedData[encodedIndex + 3] = PAD; + } else if ( fewerThan24bits == SIXTEENBIT ) { + + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex +1 ]; + l = ( byte ) ( b2 &0x0f ); + k = ( byte ) ( b1 &0x03 ); + encodedData[encodedIndex] = lookUpBase64Alphabet[ b1 >>2 ]; + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[ (b2 >>4 ) +| ( k<<4 )]; + encodedData[encodedIndex + 2] = lookUpBase64Alphabet[ l<<2 ]; + encodedData[encodedIndex + 3] = PAD; + } + return encodedData; + } + + public byte[] decode(String enc) { + return decode(enc.getBytes()); + } + + /** + * Decodes Base64 data into octects + * + * @param base64Data Byte array containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode( byte[] base64Data ) { + int numberQuadruple = base64Data.length/FOURBYTE; + byte decodedData[] = null; + byte b1=0,b2=0,b3=0, b4=0, marker0=0, marker1=0; + + // Throw away anything not in base64Data + // Adjust size + + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[ numberQuadruple*3 + 1 ]; + + for (int i = 0; i>4 ) ; + decodedData[encodedIndex+1] = (byte)(((b2 & 0xf)<<4 ) |( +(b3>>2) & 0xf) ); + decodedData[encodedIndex+2] = (byte)( b3<<6 | b4 ); + } else if ( marker0 == PAD ) { //Two PAD e.g. 3c[Pad][Pad] + decodedData[encodedIndex] = (byte)( b1 <<2 | b2>>4 ) ; + decodedData[encodedIndex+1] = (byte)((b2 & 0xf)<<4 ); + decodedData[encodedIndex+2] = (byte) 0; + } else if ( marker1 == PAD ) { //One PAD e.g. 3cQ[Pad] + b3 = base64Alphabet[ marker0 ]; + + decodedData[encodedIndex] = (byte)( b1 <<2 | b2>>4 ); + decodedData[encodedIndex+1] = (byte)(((b2 & 0xf)<<4 ) |( +(b3>>2) & 0xf) ); + decodedData[encodedIndex+2] = (byte)( b3<<6); + } + encodedIndex += 3; + } + return decodedData; + + } + + static final int base64[]= { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 + }; + + public static String base64Decode( String orig ) { + char chars[]=orig.toCharArray(); + StringBuffer sb=new StringBuffer(); + int i=0; + + int shift = 0; // # of excess bits stored in accum + int acc = 0; + + for (i=0; i= 64 ) { + if( chars[i] != '=' ) + if (log.isLoggable(Level.INFO)) + log.info("Wrong char in base64: " + chars[i]); + } else { + acc= ( acc << 6 ) | v; + shift += 6; + if ( shift >= 8 ) { + shift -= 8; + sb.append( (char) ((acc >> shift) & 0xff)); + } + } + } + return sb.toString(); + } + + +} + diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java new file mode 100644 index 000000000..fe3c69a40 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java @@ -0,0 +1,231 @@ +/* + * 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.lite.util; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Utility class to generate HTTP dates. + * + * @author Remy Maucherat + */ +public final class FastHttpDateFormat { + + + // -------------------------------------------------------------- Variables + + + protected static final int CACHE_SIZE = + Integer.parseInt(System.getProperty("org.apache.tomcat.util.http.FastHttpDateFormat.CACHE_SIZE", "1000")); + + + /** + * HTTP date format. + */ + protected static final SimpleDateFormat format = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + + + /** + * The set of SimpleDateFormat formats to use in getDateHeader(). + */ + protected static final SimpleDateFormat formats[] = { + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) + }; + + + protected final static TimeZone gmtZone = TimeZone.getTimeZone("GMT"); + + + /** + * GMT timezone - all HTTP dates are on GMT + */ + static { + + format.setTimeZone(gmtZone); + + formats[0].setTimeZone(gmtZone); + formats[1].setTimeZone(gmtZone); + formats[2].setTimeZone(gmtZone); + + } + + + /** + * Instant on which the currentDate object was generated. + */ + protected static long currentDateGenerated = 0L; + + + /** + * Current formatted date. + */ + protected static String currentDate = null; + + + /** + * Formatter cache. + */ + protected static final ConcurrentHashMap formatCache = + new ConcurrentHashMap(CACHE_SIZE); + + + /** + * Parser cache. + */ + protected static final ConcurrentHashMap parseCache = + new ConcurrentHashMap(CACHE_SIZE); + + + // --------------------------------------------------------- Public Methods + + + /** + * Get the current date in HTTP format. + */ + public static final String getCurrentDate() { + + long now = System.currentTimeMillis(); + if ((now - currentDateGenerated) > 1000) { + synchronized (format) { + if ((now - currentDateGenerated) > 1000) { + currentDateGenerated = now; + currentDate = format.format(new Date(now)); + } + } + } + return currentDate; + + } + + + /** + * Get the HTTP format of the specified date. + */ + public static final String formatDate + (long value, DateFormat threadLocalformat) { + + Long longValue = new Long(value); + String cachedDate = formatCache.get(longValue); + if (cachedDate != null) + return cachedDate; + + String newDate = null; + Date dateValue = new Date(value); + if (threadLocalformat != null) { + newDate = threadLocalformat.format(dateValue); + updateFormatCache(longValue, newDate); + } else { + synchronized (formatCache) { + synchronized (format) { + newDate = format.format(dateValue); + } + updateFormatCache(longValue, newDate); + } + } + return newDate; + + } + + + /** + * Try to parse the given date as a HTTP date. + */ + public static final long parseDate(String value, + DateFormat[] threadLocalformats) { + + Long cachedDate = parseCache.get(value); + if (cachedDate != null) + return cachedDate.longValue(); + + Long date = null; + if (threadLocalformats != null) { + date = internalParseDate(value, threadLocalformats); + updateParseCache(value, date); + } else { + synchronized (parseCache) { + date = internalParseDate(value, formats); + updateParseCache(value, date); + } + } + if (date == null) { + return (-1L); + } else { + return date.longValue(); + } + + } + + + /** + * Parse date with given formatters. + */ + private static final Long internalParseDate + (String value, DateFormat[] formats) { + Date date = null; + for (int i = 0; (date == null) && (i < formats.length); i++) { + try { + date = formats[i].parse(value); + } catch (ParseException e) { + ; + } + } + if (date == null) { + return null; + } + return new Long(date.getTime()); + } + + + /** + * Update cache. + */ + private static void updateFormatCache(Long key, String value) { + if (value == null) { + return; + } + if (formatCache.size() > CACHE_SIZE) { + formatCache.clear(); + } + formatCache.put(key, value); + } + + + /** + * Update cache. + */ + private static void updateParseCache(String key, Long value) { + if (value == null) { + return; + } + if (parseCache.size() > CACHE_SIZE) { + parseCache.clear(); + } + parseCache.put(key, value); + } + + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java new file mode 100644 index 000000000..fafdbffb8 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java @@ -0,0 +1,394 @@ +/* + * 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.lite.util; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.TreeMap; + + +/** + * Utility class for string parsing that is higher performance than + * StringParser for simple delimited text cases. Parsing is performed + * by setting the string, and then using the findXxxx() and + * skipXxxx() families of methods to remember significant + * offsets. To retrieve the parsed substrings, call the extract() + * method with the appropriate saved offset values. + * + * @author Craig R. McClanahan + */ +public final class LocaleParser { + + public LocaleParser() { + this(null); + } + + public LocaleParser(String string) { + setString(string); + } + + public TreeMap parseLocale(String value) { + // Store the accumulated languages that have been requested in + // a local collection, sorted by the quality value (so we can + // add Locales in descending order). The values will be ArrayLists + // containing the corresponding Locales to be added + TreeMap locales = new TreeMap(); + + // Preprocess the value to remove all whitespace + int white = value.indexOf(' '); + if (white < 0) + white = value.indexOf('\t'); + if (white >= 0) { + StringBuilder sb = new StringBuilder(); + int len = value.length(); + for (int i = 0; i < len; i++) { + char ch = value.charAt(i); + if ((ch != ' ') && (ch != '\t')) + sb.append(ch); + } + value = sb.toString(); + } + + LocaleParser parser = this; + // Process each comma-delimited language specification + parser.setString(value); // ASSERT: parser is available to us + int length = parser.getLength(); + while (true) { + + // Extract the next comma-delimited entry + int start = parser.getIndex(); + if (start >= length) + break; + int end = parser.findChar(','); + String entry = parser.extract(start, end).trim(); + parser.advance(); // For the following entry + + // Extract the quality factor for this entry + double quality = 1.0; + int semi = entry.indexOf(";q="); + if (semi >= 0) { + try { + quality = Double.parseDouble(entry.substring(semi + 3)); + } catch (NumberFormatException e) { + quality = 0.0; + } + entry = entry.substring(0, semi); + } + + // Skip entries we are not going to keep track of + if (quality < 0.00005) + continue; // Zero (or effectively zero) quality factors + if ("*".equals(entry)) + continue; // FIXME - "*" entries are not handled + + // Extract the language and country for this entry + String language = null; + String country = null; + String variant = null; + int dash = entry.indexOf('-'); + if (dash < 0) { + language = entry; + country = ""; + variant = ""; + } else { + language = entry.substring(0, dash); + country = entry.substring(dash + 1); + int vDash = country.indexOf('-'); + if (vDash > 0) { + String cTemp = country.substring(0, vDash); + variant = country.substring(vDash + 1); + country = cTemp; + } else { + variant = ""; + } + } + + // Add a new Locale to the list of Locales for this quality level + Locale locale = new Locale(language, country, variant); + Double key = new Double(-quality); // Reverse the order + ArrayList values = (ArrayList) locales.get(key); + if (values == null) { + values = new ArrayList(); + locales.put(key, values); + } + values.add(locale); + + } + + return locales; + } + + /** + * The characters of the current string, as a character array. Stored + * when the string is first specified to speed up access to characters + * being compared during parsing. + */ + private char chars[] = null; + + + /** + * The zero-relative index of the current point at which we are + * positioned within the string being parsed. NOTE: + * the value of this index can be one larger than the index of the last + * character of the string (i.e. equal to the string length) if you + * parse off the end of the string. This value is useful for extracting + * substrings that include the end of the string. + */ + private int index = 0; + + + /** + * The length of the String we are currently parsing. Stored when the + * string is first specified to avoid repeated recalculations. + */ + private int length = 0; + + + /** + * The String we are currently parsing. + */ + private String string = null; + + + // ------------------------------------------------------------- Properties + + + /** + * Return the zero-relative index of our current parsing position + * within the string being parsed. + */ + public int getIndex() { + + return (this.index); + + } + + + /** + * Return the length of the string we are parsing. + */ + public int getLength() { + + return (this.length); + + } + + + /** + * Return the String we are currently parsing. + */ + public String getString() { + + return (this.string); + + } + + + /** + * Set the String we are currently parsing. The parser state is also reset + * to begin at the start of this string. + * + * @param string The string to be parsed. + */ + public void setString(String string) { + + this.string = string; + if (string != null) { + this.length = string.length(); + chars = this.string.toCharArray(); + } else { + this.length = 0; + chars = new char[0]; + } + reset(); + + } + + + // --------------------------------------------------------- Public Methods + + + /** + * Advance the current parsing position by one, if we are not already + * past the end of the string. + */ + public void advance() { + + if (index < length) + index++; + + } + + + /** + * Extract and return a substring that starts at the specified position, + * and extends to the end of the string being parsed. If this is not + * possible, a zero-length string is returned. + * + * @param start Starting index, zero relative, inclusive + */ + public String extract(int start) { + + if ((start < 0) || (start >= length)) + return (""); + else + return (string.substring(start)); + + } + + + /** + * Extract and return a substring that starts at the specified position, + * and ends at the character before the specified position. If this is + * not possible, a zero-length string is returned. + * + * @param start Starting index, zero relative, inclusive + * @param end Ending index, zero relative, exclusive + */ + public String extract(int start, int end) { + + if ((start < 0) || (start >= end) || (end > length)) + return (""); + else + return (string.substring(start, end)); + + } + + + /** + * Return the index of the next occurrence of the specified character, + * or the index of the character after the last position of the string + * if no more occurrences of this character are found. The current + * parsing position is updated to the returned value. + * + * @param ch Character to be found + */ + public int findChar(char ch) { + + while ((index < length) && (ch != chars[index])) + index++; + return (index); + + } + + + /** + * Return the index of the next occurrence of a non-whitespace character, + * or the index of the character after the last position of the string + * if no more non-whitespace characters are found. The current + * parsing position is updated to the returned value. + */ + public int findText() { + + while ((index < length) && isWhite(chars[index])) + index++; + return (index); + + } + + + /** + * Return the index of the next occurrence of a whitespace character, + * or the index of the character after the last position of the string + * if no more whitespace characters are found. The current parsing + * position is updated to the returned value. + */ + public int findWhite() { + + while ((index < length) && !isWhite(chars[index])) + index++; + return (index); + + } + + + /** + * Reset the current state of the parser to the beginning of the + * current string being parsed. + */ + public void reset() { + + index = 0; + + } + + + /** + * Advance the current parsing position while it is pointing at the + * specified character, or until it moves past the end of the string. + * Return the final value. + * + * @param ch Character to be skipped + */ + public int skipChar(char ch) { + + while ((index < length) && (ch == chars[index])) + index++; + return (index); + + } + + + /** + * Advance the current parsing position while it is pointing at a + * non-whitespace character, or until it moves past the end of the string. + * Return the final value. + */ + public int skipText() { + + while ((index < length) && !isWhite(chars[index])) + index++; + return (index); + + } + + + /** + * Advance the current parsing position while it is pointing at a + * whitespace character, or until it moves past the end of the string. + * Return the final value. + */ + public int skipWhite() { + + while ((index < length) && isWhite(chars[index])) + index++; + return (index); + + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * Is the specified character considered to be whitespace? + * + * @param ch Character to be checked + */ + protected boolean isWhite(char ch) { + + if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) + return (true); + else + return (false); + + } + + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java new file mode 100644 index 000000000..bedb0ff0e --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java @@ -0,0 +1,195 @@ +/* + * 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.lite.util; + +import java.net.FileNameMap; +import java.util.Enumeration; +import java.util.Hashtable; + + +/** + * A mime type map that implements the java.net.FileNameMap interface. + * + * @author James Duncan Davidson [duncan@eng.sun.com] + * @author Jason Hunter [jch@eng.sun.com] + */ +public class MimeMap implements FileNameMap { + + // Defaults - all of them are "well-known" types, + // you can add using normal web.xml. + + public static Hashtable defaultMap = + new Hashtable(101); + static { + defaultMap.put("txt", "text/plain"); + defaultMap.put("css", "text/css"); + defaultMap.put("html","text/html"); + defaultMap.put("htm", "text/html"); + defaultMap.put("gif", "image/gif"); + defaultMap.put("jpg", "image/jpeg"); + defaultMap.put("jpe", "image/jpeg"); + defaultMap.put("jpeg", "image/jpeg"); + defaultMap.put("png", "image/png"); + defaultMap.put("java", "text/plain"); + defaultMap.put("body", "text/html"); + defaultMap.put("rtx", "text/richtext"); + defaultMap.put("tsv", "text/tab-separated-values"); + defaultMap.put("etx", "text/x-setext"); + defaultMap.put("ps", "application/x-postscript"); + defaultMap.put("class", "application/java"); + defaultMap.put("csh", "application/x-csh"); + defaultMap.put("sh", "application/x-sh"); + defaultMap.put("tcl", "application/x-tcl"); + defaultMap.put("tex", "application/x-tex"); + defaultMap.put("texinfo", "application/x-texinfo"); + defaultMap.put("texi", "application/x-texinfo"); + defaultMap.put("t", "application/x-troff"); + defaultMap.put("tr", "application/x-troff"); + defaultMap.put("roff", "application/x-troff"); + defaultMap.put("man", "application/x-troff-man"); + defaultMap.put("me", "application/x-troff-me"); + defaultMap.put("ms", "application/x-wais-source"); + defaultMap.put("src", "application/x-wais-source"); + defaultMap.put("zip", "application/zip"); + defaultMap.put("bcpio", "application/x-bcpio"); + defaultMap.put("cpio", "application/x-cpio"); + defaultMap.put("gtar", "application/x-gtar"); + defaultMap.put("shar", "application/x-shar"); + defaultMap.put("sv4cpio", "application/x-sv4cpio"); + defaultMap.put("sv4crc", "application/x-sv4crc"); + defaultMap.put("tar", "application/x-tar"); + defaultMap.put("ustar", "application/x-ustar"); + defaultMap.put("dvi", "application/x-dvi"); + defaultMap.put("hdf", "application/x-hdf"); + defaultMap.put("latex", "application/x-latex"); + defaultMap.put("bin", "application/octet-stream"); + defaultMap.put("oda", "application/oda"); + defaultMap.put("pdf", "application/pdf"); + defaultMap.put("ps", "application/postscript"); + defaultMap.put("eps", "application/postscript"); + defaultMap.put("ai", "application/postscript"); + defaultMap.put("rtf", "application/rtf"); + defaultMap.put("nc", "application/x-netcdf"); + defaultMap.put("cdf", "application/x-netcdf"); + defaultMap.put("cer", "application/x-x509-ca-cert"); + defaultMap.put("exe", "application/octet-stream"); + defaultMap.put("gz", "application/x-gzip"); + defaultMap.put("Z", "application/x-compress"); + defaultMap.put("z", "application/x-compress"); + defaultMap.put("hqx", "application/mac-binhex40"); + defaultMap.put("mif", "application/x-mif"); + defaultMap.put("ief", "image/ief"); + defaultMap.put("tiff", "image/tiff"); + defaultMap.put("tif", "image/tiff"); + defaultMap.put("ras", "image/x-cmu-raster"); + defaultMap.put("pnm", "image/x-portable-anymap"); + defaultMap.put("pbm", "image/x-portable-bitmap"); + defaultMap.put("pgm", "image/x-portable-graymap"); + defaultMap.put("ppm", "image/x-portable-pixmap"); + defaultMap.put("rgb", "image/x-rgb"); + defaultMap.put("xbm", "image/x-xbitmap"); + defaultMap.put("xpm", "image/x-xpixmap"); + defaultMap.put("xwd", "image/x-xwindowdump"); + defaultMap.put("au", "audio/basic"); + defaultMap.put("snd", "audio/basic"); + defaultMap.put("aif", "audio/x-aiff"); + defaultMap.put("aiff", "audio/x-aiff"); + defaultMap.put("aifc", "audio/x-aiff"); + defaultMap.put("wav", "audio/x-wav"); + defaultMap.put("mpeg", "video/mpeg"); + defaultMap.put("mpg", "video/mpeg"); + defaultMap.put("mpe", "video/mpeg"); + defaultMap.put("qt", "video/quicktime"); + defaultMap.put("mov", "video/quicktime"); + defaultMap.put("avi", "video/x-msvideo"); + defaultMap.put("movie", "video/x-sgi-movie"); + defaultMap.put("avx", "video/x-rad-screenplay"); + defaultMap.put("wrl", "x-world/x-vrml"); + defaultMap.put("mpv2", "video/mpeg2"); + + /* Add XML related MIMEs */ + + defaultMap.put("xml", "text/xml"); + defaultMap.put("xsl", "text/xml"); + defaultMap.put("svg", "image/svg+xml"); + defaultMap.put("svgz", "image/svg+xml"); + defaultMap.put("wbmp", "image/vnd.wap.wbmp"); + defaultMap.put("wml", "text/vnd.wap.wml"); + defaultMap.put("wmlc", "application/vnd.wap.wmlc"); + defaultMap.put("wmls", "text/vnd.wap.wmlscript"); + defaultMap.put("wmlscriptc", "application/vnd.wap.wmlscriptc"); + } + + + private Hashtable map = new Hashtable(); + + public void addContentType(String extn, String type) { + map.put(extn, type.toLowerCase()); + } + + public Enumeration getExtensions() { + return map.keys(); + } + + public String getMimeType(String ext) { + return getContentTypeFor(ext); + } + + public String getContentType(String extn) { + String type = (String)map.get(extn.toLowerCase()); + if( type == null ) type=(String)defaultMap.get( extn ); + return type; + } + + public void removeContentType(String extn) { + map.remove(extn.toLowerCase()); + } + + /** Get extension of file, without fragment id + */ + public static String getExtension( String fileName ) { + // play it safe and get rid of any fragment id + // that might be there + int length=fileName.length(); + + int newEnd = fileName.lastIndexOf('#'); + if( newEnd== -1 ) newEnd=length; + // Instead of creating a new string. + // if (i != -1) { + // fileName = fileName.substring(0, i); + // } + int i = fileName.lastIndexOf('.', newEnd ); + if (i != -1) { + return fileName.substring(i + 1, newEnd ); + } else { + // no extension, no content type + return null; + } + } + + public String getContentTypeFor(String fileName) { + String extn=getExtension( fileName ); + if (extn!=null) { + return getContentType(extn); + } else { + // no extension, no content type + return null; + } + } + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java new file mode 100644 index 000000000..6bc1f5f96 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java @@ -0,0 +1,160 @@ +/* + * 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.lite.util; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.StringTokenizer; + +/** + * Utils to process HTTP/1.1 ranges. Used by default servlet, could + * be used by any servlet that needs to deal with ranges. + * + * It is very good to support ranges if you have large content. In most + * cases supporting one range is enough - getting multiple ranges doesn't + * seem very common, and it's complex (multipart response). + * + * @author Costin Manolache + * @author Remy Maucherat + * @author - see DefaultServlet in Catalin for other contributors + */ +public class Range { + + public long start; + public long end; + public long length; + + /** + * Validate range. + */ + public boolean validate() { + if (end >= length) + end = length - 1; + return ( (start >= 0) && (end >= 0) && (start <= end) + && (length > 0) ); + } + + public void recycle() { + start = 0; + end = 0; + length = 0; + } + + /** Parse ranges. + * + * @return null if the range is invalid or can't be parsed + */ + public static ArrayList parseRanges(long fileLength, + String rangeHeader) throws IOException { + ArrayList result = new ArrayList(); + StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ","); + + // Parsing the range list + while (commaTokenizer.hasMoreTokens()) { + String rangeDefinition = commaTokenizer.nextToken().trim(); + + Range currentRange = new Range(); + currentRange.length = fileLength; + + int dashPos = rangeDefinition.indexOf('-'); + + if (dashPos == -1) { + return null; + } + + if (dashPos == 0) { + try { + long offset = Long.parseLong(rangeDefinition); + currentRange.start = fileLength + offset; + currentRange.end = fileLength - 1; + } catch (NumberFormatException e) { + return null; + } + } else { + + try { + currentRange.start = Long.parseLong + (rangeDefinition.substring(0, dashPos)); + if (dashPos < rangeDefinition.length() - 1) + currentRange.end = Long.parseLong + (rangeDefinition.substring + (dashPos + 1, rangeDefinition.length())); + else + currentRange.end = fileLength - 1; + } catch (NumberFormatException e) { + return null; + } + + } + if (!currentRange.validate()) { + return null; + } + result.add(currentRange); + } + return result; + } + + + /** + * Parse the Content-Range header. Used with PUT or in response. + * + * @return Range + */ + public static Range parseContentRange(String rangeHeader) + throws IOException { + if (rangeHeader == null) + return null; + + // bytes is the only range unit supported + if (!rangeHeader.startsWith("bytes")) { + return null; + } + + rangeHeader = rangeHeader.substring(6).trim(); + + int dashPos = rangeHeader.indexOf('-'); + int slashPos = rangeHeader.indexOf('/'); + + if (dashPos == -1) { + return null; + } + + if (slashPos == -1) { + return null; + } + + Range range = new Range(); + + try { + range.start = Long.parseLong(rangeHeader.substring(0, dashPos)); + range.end = + Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos)); + range.length = Long.parseLong + (rangeHeader.substring(slashPos + 1, rangeHeader.length())); + } catch (NumberFormatException e) { + return null; + } + + if (!range.validate()) { + return null; + } + + return range; + } + +} \ No newline at end of file diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java new file mode 100644 index 000000000..4364aea86 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java @@ -0,0 +1,227 @@ +/* + * 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.lite.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.util.BitSet; + +/** + * + * This class is very similar to the java.net.URLEncoder class. + * + * Unfortunately, with java.net.URLEncoder there is no way to specify to the + * java.net.URLEncoder which characters should NOT be encoded. + * + * This code was moved from DefaultServlet.java + * + * @author Craig R. McClanahan + * @author Remy Maucherat + */ +public class URLEncoder { + protected static final char[] hexadecimal = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F'}; + + //Array containing the safe characters set. + protected BitSet safeChars = new BitSet(128); + + public URLEncoder() { + for (char i = 'a'; i <= 'z'; i++) { + addSafeCharacter(i); + } + for (char i = 'A'; i <= 'Z'; i++) { + addSafeCharacter(i); + } + for (char i = '0'; i <= '9'; i++) { + addSafeCharacter(i); + } + //safe + safeChars.set('$'); + safeChars.set('-'); + safeChars.set('_'); + safeChars.set('.'); + + // Dangerous: someone may treat this as " " + // RFC1738 does allow it, it's not reserved + // safeChars.set('+'); + //extra + safeChars.set('!'); + safeChars.set('*'); + safeChars.set('\''); + safeChars.set('('); + safeChars.set(')'); + safeChars.set(','); + } + + public void addSafeCharacter( char c ) { + safeChars.set( c ); + } + + public String encodeURL(String path) { + return encodeURL(path, "UTF-8", true); + } + + public String encodeURL(String path, String enc, boolean allowSlash) { + int maxBytesPerChar = 10; + + StringBuffer rewrittenPath = new StringBuffer(path.length()); + ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar); + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter(buf, enc); + } catch (UnsupportedEncodingException e1) { + // shouldn't happen. + } + + for (int i = 0; i < path.length(); i++) { + int c = (int) path.charAt(i); + if (c < 128 && safeChars.get(c) || allowSlash && c == '/') { + rewrittenPath.append((char)c); + } else { + // convert to external encoding before hex conversion + try { + writer.write((char)c); + if (c >= 0xD800 && c <= 0xDBFF) { + if ( (i+1) < path.length()) { + int d = path.charAt(i+1); + if (d >= 0xDC00 && d <= 0xDFFF) { + writer.write((char) d); + i++; + } + } + } + writer.flush(); + } catch(IOException e) { + buf.reset(); + continue; + } + byte[] ba = buf.toByteArray(); + for (int j = 0; j < ba.length; j++) { + // Converting each byte in the buffer + byte toEncode = ba[j]; + rewrittenPath.append('%'); + int low = (int) (toEncode & 0x0f); + int high = (int) ((toEncode & 0xf0) >> 4); + rewrittenPath.append(hexadecimal[high]); + rewrittenPath.append(hexadecimal[low]); + } + buf.reset(); + } + } + return rewrittenPath.toString(); + } + + /** + * Decode and return the specified URL-encoded String. + * + * @param str The url-encoded string + * @param enc The encoding to use; if null, the default encoding is used + * @exception IllegalArgumentException if a '%' character is not followed + * by a valid 2-digit hexadecimal number + */ + public static String URLDecode(String str, String enc) { + + if (str == null) + return (null); + + // use the specified encoding to extract bytes out of the + // given string so that the encoding is not lost. If an + // encoding is not specified, let it use platform default + byte[] bytes = null; + try { + if (enc == null) { + bytes = str.getBytes(); + } else { + bytes = str.getBytes(enc); + } + } catch (UnsupportedEncodingException uee) {} + + return URLDecode(bytes, enc); + + } + + + /** + * Decode and return the specified URL-encoded String. + * When the byte array is converted to a string, the system default + * character encoding is used... This may be different than some other + * servers. + * + * @param str The url-encoded string + * + * @exception IllegalArgumentException if a '%' character is not followed + * by a valid 2-digit hexadecimal number + */ + public static String URLDecode(String str) { + + return URLDecode(str, null); + + } + + /** + * Decode and return the specified URL-encoded byte array. + * + * @param bytes The url-encoded byte array + * @param enc The encoding to use; if null, the default encoding is used + * @exception IllegalArgumentException if a '%' character is not followed + * by a valid 2-digit hexadecimal number + */ + private static String URLDecode(byte[] bytes, String enc) { + + if (bytes == null) + return (null); + + int len = bytes.length; + int ix = 0; + int ox = 0; + while (ix < len) { + byte b = bytes[ix++]; // Get byte to test + if (b == '+') { + b = (byte)' '; + } else if (b == '%') { + b = (byte) ((convertHexDigit(bytes[ix++]) << 4) + + convertHexDigit(bytes[ix++])); + } + bytes[ox++] = b; + } + if (enc != null) { + try { + return new String(bytes, 0, ox, enc); + } catch (Exception e) { + e.printStackTrace(); + } + } + return new String(bytes, 0, ox); + + } + + /** + * Convert a byte character value to hexidecimal digit value. + * + * @param b the character value byte + */ + private static byte convertHexDigit( byte b ) { + if ((b >= '0') && (b <= '9')) return (byte)(b - '0'); + if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10); + if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10); + return 0; + } + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java new file mode 100644 index 000000000..c0d9df0d9 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java @@ -0,0 +1,84 @@ +/* + * 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.lite.util; + +public class UrlUtils { + + /** Used by webdav. + * + * Return a context-relative path, beginning with a "/", that represents + * the canonical version of the specified path after ".." and "." elements + * are resolved out. If the specified path attempts to go outside the + * boundaries of the current context (i.e. too many ".." path elements + * are present), return null instead. + * + * @param path Path to be normalized + */ + public static String normalize(String path) { + + if (path == null) + return null; + + // Create a place for the normalized path + String normalized = path; + + if (normalized.equals("/.")) + return "/"; + + // Normalize the slashes and add leading slash if necessary + if (normalized.indexOf('\\') >= 0) + normalized = normalized.replace('\\', '/'); + + if (!normalized.startsWith("/")) + normalized = "/" + normalized; + + // Resolve occurrences of "//" in the normalized path + while (true) { + int index = normalized.indexOf("//"); + if (index < 0) + break; + normalized = normalized.substring(0, index) + + normalized.substring(index + 1); + } + + // Resolve occurrences of "/./" in the normalized path + while (true) { + int index = normalized.indexOf("/./"); + if (index < 0) + break; + normalized = normalized.substring(0, index) + + normalized.substring(index + 2); + } + + // Resolve occurrences of "/../" in the normalized path + while (true) { + int index = normalized.indexOf("/../"); + if (index < 0) + break; + if (index == 0) + return (null); // Trying to go outside our context + int index2 = normalized.lastIndexOf('/', index - 1); + normalized = normalized.substring(0, index2) + + normalized.substring(index + 3); + } + + // Return the normalized path that we have completed + return (normalized); + } + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java index 50ea4e64b..4015bf76e 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java @@ -41,8 +41,11 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.tomcat.servlets.util.Range; -import org.apache.tomcat.servlets.util.URLEncoder; + +import org.apache.tomcat.lite.util.CopyUtils; +import org.apache.tomcat.lite.util.Dir2Html; +import org.apache.tomcat.lite.util.Range; +import org.apache.tomcat.lite.util.URLEncoder; /** * The default resource-serving servlet for most web applications, @@ -124,8 +127,6 @@ public class DefaultServlet extends HttpServlet { // --------------------------------------------------------- Public Methods - protected Filesystem fs; - /** * Finalize this servlet. */ @@ -136,10 +137,6 @@ public class DefaultServlet extends HttpServlet { * Initialize this servlet. */ public void init() throws ServletException { - if (fs == null) { - // R/O - no write - fs = new Filesystem(); - } String realPath = getServletContext().getRealPath("/"); basePath = new File(realPath); @@ -148,7 +145,7 @@ public class DefaultServlet extends HttpServlet { } catch (IOException e) { basePathName = basePath.getAbsolutePath(); } - log("Init fs " + fs + " base: " + basePathName); + log("Init default serviet, base: " + basePathName); // Set our properties from the initialization parameters String value = null; @@ -183,14 +180,6 @@ public class DefaultServlet extends HttpServlet { output = 256; } - public void setFilesystem(Filesystem fs) { - this.fs = fs; - } - - public Filesystem getFilesystem() { - return fs; - } - public void setBasePath(String s) { this.basePathName = s; this.basePath = new File(s); @@ -391,6 +380,64 @@ public class DefaultServlet extends HttpServlet { // } } + public void renderDir(HttpServletRequest request, + HttpServletResponse response, + File resFile, + String fileEncoding, + boolean content, + String relativePath) throws IOException { + + String contentType = "text/html;charset=" + fileEncoding; + + ServletOutputStream ostream = null; + PrintWriter writer = null; + + if (content) { + // Trying to retrieve the servlet output stream + try { + ostream = response.getOutputStream(); + } catch (IllegalStateException e) { + // If it fails, we try to get a Writer instead if we're + // trying to serve a text file + if ( (contentType == null) + || (contentType.startsWith("text")) ) { + writer = response.getWriter(); + } else { + throw e; + } + } + + } + + // Set the appropriate output headers + response.setContentType(contentType); + + InputStream renderResult = null; + + if (content) { + // Serve the directory browser + renderResult = + dir2Html.render(request.getContextPath(), resFile, relativePath); + } + + + // Copy the input stream to our output stream (if requested) + if (content) { + try { + response.setBufferSize(output); + } catch (IllegalStateException e) { + // Silent catch + } + if (ostream != null) { + CopyUtils.copy(renderResult, ostream); + } else { + CopyUtils.copy(renderResult, writer, fileEncoding); + } + } + + + } + /** * Serve the specified resource, optionally including the data content. @@ -430,7 +477,7 @@ public class DefaultServlet extends HttpServlet { request.getRequestURI()); return; } - dir2Html.renderDir(request, response, resFile, fileEncoding, content, + renderDir(request, response, resFile, fileEncoding, content, path); return; diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java index 19a54f177..92c61d66f 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java @@ -18,6 +18,9 @@ package org.apache.tomcat.servlets.file; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -37,10 +40,13 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import org.apache.tomcat.servlets.util.FastHttpDateFormat; -import org.apache.tomcat.servlets.util.Range; +import org.apache.tomcat.lite.util.CopyUtils; +import org.apache.tomcat.lite.util.FastHttpDateFormat; +import org.apache.tomcat.lite.util.Range; +import org.apache.tomcat.lite.util.URLEncoder; +import org.apache.tomcat.lite.util.UrlUtils; +import org.apache.tomcat.lite.util.XMLWriter; import org.apache.tomcat.servlets.util.RequestUtil; -import org.apache.tomcat.servlets.util.UrlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -420,7 +426,7 @@ public class WebdavServlet extends DefaultServlet { * @param path Path which has to be rewiten */ protected String rewriteUrl(String path) { - return urlEncoder.encode( path ); + return urlEncoder.encodeURL( path ); } @@ -841,7 +847,7 @@ public class WebdavServlet extends DefaultServlet { try { // will override - OutputStream fos = getFilesystem().getOutputStream(resFile.getPath()); + OutputStream fos = getOut(resFile.getPath()); CopyUtils.copy(resourceInputStream, fos); } catch(IOException e) { result = false; @@ -866,6 +872,7 @@ public class WebdavServlet extends DefaultServlet { * Handle a partial PUT. New content specified in request is appended to * existing content in oldRevisionContent (if present). This code does * not support simultaneous partial updates to the same resource. + * @throws FileNotFoundException */ // protected File executePartialPut(HttpServletRequest req, Range range, // String path) @@ -929,6 +936,10 @@ public class WebdavServlet extends DefaultServlet { // } + private OutputStream getOut(String path) throws FileNotFoundException { + return new FileOutputStream(path); + } + /** * COPY Method. */ @@ -1044,7 +1055,7 @@ public class WebdavServlet extends DefaultServlet { } // Remove url encoding from destination - destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8"); + destinationPath = URLEncoder.URLDecode(destinationPath, "UTF8"); destinationPath = removeDestinationPrefix(req, destinationPath); @@ -1222,8 +1233,8 @@ public class WebdavServlet extends DefaultServlet { } else { try { - CopyUtils.copy(getFilesystem().getInputStream(object.getPath()), - getFilesystem().getOutputStream(dest)); + CopyUtils.copy(new FileInputStream(object.getPath()), + getOut(dest)); } catch(IOException ex ) { errorList.put (source, diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Base64.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Base64.java deleted file mode 100644 index 1f4228644..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Base64.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 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.servlets.util; - -import java.util.logging.Level; -import java.util.logging.Logger; - - -/** - * This class provides encode/decode for RFC 2045 Base64 as - * defined by RFC 2045, N. Freed and N. Borenstein. - * RFC 2045: Multipurpose Internet Mail Extensions (MIME) - * Part One: Format of Internet Message Bodies. Reference - * 1996 Available at: http://www.ietf.org/rfc/rfc2045.txt - * This class is used by XML Schema binary format validation - * - * @author Jeffrey Rodriguez - * @version $Revision: 467222 $ $Date: 2006-10-23 20:17:11 -0700 (Mon, 23 Oct 2006) $ - */ - -public final class Base64 { - - - private static Logger log= - Logger.getLogger( Base64.class.getName() ); - - static private final int BASELENGTH = 255; - static private final int LOOKUPLENGTH = 63; - static private final int TWENTYFOURBITGROUP = 24; - static private final int EIGHTBIT = 8; - static private final int SIXTEENBIT = 16; - static private final int SIXBIT = 6; - static private final int FOURBYTE = 4; - - - static private final byte PAD = ( byte ) '='; - static private byte [] base64Alphabet = new byte[BASELENGTH]; - static private byte [] lookUpBase64Alphabet = new byte[LOOKUPLENGTH + 1]; - - static { - - for (int i = 0; i= 'A'; i-- ) { - base64Alphabet[i] = (byte) (i-'A'); - } - for ( int i = 'z'; i>= 'a'; i--) { - base64Alphabet[i] = (byte) ( i-'a' + 26); - } - - for ( int i = '9'; i >= '0'; i--) { - base64Alphabet[i] = (byte) (i-'0' + 52); - } - - base64Alphabet['+'] = 62; - base64Alphabet['/'] = 63; - String lookupString = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + - "0123456789+/"; - - for (int i = 0; i < lookupString.length(); i++ ) { - lookUpBase64Alphabet[i] = (byte) lookupString.charAt(i); - } - } - - - static boolean isBase64( byte octect ) { - //shall we ignore white space? JEFF?? - return(octect == PAD || base64Alphabet[octect] != -1 ); - } - - - static boolean isArrayByteBase64( byte[] arrayOctect ) { - int length = arrayOctect.length; - if ( length == 0 ) - return false; - for ( int i=0; i < length; i++ ) { - if ( Base64.isBase64( arrayOctect[i] ) == false) - return false; - } - return true; - } - - /** - * Encodes hex octects into Base64 - * - * @param binaryData Array containing binaryData - * @return Encoded Base64 array - */ - public static byte[] encode( byte[] binaryData ) { - int lengthDataBits = binaryData.length*EIGHTBIT; - int fewerThan24bits = lengthDataBits%TWENTYFOURBITGROUP; - int numberTriplets = lengthDataBits/TWENTYFOURBITGROUP; - byte encodedData[] = null; - - - if ( fewerThan24bits != 0 ) //data not divisible by 24 bit - encodedData = new byte[ (numberTriplets + 1 )*4 ]; - else // 16 or 8 bit - encodedData = new byte[ numberTriplets*4 ]; - - int k=0, l=0, b1=0,b2=0,b3=0; - - int encodedIndex = 0; - int dataIndex = 0; - int i = 0; - for ( i = 0; i>2 ]; - encodedData[encodedIndex+1] = lookUpBase64Alphabet[(b2 >>4 ) | -( k<<4 )]; - encodedData[encodedIndex+2] = lookUpBase64Alphabet[ (l <<2 ) | -( b3>>6)]; - encodedData[encodedIndex+3] = lookUpBase64Alphabet[ b3 & 0x3f ]; - } - - // form integral number of 6-bit groups - dataIndex = i*3; - encodedIndex = i*4; - if (fewerThan24bits == EIGHTBIT ) { - b1 = binaryData[dataIndex]; - k = (byte) ( b1 &0x03 ); - encodedData[encodedIndex] = lookUpBase64Alphabet[ b1 >>2 ]; - encodedData[encodedIndex + 1] = lookUpBase64Alphabet[ k<<4 ]; - encodedData[encodedIndex + 2] = PAD; - encodedData[encodedIndex + 3] = PAD; - } else if ( fewerThan24bits == SIXTEENBIT ) { - - b1 = binaryData[dataIndex]; - b2 = binaryData[dataIndex +1 ]; - l = ( byte ) ( b2 &0x0f ); - k = ( byte ) ( b1 &0x03 ); - encodedData[encodedIndex] = lookUpBase64Alphabet[ b1 >>2 ]; - encodedData[encodedIndex + 1] = lookUpBase64Alphabet[ (b2 >>4 ) -| ( k<<4 )]; - encodedData[encodedIndex + 2] = lookUpBase64Alphabet[ l<<2 ]; - encodedData[encodedIndex + 3] = PAD; - } - return encodedData; - } - - - /** - * Decodes Base64 data into octects - * - * @param base64Data Byte array containing Base64 data - * @return Array containind decoded data. - */ - public static byte[] decode( byte[] base64Data ) { - int numberQuadruple = base64Data.length/FOURBYTE; - byte decodedData[] = null; - byte b1=0,b2=0,b3=0, b4=0, marker0=0, marker1=0; - - // Throw away anything not in base64Data - // Adjust size - - int encodedIndex = 0; - int dataIndex = 0; - decodedData = new byte[ numberQuadruple*3 + 1 ]; - - for (int i = 0; i>4 ) ; - decodedData[encodedIndex+1] = (byte)(((b2 & 0xf)<<4 ) |( -(b3>>2) & 0xf) ); - decodedData[encodedIndex+2] = (byte)( b3<<6 | b4 ); - } else if ( marker0 == PAD ) { //Two PAD e.g. 3c[Pad][Pad] - decodedData[encodedIndex] = (byte)( b1 <<2 | b2>>4 ) ; - decodedData[encodedIndex+1] = (byte)((b2 & 0xf)<<4 ); - decodedData[encodedIndex+2] = (byte) 0; - } else if ( marker1 == PAD ) { //One PAD e.g. 3cQ[Pad] - b3 = base64Alphabet[ marker0 ]; - - decodedData[encodedIndex] = (byte)( b1 <<2 | b2>>4 ); - decodedData[encodedIndex+1] = (byte)(((b2 & 0xf)<<4 ) |( -(b3>>2) & 0xf) ); - decodedData[encodedIndex+2] = (byte)( b3<<6); - } - encodedIndex += 3; - } - return decodedData; - - } - - static final int base64[]= { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, - 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, - 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 - }; - - public static String base64Decode( String orig ) { - char chars[]=orig.toCharArray(); - StringBuffer sb=new StringBuffer(); - int i=0; - - int shift = 0; // # of excess bits stored in accum - int acc = 0; - - for (i=0; i= 64 ) { - if( chars[i] != '=' ) - if (log.isLoggable(Level.INFO)) - log.info("Wrong char in base64: " + chars[i]); - } else { - acc= ( acc << 6 ) | v; - shift += 6; - if ( shift >= 8 ) { - shift -= 8; - sb.append( (char) ((acc >> shift) & 0xff)); - } - } - } - return sb.toString(); - } - - -} - diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/FastHttpDateFormat.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/FastHttpDateFormat.java deleted file mode 100644 index 7c2f905d6..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/FastHttpDateFormat.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 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.servlets.util; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Utility class to generate HTTP dates. - * - * @author Remy Maucherat - */ -public final class FastHttpDateFormat { - - - // -------------------------------------------------------------- Variables - - - protected static final int CACHE_SIZE = - Integer.parseInt(System.getProperty("org.apache.tomcat.util.http.FastHttpDateFormat.CACHE_SIZE", "1000")); - - - /** - * HTTP date format. - */ - protected static final SimpleDateFormat format = - new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); - - - /** - * The set of SimpleDateFormat formats to use in getDateHeader(). - */ - protected static final SimpleDateFormat formats[] = { - new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), - new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), - new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) - }; - - - protected final static TimeZone gmtZone = TimeZone.getTimeZone("GMT"); - - - /** - * GMT timezone - all HTTP dates are on GMT - */ - static { - - format.setTimeZone(gmtZone); - - formats[0].setTimeZone(gmtZone); - formats[1].setTimeZone(gmtZone); - formats[2].setTimeZone(gmtZone); - - } - - - /** - * Instant on which the currentDate object was generated. - */ - protected static long currentDateGenerated = 0L; - - - /** - * Current formatted date. - */ - protected static String currentDate = null; - - - /** - * Formatter cache. - */ - protected static final ConcurrentHashMap formatCache = - new ConcurrentHashMap(CACHE_SIZE); - - - /** - * Parser cache. - */ - protected static final ConcurrentHashMap parseCache = - new ConcurrentHashMap(CACHE_SIZE); - - - // --------------------------------------------------------- Public Methods - - - /** - * Get the current date in HTTP format. - */ - public static final String getCurrentDate() { - - long now = System.currentTimeMillis(); - if ((now - currentDateGenerated) > 1000) { - synchronized (format) { - if ((now - currentDateGenerated) > 1000) { - currentDateGenerated = now; - currentDate = format.format(new Date(now)); - } - } - } - return currentDate; - - } - - - /** - * Get the HTTP format of the specified date. - */ - public static final String formatDate - (long value, DateFormat threadLocalformat) { - - Long longValue = new Long(value); - String cachedDate = formatCache.get(longValue); - if (cachedDate != null) - return cachedDate; - - String newDate = null; - Date dateValue = new Date(value); - if (threadLocalformat != null) { - newDate = threadLocalformat.format(dateValue); - updateFormatCache(longValue, newDate); - } else { - synchronized (formatCache) { - synchronized (format) { - newDate = format.format(dateValue); - } - updateFormatCache(longValue, newDate); - } - } - return newDate; - - } - - - /** - * Try to parse the given date as a HTTP date. - */ - public static final long parseDate(String value, - DateFormat[] threadLocalformats) { - - Long cachedDate = parseCache.get(value); - if (cachedDate != null) - return cachedDate.longValue(); - - Long date = null; - if (threadLocalformats != null) { - date = internalParseDate(value, threadLocalformats); - updateParseCache(value, date); - } else { - synchronized (parseCache) { - date = internalParseDate(value, formats); - updateParseCache(value, date); - } - } - if (date == null) { - return (-1L); - } else { - return date.longValue(); - } - - } - - - /** - * Parse date with given formatters. - */ - private static final Long internalParseDate - (String value, DateFormat[] formats) { - Date date = null; - for (int i = 0; (date == null) && (i < formats.length); i++) { - try { - date = formats[i].parse(value); - } catch (ParseException e) { - ; - } - } - if (date == null) { - return null; - } - return new Long(date.getTime()); - } - - - /** - * Update cache. - */ - private static void updateFormatCache(Long key, String value) { - if (value == null) { - return; - } - if (formatCache.size() > CACHE_SIZE) { - formatCache.clear(); - } - formatCache.put(key, value); - } - - - /** - * Update cache. - */ - private static void updateParseCache(String key, Long value) { - if (value == null) { - return; - } - if (parseCache.size() > CACHE_SIZE) { - parseCache.clear(); - } - parseCache.put(key, value); - } - - -} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java deleted file mode 100644 index bfe3e42e4..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * 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.servlets.util; - -import java.util.ArrayList; -import java.util.Locale; -import java.util.TreeMap; - - -/** - * Utility class for string parsing that is higher performance than - * StringParser for simple delimited text cases. Parsing is performed - * by setting the string, and then using the findXxxx() and - * skipXxxx() families of methods to remember significant - * offsets. To retrieve the parsed substrings, call the extract() - * method with the appropriate saved offset values. - * - * @author Craig R. McClanahan - */ -public final class LocaleParser { - - public LocaleParser() { - this(null); - } - - public LocaleParser(String string) { - setString(string); - } - - public TreeMap parseLocale(String value) { - // Store the accumulated languages that have been requested in - // a local collection, sorted by the quality value (so we can - // add Locales in descending order). The values will be ArrayLists - // containing the corresponding Locales to be added - TreeMap locales = new TreeMap(); - - // Preprocess the value to remove all whitespace - int white = value.indexOf(' '); - if (white < 0) - white = value.indexOf('\t'); - if (white >= 0) { - StringBuilder sb = new StringBuilder(); - int len = value.length(); - for (int i = 0; i < len; i++) { - char ch = value.charAt(i); - if ((ch != ' ') && (ch != '\t')) - sb.append(ch); - } - value = sb.toString(); - } - - LocaleParser parser = this; - // Process each comma-delimited language specification - parser.setString(value); // ASSERT: parser is available to us - int length = parser.getLength(); - while (true) { - - // Extract the next comma-delimited entry - int start = parser.getIndex(); - if (start >= length) - break; - int end = parser.findChar(','); - String entry = parser.extract(start, end).trim(); - parser.advance(); // For the following entry - - // Extract the quality factor for this entry - double quality = 1.0; - int semi = entry.indexOf(";q="); - if (semi >= 0) { - try { - quality = Double.parseDouble(entry.substring(semi + 3)); - } catch (NumberFormatException e) { - quality = 0.0; - } - entry = entry.substring(0, semi); - } - - // Skip entries we are not going to keep track of - if (quality < 0.00005) - continue; // Zero (or effectively zero) quality factors - if ("*".equals(entry)) - continue; // FIXME - "*" entries are not handled - - // Extract the language and country for this entry - String language = null; - String country = null; - String variant = null; - int dash = entry.indexOf('-'); - if (dash < 0) { - language = entry; - country = ""; - variant = ""; - } else { - language = entry.substring(0, dash); - country = entry.substring(dash + 1); - int vDash = country.indexOf('-'); - if (vDash > 0) { - String cTemp = country.substring(0, vDash); - variant = country.substring(vDash + 1); - country = cTemp; - } else { - variant = ""; - } - } - - // Add a new Locale to the list of Locales for this quality level - Locale locale = new Locale(language, country, variant); - Double key = new Double(-quality); // Reverse the order - ArrayList values = (ArrayList) locales.get(key); - if (values == null) { - values = new ArrayList(); - locales.put(key, values); - } - values.add(locale); - - } - - return locales; - } - - /** - * The characters of the current string, as a character array. Stored - * when the string is first specified to speed up access to characters - * being compared during parsing. - */ - private char chars[] = null; - - - /** - * The zero-relative index of the current point at which we are - * positioned within the string being parsed. NOTE: - * the value of this index can be one larger than the index of the last - * character of the string (i.e. equal to the string length) if you - * parse off the end of the string. This value is useful for extracting - * substrings that include the end of the string. - */ - private int index = 0; - - - /** - * The length of the String we are currently parsing. Stored when the - * string is first specified to avoid repeated recalculations. - */ - private int length = 0; - - - /** - * The String we are currently parsing. - */ - private String string = null; - - - // ------------------------------------------------------------- Properties - - - /** - * Return the zero-relative index of our current parsing position - * within the string being parsed. - */ - public int getIndex() { - - return (this.index); - - } - - - /** - * Return the length of the string we are parsing. - */ - public int getLength() { - - return (this.length); - - } - - - /** - * Return the String we are currently parsing. - */ - public String getString() { - - return (this.string); - - } - - - /** - * Set the String we are currently parsing. The parser state is also reset - * to begin at the start of this string. - * - * @param string The string to be parsed. - */ - public void setString(String string) { - - this.string = string; - if (string != null) { - this.length = string.length(); - chars = this.string.toCharArray(); - } else { - this.length = 0; - chars = new char[0]; - } - reset(); - - } - - - // --------------------------------------------------------- Public Methods - - - /** - * Advance the current parsing position by one, if we are not already - * past the end of the string. - */ - public void advance() { - - if (index < length) - index++; - - } - - - /** - * Extract and return a substring that starts at the specified position, - * and extends to the end of the string being parsed. If this is not - * possible, a zero-length string is returned. - * - * @param start Starting index, zero relative, inclusive - */ - public String extract(int start) { - - if ((start < 0) || (start >= length)) - return (""); - else - return (string.substring(start)); - - } - - - /** - * Extract and return a substring that starts at the specified position, - * and ends at the character before the specified position. If this is - * not possible, a zero-length string is returned. - * - * @param start Starting index, zero relative, inclusive - * @param end Ending index, zero relative, exclusive - */ - public String extract(int start, int end) { - - if ((start < 0) || (start >= end) || (end > length)) - return (""); - else - return (string.substring(start, end)); - - } - - - /** - * Return the index of the next occurrence of the specified character, - * or the index of the character after the last position of the string - * if no more occurrences of this character are found. The current - * parsing position is updated to the returned value. - * - * @param ch Character to be found - */ - public int findChar(char ch) { - - while ((index < length) && (ch != chars[index])) - index++; - return (index); - - } - - - /** - * Return the index of the next occurrence of a non-whitespace character, - * or the index of the character after the last position of the string - * if no more non-whitespace characters are found. The current - * parsing position is updated to the returned value. - */ - public int findText() { - - while ((index < length) && isWhite(chars[index])) - index++; - return (index); - - } - - - /** - * Return the index of the next occurrence of a whitespace character, - * or the index of the character after the last position of the string - * if no more whitespace characters are found. The current parsing - * position is updated to the returned value. - */ - public int findWhite() { - - while ((index < length) && !isWhite(chars[index])) - index++; - return (index); - - } - - - /** - * Reset the current state of the parser to the beginning of the - * current string being parsed. - */ - public void reset() { - - index = 0; - - } - - - /** - * Advance the current parsing position while it is pointing at the - * specified character, or until it moves past the end of the string. - * Return the final value. - * - * @param ch Character to be skipped - */ - public int skipChar(char ch) { - - while ((index < length) && (ch == chars[index])) - index++; - return (index); - - } - - - /** - * Advance the current parsing position while it is pointing at a - * non-whitespace character, or until it moves past the end of the string. - * Return the final value. - */ - public int skipText() { - - while ((index < length) && !isWhite(chars[index])) - index++; - return (index); - - } - - - /** - * Advance the current parsing position while it is pointing at a - * whitespace character, or until it moves past the end of the string. - * Return the final value. - */ - public int skipWhite() { - - while ((index < length) && isWhite(chars[index])) - index++; - return (index); - - } - - - // ------------------------------------------------------ Protected Methods - - - /** - * Is the specified character considered to be whitespace? - * - * @param ch Character to be checked - */ - protected boolean isWhite(char ch) { - - if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) - return (true); - else - return (false); - - } - - -} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/MimeMap.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/MimeMap.java deleted file mode 100644 index a1731955c..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/MimeMap.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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.servlets.util; - -import java.net.FileNameMap; -import java.util.Enumeration; -import java.util.Hashtable; - - -/** - * A mime type map that implements the java.net.FileNameMap interface. - * - * @author James Duncan Davidson [duncan@eng.sun.com] - * @author Jason Hunter [jch@eng.sun.com] - */ -public class MimeMap implements FileNameMap { - - // Defaults - all of them are "well-known" types, - // you can add using normal web.xml. - - public static Hashtable defaultMap = - new Hashtable(101); - static { - defaultMap.put("txt", "text/plain"); - defaultMap.put("css", "text/css"); - defaultMap.put("html","text/html"); - defaultMap.put("htm", "text/html"); - defaultMap.put("gif", "image/gif"); - defaultMap.put("jpg", "image/jpeg"); - defaultMap.put("jpe", "image/jpeg"); - defaultMap.put("jpeg", "image/jpeg"); - defaultMap.put("png", "image/png"); - defaultMap.put("java", "text/plain"); - defaultMap.put("body", "text/html"); - defaultMap.put("rtx", "text/richtext"); - defaultMap.put("tsv", "text/tab-separated-values"); - defaultMap.put("etx", "text/x-setext"); - defaultMap.put("ps", "application/x-postscript"); - defaultMap.put("class", "application/java"); - defaultMap.put("csh", "application/x-csh"); - defaultMap.put("sh", "application/x-sh"); - defaultMap.put("tcl", "application/x-tcl"); - defaultMap.put("tex", "application/x-tex"); - defaultMap.put("texinfo", "application/x-texinfo"); - defaultMap.put("texi", "application/x-texinfo"); - defaultMap.put("t", "application/x-troff"); - defaultMap.put("tr", "application/x-troff"); - defaultMap.put("roff", "application/x-troff"); - defaultMap.put("man", "application/x-troff-man"); - defaultMap.put("me", "application/x-troff-me"); - defaultMap.put("ms", "application/x-wais-source"); - defaultMap.put("src", "application/x-wais-source"); - defaultMap.put("zip", "application/zip"); - defaultMap.put("bcpio", "application/x-bcpio"); - defaultMap.put("cpio", "application/x-cpio"); - defaultMap.put("gtar", "application/x-gtar"); - defaultMap.put("shar", "application/x-shar"); - defaultMap.put("sv4cpio", "application/x-sv4cpio"); - defaultMap.put("sv4crc", "application/x-sv4crc"); - defaultMap.put("tar", "application/x-tar"); - defaultMap.put("ustar", "application/x-ustar"); - defaultMap.put("dvi", "application/x-dvi"); - defaultMap.put("hdf", "application/x-hdf"); - defaultMap.put("latex", "application/x-latex"); - defaultMap.put("bin", "application/octet-stream"); - defaultMap.put("oda", "application/oda"); - defaultMap.put("pdf", "application/pdf"); - defaultMap.put("ps", "application/postscript"); - defaultMap.put("eps", "application/postscript"); - defaultMap.put("ai", "application/postscript"); - defaultMap.put("rtf", "application/rtf"); - defaultMap.put("nc", "application/x-netcdf"); - defaultMap.put("cdf", "application/x-netcdf"); - defaultMap.put("cer", "application/x-x509-ca-cert"); - defaultMap.put("exe", "application/octet-stream"); - defaultMap.put("gz", "application/x-gzip"); - defaultMap.put("Z", "application/x-compress"); - defaultMap.put("z", "application/x-compress"); - defaultMap.put("hqx", "application/mac-binhex40"); - defaultMap.put("mif", "application/x-mif"); - defaultMap.put("ief", "image/ief"); - defaultMap.put("tiff", "image/tiff"); - defaultMap.put("tif", "image/tiff"); - defaultMap.put("ras", "image/x-cmu-raster"); - defaultMap.put("pnm", "image/x-portable-anymap"); - defaultMap.put("pbm", "image/x-portable-bitmap"); - defaultMap.put("pgm", "image/x-portable-graymap"); - defaultMap.put("ppm", "image/x-portable-pixmap"); - defaultMap.put("rgb", "image/x-rgb"); - defaultMap.put("xbm", "image/x-xbitmap"); - defaultMap.put("xpm", "image/x-xpixmap"); - defaultMap.put("xwd", "image/x-xwindowdump"); - defaultMap.put("au", "audio/basic"); - defaultMap.put("snd", "audio/basic"); - defaultMap.put("aif", "audio/x-aiff"); - defaultMap.put("aiff", "audio/x-aiff"); - defaultMap.put("aifc", "audio/x-aiff"); - defaultMap.put("wav", "audio/x-wav"); - defaultMap.put("mpeg", "video/mpeg"); - defaultMap.put("mpg", "video/mpeg"); - defaultMap.put("mpe", "video/mpeg"); - defaultMap.put("qt", "video/quicktime"); - defaultMap.put("mov", "video/quicktime"); - defaultMap.put("avi", "video/x-msvideo"); - defaultMap.put("movie", "video/x-sgi-movie"); - defaultMap.put("avx", "video/x-rad-screenplay"); - defaultMap.put("wrl", "x-world/x-vrml"); - defaultMap.put("mpv2", "video/mpeg2"); - - /* Add XML related MIMEs */ - - defaultMap.put("xml", "text/xml"); - defaultMap.put("xsl", "text/xml"); - defaultMap.put("svg", "image/svg+xml"); - defaultMap.put("svgz", "image/svg+xml"); - defaultMap.put("wbmp", "image/vnd.wap.wbmp"); - defaultMap.put("wml", "text/vnd.wap.wml"); - defaultMap.put("wmlc", "application/vnd.wap.wmlc"); - defaultMap.put("wmls", "text/vnd.wap.wmlscript"); - defaultMap.put("wmlscriptc", "application/vnd.wap.wmlscriptc"); - } - - - private Hashtable map = new Hashtable(); - - public void addContentType(String extn, String type) { - map.put(extn, type.toLowerCase()); - } - - public Enumeration getExtensions() { - return map.keys(); - } - - public String getMimeType(String ext) { - return getContentTypeFor(ext); - } - - public String getContentType(String extn) { - String type = (String)map.get(extn.toLowerCase()); - if( type == null ) type=(String)defaultMap.get( extn ); - return type; - } - - public void removeContentType(String extn) { - map.remove(extn.toLowerCase()); - } - - /** Get extension of file, without fragment id - */ - public static String getExtension( String fileName ) { - // play it safe and get rid of any fragment id - // that might be there - int length=fileName.length(); - - int newEnd = fileName.lastIndexOf('#'); - if( newEnd== -1 ) newEnd=length; - // Instead of creating a new string. - // if (i != -1) { - // fileName = fileName.substring(0, i); - // } - int i = fileName.lastIndexOf('.', newEnd ); - if (i != -1) { - return fileName.substring(i + 1, newEnd ); - } else { - // no extension, no content type - return null; - } - } - - public String getContentTypeFor(String fileName) { - String extn=getExtension( fileName ); - if (extn!=null) { - return getContentType(extn); - } else { - // no extension, no content type - return null; - } - } - -} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java deleted file mode 100644 index 31d6b2718..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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.servlets.util; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.StringTokenizer; - -/** - * Utils to process HTTP/1.1 ranges. Used by default servlet, could - * be used by any servlet that needs to deal with ranges. - * - * It is very good to support ranges if you have large content. In most - * cases supporting one range is enough - getting multiple ranges doesn't - * seem very common, and it's complex (multipart response). - * - * @author Costin Manolache - * @author Remy Maucherat - * @author - see DefaultServlet in Catalin for other contributors - */ -public class Range { - - public long start; - public long end; - public long length; - - /** - * Validate range. - */ - public boolean validate() { - if (end >= length) - end = length - 1; - return ( (start >= 0) && (end >= 0) && (start <= end) - && (length > 0) ); - } - - public void recycle() { - start = 0; - end = 0; - length = 0; - } - - /** Parse ranges. - * - * @return null if the range is invalid or can't be parsed - */ - public static ArrayList parseRanges(long fileLength, - String rangeHeader) throws IOException { - ArrayList result = new ArrayList(); - StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ","); - - // Parsing the range list - while (commaTokenizer.hasMoreTokens()) { - String rangeDefinition = commaTokenizer.nextToken().trim(); - - Range currentRange = new Range(); - currentRange.length = fileLength; - - int dashPos = rangeDefinition.indexOf('-'); - - if (dashPos == -1) { - return null; - } - - if (dashPos == 0) { - try { - long offset = Long.parseLong(rangeDefinition); - currentRange.start = fileLength + offset; - currentRange.end = fileLength - 1; - } catch (NumberFormatException e) { - return null; - } - } else { - - try { - currentRange.start = Long.parseLong - (rangeDefinition.substring(0, dashPos)); - if (dashPos < rangeDefinition.length() - 1) - currentRange.end = Long.parseLong - (rangeDefinition.substring - (dashPos + 1, rangeDefinition.length())); - else - currentRange.end = fileLength - 1; - } catch (NumberFormatException e) { - return null; - } - - } - if (!currentRange.validate()) { - return null; - } - result.add(currentRange); - } - return result; - } - - - /** - * Parse the Content-Range header. Used with PUT or in response. - * - * @return Range - */ - public static Range parseContentRange(String rangeHeader) - throws IOException { - if (rangeHeader == null) - return null; - - // bytes is the only range unit supported - if (!rangeHeader.startsWith("bytes")) { - return null; - } - - rangeHeader = rangeHeader.substring(6).trim(); - - int dashPos = rangeHeader.indexOf('-'); - int slashPos = rangeHeader.indexOf('/'); - - if (dashPos == -1) { - return null; - } - - if (slashPos == -1) { - return null; - } - - Range range = new Range(); - - try { - range.start = Long.parseLong(rangeHeader.substring(0, dashPos)); - range.end = - Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos)); - range.length = Long.parseLong - (rangeHeader.substring(slashPos + 1, rangeHeader.length())); - } catch (NumberFormatException e) { - return null; - } - - if (!range.validate()) { - return null; - } - - return range; - } - -} \ No newline at end of file diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java index 5135b0044..aefc629ed 100644 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java +++ b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java @@ -304,102 +304,22 @@ public final class RequestUtil { } - /** - * Decode and return the specified URL-encoded String. - * When the byte array is converted to a string, the system default - * character encoding is used... This may be different than some other - * servers. - * - * @param str The url-encoded string - * - * @exception IllegalArgumentException if a '%' character is not followed - * by a valid 2-digit hexadecimal number - */ - public static String URLDecode(String str) { - - return URLDecode(str, null); - - } - - - /** - * Decode and return the specified URL-encoded String. - * - * @param str The url-encoded string - * @param enc The encoding to use; if null, the default encoding is used - * @exception IllegalArgumentException if a '%' character is not followed - * by a valid 2-digit hexadecimal number - */ - public static String URLDecode(String str, String enc) { - - if (str == null) - return (null); - - // use the specified encoding to extract bytes out of the - // given string so that the encoding is not lost. If an - // encoding is not specified, let it use platform default - byte[] bytes = null; - try { - if (enc == null) { - bytes = str.getBytes(); - } else { - bytes = str.getBytes(enc); - } - } catch (UnsupportedEncodingException uee) {} - return URLDecode(bytes, enc); - - } - - - /** - * Decode and return the specified URL-encoded byte array. - * - * @param bytes The url-encoded byte array - * @exception IllegalArgumentException if a '%' character is not followed - * by a valid 2-digit hexadecimal number - */ - public static String URLDecode(byte[] bytes) { - return URLDecode(bytes, null); - } + /** * Decode and return the specified URL-encoded byte array. * * @param bytes The url-encoded byte array - * @param enc The encoding to use; if null, the default encoding is used * @exception IllegalArgumentException if a '%' character is not followed * by a valid 2-digit hexadecimal number */ - public static String URLDecode(byte[] bytes, String enc) { +// public static String URLDecode(byte[] bytes) { +// return URLDecode(bytes, null); +// } - if (bytes == null) - return (null); - - int len = bytes.length; - int ix = 0; - int ox = 0; - while (ix < len) { - byte b = bytes[ix++]; // Get byte to test - if (b == '+') { - b = (byte)' '; - } else if (b == '%') { - b = (byte) ((convertHexDigit(bytes[ix++]) << 4) - + convertHexDigit(bytes[ix++])); - } - bytes[ox++] = b; - } - if (enc != null) { - try { - return new String(bytes, 0, ox, enc); - } catch (Exception e) { - e.printStackTrace(); - } - } - return new String(bytes, 0, ox); - } /** diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/URLEncoder.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/URLEncoder.java deleted file mode 100644 index bf15cd9b0..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/URLEncoder.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.servlets.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.BitSet; - -/** - * - * This class is very similar to the java.net.URLEncoder class. - * - * Unfortunately, with java.net.URLEncoder there is no way to specify to the - * java.net.URLEncoder which characters should NOT be encoded. - * - * This code was moved from DefaultServlet.java - * - * @author Craig R. McClanahan - * @author Remy Maucherat - */ -public class URLEncoder { - protected static final char[] hexadecimal = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F'}; - - //Array containing the safe characters set. - protected BitSet safeCharacters = new BitSet(256); - - public URLEncoder() { - for (char i = 'a'; i <= 'z'; i++) { - addSafeCharacter(i); - } - for (char i = 'A'; i <= 'Z'; i++) { - addSafeCharacter(i); - } - for (char i = '0'; i <= '9'; i++) { - addSafeCharacter(i); - } - } - - public void addSafeCharacter( char c ) { - safeCharacters.set( c ); - } - - public String encode( String path ) { - int maxBytesPerChar = 10; - int caseDiff = ('a' - 'A'); - StringBuffer rewrittenPath = new StringBuffer(path.length()); - ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar); - OutputStreamWriter writer = null; - try { - writer = new OutputStreamWriter(buf, "UTF8"); - } catch (Exception e) { - e.printStackTrace(); - writer = new OutputStreamWriter(buf); - } - - for (int i = 0; i < path.length(); i++) { - int c = (int) path.charAt(i); - if (safeCharacters.get(c)) { - rewrittenPath.append((char)c); - } else { - // convert to external encoding before hex conversion - try { - writer.write((char)c); - writer.flush(); - } catch(IOException e) { - buf.reset(); - continue; - } - byte[] ba = buf.toByteArray(); - for (int j = 0; j < ba.length; j++) { - // Converting each byte in the buffer - byte toEncode = ba[j]; - rewrittenPath.append('%'); - int low = (int) (toEncode & 0x0f); - int high = (int) ((toEncode & 0xf0) >> 4); - rewrittenPath.append(hexadecimal[high]); - rewrittenPath.append(hexadecimal[low]); - } - buf.reset(); - } - } - return rewrittenPath.toString(); - } -} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java deleted file mode 100644 index f968fb876..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.servlets.util; - -public class UrlUtils { - - /** Used by webdav. - * - * Return a context-relative path, beginning with a "/", that represents - * the canonical version of the specified path after ".." and "." elements - * are resolved out. If the specified path attempts to go outside the - * boundaries of the current context (i.e. too many ".." path elements - * are present), return null instead. - * - * @param path Path to be normalized - */ - public static String normalize(String path) { - - if (path == null) - return null; - - // Create a place for the normalized path - String normalized = path; - - if (normalized.equals("/.")) - return "/"; - - // Normalize the slashes and add leading slash if necessary - if (normalized.indexOf('\\') >= 0) - normalized = normalized.replace('\\', '/'); - - if (!normalized.startsWith("/")) - normalized = "/" + normalized; - - // Resolve occurrences of "//" in the normalized path - while (true) { - int index = normalized.indexOf("//"); - if (index < 0) - break; - normalized = normalized.substring(0, index) + - normalized.substring(index + 1); - } - - // Resolve occurrences of "/./" in the normalized path - while (true) { - int index = normalized.indexOf("/./"); - if (index < 0) - break; - normalized = normalized.substring(0, index) + - normalized.substring(index + 2); - } - - // Resolve occurrences of "/../" in the normalized path - while (true) { - int index = normalized.indexOf("/../"); - if (index < 0) - break; - if (index == 0) - return (null); // Trying to go outside our context - int index2 = normalized.lastIndexOf('/', index - 1); - normalized = normalized.substring(0, index2) + - normalized.substring(index + 3); - } - - // Return the normalized path that we have completed - return (normalized); - } - -} diff --git a/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java index e1653837c..4eefbf702 100644 --- a/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java +++ b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java @@ -28,7 +28,6 @@ public class TomcatLiteCoyoteTest extends TestCase { tomcat.setPort(8885); tomcat.setBaseDir("../../output/build/webapps"); - tomcat.addWebapp("/examples", "examples"); tomcat.addWebapp("/", "ROOT"); @@ -67,11 +66,11 @@ public class TomcatLiteCoyoteTest extends TestCase { public void testSimple() throws IOException { HttpConnector clientCon = DefaultHttpConnector.get(); HttpChannel ch = clientCon.get("localhost", 8885); - ch.getRequest().setRequestURI("/examples/servlets/servlet/HelloWorldExample"); + ch.getRequest().setRequestURI("/index.html"); ch.getRequest().send(); BBuffer res = ch.readAll(null, 0); - assertTrue(res.toString().indexOf("Hello World!") >= 0); + assertTrue(res.toString(), res.toString().indexOf("Apache Tomcat") >= 0); } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java index 41126d831..005f249f6 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java @@ -7,9 +7,9 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; -import org.apache.tomcat.integration.jmx.JMXProxyServlet; -import org.apache.tomcat.integration.jmx.JmxObjectManagerSpi; import org.apache.tomcat.integration.jmx.UJmxHandler; import org.apache.tomcat.integration.jmx.UJmxObjectManagerSpi; import org.apache.tomcat.integration.simple.Main; @@ -29,13 +29,12 @@ import org.apache.tomcat.lite.http.HttpConnector.HttpConnection; import org.apache.tomcat.lite.http.services.EchoCallback; import org.apache.tomcat.lite.http.services.SleepCallback; import org.apache.tomcat.lite.io.BBuffer; +import org.apache.tomcat.lite.io.IOConnector; import org.apache.tomcat.lite.io.SocketConnector; import org.apache.tomcat.lite.io.SslConnector; import org.apache.tomcat.lite.proxy.HttpProxyService; import org.apache.tomcat.lite.proxy.StaticContentService; import org.apache.tomcat.lite.service.IOStatus; -import org.apache.tomcat.lite.servlet.ServletConfigImpl; -import org.apache.tomcat.util.buf.ByteChunk; /** * Server with lost of test servlets. @@ -54,15 +53,20 @@ public class TestMain { private SimpleObjectManager om; + private boolean init = false; + private SocketConnector serverCon = new SocketConnector(); private HttpConnector testClient = DefaultHttpConnector.get(); private HttpConnector testServer = new HttpConnector(serverCon); private HttpConnector testProxy = new HttpConnector(serverCon); - + private HttpConnector sslServer; + private HttpProxyService proxy; UJmxObjectManagerSpi jmx = new UJmxObjectManagerSpi(); + + private IOConnector sslCon; public static TestMain shared() { if (defaultServer == null) { @@ -118,8 +122,6 @@ public class TestMain { }); d.addWrapper(mCtx, "/ujmx", new UJmxHandler(jmx)); - d.addWrapper(mCtx, "/jmx", - new ServletConfigImpl(new JMXProxyServlet())); } public void run() { @@ -142,10 +144,11 @@ public class TestMain { return 8443; } - protected void startAll(int basePort) throws IOException { + protected synchronized void startAll(int basePort) throws IOException { int port = basePort + 903; - if (proxy == null) { - + if (!init) { + init = true; + proxy = new HttpProxyService() .withHttpClient(testClient); testProxy.setPort(port); @@ -169,18 +172,21 @@ public class TestMain { e.printStackTrace(); } - SslConnector sslCon = new SslConnector() + sslCon = new SslConnector() .setKeysResource("org/apache/tomcat/lite/http/test.keystore", "changeit"); - HttpConnector sslServer = new HttpConnector(sslCon); + sslServer = new HttpConnector(sslCon); initTestCallback(sslServer.getDispatcher()); sslServer.setPort(basePort + 443); sslServer.start(); + +// System.setProperty("javax.net.debug", "ssl"); -// testProxy.setDebugHttp(true); -// testProxy.setDebug(true); -// testClient.setDebug(true); -// testClient.setDebugHttp(true); +// Logger.getLogger("SSL").setLevel(Level.FINEST); +// testProxy.setDebugHttp(true); +// testProxy.setDebug(true); +// testClient.setDebug(true); +// testClient.setDebugHttp(true); // testServer.setDebugHttp(true); // testServer.setDebug(true); // sslServer.setDebug(true); @@ -204,9 +210,17 @@ public class TestMain { public void bindConnector(HttpConnector con, final String base) { om.bind("HttpConnector-" + base, con); om.bind("HttpConnectionPool-" + base, con.cpool); - SocketConnector sc = (SocketConnector) con.getIOConnector(); - om.bind("NioThread-" + base, sc.getSelector()); - + IOConnector io = con.getIOConnector(); + int ioLevel = 0; + while (io != null) { + om.bind("IOConnector-" + (ioLevel++) + "-" + base, io); + if (io instanceof SocketConnector) { + om.bind("NioThread-" + base, + ((SocketConnector) io).getSelector()); + + } + io = io.getNet(); + } con.cpool.setEvents(new HttpConnectionPool.HttpConnectionPoolEvents() { @Override @@ -251,13 +265,12 @@ public class TestMain { if (om == null) { om = new SimpleObjectManager(); } - // All objects visible in JMX via util.registry - // ( optional dependency ) - om.register(new JmxObjectManagerSpi()); om.register(jmx); - + // Additional settings, via spring-like config file om.loadResource(cfgFile); + + // initialization - using runnables String run = (String) om.getProperty("RUN"); String[] runNames = run == null ? new String[] {} : run.split(","); for (String name: runNames) { @@ -271,6 +284,7 @@ public class TestMain { bindConnector(testServer, "TestServer"); bindConnector(testClient, "Client"); bindConnector(testProxy, "Proxy"); + bindConnector(sslServer, "Https"); } @@ -291,14 +305,14 @@ public class TestMain { return out; } - public static ByteChunk getUrl(String path) throws IOException { - ByteChunk out = new ByteChunk(); + public static BBuffer getUrl(String path) throws IOException { + BBuffer out = BBuffer.allocate(); getUrl(path, out); return out; } public static HttpURLConnection getUrl(String path, - ByteChunk out) throws IOException { + BBuffer out) throws IOException { URL url = new URL(path); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java index ed87256e6..25060c160 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java @@ -17,33 +17,24 @@ package org.apache.tomcat.lite.http; -import java.util.logging.Level; -import java.util.logging.Logger; - import junit.framework.TestCase; -import org.apache.commons.codec.binary.Base64; import org.apache.tomcat.lite.TestMain; import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.SslConnector; -import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.lite.util.Base64; public class HttpsTest extends TestCase { static int port = 8443; final HttpConnector httpClient = TestMain.shared().getClient(); - public void setUp() { - Logger.getLogger("SSL").setLevel(Level.FINEST); - } - public void testSimpleClient() throws Exception { checkResponse(httpClient); } - public void testSimpleServer() throws Exception { - ByteChunk res = TestMain.getUrl("https://localhost:8443/hello"); + BBuffer res = TestMain.getUrl("https://localhost:8443/hello"); assertTrue(res.toString().indexOf("Hello") >= 0); } @@ -52,7 +43,7 @@ public class HttpsTest extends TestCase { HttpRequest ch = httpCon.request("localhost", port).setSecure(true); ch.setRequestURI("/hello"); - ch.setProtocol("HTTP/1.0"); + ch.setProtocol("HTTP/1.0"); // to force close ch.send(); BBuffer res = ch.readAll(); @@ -60,7 +51,7 @@ public class HttpsTest extends TestCase { } public void testSimpleClient20() throws Exception { - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 10; i++) { checkResponse(httpClient); } } @@ -98,7 +89,7 @@ public class HttpsTest extends TestCase { TestMain.shared().initTestCallback(con.getDispatcher()); con.start(); - ByteChunk res = TestMain.getUrl("https://localhost:8444" + + BBuffer res = TestMain.getUrl("https://localhost:8444" + "/hello"); assertTrue(res.toString().indexOf("Hello") >= 0); diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java index 923a6ae20..380f8055d 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java @@ -39,7 +39,8 @@ public class LiveHttp1Test extends TestCase { // DefaultHttpConnector.get().setDebugHttp(true); TestMain.getTestServer(); - httpClient = DefaultHttpConnector.get().request("localhost", clientPort); + httpClient = DefaultHttpConnector.get().request("localhost", + clientPort); bodyRecvBuffer.recycle(); } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java index 8c51fb879..1ee5e9b9d 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java @@ -5,17 +5,24 @@ package org.apache.tomcat.lite.http; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; import junit.framework.TestCase; import org.apache.tomcat.lite.TestMain; import org.apache.tomcat.lite.http.HttpConnectionPool.RemoteServer; import org.apache.tomcat.lite.io.IOBuffer; +import org.apache.tomcat.lite.io.SocketConnector; public class SpdyTest extends TestCase { HttpConnector http11Con = TestMain.shared().getClient(); - static HttpConnector spdyCon = DefaultHttpConnector.get(); + static HttpConnector spdyCon = + new HttpConnector(new SocketConnector()); + + static HttpConnector spdyConSsl = + new HttpConnector(new SocketConnector()); HttpConnector memSpdyCon = new HttpConnector(null); @@ -23,6 +30,30 @@ public class SpdyTest extends TestCase { HttpRequest req = spdyCon.request("http://localhost:8802/echo/test1"); + // Force SPDY - no negotiation + req.setProtocol("SPDY/1.0"); + + HttpResponse res = req.waitResponse(); + + assertEquals(200, res.getStatus()); + //assertEquals("", res.getHeader("")); + + BufferedReader reader = res.getReader(); + String line1 = reader.readLine(); + //assertEquals("", line1); + } + + public void testSslClient() throws IOException { + + HttpRequest req = + spdyConSsl.request("http://localhost:8443/echo/test1"); + // Enable SSL for the connection. + // TODO: this must be done on the first request, all will be + // encrypted. + req.setSecure(true); + // Force SPDY - no negotiation + req.setProtocol("SPDY/1.0"); + HttpResponse res = req.waitResponse(); assertEquals(200, res.getStatus()); @@ -32,11 +63,12 @@ public class SpdyTest extends TestCase { String line1 = reader.readLine(); //assertEquals("", line1); } + // Initial frame generated by Chrome public void testParse() throws IOException { InputStream is = - getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreq0"); + getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreq0.bin"); IOBuffer iob = new IOBuffer(); iob.append(is); @@ -65,7 +97,7 @@ public class SpdyTest extends TestCase { // Initial frame generated by Chrome public void testParseCompressed() throws IOException { InputStream is = - getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreqCompressed"); + getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreqCompressed.bin"); IOBuffer iob = new IOBuffer(); iob.append(is); diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java index 1304038b7..a2bd28d7c 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java @@ -17,21 +17,12 @@ package org.apache.tomcat.lite.load; -import java.io.File; import java.io.IOException; -import java.lang.management.ManagementFactory; import java.net.HttpURLConnection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import javax.management.InstanceNotFoundException; -import javax.management.MBeanException; -import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; -import javax.management.ReflectionException; - import junit.framework.TestCase; import org.apache.tomcat.lite.TestMain; @@ -39,8 +30,8 @@ import org.apache.tomcat.lite.http.HttpChannel; import org.apache.tomcat.lite.http.HttpConnector; import org.apache.tomcat.lite.http.HttpRequest; import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted; +import org.apache.tomcat.lite.io.BBuffer; import org.apache.tomcat.lite.io.SocketConnector; -import org.apache.tomcat.util.buf.ByteChunk; /* Notes on memory use ( from heap dumps ): @@ -81,11 +72,9 @@ public class LiveHttpThreadedTest extends TestCase { new HttpConnector(new SocketConnector()); ThreadRunner tr; - static MBeanServer server; - static boolean dumpHeap = false; + static boolean dumpHeap = true; AtomicInteger ok = new AtomicInteger(); - Object lock = new Object(); int reqCnt; Map active = new HashMap(); @@ -95,63 +84,69 @@ public class LiveHttpThreadedTest extends TestCase { } public void test1000Async() throws Exception { - try { - asyncRequest(10, 100, false, clientCon); - } finally { - dumpHeap("heapAsync.bin"); - } +// try { + asyncRequest(10, 100, false, false, clientCon, "AsyncHttp"); +// } finally { +// dumpHeap("heapAsync.bin"); +// } } public void test10000Async() throws Exception { - try { - asyncRequest(20, 500, false, clientCon); - } finally { - dumpHeap("heapAsyncBig.bin"); - } + asyncRequest(20, 500, false, false, clientCon, "AsyncHttp"); } - public void test1000AsyncSpdy() throws Exception { - try { - asyncRequest(10, 100, true, spdyClient); - } finally { - dumpHeap("heapSpdy1000.bin"); - } + public void test1000AsyncSsl() throws Exception { + asyncRequest(20, 50, false, true, clientCon, "AsyncHttpSsl"); + } + + public void test10000AsyncSsl() throws Exception { + asyncRequest(20, 500, false, true, clientCon, "AsyncHttpSsl"); + } + + public void test1000AsyncSpdy() throws Exception { + asyncRequest(10, 100, true, false, spdyClient, "AsyncSpdy"); } public void test10000AsyncSpdy() throws Exception { - try { - asyncRequest(20, 500, true, spdyClient); - } finally { - dumpHeap("heapSpdy10000.bin"); - } + asyncRequest(20, 500, true, false, spdyClient, "AsyncSpdy"); } public void test1000AsyncSpdyComp() throws Exception { - try { - asyncRequest(10, 100, true, spdyClientCompress); - } finally { - dumpHeap("heapSpdy1000Comp.bin"); - } - + asyncRequest(10, 100, true, false, spdyClientCompress, "AsyncSpdyComp"); } public void test10000AsyncSpdyComp() throws Exception { - try { - asyncRequest(20, 500, true, spdyClientCompress); - } finally { - dumpHeap("heapSpdy10000.bin"); - } + asyncRequest(20, 500, true, false, spdyClientCompress, "AsyncSpdyComp"); } - public void asyncRequest(int thr, int perthr, - final boolean spdy, final HttpConnector clientCon) throws Exception { + public void test1000AsyncSpdySsl() throws Exception { + asyncRequest(10, 100, true, true, spdyClient, "AsyncSpdySsl"); + } + + public void test1000AsyncSpdyCompSsl() throws Exception { + asyncRequest(10, 100, true, true, spdyClientCompress, "AsyncSpdyCompSsl"); + } + + public void test10000AsyncSpdyCompSsl() throws Exception { + asyncRequest(20, 500, true, true, spdyClientCompress, "AsyncSpdyCompSsl"); + } + + Object thrlock = new Object(); + Object lock = new Object(); + + public void asyncRequest(final int thr, int perthr, + final boolean spdy, final boolean ssl, + final HttpConnector clientCon, String test) throws Exception { + clientCon.getConnectionPool().clear(); reqCnt = thr * perthr; long t0 = System.currentTimeMillis(); + tr = new ThreadRunner(thr, perthr) { public void makeRequest(int i) throws Exception { - HttpRequest cstate = clientCon.request("localhost", 8802); + HttpRequest cstate = clientCon.request("localhost", + ssl ? 8443 : 8802); synchronized (active) { active.put(cstate, cstate); } @@ -160,39 +155,96 @@ public class LiveHttpThreadedTest extends TestCase { // a negotiation. cstate.setProtocol("SPDY/1.0"); } + if (ssl) { + cstate.setSecure(true); + } cstate.requestURI().set("/hello"); cstate.setCompletedCallback(reqCallback); // no body cstate.getBody().close(); - // Send the request, wait response - Thread.currentThread().sleep(20); + cstate.send(); + + while (active.size() >= thr) { + synchronized(thrlock) { + thrlock.wait(); + } + } } }; tr.run(); - assertEquals(0, tr.errors.get()); synchronized (lock) { if (ok.get() < reqCnt) { lock.wait(reqCnt * 100); } } + long time = (System.currentTimeMillis() - t0); + + System.err.println("====== " + test + + " threads: " + thr + ", req: " + + reqCnt + ", sendTime" + tr.time + + ", time: " + time + + ", connections: " + clientCon.getConnectionPool().getSocketCount() + + ", avg: " + (time / reqCnt)); + assertEquals(reqCnt, ok.get()); - System.err.println(reqCnt + " Async requests: " + (System.currentTimeMillis() - t0)); + assertEquals(0, tr.errors.get()); } + + RequestCompleted reqCallback = new RequestCompleted() { + @Override + public void handle(HttpChannel data, Object extraData) + throws IOException { + String out = data.getIn().copyAll(null).toString(); + if (200 != data.getResponse().getStatus()) { + System.err.println("Wrong status"); + tr.errors.incrementAndGet(); + } else if (!"Hello world".equals(out)) { + tr.errors.incrementAndGet(); + System.err.println("bad result " + out); + } + synchronized (active) { + active.remove(data.getRequest()); + } + synchronized (thrlock) { + thrlock.notify(); + } + data.release(); + int okres = ok.incrementAndGet(); + if (okres >= reqCnt) { + synchronized (lock) { + lock.notify(); + } + } + } + }; + + public void testURLRequest1000() throws Exception { - urlRequest(10, 100); + urlRequest(10, 100, false, "HttpURLConnection"); } public void xtestURLRequest10000() throws Exception { - urlRequest(20, 500); + urlRequest(20, 500, false, "HttpURLConnection"); + + } + + // I can't seem to get 1000 requests to all complete... + public void xtestURLRequestSsl100() throws Exception { + urlRequest(10, 10, true, "HttpURLConnectionSSL"); + } + + public void xtestURLRequestSsl10000() throws Exception { + urlRequest(20, 500, true, "HttpURLConnectionSSL"); } /** * HttpURLConnection client against lite.http server. */ - public void urlRequest(int thr, int cnt) throws Exception { + public void urlRequest(int thr, int cnt, final boolean ssl, String test) + throws Exception { long t0 = System.currentTimeMillis(); @@ -203,8 +255,11 @@ public class LiveHttpThreadedTest extends TestCase { public void makeRequest(int i) throws Exception { try { - ByteChunk out = new ByteChunk(); - HttpURLConnection con = TestMain.getUrl("http://localhost:8802/hello", out); + BBuffer out = BBuffer.allocate(); + String url = ssl ? "https://localhost:8443/hello" : + "http://localhost:8802/hello"; + HttpURLConnection con = + TestMain.getUrl(url, out); if (con.getResponseCode() != 200) { errors.incrementAndGet(); } @@ -220,62 +275,17 @@ public class LiveHttpThreadedTest extends TestCase { }; tr.run(); assertEquals(0, tr.errors.get()); + long time = (System.currentTimeMillis() - t0); - System.err.println(thr + " threads, " + (thr * cnt) + " total blocking URL requests: " + - (System.currentTimeMillis() - t0)); - - //assertEquals(testServer., actual) + System.err.println("====== " + test + " threads: " + thr + ", req: " + + (thr * cnt) + ", time: " + time + ", avg: " + + (time / (thr * cnt))); } finally { - dumpHeap("heapURLReq.bin"); + //dumpHeap("heapURLReq.bin"); } } // TODO: move to a servlet - private void dumpHeap(String file) throws InstanceNotFoundException, - MBeanException, ReflectionException, MalformedObjectNameException { - if (!dumpHeap) { - return; - } - if (server == null) { - server = ManagementFactory.getPlatformMBeanServer(); - - } - File f1 = new java.io.File(file); - if (f1.exists()) { - f1.delete(); - } - server.invoke(new ObjectName("com.sun.management:type=HotSpotDiagnostic"), - "dumpHeap", - new Object[] {file, Boolean.FALSE /* live */}, - new String[] {String.class.getName(), "boolean"}); - } - - - RequestCompleted reqCallback = new RequestCompleted() { - @Override - public void handle(HttpChannel data, Object extraData) - throws IOException { - String out = data.getIn().copyAll(null).toString(); - if (200 != data.getResponse().getStatus()) { - System.err.println("Wrong status"); - tr.errors.incrementAndGet(); - } - if (!"Hello world".equals(out)) { - tr.errors.incrementAndGet(); - System.err.println("bad result " + out); - } - synchronized (active) { - active.remove(data.getRequest()); - } - data.release(); - int okres = ok.incrementAndGet(); - if (okres >= reqCnt) { - synchronized (lock) { - lock.notify(); - } - } - } - }; } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java index bbc6a4781..4539312f8 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java @@ -44,6 +44,7 @@ public class MicroTest extends TestCase { mappingData.recycle(); mapper.map(host, uri, mappingData); } - System.out.println("Elapsed:" + (System.currentTimeMillis() - time)); + // TODO: asserts + //System.out.println("Elapsed:" + (System.currentTimeMillis() - time)); } } diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java index 8f61eadcc..96fea90fe 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java @@ -45,7 +45,6 @@ public class ThreadRunner { } long t1 = System.currentTimeMillis(); time = t1 - t0; - System.err.println("TimeNB: " + (t1 - t0) + " " + res); } public void makeRequests(int cnt) { diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties b/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties index baade733b..184b583d3 100644 --- a/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/test.properties @@ -1,4 +1,7 @@ -RUN=Log,Socks,TomcatLite +RUN=JMX,Log,Socks,TomcatLite,JMXHandler + +JMX.(class)=org.apache.tomcat.integration.jmx.JmxObjectManagerSpi +JMXHandler.(class)=org.apache.tomcat.integration.jmx.JmxHandler Log.(class)=org.apache.tomcat.lite.service.LogConfig Log.debug=org.apache.tomcat.lite.http.HttpConnector diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java new file mode 100644 index 000000000..5048e2875 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java @@ -0,0 +1,51 @@ +/* + * 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.lite.util; + +import junit.framework.TestCase; + +public class UEncoderTest extends TestCase { + URLEncoder enc=new URLEncoder(); + + /* + * + * Test method for 'org.apache.tomcat.util.buf.UEncoder.encodeURL(String)' + * TODO: find the relevant rfc and apache tests and add more + */ + public void testEncodeURL() { + + String eurl1=enc.encodeURL("test"); + assertEquals("test", eurl1); + + eurl1=enc.encodeURL("/test"); + assertEquals("/test", eurl1); + + // safe ranges + eurl1=enc.encodeURL("test$-_."); + assertEquals("test$-_.", eurl1); + + eurl1=enc.encodeURL("test$-_.!*'(),"); + assertEquals("test$-_.!*'(),", eurl1); + + eurl1=enc.encodeURL("//test"); + assertEquals("//test", eurl1); + + + } + +} diff --git a/modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java b/modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java deleted file mode 100644 index f3f8f40e2..000000000 --- a/modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.buf; - -import junit.framework.TestCase; - -public class UEncoderTest extends TestCase { - UEncoder enc=new UEncoder(); - - /* - * - * Test method for 'org.apache.tomcat.util.buf.UEncoder.encodeURL(String)' - * TODO: find the relevant rfc and apache tests and add more - */ - public void testEncodeURL() { - - String eurl1=enc.encodeURL("test"); - assertEquals("test", eurl1); - - eurl1=enc.encodeURL("/test"); - assertEquals("%2ftest", eurl1); - - // safe ranges - eurl1=enc.encodeURL("test$-_."); - assertEquals("test$-_.", eurl1); - - eurl1=enc.encodeURL("test$-_.!*'(),"); - assertEquals("test$-_.!*'(),", eurl1); - - eurl1=enc.encodeURL("//test"); - assertEquals("%2f%2ftest", eurl1); - - - } - -}