0001: /*
0002: * @(#)Response.java 0.3-2 18/06/1999
0003: *
0004: * This file is part of the HTTPClient package
0005: * Copyright (C) 1996-1999 Ronald Tschalär
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free
0019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
0020: * MA 02111-1307, USA
0021: *
0022: * For questions, suggestions, bug-reports, enhancement-requests etc.
0023: * I may be contacted at:
0024: *
0025: * ronald@innovation.ch
0026: *
0027: */
0028:
0029: package HTTPClient;
0030:
0031: import java.io.InputStream;
0032: import java.io.SequenceInputStream;
0033: import java.io.ByteArrayInputStream;
0034: import java.io.IOException;
0035: import java.io.InterruptedIOException;
0036: import java.io.EOFException;
0037: import java.net.URL;
0038: import java.net.ProtocolException;
0039: import java.util.Date;
0040: import java.util.Vector;
0041: import java.util.StringTokenizer;
0042: import java.util.NoSuchElementException;
0043:
0044: /**
0045: * This class represents an intermediate response. It's used internally by the
0046: * modules. When all modules have handled the response then the HTTPResponse
0047: * fills in its fields with the data from this class.
0048: *
0049: * @version 0.3-2 18/06/1999
0050: * @author Ronald Tschalär
0051: */
0052:
0053: public final class Response implements RoResponse, GlobalConstants {
0054: /** our http connection */
0055: private HTTPConnection connection;
0056:
0057: /** our stream demux */
0058: private StreamDemultiplexor stream_handler;
0059:
0060: /** the HTTPResponse we're coupled with */
0061: HTTPResponse http_resp;
0062:
0063: /** the timeout for read operations */
0064: int timeout = 0;
0065:
0066: /** our input stream (usually from the stream demux). Push input streams
0067: * onto this if necessary. */
0068: public InputStream inp_stream;
0069:
0070: /** our response input stream from the stream demux */
0071: private RespInputStream resp_inp_stream = null;
0072:
0073: /** the method used in the request */
0074: private String method;
0075:
0076: /** the resource in the request (for debugging purposes) */
0077: String resource;
0078:
0079: /** was a proxy used for the request? */
0080: private boolean used_proxy;
0081:
0082: /** did the request contain an entity? */
0083: private boolean sent_entity;
0084:
0085: /** the status code returned. */
0086: int StatusCode = 0;
0087:
0088: /** the reason line associated with the status code. */
0089: String ReasonLine;
0090:
0091: /** the HTTP version of the response. */
0092: String Version;
0093:
0094: /** the final URI of the document. */
0095: URI EffectiveURI = null;
0096:
0097: /** any headers which were received and do not fit in the above list. */
0098: CIHashtable Headers = new CIHashtable();
0099:
0100: /** any trailers which were received and do not fit in the above list. */
0101: CIHashtable Trailers = new CIHashtable();
0102:
0103: /** the message length of the response if either there is no data (in which
0104: * case ContentLength=0) or if the message length is controlled by a
0105: * Content-Length header. If neither of these, then it's -1 */
0106: int ContentLength = -1;
0107:
0108: /** this indicates how the length of the entity body is determined */
0109: int cd_type = CD_HDRS;
0110:
0111: /** the data (body) returned. */
0112: byte[] Data = null;
0113:
0114: /** signals if we in the process of reading the headers */
0115: boolean reading_headers = false;
0116:
0117: /** signals if we have got and parsed the headers yet */
0118: boolean got_headers = false;
0119:
0120: /** signals if we have got and parsed the trailers yet */
0121: boolean got_trailers = false;
0122:
0123: /** remembers any exception received while reading/parsing headers */
0124: private IOException exception = null;
0125:
0126: /** should this response be handled further? */
0127: boolean final_resp = false;
0128:
0129: // Constructors
0130:
0131: /**
0132: * Creates a new Response and registers it with the stream-demultiplexor.
0133: */
0134: Response(Request request, boolean used_proxy,
0135: StreamDemultiplexor stream_handler) throws IOException {
0136: this .connection = request.getConnection();
0137: this .method = request.getMethod();
0138: this .resource = request.getRequestURI();
0139: this .used_proxy = used_proxy;
0140: this .stream_handler = stream_handler;
0141: sent_entity = (request.getData() != null) ? true : false;
0142:
0143: stream_handler.register(this , request);
0144: resp_inp_stream = stream_handler.getStream(this );
0145: inp_stream = resp_inp_stream;
0146: }
0147:
0148: /**
0149: * Creates a new Response that reads from the given stream. This is
0150: * used for the CONNECT subrequest which is used in establishing an
0151: * SSL tunnel through a proxy.
0152: *
0153: * @param request the subrequest
0154: * @param is the input stream from which to read the headers and
0155: * data.
0156: */
0157: Response(Request request, InputStream is) throws IOException {
0158: this .connection = request.getConnection();
0159: this .method = request.getMethod();
0160: this .resource = request.getRequestURI();
0161: used_proxy = false;
0162: stream_handler = null;
0163: sent_entity = (request.getData() != null) ? true : false;
0164: inp_stream = is;
0165: }
0166:
0167: /**
0168: * Create a new response with the given info. This is used when
0169: * creating a response in a requestHandler().
0170: *
0171: * <P>If <var>data</var> is not null then that is used; else if the
0172: * <var>is</var> is not null that is used; else the entity is empty.
0173: * If the input stream is used then <var>cont_len</var> specifies
0174: * the length of the data that can be read from it, or -1 if unknown.
0175: *
0176: * @param version the response version (such as "HTTP/1.1")
0177: * @param status the status code
0178: * @param reason the reason line
0179: * @param headers the response headers
0180: * @param data the response entity
0181: * @param is the response entity as an InputStream
0182: * @param cont_len the length of the data in the InputStream
0183: */
0184: public Response(String version, int status, String reason,
0185: NVPair[] headers, byte[] data, InputStream is, int cont_len) {
0186: this .Version = version;
0187: this .StatusCode = status;
0188: this .ReasonLine = reason;
0189: if (headers != null)
0190: for (int idx = 0; idx < headers.length; idx++)
0191: setHeader(headers[idx].getName(), headers[idx]
0192: .getValue());
0193: if (data != null)
0194: this .Data = data;
0195: else if (is == null)
0196: this .Data = new byte[0];
0197: else {
0198: this .inp_stream = is;
0199: ContentLength = cont_len;
0200: }
0201:
0202: got_headers = true;
0203: got_trailers = true;
0204: }
0205:
0206: // Methods
0207:
0208: /**
0209: * give the status code for this request. These are grouped as follows:
0210: * <UL>
0211: * <LI> 1xx - Informational (new in HTTP/1.1)
0212: * <LI> 2xx - Success
0213: * <LI> 3xx - Redirection
0214: * <LI> 4xx - Client Error
0215: * <LI> 5xx - Server Error
0216: * </UL>
0217: *
0218: * @exception IOException If any exception occurs on the socket.
0219: */
0220: public final int getStatusCode() throws IOException {
0221: if (!got_headers)
0222: getHeaders(true);
0223: return StatusCode;
0224: }
0225:
0226: /**
0227: * give the reason line associated with the status code.
0228: *
0229: * @exception IOException If any exception occurs on the socket.
0230: */
0231: public final String getReasonLine() throws IOException {
0232: if (!got_headers)
0233: getHeaders(true);
0234: return ReasonLine;
0235: }
0236:
0237: /**
0238: * get the HTTP version used for the response.
0239: *
0240: * @exception IOException If any exception occurs on the socket.
0241: */
0242: public final String getVersion() throws IOException {
0243: if (!got_headers)
0244: getHeaders(true);
0245: return Version;
0246: }
0247:
0248: /**
0249: * Wait for either a '100 Continue' or an error.
0250: *
0251: * @return the return status.
0252: */
0253: int getContinue() throws IOException {
0254: getHeaders(false);
0255: return StatusCode;
0256: }
0257:
0258: /**
0259: * get the final URI of the document. This is set if the original
0260: * request was deferred via the "moved" (301, 302, or 303) return
0261: * status.
0262: *
0263: * @return the new URI, or null if not redirected
0264: * @exception IOException If any exception occurs on the socket.
0265: */
0266: public final URI getEffectiveURI() throws IOException {
0267: if (!got_headers)
0268: getHeaders(true);
0269: return EffectiveURI;
0270: }
0271:
0272: /**
0273: * set the final URI of the document. This is only for internal use.
0274: */
0275: public void setEffectiveURI(URI final_uri) {
0276: EffectiveURI = final_uri;
0277: }
0278:
0279: /**
0280: * get the final URL of the document. This is set if the original
0281: * request was deferred via the "moved" (301, 302, or 303) return
0282: * status.
0283: *
0284: * @exception IOException If any exception occurs on the socket.
0285: * @deprecated use getEffectiveURI() instead
0286: * @see #getEffectiveURI
0287: */
0288: public final URL getEffectiveURL() throws IOException {
0289: return getEffectiveURI().toURL();
0290: }
0291:
0292: /**
0293: * set the final URL of the document. This is only for internal use.
0294: *
0295: * @deprecated use setEffectiveURI() instead
0296: * @see #setEffectiveURI
0297: */
0298: public void setEffectiveURL(URL final_url) {
0299: try {
0300: setEffectiveURI(new URI(final_url));
0301: } catch (ParseException pe) {
0302: throw new Error(pe.toString());
0303: } // shouldn't happen
0304: }
0305:
0306: /**
0307: * retrieves the field for a given header.
0308: *
0309: * @param hdr the header name.
0310: * @return the value for the header, or null if non-existent.
0311: * @exception IOException If any exception occurs on the socket.
0312: */
0313: public String getHeader(String hdr) throws IOException {
0314: if (!got_headers)
0315: getHeaders(true);
0316: return (String) Headers.get(hdr.trim());
0317: }
0318:
0319: /**
0320: * retrieves the field for a given header. The value is parsed as an
0321: * int.
0322: *
0323: * @param hdr the header name.
0324: * @return the value for the header if the header exists
0325: * @exception NumberFormatException if the header's value is not a number
0326: * or if the header does not exist.
0327: * @exception IOException if any exception occurs on the socket.
0328: */
0329: public int getHeaderAsInt(String hdr) throws IOException,
0330: NumberFormatException {
0331: return Integer.parseInt(getHeader(hdr));
0332: }
0333:
0334: /**
0335: * retrieves the field for a given header. The value is parsed as a
0336: * date; if this fails it is parsed as a long representing the number
0337: * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
0338: * IllegalArgumentException is thrown.
0339: *
0340: * <P>Note: When sending dates use Util.httpDate().
0341: *
0342: * @param hdr the header name.
0343: * @return the value for the header, or null if non-existent.
0344: * @exception IOException If any exception occurs on the socket.
0345: * @exception IllegalArgumentException If the header cannot be parsed
0346: * as a date or time.
0347: */
0348: public Date getHeaderAsDate(String hdr) throws IOException,
0349: IllegalArgumentException {
0350: String raw_date = getHeader(hdr);
0351: if (raw_date == null)
0352: return null;
0353:
0354: // asctime() format is missing an explicit GMT specifier
0355: if (raw_date.toUpperCase().indexOf("GMT") == -1)
0356: raw_date += " GMT";
0357:
0358: Date date;
0359:
0360: try {
0361: date = new Date(raw_date);
0362: } catch (IllegalArgumentException iae) {
0363: long time;
0364: try {
0365: time = Long.parseLong(raw_date);
0366: } catch (NumberFormatException nfe) {
0367: throw iae;
0368: }
0369: if (time < 0)
0370: time = 0;
0371: date = new Date(time * 1000L);
0372: }
0373:
0374: return date;
0375: }
0376:
0377: /**
0378: * Set a header field in the list of headers. If the header already
0379: * exists it will be overwritten; otherwise the header will be added
0380: * to the list. This is used by some modules when they process the
0381: * header so that higher level stuff doesn't get confused when the
0382: * headers and data don't match.
0383: *
0384: * @param header The name of header field to set.
0385: * @param value The value to set the field to.
0386: */
0387: public void setHeader(String header, String value) {
0388: Headers.put(header.trim(), value.trim());
0389: }
0390:
0391: /**
0392: * Removes a header field from the list of headers. This is used by
0393: * some modules when they process the header so that higher level stuff
0394: * doesn't get confused when the headers and data don't match.
0395: *
0396: * @param header The name of header field to remove.
0397: */
0398: public void deleteHeader(String header) {
0399: Headers.remove(header.trim());
0400: }
0401:
0402: /**
0403: * Retrieves the field for a given trailer. Note that this should not
0404: * be invoked until all the response data has been read. If invoked
0405: * before, it will force the data to be read via <code>getData()</code>.
0406: *
0407: * @param trailer the trailer name.
0408: * @return the value for the trailer, or null if non-existent.
0409: * @exception IOException If any exception occurs on the socket.
0410: */
0411: public String getTrailer(String trailer) throws IOException {
0412: if (!got_trailers)
0413: getTrailers();
0414: return (String) Trailers.get(trailer.trim());
0415: }
0416:
0417: /**
0418: * Retrieves the field for a given tailer. The value is parsed as an
0419: * int.
0420: *
0421: * @param trailer the tailer name.
0422: * @return the value for the trailer if the trailer exists
0423: * @exception NumberFormatException if the trailer's value is not a number
0424: * or if the trailer does not exist.
0425: * @exception IOException if any exception occurs on the socket.
0426: */
0427: public int getTrailerAsInt(String trailer) throws IOException,
0428: NumberFormatException {
0429: return Integer.parseInt(getTrailer(trailer));
0430: }
0431:
0432: /**
0433: * Retrieves the field for a given trailer. The value is parsed as a
0434: * date; if this fails it is parsed as a long representing the number
0435: * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
0436: * IllegalArgumentException is thrown.
0437: *
0438: * <P>Note: When sending dates use Util.httpDate().
0439: *
0440: * @param trailer the trailer name.
0441: * @return the value for the trailer, or null if non-existent.
0442: * @exception IllegalArgumentException if the trailer's value is neither a
0443: * legal date nor a number.
0444: * @exception IOException if any exception occurs on the socket.
0445: * @exception IllegalArgumentException If the header cannot be parsed
0446: * as a date or time.
0447: */
0448: public Date getTrailerAsDate(String trailer) throws IOException,
0449: IllegalArgumentException {
0450: String raw_date = getTrailer(trailer);
0451: if (raw_date == null)
0452: return null;
0453:
0454: // asctime() format is missing an explicit GMT specifier
0455: if (raw_date.toUpperCase().indexOf("GMT") == -1)
0456: raw_date += " GMT";
0457:
0458: Date date;
0459:
0460: try {
0461: date = new Date(raw_date);
0462: } catch (IllegalArgumentException iae) {
0463: // some servers erroneously send a number, so let's try that
0464: long time;
0465: try {
0466: time = Long.parseLong(raw_date);
0467: } catch (NumberFormatException nfe) {
0468: throw iae;
0469: } // give up
0470: if (time < 0)
0471: time = 0;
0472: date = new Date(time * 1000L);
0473: }
0474:
0475: return date;
0476: }
0477:
0478: /**
0479: * Set a trailer field in the list of trailers. If the trailer already
0480: * exists it will be overwritten; otherwise the trailer will be added
0481: * to the list. This is used by some modules when they process the
0482: * trailer so that higher level stuff doesn't get confused when the
0483: * trailer and data don't match.
0484: *
0485: * @param trailer The name of trailer field to set.
0486: * @param value The value to set the field to.
0487: */
0488: public void setTrailer(String trailer, String value) {
0489: Trailers.put(trailer.trim(), value.trim());
0490: }
0491:
0492: /**
0493: * Removes a trailer field from the list of trailers. This is used by
0494: * some modules when they process the trailer so that higher level stuff
0495: * doesn't get confused when the trailers and data don't match.
0496: *
0497: * @param trailer The name of trailer field to remove.
0498: */
0499: public void deleteTrailer(String trailer) {
0500: Trailers.remove(trailer.trim());
0501: }
0502:
0503: /**
0504: * Reads all the response data into a byte array. Note that this method
0505: * won't return until <em>all</em> the data has been received (so for
0506: * instance don't invoke this method if the server is doing a server
0507: * push). If getInputStream() had been previously called then this method
0508: * only returns any unread data remaining on the stream and then closes
0509: * it.
0510: *
0511: * @see #getInputStream()
0512: * @return an array containing the data (body) returned. If no data
0513: * was returned then it's set to a zero-length array.
0514: * @exception IOException If any io exception occured while reading
0515: * the data
0516: */
0517: public synchronized byte[] getData() throws IOException {
0518: if (!got_headers)
0519: getHeaders(true);
0520:
0521: if (Data == null) {
0522: try {
0523: readResponseData(inp_stream);
0524: } catch (InterruptedIOException ie) // don't intercept
0525: {
0526: throw ie;
0527: } catch (IOException ioe) {
0528: if (DebugResp) {
0529: System.err.print("Resp: (" + inp_stream.hashCode()
0530: + ") (" + Thread.currentThread() + ")");
0531: ioe.printStackTrace();
0532: }
0533: try {
0534: inp_stream.close();
0535: } catch (Exception e) {
0536: }
0537: throw ioe;
0538: }
0539:
0540: inp_stream.close();
0541: }
0542:
0543: return Data;
0544: }
0545:
0546: /**
0547: * Gets an input stream from which the returned data can be read. Note
0548: * that if getData() had been previously called it will actually return
0549: * a ByteArrayInputStream created from that data.
0550: *
0551: * @see #getData()
0552: * @return the InputStream.
0553: * @exception IOException If any exception occurs on the socket.
0554: */
0555: public synchronized InputStream getInputStream() throws IOException {
0556: if (!got_headers)
0557: getHeaders(true);
0558:
0559: if (Data == null)
0560: return inp_stream;
0561: else
0562: return new ByteArrayInputStream(Data);
0563: }
0564:
0565: /**
0566: * Some responses such as those from a HEAD or with certain status
0567: * codes don't have an entity. This is detected by the client and
0568: * can be queried here. Note that this won't try to do a read() on
0569: * the input stream (it will however cause the headers to be read
0570: * and parsed if not already done).
0571: *
0572: * @return true if the response has an entity, false otherwise
0573: * @since V0.3-1
0574: */
0575: public synchronized boolean hasEntity() throws IOException {
0576: if (!got_headers)
0577: getHeaders(true);
0578:
0579: return (cd_type != CD_0);
0580: }
0581:
0582: // Helper Methods
0583:
0584: /**
0585: * Gets and parses the headers. Sets up Data if no data will be received.
0586: *
0587: * @param skip_cont if true skips over '100 Continue' status codes.
0588: * @exception IOException If any exception occurs while reading the headers.
0589: */
0590: private synchronized void getHeaders(boolean skip_cont)
0591: throws IOException {
0592: if (got_headers)
0593: return;
0594: if (exception != null)
0595: throw (IOException) exception.fillInStackTrace();
0596:
0597: reading_headers = true;
0598: try {
0599: do {
0600: Headers.clear(); // clear any headers from 100 Continue
0601: String headers = readResponseHeaders(inp_stream);
0602: parseResponseHeaders(headers);
0603: } while ((StatusCode == 100 && skip_cont) || // Continue
0604: (StatusCode > 101 && StatusCode < 200)); // Unknown
0605: } catch (IOException ioe) {
0606: if (!(ioe instanceof InterruptedIOException))
0607: exception = ioe;
0608: if (ioe instanceof ProtocolException) // thrown internally
0609: {
0610: cd_type = CD_CLOSE;
0611: if (stream_handler != null)
0612: stream_handler.markForClose(this );
0613: }
0614: throw ioe;
0615: } finally {
0616: reading_headers = false;
0617: }
0618: if (StatusCode == 100)
0619: return;
0620:
0621: // parse the Content-Length header
0622:
0623: int cont_len = -1;
0624: String cl_hdr = (String) Headers.get("Content-Length");
0625: if (cl_hdr != null) {
0626: try {
0627: cont_len = Integer.parseInt(cl_hdr);
0628: if (cont_len < 0)
0629: throw new NumberFormatException();
0630: } catch (NumberFormatException nfe) {
0631: throw new ProtocolException(
0632: "Invalid Content-length header" + " received: "
0633: + cl_hdr);
0634: }
0635: }
0636:
0637: // parse the Transfer-Encoding header
0638:
0639: boolean te_chunked = false, te_is_identity = true, ct_mpbr = false;
0640: Vector te_hdr = null;
0641: try {
0642: te_hdr = Util.parseHeader((String) Headers
0643: .get("Transfer-Encoding"));
0644: } catch (ParseException pe) {
0645: }
0646: if (te_hdr != null) {
0647: te_chunked = ((HttpHeaderElement) te_hdr.lastElement())
0648: .getName().equalsIgnoreCase("chunked");
0649: for (int idx = 0; idx < te_hdr.size(); idx++)
0650: if (((HttpHeaderElement) te_hdr.elementAt(idx))
0651: .getName().equalsIgnoreCase("identity"))
0652: te_hdr.removeElementAt(idx--);
0653: else
0654: te_is_identity = false;
0655: }
0656:
0657: // parse Content-Type header
0658:
0659: try {
0660: String hdr;
0661: if ((hdr = (String) Headers.get("Content-Type")) != null) {
0662: Vector phdr = Util.parseHeader(hdr);
0663: ct_mpbr = phdr.contains(new HttpHeaderElement(
0664: "multipart/byteranges"))
0665: || phdr.contains(new HttpHeaderElement(
0666: "multipart/x-byteranges"));
0667: }
0668: } catch (ParseException pe) {
0669: }
0670:
0671: // now determine content-delimiter
0672:
0673: if (StatusCode < 200 || StatusCode == 204 || StatusCode == 205
0674: || StatusCode == 304) {
0675: cd_type = CD_0;
0676: } else if (te_chunked) {
0677: cd_type = CD_CHUNKED;
0678:
0679: te_hdr.removeElementAt(te_hdr.size() - 1);
0680: if (te_hdr.size() > 0)
0681: setHeader("Transfer-Encoding", Util
0682: .assembleHeader(te_hdr));
0683: else
0684: deleteHeader("Transfer-Encoding");
0685: } else if (cont_len != -1 && te_is_identity)
0686: cd_type = CD_CONTLEN;
0687: else if (ct_mpbr && te_is_identity)
0688: cd_type = CD_MP_BR;
0689: else if (!method.equals("HEAD")) {
0690: cd_type = CD_CLOSE;
0691: if (stream_handler != null)
0692: stream_handler.markForClose(this );
0693:
0694: if (Version.equals("HTTP/0.9")) {
0695: inp_stream = new SequenceInputStream(
0696: new ByteArrayInputStream(Data), inp_stream);
0697: Data = null;
0698: }
0699: }
0700:
0701: if (cd_type == CD_CONTLEN)
0702: ContentLength = cont_len;
0703: else
0704: deleteHeader("Content-Length"); // Content-Length is not valid in this case
0705:
0706: /* We treat HEAD specially down here because the above code needs
0707: * to know whether to remove the Content-length header or not.
0708: */
0709: if (method.equals("HEAD"))
0710: cd_type = CD_0;
0711:
0712: if (cd_type == CD_0) {
0713: ContentLength = 0;
0714: Data = new byte[0];
0715: inp_stream.close(); // we will not receive any more data
0716: }
0717:
0718: if (DebugResp) {
0719: System.err
0720: .println("Resp: Response entity delimiter: "
0721: + (cd_type == CD_0 ? "No Entity"
0722: : cd_type == CD_CLOSE ? "Close"
0723: : cd_type == CD_CONTLEN ? "Content-Length"
0724: : cd_type == CD_CHUNKED ? "Chunked"
0725: : cd_type == CD_MP_BR ? "Multipart"
0726: : "???")
0727: + " (" + inp_stream.hashCode() + ") ("
0728: + Thread.currentThread() + ")");
0729: }
0730:
0731: // remove erroneous connection tokens
0732:
0733: if (connection.ServerProtocolVersion >= HTTP_1_1)
0734: deleteHeader("Proxy-Connection");
0735: else // HTTP/1.0
0736: {
0737: if (connection.getProxyHost() != null)
0738: deleteHeader("Connection");
0739: else
0740: deleteHeader("Proxy-Connection");
0741:
0742: Vector pco;
0743: try {
0744: pco = Util.parseHeader((String) Headers
0745: .get("Connection"));
0746: } catch (ParseException pe) {
0747: pco = null;
0748: }
0749:
0750: if (pco != null) {
0751: for (int idx = 0; idx < pco.size(); idx++) {
0752: String name = ((HttpHeaderElement) pco
0753: .elementAt(idx)).getName();
0754: if (!name.equalsIgnoreCase("keep-alive")) {
0755: pco.removeElementAt(idx);
0756: deleteHeader(name);
0757: idx--;
0758: }
0759: }
0760:
0761: if (pco.size() > 0)
0762: setHeader("Connection", Util.assembleHeader(pco));
0763: else
0764: deleteHeader("Connection");
0765: }
0766:
0767: try {
0768: pco = Util.parseHeader((String) Headers
0769: .get("Proxy-Connection"));
0770: } catch (ParseException pe) {
0771: pco = null;
0772: }
0773:
0774: if (pco != null) {
0775: for (int idx = 0; idx < pco.size(); idx++) {
0776: String name = ((HttpHeaderElement) pco
0777: .elementAt(idx)).getName();
0778: if (!name.equalsIgnoreCase("keep-alive")) {
0779: pco.removeElementAt(idx);
0780: deleteHeader(name);
0781: idx--;
0782: }
0783: }
0784:
0785: if (pco.size() > 0)
0786: setHeader("Proxy-Connection", Util
0787: .assembleHeader(pco));
0788: else
0789: deleteHeader("Proxy-Connection");
0790: }
0791: }
0792:
0793: // this must be set before we invoke handleFirstRequest()
0794: got_headers = true;
0795:
0796: // special handling if this is the first response received
0797: if (isFirstResponse) {
0798: if (!connection.handleFirstRequest(req, this )) {
0799: // got a buggy server - need to redo the request
0800: Response resp;
0801: try {
0802: resp = connection.sendRequest(req, timeout);
0803: } catch (ModuleException me) {
0804: throw new IOException(me.toString());
0805: }
0806: resp.getVersion();
0807:
0808: this .StatusCode = resp.StatusCode;
0809: this .ReasonLine = resp.ReasonLine;
0810: this .Version = resp.Version;
0811: this .EffectiveURI = resp.EffectiveURI;
0812: this .ContentLength = resp.ContentLength;
0813: this .Headers = resp.Headers;
0814: this .inp_stream = resp.inp_stream;
0815: this .Data = resp.Data;
0816:
0817: req = null;
0818: }
0819: }
0820: }
0821:
0822: /* these are external to readResponseHeaders() because we need to be
0823: * able to restart after an InterruptedIOException
0824: */
0825: private byte[] buf = new byte[7];
0826: private int buf_pos = 0;
0827: private StringBuffer hdrs = new StringBuffer(400);
0828: private boolean reading_lines = false;
0829: private boolean bol = true;
0830: private boolean got_cr = false;
0831:
0832: /**
0833: * Reads the response headers received, folding continued lines.
0834: *
0835: * <P>Some of the code is a bit convoluted because we have to be able
0836: * restart after an InterruptedIOException.
0837: *
0838: * @inp the input stream from which to read the response
0839: * @return a (newline separated) list of headers
0840: * @exception IOException if any read on the input stream fails
0841: */
0842: private String readResponseHeaders(InputStream inp)
0843: throws IOException {
0844: if (DebugResp) {
0845: if (buf_pos == 0)
0846: System.err.println("Resp: Reading Response headers "
0847: + inp_stream.hashCode() + " ("
0848: + Thread.currentThread() + ")");
0849: else
0850: System.err
0851: .println("Resp: Resuming reading Response headers "
0852: + inp_stream.hashCode()
0853: + " ("
0854: + Thread.currentThread() + ")");
0855: }
0856:
0857: // read 7 bytes to see type of response
0858: if (!reading_lines) {
0859: try {
0860: // Skip any leading white space to accomodate buggy responses
0861: if (buf_pos == 0) {
0862: int c;
0863: do {
0864: if ((c = inp.read()) == -1)
0865: throw new EOFException(
0866: "Encountered premature EOF "
0867: + "while reading Version");
0868: } while (Character.isSpace((char) (c & 0xFF)));
0869: buf[0] = (byte) (c & 0xFF);
0870: buf_pos = 1;
0871: }
0872:
0873: // Now read first seven bytes (the version string)
0874: while (buf_pos < buf.length) {
0875: int got = inp.read(buf, buf_pos, buf.length
0876: - buf_pos);
0877: if (got == -1)
0878: throw new EOFException(
0879: "Encountered premature EOF "
0880: + "while reading Version");
0881: buf_pos += got;
0882: }
0883: } catch (EOFException eof) {
0884: if (DebugResp) {
0885: System.err.print("Resp: (" + inp_stream.hashCode()
0886: + ") (" + Thread.currentThread() + ")");
0887: eof.printStackTrace();
0888: }
0889: throw eof;
0890: }
0891: for (int idx = 0; idx < buf.length; idx++)
0892: hdrs.append((char) buf[idx]);
0893:
0894: reading_lines = true;
0895: }
0896:
0897: if (hdrs.toString().startsWith("HTTP/") || // It's x.x
0898: hdrs.toString().startsWith("HTTP ")) // NCSA bug
0899: readLines(inp);
0900:
0901: // reset variables for next round
0902: buf_pos = 0;
0903: reading_lines = false;
0904: bol = true;
0905: got_cr = false;
0906:
0907: String tmp = hdrs.toString();
0908: hdrs.setLength(0);
0909: return tmp;
0910: }
0911:
0912: boolean trailers_read = false;
0913:
0914: /**
0915: * This is called by the StreamDemultiplexor to read all the trailers
0916: * of a chunked encoded entity.
0917: *
0918: * @param inp the raw input stream to read from
0919: * @exception IOException if any IOException is thrown by the stream
0920: */
0921: void readTrailers(InputStream inp) throws IOException {
0922: try {
0923: readLines(inp);
0924: trailers_read = true;
0925: } catch (IOException ioe) {
0926: if (!(ioe instanceof InterruptedIOException))
0927: exception = ioe;
0928: throw ioe;
0929: }
0930: }
0931:
0932: /**
0933: * This reads a set of lines up to and including the first empty line.
0934: * A line is terminated by either a <CR><LF> or <LF>. The lines are
0935: * stored in the <var>hdrs</var> buffers. Continued lines are merged
0936: * and stored as one line.
0937: *
0938: * <P>This method is restartable after an InterruptedIOException.
0939: *
0940: * @param inp the input stream to read from
0941: * @exception IOException if any IOException is thrown by the stream
0942: */
0943: private void readLines(InputStream inp) throws IOException {
0944: /* This loop is a merge of readLine() from DataInputStream and
0945: * the necessary header logic to merge continued lines and terminate
0946: * after an empty line. The reason this is explicit is because of
0947: * the need to handle InterruptedIOExceptions.
0948: */
0949: loop: while (true) {
0950: int b = inp.read();
0951: switch (b) {
0952: case -1:
0953: throw new EOFException(
0954: "Encountered premature EOF while reading headers:\n"
0955: + hdrs);
0956: case '\r':
0957: got_cr = true;
0958: break;
0959: case '\n':
0960: if (bol)
0961: break loop; // all headers read
0962: hdrs.append('\n');
0963: bol = true;
0964: got_cr = false;
0965: break;
0966: case ' ':
0967: case '\t':
0968: if (bol) // a continued line
0969: {
0970: // replace previous \n with SP
0971: hdrs.setCharAt(hdrs.length() - 1, ' ');
0972: bol = false;
0973: break;
0974: }
0975: default:
0976: if (got_cr) {
0977: hdrs.append('\r');
0978: got_cr = false;
0979: }
0980: hdrs.append((char) (b & 0xFF));
0981: bol = false;
0982: break;
0983: }
0984: }
0985: }
0986:
0987: /**
0988: * Parses the headers received into a new Response structure.
0989: *
0990: * @param headers a (newline separated) list of headers
0991: * @exception ProtocolException if any part of the headers do not
0992: * conform
0993: */
0994: private void parseResponseHeaders(String headers)
0995: throws ProtocolException {
0996: String sts_line = null;
0997: StringTokenizer lines = new StringTokenizer(headers, "\r\n"), elem;
0998:
0999: if (DebugResp)
1000: System.err
1001: .println("Resp: Parsing Response headers from Request "
1002: + "\""
1003: + method
1004: + " "
1005: + resource
1006: + "\": ("
1007: + inp_stream.hashCode()
1008: + ") ("
1009: + Thread.currentThread()
1010: + ")\n\n"
1011: + headers);
1012:
1013: // Detect and handle HTTP/0.9 responses
1014:
1015: if (!headers.regionMatches(true, 0, "HTTP/", 0, 5)
1016: && !headers.regionMatches(true, 0, "HTTP ", 0, 5)) // NCSA bug
1017: {
1018: Version = "HTTP/0.9";
1019: StatusCode = 200;
1020: ReasonLine = "OK";
1021:
1022: Data = new byte[headers.length()];
1023: headers.getBytes(0, headers.length(), Data, 0);
1024:
1025: return;
1026: }
1027:
1028: // get the status line
1029:
1030: try {
1031: sts_line = lines.nextToken();
1032: elem = new StringTokenizer(sts_line, " \t");
1033:
1034: Version = elem.nextToken();
1035: StatusCode = Integer.valueOf(elem.nextToken()).intValue();
1036:
1037: if (Version.equalsIgnoreCase("HTTP")) // NCSA bug
1038: Version = "HTTP/1.0";
1039: } catch (NoSuchElementException e) {
1040: throw new ProtocolException(
1041: "Invalid HTTP status line received: " + sts_line);
1042: }
1043: try {
1044: ReasonLine = elem.nextToken("").trim();
1045: } catch (NoSuchElementException e) {
1046: ReasonLine = "";
1047: }
1048:
1049: /* If the status code shows an error and we're sending (or have sent)
1050: * an entity and it's length is delimited by a Content-length header,
1051: * then we must close the the connection (if indeed it hasn't already
1052: * been done) - RFC-2068, Section 8.2 .
1053: */
1054: if (StatusCode >= 300 && sent_entity) {
1055: if (stream_handler != null)
1056: stream_handler.markForClose(this );
1057: }
1058:
1059: // get the rest of the headers
1060:
1061: parseHeaderFields(lines, Headers);
1062:
1063: /* make sure the connection isn't closed prematurely if we have
1064: * trailer fields
1065: */
1066: if (Headers.get("Trailer") != null && resp_inp_stream != null)
1067: resp_inp_stream.dontTruncate();
1068:
1069: // Mark the end of the connection if it's not to be kept alive
1070:
1071: int vers;
1072: if (Version.equalsIgnoreCase("HTTP/0.9")
1073: || Version.equalsIgnoreCase("HTTP/1.0"))
1074: vers = 0;
1075: else
1076: vers = 1;
1077:
1078: try {
1079: String con = (String) Headers.get("Connection"), pcon = (String) Headers
1080: .get("Proxy-Connection");
1081:
1082: // parse connection header
1083: if ((vers == 1 && con != null && Util
1084: .hasToken(con, "close"))
1085: || (vers == 0 && !((!used_proxy && con != null && Util
1086: .hasToken(con, "keep-alive")) || (used_proxy
1087: && pcon != null && Util.hasToken(pcon,
1088: "keep-alive")))))
1089: if (stream_handler != null)
1090: stream_handler.markForClose(this );
1091: } catch (ParseException pe) {
1092: }
1093: }
1094:
1095: /**
1096: * If the trailers have not been read it calls <code>getData()</code>
1097: * to first force all data and trailers to be read. Then the trailers
1098: * parsed into the <var>Trailers</var> hashtable.
1099: *
1100: * @exception IOException if any exception occured during reading of the
1101: * response
1102: */
1103: private synchronized void getTrailers() throws IOException {
1104: if (got_trailers)
1105: return;
1106: if (exception != null)
1107: throw (IOException) exception.fillInStackTrace();
1108:
1109: if (DebugResp)
1110: System.err.println("Resp: Reading Response trailers "
1111: + inp_stream.hashCode() + " ("
1112: + Thread.currentThread() + ")");
1113:
1114: try {
1115: if (!trailers_read) {
1116: if (resp_inp_stream != null)
1117: resp_inp_stream.readAll(timeout);
1118: }
1119:
1120: if (trailers_read) {
1121: if (DebugResp)
1122: System.err
1123: .println("Resp: Parsing Response trailers from "
1124: + "Request \""
1125: + method
1126: + " "
1127: + resource
1128: + "\": ("
1129: + inp_stream.hashCode()
1130: + ") ("
1131: + Thread.currentThread()
1132: + ")\n\n"
1133: + hdrs);
1134:
1135: parseHeaderFields(new StringTokenizer(hdrs.toString(),
1136: "\r\n"), Trailers);
1137: }
1138: } finally {
1139: got_trailers = true;
1140: }
1141: }
1142:
1143: /**
1144: * Parses the given lines as header fields of the form "<name>: <value>"
1145: * into the given list.
1146: *
1147: * @param lines the header or trailer lines, one header field per line
1148: * @param list the Hashtable to store the parsed fields in
1149: * @exception ProtocolException if any part of the headers do not
1150: * conform
1151: */
1152: private void parseHeaderFields(StringTokenizer lines,
1153: CIHashtable list) throws ProtocolException {
1154: while (lines.hasMoreTokens()) {
1155: String hdr = lines.nextToken();
1156: int sep = hdr.indexOf(':');
1157:
1158: /* Once again we have to deal with broken servers and try
1159: * to wing it here. If no ':' is found, try using the first
1160: * space:
1161: */
1162: if (sep == -1)
1163: sep = hdr.indexOf(' ');
1164: if (sep == -1) {
1165: throw new ProtocolException(
1166: "Invalid HTTP header received: " + hdr);
1167: }
1168:
1169: String hdr_name = hdr.substring(0, sep).trim();
1170:
1171: int len = hdr.length();
1172: sep++;
1173: while (sep < len && Character.isSpace(hdr.charAt(sep)))
1174: sep++;
1175: String hdr_value = hdr.substring(sep);
1176:
1177: String old_value = (String) list.get(hdr_name);
1178: if (old_value == null)
1179: list.put(hdr_name, hdr_value);
1180: else
1181: list.put(hdr_name, old_value + ", " + hdr_value);
1182: }
1183: }
1184:
1185: /**
1186: * Reads the response data received. Does not return until either
1187: * Content-Length bytes have been read or EOF is reached.
1188: *
1189: * @inp the input stream from which to read the data
1190: * @exception IOException if any read on the input stream fails
1191: */
1192: private void readResponseData(InputStream inp) throws IOException {
1193: if (ContentLength == 0)
1194: return;
1195:
1196: if (Data == null)
1197: Data = new byte[0];
1198:
1199: // read response data
1200:
1201: int off = Data.length;
1202:
1203: try {
1204: // check Content-length header in case CE-Module removed it
1205: if (getHeader("Content-Length") != null) {
1206: int rcvd = 0;
1207: Data = new byte[ContentLength];
1208:
1209: do {
1210: off += rcvd;
1211: rcvd = inp.read(Data, off, ContentLength - off);
1212: } while (rcvd != -1 && off + rcvd < ContentLength);
1213:
1214: /* Don't do this!
1215: * If we do, then getData() won't work after a getInputStream()
1216: * because we'll never get all the expected data. Instead, let
1217: * the underlying RespInputStream throw the EOF.
1218: if (rcvd == -1) // premature EOF
1219: {
1220: throw new EOFException("Encountered premature EOF while " +
1221: "reading headers: received " + off +
1222: " bytes instead of the expected " +
1223: ContentLength + " bytes");
1224: }
1225: */
1226: } else {
1227: int inc = 1000, rcvd = 0;
1228:
1229: do {
1230: off += rcvd;
1231: Data = Util.resizeArray(Data, off + inc);
1232: } while ((rcvd = inp.read(Data, off, inc)) != -1);
1233:
1234: Data = Util.resizeArray(Data, off);
1235: }
1236: } catch (IOException ioe) {
1237: Data = Util.resizeArray(Data, off);
1238: throw ioe;
1239: } finally {
1240: try {
1241: inp.close();
1242: } catch (IOException ioe) {
1243: }
1244: }
1245: }
1246:
1247: Request req = null;
1248: boolean isFirstResponse = false;
1249:
1250: /**
1251: * This marks this response as belonging to the first request made
1252: * over an HTTPConnection. The <var>con</var> and <var>req</var>
1253: * parameters are needed in case we have to do a resend of the request -
1254: * this is to handle buggy servers which barf upon receiving a request
1255: * marked as HTTP/1.1 .
1256: *
1257: * @param con The HTTPConnection used
1258: * @param req The Request sent
1259: */
1260: void markAsFirstResponse(Request req) {
1261: this .req = req;
1262: isFirstResponse = true;
1263: }
1264: }
|