0001: /*
0002: * Copyright 1999-2004 The Apache Software Foundation
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.apache.coyote.http11;
0018:
0019: import java.io.IOException;
0020: import java.io.InputStream;
0021: import java.io.InterruptedIOException;
0022: import java.io.OutputStream;
0023: import java.net.InetAddress;
0024: import java.net.Socket;
0025: import java.util.StringTokenizer;
0026: import java.security.AccessController;
0027: import java.security.PrivilegedAction;
0028:
0029: import org.apache.coyote.ActionCode;
0030: import org.apache.coyote.ActionHook;
0031: import org.apache.coyote.Adapter;
0032: import org.apache.coyote.Processor;
0033: import org.apache.coyote.Request;
0034: import org.apache.coyote.RequestInfo;
0035: import org.apache.coyote.Response;
0036: import org.apache.coyote.http11.filters.ChunkedInputFilter;
0037: import org.apache.coyote.http11.filters.ChunkedOutputFilter;
0038: import org.apache.coyote.http11.filters.GzipOutputFilter;
0039: import org.apache.coyote.http11.filters.IdentityInputFilter;
0040: import org.apache.coyote.http11.filters.IdentityOutputFilter;
0041: import org.apache.coyote.http11.filters.VoidInputFilter;
0042: import org.apache.coyote.http11.filters.VoidOutputFilter;
0043: import org.apache.coyote.http11.filters.BufferedInputFilter;
0044: import org.apache.regexp.RE;
0045: import org.apache.regexp.RESyntaxException;
0046: import org.apache.tomcat.util.buf.Ascii;
0047: import org.apache.tomcat.util.buf.ByteChunk;
0048: import org.apache.tomcat.util.buf.HexUtils;
0049: import org.apache.tomcat.util.buf.MessageBytes;
0050: import org.apache.tomcat.util.http.FastHttpDateFormat;
0051: import org.apache.tomcat.util.http.MimeHeaders;
0052: import org.apache.tomcat.util.net.SSLSupport;
0053: import org.apache.tomcat.util.threads.ThreadPool;
0054: import org.apache.tomcat.util.threads.ThreadWithAttributes;
0055:
0056: /**
0057: * Processes HTTP requests.
0058: *
0059: * @author Remy Maucherat
0060: */
0061: public class Http11Processor implements Processor, ActionHook {
0062:
0063: /**
0064: * Logger.
0065: */
0066: protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
0067: .getLog(Http11Processor.class);
0068:
0069: // ----------------------------------------------------------- Constructors
0070:
0071: /**
0072: * Default constructor.
0073: */
0074: public Http11Processor() {
0075: this (Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE);
0076: }
0077:
0078: public Http11Processor(int headerBufferSize) {
0079:
0080: request = new Request();
0081: inputBuffer = new InternalInputBuffer(request, headerBufferSize);
0082: request.setInputBuffer(inputBuffer);
0083:
0084: response = new Response();
0085: response.setHook(this );
0086: outputBuffer = new InternalOutputBuffer(response,
0087: headerBufferSize);
0088: response.setOutputBuffer(outputBuffer);
0089: request.setResponse(response);
0090:
0091: initializeFilters();
0092:
0093: // Cause loading of HexUtils
0094: int foo = HexUtils.DEC[0];
0095:
0096: }
0097:
0098: // ----------------------------------------------------- Instance Variables
0099:
0100: /**
0101: * Associated adapter.
0102: */
0103: protected Adapter adapter = null;
0104:
0105: /**
0106: * Request object.
0107: */
0108: protected Request request = null;
0109:
0110: /**
0111: * Response object.
0112: */
0113: protected Response response = null;
0114:
0115: /**
0116: * Input.
0117: */
0118: protected InternalInputBuffer inputBuffer = null;
0119:
0120: /**
0121: * Output.
0122: */
0123: protected InternalOutputBuffer outputBuffer = null;
0124:
0125: /**
0126: * State flag.
0127: */
0128: protected boolean started = false;
0129:
0130: /**
0131: * Error flag.
0132: */
0133: protected boolean error = false;
0134:
0135: /**
0136: * Keep-alive.
0137: */
0138: protected boolean keepAlive = true;
0139:
0140: /**
0141: * HTTP/1.1 flag.
0142: */
0143: protected boolean http11 = true;
0144:
0145: /**
0146: * HTTP/0.9 flag.
0147: */
0148: protected boolean http09 = false;
0149:
0150: /**
0151: * Content delimitator for the request (if false, the connection will
0152: * be closed at the end of the request).
0153: */
0154: protected boolean contentDelimitation = true;
0155:
0156: /**
0157: * Is there an expectation ?
0158: */
0159: protected boolean expectation = false;
0160:
0161: /**
0162: * List of restricted user agents.
0163: */
0164: protected RE[] restrictedUserAgents = null;
0165:
0166: /**
0167: * Maximum number of Keep-Alive requests to honor.
0168: */
0169: protected int maxKeepAliveRequests = -1;
0170:
0171: /**
0172: * SSL information.
0173: */
0174: protected SSLSupport sslSupport;
0175:
0176: /**
0177: * Socket associated with the current connection.
0178: */
0179: protected Socket socket;
0180:
0181: /**
0182: * Remote Address associated with the current connection.
0183: */
0184: protected String remoteAddr = null;
0185:
0186: /**
0187: * Remote Host associated with the current connection.
0188: */
0189: protected String remoteHost = null;
0190:
0191: /**
0192: * Local Host associated with the current connection.
0193: */
0194: protected String localName = null;
0195:
0196: /**
0197: * Local port to which the socket is connected
0198: */
0199: protected int localPort = -1;
0200:
0201: /**
0202: * Remote port to which the socket is connected
0203: */
0204: protected int remotePort = -1;
0205:
0206: /**
0207: * The local Host address.
0208: */
0209: protected String localAddr = null;
0210:
0211: /**
0212: * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server.
0213: */
0214: protected int timeout = 300000;
0215:
0216: /**
0217: * Flag to disable setting a different time-out on uploads.
0218: */
0219: protected boolean disableUploadTimeout = false;
0220:
0221: /**
0222: * Allowed compression level.
0223: */
0224: protected int compressionLevel = 0;
0225:
0226: /**
0227: * Minimum contentsize to make compression.
0228: */
0229: protected int compressionMinSize = 2048;
0230:
0231: /**
0232: * Socket buffering.
0233: */
0234: protected int socketBuffer = -1;
0235:
0236: /**
0237: * Max post size.
0238: */
0239: protected int maxPostSize = 2 * 1024 * 1024;
0240:
0241: /**
0242: * List of user agents to not use gzip with
0243: */
0244: protected RE noCompressionUserAgents[] = null;
0245:
0246: /**
0247: * List of MIMES which could be gzipped
0248: */
0249: protected String[] compressableMimeTypes = { "text/html",
0250: "text/xml", "text/plain" };
0251:
0252: /**
0253: * Host name (used to avoid useless B2C conversion on the host name).
0254: */
0255: protected char[] hostNameC = new char[0];
0256:
0257: /**
0258: * Associated thread pool.
0259: */
0260: protected ThreadPool threadPool;
0261:
0262: // ------------------------------------------------------------- Properties
0263:
0264: /**
0265: * Return compression level.
0266: */
0267: public String getCompression() {
0268: switch (compressionLevel) {
0269: case 0:
0270: return "off";
0271: case 1:
0272: return "on";
0273: case 2:
0274: return "force";
0275: }
0276: return "off";
0277: }
0278:
0279: /**
0280: * Set compression level.
0281: */
0282: public void setCompression(String compression) {
0283: if (compression.equals("on")) {
0284: this .compressionLevel = 1;
0285: } else if (compression.equals("force")) {
0286: this .compressionLevel = 2;
0287: } else if (compression.equals("off")) {
0288: this .compressionLevel = 0;
0289: } else {
0290: try {
0291: // Try to parse compression as an int, which would give the
0292: // minimum compression size
0293: compressionMinSize = Integer.parseInt(compression);
0294: this .compressionLevel = 1;
0295: } catch (Exception e) {
0296: this .compressionLevel = 0;
0297: }
0298: }
0299: }
0300:
0301: /**
0302: * Set Minimum size to trigger compression.
0303: */
0304: public void setCompressionMinSize(int compressionMinSize) {
0305: this .compressionMinSize = compressionMinSize;
0306: }
0307:
0308: public void setThreadPool(ThreadPool threadPool) {
0309: this .threadPool = threadPool;
0310: }
0311:
0312: /**
0313: * Add user-agent for which gzip compression didn't works
0314: * The user agent String given will be exactly matched
0315: * to the user-agent header submitted by the client.
0316: *
0317: * @param userAgent user-agent string
0318: */
0319: public void addNoCompressionUserAgent(String userAgent) {
0320: try {
0321: RE nRule = new RE(userAgent);
0322: noCompressionUserAgents = addREArray(
0323: noCompressionUserAgents, nRule);
0324: } catch (RESyntaxException ree) {
0325: log.error("Error parsing regular expression: " + userAgent,
0326: ree);
0327: }
0328: }
0329:
0330: /**
0331: * Set no compression user agent list (this method is best when used with
0332: * a large number of connectors, where it would be better to have all of
0333: * them referenced a single array).
0334: */
0335: public void setNoCompressionUserAgents(RE[] noCompressionUserAgents) {
0336: this .noCompressionUserAgents = noCompressionUserAgents;
0337: }
0338:
0339: /**
0340: * Set no compression user agent list.
0341: * List contains users agents separated by ',' :
0342: *
0343: * ie: "gorilla,desesplorer,tigrus"
0344: */
0345: public void setNoCompressionUserAgents(
0346: String noCompressionUserAgents) {
0347: if (noCompressionUserAgents != null) {
0348: StringTokenizer st = new StringTokenizer(
0349: noCompressionUserAgents, ",");
0350:
0351: while (st.hasMoreTokens()) {
0352: addNoCompressionUserAgent(st.nextToken().trim());
0353: }
0354: }
0355: }
0356:
0357: /**
0358: * Add a mime-type which will be compressable
0359: * The mime-type String will be exactly matched
0360: * in the response mime-type header .
0361: *
0362: * @param userAgent user-agent string
0363: */
0364: public void addCompressableMimeType(String mimeType) {
0365: compressableMimeTypes = addStringArray(compressableMimeTypes,
0366: mimeType);
0367: }
0368:
0369: /**
0370: * Set compressable mime-type list (this method is best when used with
0371: * a large number of connectors, where it would be better to have all of
0372: * them referenced a single array).
0373: */
0374: public void setCompressableMimeTypes(String[] compressableMimeTypes) {
0375: this .compressableMimeTypes = compressableMimeTypes;
0376: }
0377:
0378: /**
0379: * Set compressable mime-type list
0380: * List contains users agents separated by ',' :
0381: *
0382: * ie: "text/html,text/xml,text/plain"
0383: */
0384: public void setCompressableMimeTypes(String compressableMimeTypes) {
0385: if (compressableMimeTypes != null) {
0386: StringTokenizer st = new StringTokenizer(
0387: compressableMimeTypes, ",");
0388:
0389: while (st.hasMoreTokens()) {
0390: addCompressableMimeType(st.nextToken().trim());
0391: }
0392: }
0393: }
0394:
0395: /**
0396: * Return the list of restricted user agents.
0397: */
0398: public String[] findCompressableMimeTypes() {
0399: return (compressableMimeTypes);
0400: }
0401:
0402: // --------------------------------------------------------- Public Methods
0403:
0404: /**
0405: * Add input or output filter.
0406: *
0407: * @param className class name of the filter
0408: */
0409: protected void addFilter(String className) {
0410: try {
0411: Class clazz = Class.forName(className);
0412: Object obj = clazz.newInstance();
0413: if (obj instanceof InputFilter) {
0414: inputBuffer.addFilter((InputFilter) obj);
0415: } else if (obj instanceof OutputFilter) {
0416: outputBuffer.addFilter((OutputFilter) obj);
0417: } else {
0418: log.warn("Unknown filter: " + className);
0419: }
0420: } catch (Exception e) {
0421: log.error("Error intializing filter: " + className, e);
0422: }
0423: }
0424:
0425: /**
0426: * General use method
0427: *
0428: * @param sArray the StringArray
0429: * @param value string
0430: */
0431: private String[] addStringArray(String sArray[], String value) {
0432: String[] result = null;
0433: if (sArray == null) {
0434: result = new String[1];
0435: result[0] = value;
0436: } else {
0437: result = new String[sArray.length + 1];
0438: for (int i = 0; i < sArray.length; i++)
0439: result[i] = sArray[i];
0440: result[sArray.length] = value;
0441: }
0442: return result;
0443: }
0444:
0445: /**
0446: * General use method
0447: *
0448: * @param rArray the REArray
0449: * @param value Obj
0450: */
0451: private RE[] addREArray(RE rArray[], RE value) {
0452: RE[] result = null;
0453: if (rArray == null) {
0454: result = new RE[1];
0455: result[0] = value;
0456: } else {
0457: result = new RE[rArray.length + 1];
0458: for (int i = 0; i < rArray.length; i++)
0459: result[i] = rArray[i];
0460: result[rArray.length] = value;
0461: }
0462: return result;
0463: }
0464:
0465: /**
0466: * General use method
0467: *
0468: * @param sArray the StringArray
0469: * @param value string
0470: */
0471: private boolean inStringArray(String sArray[], String value) {
0472: for (int i = 0; i < sArray.length; i++) {
0473: if (sArray[i].equals(value)) {
0474: return true;
0475: }
0476: }
0477: return false;
0478: }
0479:
0480: /**
0481: * Checks if any entry in the string array starts with the specified value
0482: *
0483: * @param sArray the StringArray
0484: * @param value string
0485: */
0486: private boolean startsWithStringArray(String sArray[], String value) {
0487: if (value == null)
0488: return false;
0489: for (int i = 0; i < sArray.length; i++) {
0490: if (value.startsWith(sArray[i])) {
0491: return true;
0492: }
0493: }
0494: return false;
0495: }
0496:
0497: /**
0498: * Add restricted user-agent (which will downgrade the connector
0499: * to HTTP/1.0 mode). The user agent String given will be matched
0500: * via regexp to the user-agent header submitted by the client.
0501: *
0502: * @param userAgent user-agent string
0503: */
0504: public void addRestrictedUserAgent(String userAgent) {
0505: try {
0506: RE nRule = new RE(userAgent);
0507: restrictedUserAgents = addREArray(restrictedUserAgents,
0508: nRule);
0509: } catch (RESyntaxException ree) {
0510: log.error("Error parsing regular expression: " + userAgent,
0511: ree);
0512: }
0513: }
0514:
0515: /**
0516: * Set restricted user agent list (this method is best when used with
0517: * a large number of connectors, where it would be better to have all of
0518: * them referenced a single array).
0519: */
0520: public void setRestrictedUserAgents(RE[] restrictedUserAgents) {
0521: this .restrictedUserAgents = restrictedUserAgents;
0522: }
0523:
0524: /**
0525: * Set restricted user agent list (which will downgrade the connector
0526: * to HTTP/1.0 mode). List contains users agents separated by ',' :
0527: *
0528: * ie: "gorilla,desesplorer,tigrus"
0529: */
0530: public void setRestrictedUserAgents(String restrictedUserAgents) {
0531: if (restrictedUserAgents != null) {
0532: StringTokenizer st = new StringTokenizer(
0533: restrictedUserAgents, ",");
0534: while (st.hasMoreTokens()) {
0535: addRestrictedUserAgent(st.nextToken().trim());
0536: }
0537: }
0538: }
0539:
0540: /**
0541: * Return the list of restricted user agents.
0542: */
0543: public String[] findRestrictedUserAgents() {
0544: String[] sarr = new String[restrictedUserAgents.length];
0545:
0546: for (int i = 0; i < restrictedUserAgents.length; i++)
0547: sarr[i] = restrictedUserAgents[i].toString();
0548:
0549: return (sarr);
0550: }
0551:
0552: /**
0553: * Set the maximum number of Keep-Alive requests to honor.
0554: * This is to safeguard from DoS attacks. Setting to a negative
0555: * value disables the check.
0556: */
0557: public void setMaxKeepAliveRequests(int mkar) {
0558: maxKeepAliveRequests = mkar;
0559: }
0560:
0561: /**
0562: * Return the number of Keep-Alive requests that we will honor.
0563: */
0564: public int getMaxKeepAliveRequests() {
0565: return maxKeepAliveRequests;
0566: }
0567:
0568: /**
0569: * Set the maximum size of a POST which will be buffered in SSL mode.
0570: */
0571: public void setMaxPostSize(int mps) {
0572: maxPostSize = mps;
0573: }
0574:
0575: /**
0576: * Return the maximum size of a POST which will be buffered in SSL mode.
0577: */
0578: public int getMaxPostSize() {
0579: return maxPostSize;
0580: }
0581:
0582: /**
0583: * Set the SSL information for this HTTP connection.
0584: */
0585: public void setSSLSupport(SSLSupport sslSupport) {
0586: this .sslSupport = sslSupport;
0587: }
0588:
0589: /**
0590: * Set the socket associated with this HTTP connection.
0591: */
0592: public void setSocket(Socket socket) throws IOException {
0593: this .socket = socket;
0594: }
0595:
0596: /**
0597: * Set the flag to control upload time-outs.
0598: */
0599: public void setDisableUploadTimeout(boolean isDisabled) {
0600: disableUploadTimeout = isDisabled;
0601: }
0602:
0603: /**
0604: * Get the flag that controls upload time-outs.
0605: */
0606: public boolean getDisableUploadTimeout() {
0607: return disableUploadTimeout;
0608: }
0609:
0610: /**
0611: * Set the socket buffer flag.
0612: */
0613: public void setSocketBuffer(int socketBuffer) {
0614: this .socketBuffer = socketBuffer;
0615: outputBuffer.setSocketBuffer(socketBuffer);
0616: }
0617:
0618: /**
0619: * Get the socket buffer flag.
0620: */
0621: public int getSocketBuffer() {
0622: return socketBuffer;
0623: }
0624:
0625: /**
0626: * Set the upload timeout.
0627: */
0628: public void setTimeout(int timeouts) {
0629: timeout = timeouts;
0630: }
0631:
0632: /**
0633: * Get the upload timeout.
0634: */
0635: public int getTimeout() {
0636: return timeout;
0637: }
0638:
0639: /** Get the request associated with this processor.
0640: *
0641: * @return
0642: */
0643: public Request getRequest() {
0644: return request;
0645: }
0646:
0647: /**
0648: * Process pipelined HTTP requests using the specified input and output
0649: * streams.
0650: *
0651: * @param input stream from which the HTTP requests will be read
0652: * @param output stream which will be used to output the HTTP
0653: * responses
0654: * @throws IOException error during an I/O operation
0655: */
0656: public void process(InputStream input, OutputStream output)
0657: throws IOException {
0658: ThreadWithAttributes thrA = (ThreadWithAttributes) Thread
0659: .currentThread();
0660: RequestInfo rp = request.getRequestProcessor();
0661: thrA.setCurrentStage(threadPool, "parsing http request");
0662: rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
0663:
0664: // Set the remote address
0665: remoteAddr = null;
0666: remoteHost = null;
0667: localAddr = null;
0668: remotePort = -1;
0669: localPort = -1;
0670:
0671: // Setting up the I/O
0672: inputBuffer.setInputStream(input);
0673: outputBuffer.setOutputStream(output);
0674:
0675: // Error flag
0676: error = false;
0677: keepAlive = true;
0678:
0679: int keepAliveLeft = maxKeepAliveRequests;
0680: int soTimeout = socket.getSoTimeout();
0681:
0682: float threadRatio = (float) threadPool.getCurrentThreadsBusy()
0683: / (float) threadPool.getMaxThreads();
0684: if ((threadRatio > 0.33) && (threadRatio <= 0.66)) {
0685: soTimeout = soTimeout / 2;
0686: } else if (threadRatio > 0.66) {
0687: soTimeout = soTimeout / 3;
0688: keepAliveLeft = 1;
0689: }
0690:
0691: boolean keptAlive = false;
0692:
0693: while (started && !error && keepAlive) {
0694:
0695: // Parsing the request header
0696: try {
0697: if (!disableUploadTimeout && keptAlive && soTimeout > 0) {
0698: socket.setSoTimeout(soTimeout);
0699: }
0700: inputBuffer.parseRequestLine();
0701: request.setStartTime(System.currentTimeMillis());
0702: thrA.setParam(threadPool, request.requestURI());
0703: keptAlive = true;
0704: if (!disableUploadTimeout) {
0705: socket.setSoTimeout(timeout);
0706: }
0707: inputBuffer.parseHeaders();
0708: } catch (IOException e) {
0709: error = true;
0710: break;
0711: } catch (Throwable t) {
0712: log.debug("Error parsing HTTP request", t);
0713: // 400 - Bad Request
0714: response.setStatus(400);
0715: error = true;
0716: }
0717:
0718: // Setting up filters, and parse some request headers
0719: thrA.setCurrentStage(threadPool, "prepareRequest");
0720: rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
0721: try {
0722: prepareRequest();
0723: } catch (Throwable t) {
0724: log.debug("Error preparing request", t);
0725: // 400 - Internal Server Error
0726: response.setStatus(400);
0727: error = true;
0728: }
0729:
0730: if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0)
0731: keepAlive = false;
0732:
0733: // Process the request in the adapter
0734: if (!error) {
0735: try {
0736: thrA.setCurrentStage(threadPool, "service");
0737: rp
0738: .setStage(org.apache.coyote.Constants.STAGE_SERVICE);
0739: adapter.service(request, response);
0740: // Handle when the response was committed before a serious
0741: // error occurred. Throwing a ServletException should both
0742: // set the status to 500 and set the errorException.
0743: // If we fail here, then the response is likely already
0744: // committed, so we can't try and set headers.
0745: if (keepAlive && !error) { // Avoid checking twice.
0746: error = response.getErrorException() != null
0747: || statusDropsConnection(response
0748: .getStatus());
0749: }
0750:
0751: } catch (InterruptedIOException e) {
0752: error = true;
0753: } catch (Throwable t) {
0754: log.error("Error processing request", t);
0755: // 500 - Internal Server Error
0756: response.setStatus(500);
0757: error = true;
0758: }
0759: }
0760:
0761: // Finish the handling of the request
0762: try {
0763: thrA.setCurrentStage(threadPool, "endRequestIB");
0764: rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
0765: inputBuffer.endRequest();
0766: } catch (IOException e) {
0767: error = true;
0768: } catch (Throwable t) {
0769: log.error("Error finishing request", t);
0770: // 500 - Internal Server Error
0771: response.setStatus(500);
0772: error = true;
0773: }
0774: try {
0775: thrA.setCurrentStage(threadPool, "endRequestOB");
0776: rp
0777: .setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
0778: outputBuffer.endRequest();
0779: } catch (IOException e) {
0780: error = true;
0781: } catch (Throwable t) {
0782: log.error("Error finishing response", t);
0783: error = true;
0784: }
0785:
0786: // If there was an error, make sure the request is counted as
0787: // and error, and update the statistics counter
0788: if (error) {
0789: response.setStatus(500);
0790: }
0791: request.updateCounters();
0792:
0793: thrA.setCurrentStage(threadPool, "ended");
0794: rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
0795:
0796: // Don't reset the param - we'll see it as ended. Next request
0797: // will reset it
0798: // thrA.setParam(null);
0799: // Next request
0800: inputBuffer.nextRequest();
0801: outputBuffer.nextRequest();
0802:
0803: }
0804:
0805: rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
0806:
0807: // Recycle
0808: inputBuffer.recycle();
0809: outputBuffer.recycle();
0810:
0811: // Recycle ssl info
0812: sslSupport = null;
0813: }
0814:
0815: // ----------------------------------------------------- ActionHook Methods
0816:
0817: /**
0818: * Send an action to the connector.
0819: *
0820: * @param actionCode Type of the action
0821: * @param param Action parameter
0822: */
0823: public void action(ActionCode actionCode, Object param) {
0824:
0825: if (actionCode == ActionCode.ACTION_COMMIT) {
0826: // Commit current response
0827:
0828: if (response.isCommitted())
0829: return;
0830:
0831: // Validate and write response headers
0832: prepareResponse();
0833: try {
0834: outputBuffer.commit();
0835: } catch (IOException e) {
0836: // Set error flag
0837: error = true;
0838: }
0839:
0840: } else if (actionCode == ActionCode.ACTION_ACK) {
0841:
0842: // Acknowlege request
0843:
0844: // Send a 100 status back if it makes sense (response not committed
0845: // yet, and client specified an expectation for 100-continue)
0846:
0847: if ((response.isCommitted()) || !expectation)
0848: return;
0849:
0850: inputBuffer.setSwallowInput(true);
0851: try {
0852: outputBuffer.sendAck();
0853: } catch (IOException e) {
0854: // Set error flag
0855: error = true;
0856: }
0857:
0858: } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
0859:
0860: try {
0861: outputBuffer.flush();
0862: } catch (IOException e) {
0863: // Set error flag
0864: error = true;
0865: response.setErrorException(e);
0866: }
0867:
0868: } else if (actionCode == ActionCode.ACTION_CLOSE) {
0869: // Close
0870:
0871: // End the processing of the current request, and stop any further
0872: // transactions with the client
0873:
0874: try {
0875: outputBuffer.endRequest();
0876: } catch (IOException e) {
0877: // Set error flag
0878: error = true;
0879: }
0880:
0881: } else if (actionCode == ActionCode.ACTION_RESET) {
0882:
0883: // Reset response
0884:
0885: // Note: This must be called before the response is committed
0886:
0887: outputBuffer.reset();
0888:
0889: } else if (actionCode == ActionCode.ACTION_CUSTOM) {
0890:
0891: // Do nothing
0892:
0893: } else if (actionCode == ActionCode.ACTION_START) {
0894:
0895: started = true;
0896:
0897: } else if (actionCode == ActionCode.ACTION_STOP) {
0898:
0899: started = false;
0900:
0901: } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE) {
0902:
0903: try {
0904: if (sslSupport != null) {
0905: Object sslO = sslSupport.getCipherSuite();
0906: if (sslO != null)
0907: request.setAttribute(
0908: SSLSupport.CIPHER_SUITE_KEY, sslO);
0909: sslO = sslSupport.getPeerCertificateChain(false);
0910: if (sslO != null)
0911: request.setAttribute(
0912: SSLSupport.CERTIFICATE_KEY, sslO);
0913: sslO = sslSupport.getKeySize();
0914: if (sslO != null)
0915: request.setAttribute(SSLSupport.KEY_SIZE_KEY,
0916: sslO);
0917: sslO = sslSupport.getSessionId();
0918: if (sslO != null)
0919: request.setAttribute(SSLSupport.SESSION_ID_KEY,
0920: sslO);
0921: }
0922: } catch (Exception e) {
0923: log.warn("Exception getting SSL attributes ", e);
0924: }
0925:
0926: } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) {
0927:
0928: if ((remoteAddr == null) && (socket != null)) {
0929: InetAddress inetAddr = socket.getInetAddress();
0930: if (inetAddr != null) {
0931: remoteAddr = inetAddr.getHostAddress();
0932: }
0933: }
0934: request.remoteAddr().setString(remoteAddr);
0935:
0936: } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) {
0937:
0938: if ((localName == null) && (socket != null)) {
0939: InetAddress inetAddr = socket.getLocalAddress();
0940: if (inetAddr != null) {
0941: localName = inetAddr.getHostName();
0942: }
0943: }
0944: request.localName().setString(localName);
0945:
0946: } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
0947:
0948: if ((remoteHost == null) && (socket != null)) {
0949: InetAddress inetAddr = socket.getInetAddress();
0950: if (inetAddr != null) {
0951: remoteHost = inetAddr.getHostName();
0952: }
0953: }
0954: request.remoteHost().setString(remoteHost);
0955:
0956: } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
0957:
0958: if (localAddr == null)
0959: localAddr = socket.getLocalAddress().getHostAddress();
0960:
0961: request.localAddr().setString(localAddr);
0962:
0963: } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) {
0964:
0965: if ((remotePort == -1) && (socket != null)) {
0966: remotePort = socket.getPort();
0967: }
0968: request.setRemotePort(remotePort);
0969:
0970: } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) {
0971:
0972: if ((localPort == -1) && (socket != null)) {
0973: localPort = socket.getLocalPort();
0974: }
0975: request.setLocalPort(localPort);
0976:
0977: } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) {
0978: if (sslSupport != null) {
0979: /*
0980: * Consume and buffer the request body, so that it does not
0981: * interfere with the client's handshake messages
0982: */
0983: InputFilter[] inputFilters = inputBuffer.getFilters();
0984: ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
0985: .setLimit(maxPostSize);
0986: inputBuffer
0987: .addActiveFilter(inputFilters[Constants.BUFFERED_FILTER]);
0988: try {
0989: Object sslO = sslSupport
0990: .getPeerCertificateChain(true);
0991: if (sslO != null) {
0992: request.setAttribute(
0993: SSLSupport.CERTIFICATE_KEY, sslO);
0994: }
0995: } catch (Exception e) {
0996: log.warn("Exception getting SSL Cert", e);
0997: }
0998: }
0999: }
1000:
1001: }
1002:
1003: // ------------------------------------------------------ Connector Methods
1004:
1005: /**
1006: * Set the associated adapter.
1007: *
1008: * @param adapter the new adapter
1009: */
1010: public void setAdapter(Adapter adapter) {
1011: this .adapter = adapter;
1012: }
1013:
1014: /**
1015: * Get the associated adapter.
1016: *
1017: * @return the associated adapter
1018: */
1019: public Adapter getAdapter() {
1020: return adapter;
1021: }
1022:
1023: // ------------------------------------------------------ Protected Methods
1024:
1025: /**
1026: * After reading the request headers, we have to setup the request filters.
1027: */
1028: protected void prepareRequest() {
1029:
1030: http11 = true;
1031: http09 = false;
1032: contentDelimitation = false;
1033: expectation = false;
1034: if (sslSupport != null) {
1035: request.scheme().setString("https");
1036: }
1037: MessageBytes protocolMB = request.protocol();
1038: if (protocolMB.equals(Constants.HTTP_11)) {
1039: http11 = true;
1040: protocolMB.setString(Constants.HTTP_11);
1041: } else if (protocolMB.equals(Constants.HTTP_10)) {
1042: http11 = false;
1043: keepAlive = false;
1044: protocolMB.setString(Constants.HTTP_10);
1045: } else if (protocolMB.equals("")) {
1046: // HTTP/0.9
1047: http09 = true;
1048: http11 = false;
1049: keepAlive = false;
1050: } else {
1051: // Unsupported protocol
1052: http11 = false;
1053: error = true;
1054: // Send 505; Unsupported HTTP version
1055: response.setStatus(505);
1056: }
1057:
1058: MessageBytes methodMB = request.method();
1059: if (methodMB.equals(Constants.GET)) {
1060: methodMB.setString(Constants.GET);
1061: } else if (methodMB.equals(Constants.POST)) {
1062: methodMB.setString(Constants.POST);
1063: }
1064:
1065: // Check connection header
1066: MessageBytes connectionValueMB = request.getMimeHeaders()
1067: .getValue("connection");
1068: if (connectionValueMB != null) {
1069: ByteChunk connectionValueBC = connectionValueMB
1070: .getByteChunk();
1071: if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
1072: keepAlive = false;
1073: } else if (findBytes(connectionValueBC,
1074: Constants.KEEPALIVE_BYTES) != -1) {
1075: keepAlive = true;
1076: }
1077: }
1078:
1079: MessageBytes expectMB = null;
1080: if (http11)
1081: expectMB = request.getMimeHeaders().getValue("expect");
1082: if ((expectMB != null)
1083: && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {
1084: inputBuffer.setSwallowInput(false);
1085: expectation = true;
1086: }
1087:
1088: // Check user-agent header
1089: if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
1090: MessageBytes userAgentValueMB = request.getMimeHeaders()
1091: .getValue("user-agent");
1092: // Check in the restricted list, and adjust the http11
1093: // and keepAlive flags accordingly
1094: String userAgentValue = userAgentValueMB.toString();
1095: for (int i = 0; i < restrictedUserAgents.length; i++) {
1096: if (restrictedUserAgents[i].match(userAgentValue)) {
1097: http11 = false;
1098: keepAlive = false;
1099: break;
1100: }
1101: }
1102: }
1103:
1104: // Check for a full URI (including protocol://host:port/)
1105: ByteChunk uriBC = request.requestURI().getByteChunk();
1106: if (uriBC.startsWithIgnoreCase("http", 0)) {
1107:
1108: int pos = uriBC.indexOf("://", 0, 3, 4);
1109: int uriBCStart = uriBC.getStart();
1110: int slashPos = -1;
1111: if (pos != -1) {
1112: byte[] uriB = uriBC.getBytes();
1113: slashPos = uriBC.indexOf('/', pos + 3);
1114: if (slashPos == -1) {
1115: slashPos = uriBC.getLength();
1116: // Set URI as "/"
1117: request.requestURI().setBytes(uriB,
1118: uriBCStart + pos + 1, 1);
1119: } else {
1120: request.requestURI().setBytes(uriB,
1121: uriBCStart + slashPos,
1122: uriBC.getLength() - slashPos);
1123: }
1124: MessageBytes hostMB = request.getMimeHeaders()
1125: .setValue("host");
1126: hostMB.setBytes(uriB, uriBCStart + pos + 3, slashPos
1127: - pos - 3);
1128: }
1129:
1130: }
1131:
1132: // Input filter setup
1133: InputFilter[] inputFilters = inputBuffer.getFilters();
1134:
1135: // Parse content-length header
1136: long contentLength = request.getContentLengthLong();
1137: if (contentLength >= 0) {
1138: inputBuffer
1139: .addActiveFilter(inputFilters[Constants.IDENTITY_FILTER]);
1140: contentDelimitation = true;
1141: }
1142:
1143: // Parse transfer-encoding header
1144: MessageBytes transferEncodingValueMB = null;
1145: if (http11)
1146: transferEncodingValueMB = request.getMimeHeaders()
1147: .getValue("transfer-encoding");
1148: if (transferEncodingValueMB != null) {
1149: String transferEncodingValue = transferEncodingValueMB
1150: .toString();
1151: // Parse the comma separated list. "identity" codings are ignored
1152: int startPos = 0;
1153: int commaPos = transferEncodingValue.indexOf(',');
1154: String encodingName = null;
1155: while (commaPos != -1) {
1156: encodingName = transferEncodingValue.substring(
1157: startPos, commaPos).toLowerCase().trim();
1158: if (!addInputFilter(inputFilters, encodingName)) {
1159: // Unsupported transfer encoding
1160: error = true;
1161: // 501 - Unimplemented
1162: response.setStatus(501);
1163: }
1164: startPos = commaPos + 1;
1165: commaPos = transferEncodingValue.indexOf(',', startPos);
1166: }
1167: encodingName = transferEncodingValue.substring(startPos)
1168: .toLowerCase().trim();
1169: if (!addInputFilter(inputFilters, encodingName)) {
1170: // Unsupported transfer encoding
1171: error = true;
1172: // 501 - Unimplemented
1173: response.setStatus(501);
1174: }
1175: }
1176:
1177: MessageBytes valueMB = request.getMimeHeaders()
1178: .getValue("host");
1179:
1180: // Check host header
1181: if (http11 && (valueMB == null)) {
1182: error = true;
1183: // 400 - Bad request
1184: response.setStatus(400);
1185: }
1186:
1187: parseHost(valueMB);
1188:
1189: if (!contentDelimitation) {
1190: // If there's no content length and we're using keep-alive
1191: // (HTTP/1.0 with keep-alive or HTTP/1.1), assume
1192: // the client is not broken and didn't send a body
1193: if (keepAlive) {
1194: inputBuffer
1195: .addActiveFilter(inputFilters[Constants.VOID_FILTER]);
1196: contentDelimitation = true;
1197: }
1198: }
1199:
1200: if (!contentDelimitation)
1201: keepAlive = false;
1202:
1203: }
1204:
1205: /**
1206: * Parse host.
1207: */
1208: public void parseHost(MessageBytes valueMB) {
1209:
1210: if (valueMB == null || valueMB.isNull()) {
1211: // HTTP/1.0
1212: // Default is what the socket tells us. Overriden if a host is
1213: // found/parsed
1214: request.setServerPort(socket.getLocalPort());
1215: InetAddress localAddress = socket.getLocalAddress();
1216: // Setting the socket-related fields. The adapter doesn't know
1217: // about socket.
1218: request.setLocalHost(localAddress.getHostName());
1219: request.serverName().setString(localAddress.getHostName());
1220: return;
1221: }
1222:
1223: ByteChunk valueBC = valueMB.getByteChunk();
1224: byte[] valueB = valueBC.getBytes();
1225: int valueL = valueBC.getLength();
1226: int valueS = valueBC.getStart();
1227: int colonPos = -1;
1228: if (hostNameC.length < valueL) {
1229: hostNameC = new char[valueL];
1230: }
1231:
1232: boolean ipv6 = (valueB[valueS] == '[');
1233: boolean bracketClosed = false;
1234: for (int i = 0; i < valueL; i++) {
1235: char b = (char) valueB[i + valueS];
1236: hostNameC[i] = b;
1237: if (b == ']') {
1238: bracketClosed = true;
1239: } else if (b == ':') {
1240: if (!ipv6 || bracketClosed) {
1241: colonPos = i;
1242: break;
1243: }
1244: }
1245: }
1246:
1247: if (colonPos < 0) {
1248: if (sslSupport == null) {
1249: // 80 - Default HTTTP port
1250: request.setServerPort(80);
1251: } else {
1252: // 443 - Default HTTPS port
1253: request.setServerPort(443);
1254: }
1255: request.serverName().setChars(hostNameC, 0, valueL);
1256: } else {
1257:
1258: request.serverName().setChars(hostNameC, 0, colonPos);
1259:
1260: int port = 0;
1261: int mult = 1;
1262: for (int i = valueL - 1; i > colonPos; i--) {
1263: int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
1264: if (charValue == -1) {
1265: // Invalid character
1266: error = true;
1267: // 400 - Bad request
1268: response.setStatus(400);
1269: break;
1270: }
1271: port = port + (charValue * mult);
1272: mult = 10 * mult;
1273: }
1274: request.setServerPort(port);
1275:
1276: }
1277:
1278: }
1279:
1280: /**
1281: * Check for compression
1282: */
1283: private boolean isCompressable() {
1284:
1285: // Nope Compression could works in HTTP 1.0 also
1286: // cf: mod_deflate
1287:
1288: // Compression only since HTTP 1.1
1289: // if (! http11)
1290: // return false;
1291:
1292: // Check if browser support gzip encoding
1293: MessageBytes acceptEncodingMB = request.getMimeHeaders()
1294: .getValue("accept-encoding");
1295:
1296: if ((acceptEncodingMB == null)
1297: || (acceptEncodingMB.indexOf("gzip") == -1))
1298: return false;
1299:
1300: // Check if content is not allready gzipped
1301: MessageBytes contentEncodingMB = response.getMimeHeaders()
1302: .getValue("Content-Encoding");
1303:
1304: if ((contentEncodingMB != null)
1305: && (contentEncodingMB.indexOf("gzip") != -1))
1306: return false;
1307:
1308: // If force mode, allways compress (test purposes only)
1309: if (compressionLevel == 2)
1310: return true;
1311:
1312: // Check for incompatible Browser
1313: if (noCompressionUserAgents != null) {
1314: MessageBytes userAgentValueMB = request.getMimeHeaders()
1315: .getValue("user-agent");
1316: String userAgentValue = userAgentValueMB.toString();
1317:
1318: // If one Regexp rule match, disable compression
1319: for (int i = 0; i < noCompressionUserAgents.length; i++)
1320: if (noCompressionUserAgents[i].match(userAgentValue))
1321: return false;
1322: }
1323:
1324: // Check if suffisant len to trig the compression
1325: int contentLength = response.getContentLength();
1326: if ((contentLength == -1)
1327: || (contentLength > compressionMinSize)) {
1328: // Check for compatible MIME-TYPE
1329: if (compressableMimeTypes != null) {
1330: return (startsWithStringArray(compressableMimeTypes,
1331: response.getContentType()));
1332: }
1333: }
1334:
1335: return false;
1336: }
1337:
1338: /**
1339: * When committing the response, we have to validate the set of headers, as
1340: * well as setup the response filters.
1341: */
1342: protected void prepareResponse() {
1343:
1344: boolean entityBody = true;
1345: contentDelimitation = false;
1346:
1347: OutputFilter[] outputFilters = outputBuffer.getFilters();
1348:
1349: if (http09 == true) {
1350: // HTTP/0.9
1351: outputBuffer
1352: .addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
1353: return;
1354: }
1355:
1356: int statusCode = response.getStatus();
1357: if ((statusCode == 204) || (statusCode == 205)
1358: || (statusCode == 304)) {
1359: // No entity body
1360: outputBuffer
1361: .addActiveFilter(outputFilters[Constants.VOID_FILTER]);
1362: entityBody = false;
1363: contentDelimitation = true;
1364: }
1365:
1366: MessageBytes methodMB = request.method();
1367: if (methodMB.equals("HEAD")) {
1368: // No entity body
1369: outputBuffer
1370: .addActiveFilter(outputFilters[Constants.VOID_FILTER]);
1371: contentDelimitation = true;
1372: }
1373:
1374: // Check for compression
1375: boolean useCompression = false;
1376: if (entityBody && (compressionLevel > 0)) {
1377: useCompression = isCompressable();
1378:
1379: // Change content-length to -1 to force chunking
1380: if (useCompression) {
1381: response.setContentLength(-1);
1382: }
1383: }
1384:
1385: MimeHeaders headers = response.getMimeHeaders();
1386: if (!entityBody) {
1387: response.setContentLength(-1);
1388: } else {
1389: String contentType = response.getContentType();
1390: if (contentType != null) {
1391: headers.setValue("Content-Type").setString(contentType);
1392: }
1393: String contentLanguage = response.getContentLanguage();
1394: if (contentLanguage != null) {
1395: headers.setValue("Content-Language").setString(
1396: contentLanguage);
1397: }
1398: }
1399:
1400: int contentLength = response.getContentLength();
1401: if (contentLength != -1) {
1402: response.getMimeHeaders().setValue("Content-Length")
1403: .setInt(contentLength);
1404: outputBuffer
1405: .addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
1406: contentDelimitation = true;
1407: } else {
1408: if (entityBody && http11 && keepAlive) {
1409: outputBuffer
1410: .addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
1411: contentDelimitation = true;
1412: response.addHeader("Transfer-Encoding", "chunked");
1413: } else {
1414: outputBuffer
1415: .addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
1416: }
1417: }
1418:
1419: if (useCompression) {
1420: outputBuffer
1421: .addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
1422: // FIXME: Make content-encoding generation dynamic
1423: response.setHeader("Content-Encoding", "gzip");
1424: // Make Proxies happy via Vary (from mod_deflate)
1425: response.setHeader("Vary", "Accept-Encoding");
1426: }
1427:
1428: // Add date header
1429: if (!response.containsHeader("Date")) {
1430:
1431: String date = null;
1432: if (System.getSecurityManager() != null) {
1433: date = (String) AccessController
1434: .doPrivileged(new PrivilegedAction() {
1435: public Object run() {
1436: return FastHttpDateFormat
1437: .getCurrentDate();
1438: }
1439: });
1440: } else {
1441: date = FastHttpDateFormat.getCurrentDate();
1442: }
1443: response.addHeader("Date", date);
1444: }
1445:
1446: // Add server header
1447: response.addHeader("Server", Constants.SERVER);
1448:
1449: // Add transfer encoding header
1450: // FIXME
1451:
1452: if ((entityBody) && (!contentDelimitation)) {
1453: // Mark as close the connection after the request, and add the
1454: // connection: close header
1455: keepAlive = false;
1456: }
1457:
1458: // If we know that the request is bad this early, add the
1459: // Connection: close header.
1460: keepAlive = keepAlive && !statusDropsConnection(statusCode);
1461: if (!keepAlive) {
1462: response.addHeader("Connection", "close");
1463: } else if (!http11) {
1464: response.addHeader("Connection", "Keep-Alive");
1465: }
1466:
1467: // Build the response header
1468: outputBuffer.sendStatus();
1469:
1470: int size = headers.size();
1471: for (int i = 0; i < size; i++) {
1472: outputBuffer.sendHeader(headers.getName(i), headers
1473: .getValue(i));
1474: }
1475: outputBuffer.endHeaders();
1476:
1477: }
1478:
1479: /**
1480: * Initialize standard input and output filters.
1481: */
1482: protected void initializeFilters() {
1483:
1484: // Create and add the identity filters.
1485: inputBuffer.addFilter(new IdentityInputFilter());
1486: outputBuffer.addFilter(new IdentityOutputFilter());
1487:
1488: // Create and add the chunked filters.
1489: inputBuffer.addFilter(new ChunkedInputFilter());
1490: outputBuffer.addFilter(new ChunkedOutputFilter());
1491:
1492: // Create and add the void filters.
1493: inputBuffer.addFilter(new VoidInputFilter());
1494: outputBuffer.addFilter(new VoidOutputFilter());
1495:
1496: // Create and add buffered input filter
1497: inputBuffer.addFilter(new BufferedInputFilter());
1498:
1499: // Create and add the chunked filters.
1500: //inputBuffer.addFilter(new GzipInputFilter());
1501: outputBuffer.addFilter(new GzipOutputFilter());
1502:
1503: }
1504:
1505: /**
1506: * Add an input filter to the current request.
1507: *
1508: * @return false if the encoding was not found (which would mean it is
1509: * unsupported)
1510: */
1511: protected boolean addInputFilter(InputFilter[] inputFilters,
1512: String encodingName) {
1513: if (encodingName.equals("identity")) {
1514: // Skip
1515: } else if (encodingName.equals("chunked")) {
1516: inputBuffer
1517: .addActiveFilter(inputFilters[Constants.CHUNKED_FILTER]);
1518: contentDelimitation = true;
1519: } else {
1520: for (int i = 2; i < inputFilters.length; i++) {
1521: if (inputFilters[i].getEncodingName().toString()
1522: .equals(encodingName)) {
1523: inputBuffer.addActiveFilter(inputFilters[i]);
1524: return true;
1525: }
1526: }
1527: return false;
1528: }
1529: return true;
1530: }
1531:
1532: /**
1533: * Specialized utility method: find a sequence of lower case bytes inside
1534: * a ByteChunk.
1535: */
1536: protected int findBytes(ByteChunk bc, byte[] b) {
1537:
1538: byte first = b[0];
1539: byte[] buff = bc.getBuffer();
1540: int start = bc.getStart();
1541: int end = bc.getEnd();
1542:
1543: // Look for first char
1544: int srcEnd = b.length;
1545:
1546: for (int i = start; i <= (end - srcEnd); i++) {
1547: if (Ascii.toLower(buff[i]) != first)
1548: continue;
1549: // found first char, now look for a match
1550: int myPos = i + 1;
1551: for (int srcPos = 1; srcPos < srcEnd;) {
1552: if (Ascii.toLower(buff[myPos++]) != b[srcPos++])
1553: break;
1554: if (srcPos == srcEnd)
1555: return i - start; // found it
1556: }
1557: }
1558: return -1;
1559:
1560: }
1561:
1562: /**
1563: * Determine if we must drop the connection because of the HTTP status
1564: * code. Use the same list of codes as Apache/httpd.
1565: */
1566: protected boolean statusDropsConnection(int status) {
1567: return status == 400 /* SC_BAD_REQUEST */|| status == 408 /* SC_REQUEST_TIMEOUT */
1568: || status == 411 /* SC_LENGTH_REQUIRED */
1569: || status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */
1570: || status == 414 /* SC_REQUEST_URI_TOO_LARGE */
1571: || status == 500 /* SC_INTERNAL_SERVER_ERROR */
1572: || status == 503 /* SC_SERVICE_UNAVAILABLE */
1573: || status == 501 /* SC_NOT_IMPLEMENTED */;
1574: }
1575:
1576: }
|