View Javadoc

1   /*
2    * @(#)Response.java					0.3-3 06/05/2001
3    *
4    *  This file is part of the HTTPClient package
5    *  Copyright (C) 1996-2001  Ronald Tschalär
6    *
7    *  This library is free software; you can redistribute it and/or
8    *  modify it under the terms of the GNU Lesser General Public
9    *  License as published by the Free Software Foundation; either
10   *  version 2 of the License, or (at your option) any later version.
11   *
12   *  This library is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   *  Lesser General Public License for more details.
16   *
17   *  You should have received a copy of the GNU Lesser General Public
18   *  License along with this library; if not, write to the Free
19   *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20   *  MA 02111-1307, USA
21   *
22   *  For questions, suggestions, bug-reports, enhancement-requests etc.
23   *  I may be contacted at:
24   *
25   *  ronald@innovation.ch
26   *
27   *  The HTTPClient's home page is located at:
28   *
29   *  http://www.innovation.ch/java/HTTPClient/
30   *
31   * This file contains modifications for use with "The Grinder"
32   * (http://grinder.sourceforge.net) under the terms of the LGPL. They
33   * are marked below with the comment "GRINDER MODIFICATION".
34   *
35   */
36  
37  package HTTPClient;
38  
39  import java.io.InputStream;
40  import java.io.SequenceInputStream;
41  import java.io.ByteArrayInputStream;
42  import java.io.IOException;
43  import java.io.InterruptedIOException;
44  import java.io.EOFException;
45  import java.io.UnsupportedEncodingException;
46  import java.net.URL;
47  import java.net.ProtocolException;
48  import java.util.Date;
49  import java.util.Vector;
50  import java.util.Hashtable;
51  import java.util.StringTokenizer;
52  import java.util.NoSuchElementException;
53  
54  
55  /**
56   * This class represents an intermediate response. It's used internally by the
57   * modules. When all modules have handled the response then the HTTPResponse
58   * fills in its fields with the data from this class.
59   *
60   * @version	0.3-3  06/05/2001
61   * @author	Ronald Tschalär
62   */
63  public final class Response implements RoResponse, GlobalConstants, Cloneable
64  {
65      /** This contains a list of headers which may only have a single value */
66      private static final Hashtable singleValueHeaders;
67  
68      /** our http connection */
69      private HTTPConnection connection;
70  
71      /** our stream demux */
72      private StreamDemultiplexor stream_handler;
73  
74      /** the HTTPResponse we're coupled with */
75              HTTPResponse http_resp;
76  
77      /** the timeout for read operations */
78              int          timeout = 0;
79  
80      /** our input stream (usually from the stream demux). Push input streams
81       *  onto this if necessary. */
82      public  InputStream  inp_stream;
83  
84      /** our response input stream from the stream demux */
85      private RespInputStream  resp_inp_stream = null;
86  
87      /** the method used in the request */
88      private String       method;
89  
90      /** the resource in the request (for debugging purposes) */
91              String       resource;
92  
93      /** was a proxy used for the request? */
94      private boolean      used_proxy;
95  
96      /** did the request contain an entity? */
97      private boolean      sent_entity;
98  
99      /** the status code returned. */
100             int          StatusCode = 0;
101 
102     /** the reason line associated with the status code. */
103             String       ReasonLine;
104 
105     /** the HTTP version of the response. */
106             String       Version;
107 
108     /** the final URI of the document. */
109             URI          EffectiveURI = null;
110 
111     /** any headers which were received and do not fit in the above list. */
112             CIHashtable  Headers = new CIHashtable();
113 
114     /** any trailers which were received and do not fit in the above list. */
115             CIHashtable  Trailers = new CIHashtable();
116 
117     /** the message length of the response if either there is no data (in which
118      *  case ContentLength=0) or if the message length is controlled by a
119      *  Content-Length header. If neither of these, then it's -1  */
120             int          ContentLength = -1;
121 
122     /** this indicates how the length of the entity body is determined */
123             int          cd_type = CD_HDRS;
124 
125     /** the data (body) returned. */
126             byte[]       Data = null;
127 
128     /** signals if we in the process of reading the headers */
129             boolean      reading_headers = false;
130 
131     /** signals if we have got and parsed the headers yet */
132             boolean      got_headers = false;
133 
134     /** signals if we have got and parsed the trailers yet */
135             boolean      got_trailers = false;
136 
137     /** remembers any exception received while reading/parsing headers */
138     private IOException  exception = null;
139 
140     /** should this response be handled further? */
141             boolean      final_resp = false;
142 
143     /** should the request be retried by the application? */
144             boolean      retry = false;
145 
146 
147     static
148     {
149 	/* This static initializer creates a hashtable of header names that
150 	 * should only have at most a single value in a server response. Other
151 	 * headers that may have multiple values (ie Set-Cookie) will have
152 	 * their values combined into one header, with individual values being
153 	 * separated by commas.
154 	 */
155 	String[] singleValueHeaderNames = {
156 	    "age", "location", "content-base", "content-length",
157 	    "content-location", "content-md5", "content-range", "content-type",
158 	    "date", "etag", "expires", "proxy-authenticate", "retry-after",
159 	};
160 
161 	singleValueHeaders = new Hashtable(singleValueHeaderNames.length);
162 	for (int idx=0; idx<singleValueHeaderNames.length; idx++)
163 	  singleValueHeaders.put(singleValueHeaderNames[idx],
164 				 singleValueHeaderNames[idx]);
165     }
166 
167 
168     // Constructors
169 
170     /**
171      * Creates a new Response and registers it with the stream-demultiplexor.
172      */
173     Response(Request request, boolean used_proxy,
174 	     StreamDemultiplexor stream_handler)
175 	    throws IOException
176     {
177 	this.connection     = request.getConnection();
178 	this.method         = request.getMethod();
179 	this.resource       = request.getRequestURI();
180 	this.used_proxy     = used_proxy;
181 	this.stream_handler = stream_handler;
182 	sent_entity         = (request.getData() != null) ? true : false;
183 
184 	stream_handler.register(this, request);
185 	resp_inp_stream     = stream_handler.getStream(this);
186 	inp_stream          = resp_inp_stream;
187     }
188 
189 
190     /**
191      * Creates a new Response that reads from the given stream. This is
192      * used for the CONNECT subrequest which is used in establishing an
193      * SSL tunnel through a proxy.
194      *
195      * @param request the subrequest
196      * @param is      the input stream from which to read the headers and
197      *                data.
198      */
199     Response(Request request, InputStream is) throws IOException
200     {
201 	this.connection = request.getConnection();
202 	this.method     = request.getMethod();
203 	this.resource   = request.getRequestURI();
204 	used_proxy      = false;
205 	stream_handler  = null;
206 	sent_entity     = (request.getData() != null) ? true : false;
207 	inp_stream      = is;
208     }
209 
210 
211     /**
212      * Create a new response with the given info. This is used when
213      * creating a response in a requestHandler().
214      *
215      * <P>If <var>data</var> is not null then that is used; else if the
216      * <var>is</var> is not null that is used; else the entity is empty.
217      * If the input stream is used then <var>cont_len</var> specifies
218      * the length of the data that can be read from it, or -1 if unknown.
219      *
220      * @param version  the response version (such as "HTTP/1.1")
221      * @param status   the status code
222      * @param reason   the reason line
223      * @param headers  the response headers
224      * @param data     the response entity
225      * @param is       the response entity as an InputStream
226      * @param cont_len the length of the data in the InputStream
227      */
228     public Response(String version, int status, String reason, NVPair[] headers,
229 		    byte[] data, InputStream is, int cont_len)
230     {
231 	this.Version    = version;
232 	this.StatusCode = status;
233 	this.ReasonLine = reason;
234 	if (headers != null)
235 	    for (int idx=0; idx<headers.length; idx++)
236 		setHeader(headers[idx].getName(), headers[idx].getValue());
237 	if (data != null)
238 	    this.Data   = data;
239 	else if (is == null)
240 	    this.Data   = new byte[0];
241 	else
242 	{
243 	    this.inp_stream = is;
244 	    ContentLength   = cont_len;
245 	}
246 
247 	got_headers  = true;
248 	got_trailers = true;
249     }
250 
251 
252     // Methods
253 
254     /**
255      * give the status code for this request. These are grouped as follows:
256      * <UL>
257      *   <LI> 1xx - Informational (new in HTTP/1.1)
258      *   <LI> 2xx - Success
259      *   <LI> 3xx - Redirection
260      *   <LI> 4xx - Client Error
261      *   <LI> 5xx - Server Error
262      * </UL>
263      *
264      * @exception IOException If any exception occurs on the socket.
265      */
266     public final int getStatusCode()  throws IOException
267     {
268 	if (!got_headers)  getHeaders(true);
269 	return StatusCode;
270     }
271 
272     /**
273      * give the reason line associated with the status code.
274      *
275      * @exception IOException If any exception occurs on the socket.
276      */
277     public final String getReasonLine()  throws IOException
278     {
279 	if (!got_headers)  getHeaders(true);
280 	return ReasonLine;
281     }
282 
283     /**
284      * get the HTTP version used for the response.
285      *
286      * @exception IOException If any exception occurs on the socket.
287      */
288     public final String getVersion()  throws IOException
289     {
290 	if (!got_headers)  getHeaders(true);
291 	return Version;
292     }
293 
294     /**
295      * Wait for either a '100 Continue' or an error.
296      *
297      * @return the return status.
298      */
299     int getContinue()  throws IOException
300     {
301 	getHeaders(false);
302 	return StatusCode;
303     }
304 
305     /**
306      * get the final URI of the document. This is set if the original
307      * request was deferred via the "moved" (301, 302, or 303) return
308      * status.
309      *
310      * @return the new URI, or null if not redirected
311      * @exception IOException If any exception occurs on the socket.
312      */
313     public final URI getEffectiveURI()  throws IOException
314     {
315 	if (!got_headers)  getHeaders(true);
316 	return EffectiveURI;
317     }
318 
319     /**
320      * set the final URI of the document. This is only for internal use.
321      */
322     public void setEffectiveURI(URI final_uri)
323     {
324 	EffectiveURI = final_uri;
325     }
326 
327     /**
328      * get the final URL of the document. This is set if the original
329      * request was deferred via the "moved" (301, 302, or 303) return
330      * status.
331      *
332      * @exception IOException If any exception occurs on the socket.
333      * @deprecated use getEffectiveURI() instead
334      * @see #getEffectiveURI
335      */
336     public final URL getEffectiveURL()  throws IOException
337     {
338 	return getEffectiveURI().toURL();
339     }
340 
341     /**
342      * set the final URL of the document. This is only for internal use.
343      *
344      * @deprecated use setEffectiveURI() instead
345      * @see #setEffectiveURI
346      */
347     public void setEffectiveURL(URL final_url)
348     {
349 	try
350 	    { setEffectiveURI(new URI(final_url)); }
351 	catch (ParseException pe)
352 	    { throw new Error(pe.toString()); }		// shouldn't happen
353     }
354 
355     /**
356      * retrieves the field for a given header.
357      *
358      * @param  hdr the header name.
359      * @return the value for the header, or null if non-existent.
360      * @exception IOException If any exception occurs on the socket.
361      */
362     public String getHeader(String hdr)  throws IOException
363     {
364 	if (!got_headers)  getHeaders(true);
365 	return (String) Headers.get(hdr.trim());
366     }
367 
368     /**
369      * retrieves the field for a given header. The value is parsed as an
370      * int.
371      *
372      * @param  hdr the header name.
373      * @return the value for the header if the header exists
374      * @exception NumberFormatException if the header's value is not a number
375      *                                  or if the header does not exist.
376      * @exception IOException if any exception occurs on the socket.
377      */
378     public int getHeaderAsInt(String hdr)
379 		throws IOException, NumberFormatException
380     {
381 	String val = getHeader(hdr);
382 	if (val == null)
383 	    throw new NumberFormatException("null");
384 	return Integer.parseInt(val);
385     }
386 
387     /**
388      * retrieves the field for a given header. The value is parsed as a
389      * date; if this fails it is parsed as a long representing the number
390      * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
391      * IllegalArgumentException is thrown.
392      *
393      * <P>Note: When sending dates use Util.httpDate().
394      *
395      * @param  hdr the header name.
396      * @return the value for the header, or null if non-existent.
397      * @exception IOException If any exception occurs on the socket.
398      * @exception IllegalArgumentException If the header cannot be parsed
399      *            as a date or time.
400      */
401     public Date getHeaderAsDate(String hdr)
402 		throws IOException, IllegalArgumentException
403     {
404 	String raw_date = getHeader(hdr);
405 	if (raw_date == null)  return null;
406 
407 	// asctime() format is missing an explicit GMT specifier
408 	if (raw_date.toUpperCase().indexOf("GMT") == -1  &&
409 	    raw_date.indexOf(' ') > 0)
410 	    raw_date += " GMT";
411 
412 	Date date;
413 
414 	try
415 	    { date = Util.parseHttpDate(raw_date); }
416 	catch (IllegalArgumentException iae)
417 	{
418 	    long time;
419 	    try
420 		{ time = Long.parseLong(raw_date); }
421 	    catch (NumberFormatException nfe)
422 		{ throw iae; }
423 	    if (time < 0)  time = 0;
424 	    date = new Date(time * 1000L);
425 	}
426 
427 	return date;
428     }
429 
430 
431     /**
432      * Set a header field in the list of headers. If the header already
433      * exists it will be overwritten; otherwise the header will be added
434      * to the list. This is used by some modules when they process the
435      * header so that higher level stuff doesn't get confused when the
436      * headers and data don't match.
437      *
438      * @param header The name of header field to set.
439      * @param value  The value to set the field to.
440      */
441     public void setHeader(String header, String value)
442     {
443 	Headers.put(header.trim(), value.trim());
444     }
445 
446 
447     /**
448      * Removes a header field from the list of headers. This is used by
449      * some modules when they process the header so that higher level stuff
450      * doesn't get confused when the headers and data don't match.
451      *
452      * @param header The name of header field to remove.
453      */
454     public void deleteHeader(String header)
455     {
456 	Headers.remove(header.trim());
457     }
458 
459 
460     /**
461      * Retrieves the field for a given trailer. Note that this should not
462      * be invoked until all the response data has been read. If invoked
463      * before, it will force the data to be read via <code>getData()</code>.
464      *
465      * @param  trailer the trailer name.
466      * @return the value for the trailer, or null if non-existent.
467      * @exception IOException If any exception occurs on the socket.
468      */
469     public String getTrailer(String trailer)  throws IOException
470     {
471 	if (!got_trailers)  getTrailers();
472 	return (String) Trailers.get(trailer.trim());
473     }
474 
475 
476     /**
477      * Retrieves the field for a given tailer. The value is parsed as an
478      * int.
479      *
480      * @param  trailer the tailer name.
481      * @return the value for the trailer if the trailer exists
482      * @exception NumberFormatException if the trailer's value is not a number
483      *                                  or if the trailer does not exist.
484      * @exception IOException if any exception occurs on the socket.
485      */
486     public int getTrailerAsInt(String trailer)
487 		throws IOException, NumberFormatException
488     {
489 	String val = getTrailer(trailer);
490 	if (val == null)
491 	    throw new NumberFormatException("null");
492 	return Integer.parseInt(val);
493     }
494 
495 
496     /**
497      * Retrieves the field for a given trailer. The value is parsed as a
498      * date; if this fails it is parsed as a long representing the number
499      * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
500      * IllegalArgumentException is thrown.
501      *
502      * <P>Note: When sending dates use Util.httpDate().
503      *
504      * @param  trailer the trailer name.
505      * @return the value for the trailer, or null if non-existent.
506      * @exception IllegalArgumentException if the trailer's value is neither a
507      *            legal date nor a number.
508      * @exception IOException if any exception occurs on the socket.
509      * @exception IllegalArgumentException If the header cannot be parsed
510      *            as a date or time.
511      */
512     public Date getTrailerAsDate(String trailer)
513 		throws IOException, IllegalArgumentException
514     {
515 	String raw_date = getTrailer(trailer);
516 	if (raw_date == null) return null;
517 
518 	// asctime() format is missing an explicit GMT specifier
519 	if (raw_date.toUpperCase().indexOf("GMT") == -1  &&
520 	    raw_date.indexOf(' ') > 0)
521 	    raw_date += " GMT";
522 
523 	Date   date;
524 
525 	try
526 	    { date = Util.parseHttpDate(raw_date); }
527 	catch (IllegalArgumentException iae)
528 	{
529 	    // some servers erroneously send a number, so let's try that
530 	    long time;
531 	    try
532 		{ time = Long.parseLong(raw_date); }
533 	    catch (NumberFormatException nfe)
534 		{ throw iae; }	// give up
535 	    if (time < 0)  time = 0;
536 	    date = new Date(time * 1000L);
537 	}
538 
539 	return date;
540     }
541 
542 
543     /**
544      * Set a trailer field in the list of trailers. If the trailer already
545      * exists it will be overwritten; otherwise the trailer will be added
546      * to the list. This is used by some modules when they process the
547      * trailer so that higher level stuff doesn't get confused when the
548      * trailer and data don't match.
549      *
550      * @param trailer The name of trailer field to set.
551      * @param value   The value to set the field to.
552      */
553     public void setTrailer(String trailer, String value)
554     {
555 	Trailers.put(trailer.trim(), value.trim());
556     }
557 
558 
559     /**
560      * Removes a trailer field from the list of trailers. This is used by
561      * some modules when they process the trailer so that higher level stuff
562      * doesn't get confused when the trailers and data don't match.
563      *
564      * @param trailer The name of trailer field to remove.
565      */
566     public void deleteTrailer(String trailer)
567     {
568 	Trailers.remove(trailer.trim());
569     }
570 
571 
572     /**
573      * Reads all the response data into a byte array. Note that this method
574      * won't return until <em>all</em> the data has been received (so for
575      * instance don't invoke this method if the server is doing a server
576      * push). If getInputStream() had been previously called then this method
577      * only returns any unread data remaining on the stream and then closes
578      * it.
579      *
580      * @see #getInputStream()
581      * @return an array containing the data (body) returned. If no data
582      *         was returned then it's set to a zero-length array.
583      * @exception IOException If any io exception occured while reading
584      *			      the data
585      */
586     public synchronized byte[] getData()  throws IOException
587     {
588 	if (!got_headers)  getHeaders(true);
589 
590 	if (Data == null)
591 	{
592 	    try
593 		{ readResponseData(inp_stream); }
594 	    catch (InterruptedIOException ie)		// don't intercept
595 		{ throw ie; }
596 	    catch (IOException ioe)
597 	    {
598 		Log.write(Log.RESP, "Resp:  (" + inp_stream.hashCode() + ")",
599 			  ioe);
600 
601 		try { inp_stream.close(); } catch (Exception e) { }
602 		throw ioe;
603 	    }
604 
605 	    inp_stream.close();
606 	}
607 
608 	return Data;
609     }
610 
611     /**
612      * Gets an input stream from which the returned data can be read. Note
613      * that if getData() had been previously called it will actually return
614      * a ByteArrayInputStream created from that data.
615      *
616      * @see #getData()
617      * @return the InputStream.
618      * @exception IOException If any exception occurs on the socket.
619      */
620     public synchronized InputStream getInputStream()  throws IOException
621     {
622 	if (!got_headers)  getHeaders(true);
623 
624 	if (Data == null)
625 	    return inp_stream;
626 	else
627 	    return new ByteArrayInputStream(Data);
628     }
629 
630     /**
631      * Some responses such as those from a HEAD or with certain status
632      * codes don't have an entity. This is detected by the client and
633      * can be queried here. Note that this won't try to do a read() on
634      * the input stream (it will however cause the headers to be read
635      * and parsed if not already done).
636      *
637      * @return true if the response has an entity, false otherwise
638      * @since V0.3-1
639      */
640     public synchronized boolean hasEntity()  throws IOException
641     {
642 	if (!got_headers)  getHeaders(true);
643 
644 	return (cd_type != CD_0);
645     }
646 
647     /**
648      * Should the request be retried by the application? This can be used
649      * by modules to signal to the application that it should retry the
650      * request. It's used when the request used an <var>HttpOutputStream</var>
651      * and the module is therefore not able to retry the request itself.
652      * This flag is <var>false</var> by default.
653      *
654      * <P>If a module sets this flag then it must also reset() the
655      * the <var>HttpOutputStream</var> so it may be reused by the application.
656      * It should then also use this <var>HttpOutputStream</var> to recognize
657      * the retried request in the requestHandler().
658      *
659      * @param flag indicates whether the application should retry the request.
660      */
661     public void setRetryRequest(boolean flag)
662     {
663 	retry = flag;
664     }
665 
666     /**
667      * @return true if the request should be retried.
668      */
669     public boolean retryRequest()
670     {
671 	return retry;
672     }
673 
674 
675     // Helper Methods
676 
677     /**
678      * Gets and parses the headers. Sets up Data if no data will be received.
679      *
680      * @param skip_cont  if true skips over '100 Continue' status codes.
681      * @exception IOException If any exception occurs while reading the headers.
682      */
683     private synchronized void getHeaders(boolean skip_cont)  throws IOException
684     {
685 	if (got_headers)  return;
686 	if (exception != null)
687 	{
688 	    exception.fillInStackTrace();
689 	    throw exception;
690 	}
691 
692 	reading_headers = true;
693 	try
694 	{
695 	    do
696 	    {
697 		Headers.clear();	// clear any headers from 100 Continue
698 		String headers = readResponseHeaders(inp_stream);
699 		parseResponseHeaders(headers);
700 	    } while ((StatusCode == 100  &&  skip_cont)  ||	// Continue
701 		     (StatusCode > 101  &&  StatusCode < 200));	// Unknown
702 	}
703 	catch (IOException ioe)
704 	{
705 	    if (!(ioe instanceof InterruptedIOException))
706 		exception = ioe;
707 	    if (ioe instanceof ProtocolException)	// thrown internally
708 	    {
709 		cd_type = CD_CLOSE;
710 		if (stream_handler != null)
711 		    stream_handler.markForClose(this);
712 	    }
713 	    throw ioe;
714 	}
715 	finally
716 	    { reading_headers = false; }
717 	if (StatusCode == 100) return;
718 
719 
720 	// parse the Content-Length header
721 
722 	int cont_len = -1;
723 	String cl_hdr = (String) Headers.get("Content-Length");
724 	if (cl_hdr != null)
725 	{
726 	    try
727 	    {
728 		cont_len = Integer.parseInt(cl_hdr);
729 		if (cont_len < 0)
730 		    throw new NumberFormatException();
731 	    }
732 	    catch (NumberFormatException nfe)
733 	    {
734 		throw new ProtocolException("Invalid Content-length header"+
735 					    " received: "+cl_hdr);
736 	    }
737 	}
738 
739 
740 	// parse the Transfer-Encoding header
741 
742 	boolean te_chunked = false, te_is_identity = true, ct_mpbr = false;
743 	Vector  te_hdr = null;
744 	try
745 	    { te_hdr = Util.parseHeader((String) Headers.get("Transfer-Encoding")); }
746 	catch (ParseException pe)
747 	    { }
748 	if (te_hdr != null)
749 	{
750 	    te_chunked = ((HttpHeaderElement) te_hdr.lastElement()).getName().
751 			 equalsIgnoreCase("chunked");
752 	    for (int idx=0; idx<te_hdr.size(); idx++)
753 		if (((HttpHeaderElement) te_hdr.elementAt(idx)).getName().
754 		    equalsIgnoreCase("identity"))
755 		    te_hdr.removeElementAt(idx--);
756 		else
757 		    te_is_identity = false;
758 	}
759 
760 
761 	// parse Content-Type header
762 
763 	try
764 	{
765 	    String hdr;
766 	    if ((hdr = (String) Headers.get("Content-Type")) != null)
767 	    {
768 		Vector phdr = Util.parseHeader(hdr);
769 		ct_mpbr = phdr.contains(new HttpHeaderElement("multipart/byteranges"))  ||
770 			  phdr.contains(new HttpHeaderElement("multipart/x-byteranges"));
771 	    }
772 	}
773 	catch (ParseException pe)
774 	    { }
775 
776 
777 	// now determine content-delimiter
778 
779 	if (StatusCode < 200  ||  StatusCode == 204  ||  StatusCode == 205  ||
780 	    StatusCode == 304)
781 	{
782 	    cd_type = CD_0;
783 	}
784 	else if (te_chunked)
785 	{
786 	    cd_type = CD_CHUNKED;
787 
788 	    te_hdr.removeElementAt(te_hdr.size()-1);
789 	    if (te_hdr.size() > 0)
790 		setHeader("Transfer-Encoding", Util.assembleHeader(te_hdr));
791 	    else
792 		deleteHeader("Transfer-Encoding");
793 	}
794 	else if (cont_len != -1  &&  te_is_identity)
795 	    cd_type = CD_CONTLEN;
796 	else if (ct_mpbr  &&  te_is_identity)
797 	    cd_type = CD_MP_BR;
798 	else if (!method.equals("HEAD"))
799 	{
800 	    cd_type = CD_CLOSE;
801 	    if (stream_handler != null)
802 		stream_handler.markForClose(this);
803 
804 	    if (Version.equals("HTTP/0.9"))
805 	    {
806 		inp_stream =
807 			new SequenceInputStream(new ByteArrayInputStream(Data),
808 						inp_stream);
809 		Data = null;
810 	    }
811 	}
812 
813 	if (cd_type == CD_CONTLEN)
814 	    ContentLength = cont_len;
815 	else
816 	    deleteHeader("Content-Length");	// Content-Length is not valid in this case
817 
818 	/* We treat HEAD specially down here because the above code needs
819 	 * to know whether to remove the Content-length header or not.
820 	 */
821 	if (method.equals("HEAD"))
822 	    cd_type = CD_0;
823 
824 	if (cd_type == CD_0)
825 	{
826 	    ContentLength = 0;
827 	    Data = new byte[0];
828 	    inp_stream.close();		// we will not receive any more data
829 	}
830 
831 	Log.write(Log.RESP, "Resp:  Response entity delimiter: " +
832 	    (cd_type == CD_0       ? "No Entity"      :
833 	     cd_type == CD_CLOSE   ? "Close"          :
834 	     cd_type == CD_CONTLEN ? "Content-Length" :
835 	     cd_type == CD_CHUNKED ? "Chunked"        :
836 	     cd_type == CD_MP_BR   ? "Multipart"      :
837 	     "???" ) + " (" + inp_stream.hashCode() + ")");
838 
839 
840 	// remove erroneous connection tokens
841 
842 	if (connection.ServerProtocolVersion >= HTTP_1_1)
843 	    deleteHeader("Proxy-Connection");
844 	else					// HTTP/1.0
845 	{
846 	    if (connection.getProxyHost() != null)
847 		deleteHeader("Connection");
848 	    else
849 		deleteHeader("Proxy-Connection");
850 
851 	    Vector pco;
852 	    try
853 		{ pco = Util.parseHeader((String) Headers.get("Connection")); }
854 	    catch (ParseException pe)
855 		{ pco = null; }
856 
857 	    if (pco != null)
858 	    {
859 		for (int idx=0; idx<pco.size(); idx++)
860 		{
861 		    String name =
862 			    ((HttpHeaderElement) pco.elementAt(idx)).getName();
863 		    if (!name.equalsIgnoreCase("keep-alive"))
864 		    {
865 			pco.removeElementAt(idx);
866 			deleteHeader(name);
867 			idx--;
868 		    }
869 		}
870 
871 		if (pco.size() > 0)
872 		    setHeader("Connection", Util.assembleHeader(pco));
873 		else
874 		    deleteHeader("Connection");
875 	    }
876 
877 	    try
878 		{ pco = Util.parseHeader((String) Headers.get("Proxy-Connection")); }
879 	    catch (ParseException pe)
880 		{ pco = null; }
881 
882 	    if (pco != null)
883 	    {
884 		for (int idx=0; idx<pco.size(); idx++)
885 		{
886 		    String name =
887 			    ((HttpHeaderElement) pco.elementAt(idx)).getName();
888 		    if (!name.equalsIgnoreCase("keep-alive"))
889 		    {
890 			pco.removeElementAt(idx);
891 			deleteHeader(name);
892 			idx--;
893 		    }
894 		}
895 
896 		if (pco.size() > 0)
897 		    setHeader("Proxy-Connection", Util.assembleHeader(pco));
898 		else
899 		    deleteHeader("Proxy-Connection");
900 	    }
901 	}
902 
903 
904 	// this must be set before we invoke handleFirstRequest()
905 	got_headers = true;
906 
907 	// special handling if this is the first response received
908 	if (isFirstResponse)
909 	{
910 	    if (!connection.handleFirstRequest(req, this))
911 	    {
912 		// got a buggy server - need to redo the request
913 		Response resp;
914 		try
915 		    { resp = connection.sendRequest(req, timeout); }
916 		catch (ModuleException me)
917 		    { throw new IOException(me.toString()); }
918 		resp.getVersion();
919 
920 		this.StatusCode    = resp.StatusCode;
921 		this.ReasonLine    = resp.ReasonLine;
922 		this.Version       = resp.Version;
923 		this.EffectiveURI  = resp.EffectiveURI;
924 		this.ContentLength = resp.ContentLength;
925 		this.Headers       = resp.Headers;
926 		this.inp_stream    = resp.inp_stream;
927 		this.Data          = resp.Data;
928 
929 		req = null;
930 	    }
931 	}
932     }
933 
934 
935     /* these are external to readResponseHeaders() because we need to be
936      * able to restart after an InterruptedIOException
937      */
938     private byte[]       buf     = new byte[7];
939     private int          buf_pos = 0;
940     private StringBuffer hdrs    = new StringBuffer(400);
941     private boolean      reading_lines = false;
942     private boolean      bol     = true;
943     private boolean      got_cr  = false;
944     /** ++GRINDER MODIFICATION **/
945     private long         ttfb    = 0;
946     /** --GRINDER MODIFICATION **/
947 
948     /**
949      * Reads the response headers received, folding continued lines.
950      *
951      * <P>Some of the code is a bit convoluted because we have to be able
952      * restart after an InterruptedIOException.
953      *
954      * @inp    the input stream from which to read the response
955      * @return a (newline separated) list of headers
956      * @exception IOException if any read on the input stream fails
957      */
958     private String readResponseHeaders(InputStream inp)  throws IOException
959     {
960 	if (buf_pos == 0)
961 	    Log.write(Log.RESP, "Resp:  Reading Response headers " +
962 				inp_stream.hashCode());
963 	else
964 	    Log.write(Log.RESP, "Resp:  Resuming reading Response headers " +
965 				inp_stream.hashCode());
966 
967 
968 	// read 7 bytes to see type of response
969 	if (!reading_lines)
970 	{
971 	    try
972 	    {
973 		// Skip any leading white space to accomodate buggy responses
974 		if (buf_pos == 0)
975 		{
976 		    int c;
977 		    boolean gotFirstByte = false;
978 		    do
979 		    {
980 			    if ((c = inp.read()) == -1)
981 				    throw new EOFException("Encountered premature EOF "
982 				    	+ "while reading Version");
983 			/** ++GRINDER MODIFICATION **/
984 			    if (!gotFirstByte) {
985 				    gotFirstByte = true;
986 				    ttfb =
987 				      connection.getTimeAuthority().getTimeInMilliseconds();
988 			    }
989 			/** --GRINDER MODIFICATION **/
990 		    } while (Character.isWhitespace((char) c)) ;
991 		    buf[0] = (byte) c;
992 		    buf_pos = 1;
993 		}
994 
995 		// Now read first seven bytes (the version string)
996 		while (buf_pos < buf.length)
997 		{
998 		    int got = inp.read(buf, buf_pos, buf.length-buf_pos);
999 		    if (got == -1)
1000 			throw new EOFException("Encountered premature EOF " +
1001 						"while reading Version");
1002 		    buf_pos += got;
1003 		}
1004 	    }
1005 	    catch (EOFException eof)
1006 	    {
1007 		Log.write(Log.RESP, "Resp:  (" + inp_stream.hashCode() + ")",
1008 			  eof);
1009 
1010 		throw eof;
1011 	    }
1012 	    for (int idx=0; idx<buf.length; idx++)
1013 		hdrs.append((char) buf[idx]);
1014 
1015 	    reading_lines = true;
1016 	}
1017 
1018 	if (hdrs.toString().startsWith("HTTP/")  ||		// It's x.x
1019 	    hdrs.toString().startsWith("HTTP "))		// NCSA bug
1020 	    readLines(inp);
1021 
1022 	// reset variables for next round
1023 	buf_pos = 0;
1024 	reading_lines = false;
1025 	bol     = true;
1026 	got_cr  = false;
1027 
1028 	String tmp = hdrs.toString();
1029 	hdrs.setLength(0);
1030 	return tmp;
1031     }
1032     /** ++GRINDER MODIFICATION **/
1033     public long getTtfb(){
1034 	    return ttfb;
1035     }
1036     /** --GRINDER MODIFICATION **/
1037     boolean trailers_read = false;
1038 
1039     /**
1040      * This is called by the StreamDemultiplexor to read all the trailers
1041      * of a chunked encoded entity.
1042      *
1043      * @param inp the raw input stream to read from
1044      * @exception IOException if any IOException is thrown by the stream
1045      */
1046     void readTrailers(InputStream inp)  throws IOException
1047     {
1048 	try
1049 	{
1050 	    readLines(inp);
1051 	    trailers_read = true;
1052 	}
1053 	catch (IOException ioe)
1054 	{
1055 	    if (!(ioe instanceof InterruptedIOException))
1056 		exception = ioe;
1057 	    throw ioe;
1058 	}
1059     }
1060 
1061 
1062     /**
1063      * This reads a set of lines up to and including the first empty line.
1064      * A line is terminated by either a <CR><LF> or <LF>. The lines are
1065      * stored in the <var>hdrs</var> buffers. Continued lines are merged
1066      * and stored as one line.
1067      *
1068      * <P>This method is restartable after an InterruptedIOException.
1069      *
1070      * @param inp the input stream to read from
1071      * @exception IOException if any IOException is thrown by the stream
1072      */
1073     private void readLines(InputStream inp)  throws IOException
1074     {
1075 	/* This loop is a merge of readLine() from DataInputStream and
1076 	 * the necessary header logic to merge continued lines and terminate
1077 	 * after an empty line. The reason this is explicit is because of
1078 	 * the need to handle InterruptedIOExceptions.
1079 	 */
1080 	loop: while (true)
1081 	{
1082 	    int b = inp.read();
1083 	    switch (b)
1084 	    {
1085 		case -1:
1086 		    throw new EOFException("Encountered premature EOF while reading headers:\n" + hdrs);
1087 		case '\r':
1088 		    got_cr = true;
1089 		    break;
1090 		case '\n':
1091 		    if (bol)  break loop;	// all headers read
1092 		    hdrs.append('\n');
1093 		    bol    = true;
1094 		    got_cr = false;
1095 		    break;
1096 		case ' ':
1097 		case '\t':
1098 		    if (bol)		// a continued line
1099 		    {
1100 			// replace previous \n with SP
1101 			hdrs.setCharAt(hdrs.length()-1, ' ');
1102 			bol = false;
1103 			break;
1104 		    }
1105 		default:
1106 		    if (got_cr)
1107 		    {
1108 			hdrs.append('\r');
1109 			got_cr = false;
1110 		    }
1111 		    hdrs.append((char) (b & 0xFF));
1112 		    bol = false;
1113 		    break;
1114 	    }
1115 	}
1116     }
1117 
1118 
1119     /**
1120      * Parses the headers received into a new Response structure.
1121      *
1122      * @param  headers a (newline separated) list of headers
1123      * @exception ProtocolException if any part of the headers do not
1124      *            conform
1125      */
1126     private void parseResponseHeaders(String headers) throws ProtocolException
1127     {
1128 	String          sts_line = null;
1129 	StringTokenizer lines = new StringTokenizer(headers, "\r\n"),
1130 			elem;
1131 
1132 	if (Log.isEnabled(Log.RESP))
1133 	    Log.write(Log.RESP, "Resp:  Parsing Response headers from Request "+
1134 				"\"" + method + " " + resource + "\":  (" +
1135 				inp_stream.hashCode() + ")\n\n" + headers);
1136 
1137 
1138 	// Detect and handle HTTP/0.9 responses
1139 
1140 	if (!headers.regionMatches(true, 0, "HTTP/", 0, 5)  &&
1141 	    !headers.regionMatches(true, 0, "HTTP ", 0, 5))	// NCSA bug
1142 	{
1143 	    Version    = "HTTP/0.9";
1144 	    StatusCode = 200;
1145 	    ReasonLine = "OK";
1146 
1147 	    try
1148 		{ Data = headers.getBytes("8859_1"); }
1149 	    catch (UnsupportedEncodingException uee)
1150 		{ throw new Error(uee.toString()); }
1151 
1152 	    return;
1153 	}
1154 
1155 
1156 	// get the status line
1157 
1158 	try
1159 	{
1160 	    sts_line = lines.nextToken();
1161 	    elem     = new StringTokenizer(sts_line, " \t");
1162 
1163 	    Version    = elem.nextToken();
1164 	    StatusCode = Integer.valueOf(elem.nextToken()).intValue();
1165 
1166 	    if (Version.equalsIgnoreCase("HTTP"))	// NCSA bug
1167 		Version = "HTTP/1.0";
1168 	}
1169 	catch (NoSuchElementException e)
1170 	{
1171 	    throw new ProtocolException("Invalid HTTP status line received: " +
1172 					sts_line);
1173 	}
1174 	try
1175 	    { ReasonLine = elem.nextToken("").trim(); }
1176 	catch (NoSuchElementException e)
1177 	    { ReasonLine = ""; }
1178 
1179 
1180 	/* If the status code shows an error and we're sending (or have sent)
1181 	 * an entity and it's length is delimited by a Content-length header,
1182 	 * then we must close the the connection (if indeed it hasn't already
1183 	 * been done) - RFC-2616, Section 8.2.2 .
1184 	 */
1185 	if (StatusCode >= 300  &&  sent_entity)
1186 	{
1187 	    if (stream_handler != null)
1188 		stream_handler.markForClose(this);
1189 	}
1190 
1191 
1192 	// get the rest of the headers
1193 
1194 	parseHeaderFields(lines, Headers);
1195 
1196 
1197 	/* make sure the connection isn't closed prematurely if we have
1198 	 * trailer fields
1199 	 */
1200 	if (Headers.get("Trailer") != null  &&  resp_inp_stream != null)
1201 	    resp_inp_stream.dontTruncate();
1202 
1203 	// Mark the end of the connection if it's not to be kept alive
1204 
1205 	int vers;
1206 	if (Version.equalsIgnoreCase("HTTP/0.9")  ||
1207 	    Version.equalsIgnoreCase("HTTP/1.0"))
1208 	    vers = 0;
1209 	else
1210 	    vers = 1;
1211 
1212 	try
1213 	{
1214 	    String con = (String) Headers.get("Connection"),
1215 		  pcon = (String) Headers.get("Proxy-Connection");
1216 
1217 	    // parse connection header
1218 	    if ((vers == 1  &&  con != null  &&  Util.hasToken(con, "close"))
1219 		||
1220 		(vers == 0  &&
1221 		 !((!used_proxy && con != null &&
1222 					Util.hasToken(con, "keep-alive"))  ||
1223 		   (used_proxy && pcon != null &&
1224 					Util.hasToken(pcon, "keep-alive")))
1225 		)
1226 	       )
1227 		if (stream_handler != null)
1228 		    stream_handler.markForClose(this);
1229 	}
1230 	catch (ParseException pe) { }
1231     }
1232 
1233 
1234     /**
1235      * If the trailers have not been read it calls <code>getData()</code>
1236      * to first force all data and trailers to be read. Then the trailers
1237      * parsed into the <var>Trailers</var> hashtable.
1238      *
1239      * @exception IOException if any exception occured during reading of the
1240      *                        response
1241      */
1242     private synchronized void getTrailers()  throws IOException
1243     {
1244 	if (got_trailers)  return;
1245 	if (exception != null)
1246 	{
1247 	    exception.fillInStackTrace();
1248 	    throw exception;
1249 	}
1250 
1251 	Log.write(Log.RESP, "Resp:  Reading Response trailers " +
1252 			    inp_stream.hashCode());
1253 
1254 	try
1255 	{
1256 	    if (!trailers_read)
1257 	    {
1258 		if (resp_inp_stream != null)
1259 		    resp_inp_stream.readAll(timeout);
1260 	    }
1261 
1262 	    if (trailers_read)
1263 	    {
1264 		Log.write(Log.RESP, "Resp:  Parsing Response trailers from "+
1265 				    "Request \"" + method + " " + resource +
1266 				    "\":  (" + inp_stream.hashCode() +
1267 				    ")\n\n" + hdrs);
1268 
1269 		parseHeaderFields(new StringTokenizer(hdrs.toString(), "\r\n"),
1270 				  Trailers);
1271 	    }
1272 	}
1273 	finally
1274 	{
1275 	    got_trailers = true;
1276 	}
1277     }
1278 
1279 
1280     /**
1281      * Parses the given lines as header fields of the form "<name>: <value>"
1282      * into the given list.
1283      *
1284      * @param lines the header or trailer lines, one header field per line
1285      * @param list  the Hashtable to store the parsed fields in
1286      * @exception ProtocolException if any part of the headers do not
1287      *                              conform
1288      */
1289     private void parseHeaderFields(StringTokenizer lines, CIHashtable list)
1290 	    throws ProtocolException
1291     {
1292 	while (lines.hasMoreTokens())
1293 	{
1294 	    String hdr = lines.nextToken();
1295 	    int    sep = hdr.indexOf(':');
1296 
1297 	    /* Once again we have to deal with broken servers and try
1298 	     * to wing it here. If no ':' is found, try using the first
1299 	     * space:
1300 	     */
1301 	    if (sep == -1)
1302 		sep = hdr.indexOf(' ');
1303 	    if (sep == -1)
1304 	    {
1305 		throw new ProtocolException("Invalid HTTP header received: " +
1306 					    hdr);
1307 	    }
1308 
1309 	    String hdr_name  = hdr.substring(0, sep).trim();
1310 	    String hdr_value = hdr.substring(sep+1).trim();
1311 
1312 	    // Can header have multiple values?
1313 	    if (!singleValueHeaders.containsKey(hdr_name.toLowerCase()))
1314 	    {
1315 		String old_value  = (String) list.get(hdr_name);
1316 		if (old_value == null)
1317 		    list.put(hdr_name, hdr_value);
1318 		else
1319 		    list.put(hdr_name, old_value + ", " + hdr_value);
1320 	    }
1321 	    else
1322 		// No multiple values--just replace/put latest header value
1323 		list.put(hdr_name, hdr_value);
1324 	}
1325     }
1326 
1327 
1328     /**
1329      * Reads the response data received. Does not return until either
1330      * Content-Length bytes have been read or EOF is reached.
1331      *
1332      * @inp       the input stream from which to read the data
1333      * @exception IOException if any read on the input stream fails
1334      */
1335     private void readResponseData(InputStream inp) throws IOException
1336     {
1337 	if (ContentLength == 0)
1338 	    return;
1339 
1340 	if (Data == null)
1341 	    Data = new byte[0];
1342 
1343 
1344 	// read response data
1345 
1346 	int off = Data.length;
1347 
1348 	try
1349 	{
1350 	    // check Content-length header in case CE-Module removed it
1351 	    if (getHeader("Content-Length") != null)
1352 	    {
1353 		int rcvd = 0;
1354 		Data = new byte[ContentLength];
1355 
1356 		do
1357 		{
1358 		    off  += rcvd;
1359 		    rcvd  = inp.read(Data, off, ContentLength-off);
1360 		} while (rcvd != -1  &&  off+rcvd < ContentLength);
1361 
1362 		/* Don't do this!
1363 		 * If we do, then getData() won't work after a getInputStream()
1364 		 * because we'll never get all the expected data. Instead, let
1365 		 * the underlying RespInputStream throw the EOF.
1366 		if (rcvd == -1)	// premature EOF
1367 		{
1368 		    throw new EOFException("Encountered premature EOF while " +
1369 					    "reading headers: received " + off +
1370 					    " bytes instead of the expected " +
1371 					    ContentLength + " bytes");
1372 		}
1373 		*/
1374 	    }
1375 	    else
1376 	    {
1377 		int inc  = 1000,
1378 		    rcvd = 0;
1379 
1380 		do
1381 		{
1382 		    off  += rcvd;
1383 		    Data  = Util.resizeArray(Data, off+inc);
1384 		} while ((rcvd = inp.read(Data, off, inc)) != -1);
1385 
1386 		Data = Util.resizeArray(Data, off);
1387 	    }
1388 	}
1389 	catch (IOException ioe)
1390 	{
1391 	    Data = Util.resizeArray(Data, off);
1392 	    throw ioe;
1393 	}
1394 	finally
1395 	{
1396 	    try
1397 		{ inp.close(); }
1398 	    catch (IOException ioe)
1399 		{ }
1400 	}
1401     }
1402 
1403 
1404     Request        req = null;
1405     boolean isFirstResponse = false;
1406     /**
1407      * This marks this response as belonging to the first request made
1408      * over an HTTPConnection. The <var>con</var> and <var>req</var>
1409      * parameters are needed in case we have to do a resend of the request -
1410      * this is to handle buggy servers which barf upon receiving a request
1411      * marked as HTTP/1.1 .
1412      *
1413      * @param con The HTTPConnection used
1414      * @param req The Request sent
1415      */
1416     void markAsFirstResponse(Request req)
1417     {
1418 	this.req = req;
1419 	isFirstResponse = true;
1420     }
1421 
1422 
1423     /**
1424      * @return a clone of this request object
1425      */
1426     public Object clone()
1427     {
1428 	Response cl;
1429 	try
1430 	    { cl = (Response) super.clone(); }
1431 	catch (CloneNotSupportedException cnse)
1432 	    { throw new InternalError(cnse.toString()); /* shouldn't happen */ }
1433 
1434 	cl.Headers  = (CIHashtable) Headers.clone();
1435 	cl.Trailers = (CIHashtable) Trailers.clone();
1436 
1437 	return cl;
1438     }
1439 }