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