File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\util\ChunkedInputStream.java.html

1 /*
2 * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v 1.24 2004/10/10 15:18:55 olegk Exp $
3 * $Revision: 480424 $
4 * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
5 *
6 * ====================================================================
7 *
8 * Licensed to the Apache Software Foundation (ASF) under one or more
9 * contributor license agreements. See the NOTICE file distributed with
10 * this work for additional information regarding copyright ownership.
11 * The ASF licenses this file to You under the Apache License, Version 2.0
12 * (the "License"); you may not use this file except in compliance with
13 * the License. You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 * ====================================================================
23 *
24 * This software consists of voluntary contributions made by many
25 * individuals on behalf of the Apache Software Foundation. For more
26 * information on the Apache Software Foundation, please see
27 * <http://www.apache.org/>.
28 *
29 */
30
31 package hudson.util;
32
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.util.logging.Logger;
37
38
39 /**
40 * <p>Transparently coalesces chunks of a HTTP stream that uses
41 * Transfer-Encoding chunked.</p>
42 *
43 * <p>Note that this class NEVER closes the underlying stream, even when close
44 * gets called. Instead, it will read until the "end" of its chunking on close,
45 * which allows for the seamless invocation of subsequent HTTP 1.1 calls, while
46 * not requiring the client to remember to read the entire contents of the
47 * response.</p>
48 *
49 * @author Ortwin Glueck
50 * @author Sean C. Sullivan
51 * @author Martin Elwin
52 * @author Eric Johnson
53 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
54 * @author Michael Becke
55 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
56 *
57 * @since 2.0
58 *
59 */
60 public class ChunkedInputStream extends InputStream {
61 /** The inputstream that we're wrapping */
62 private InputStream in;
63
64 /** The chunk size */
65 private int chunkSize;
66
67 /** The current position within the current chunk */
68 private int pos;
69
70 /** True if we'are at the beginning of stream */
71 private boolean bof = true;
72
73 /** True if we've reached the end of stream */
74 private boolean eof = false;
75
76 /** True if this stream is closed */
77 private boolean closed = false;
78
79 /** Log object for this class. */
80 private static final Logger LOGGER = Logger.getLogger(ChunkedInputStream.class.getName());
81
82 /**
83 * ChunkedInputStream constructor
84 *
85 * @param in the raw input stream
86 *
87 * @throws IOException If an IO error occurs
88 */
89 public ChunkedInputStream(
90 final InputStream in) throws IOException {
91
92 if (in == null) {
93 throw new IllegalArgumentException("InputStream parameter may not be null");
94 }
95 this.in = in;
96 this.pos = 0;
97 }
98
99 /**
100 * <p> Returns all the data in a chunked stream in coalesced form. A chunk
101 * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0
102 * is detected.</p>
103 *
104 * <p> Trailer headers are read automcatically at the end of the stream and
105 * can be obtained with the getResponseFooters() method.</p>
106 *
107 * @return -1 of the end of the stream has been reached or the next data
108 * byte
109 * @throws IOException If an IO problem occurs
110 */
111 public int read() throws IOException {
112
113 if (closed) {
114 throw new IOException("Attempted read from closed stream.");
115 }
116 if (eof) {
117 return -1;
118 }
119 if (pos >= chunkSize) {
120 nextChunk();
121 if (eof) {
122 return -1;
123 }
124 }
125 pos++;
126 return in.read();
127 }
128
129 /**
130 * Read some bytes from the stream.
131 * @param b The byte array that will hold the contents from the stream.
132 * @param off The offset into the byte array at which bytes will start to be
133 * placed.
134 * @param len the maximum number of bytes that can be returned.
135 * @return The number of bytes returned or -1 if the end of stream has been
136 * reached.
137 * @see java.io.InputStream#read(byte[], int, int)
138 * @throws IOException if an IO problem occurs.
139 */
140 @Override
141 public int read (byte[] b, int off, int len) throws IOException {
142
143 if (closed) {
144 throw new IOException("Attempted read from closed stream.");
145 }
146
147 if (eof) {
148 return -1;
149 }
150 if (pos >= chunkSize) {
151 nextChunk();
152 if (eof) {
153 return -1;
154 }
155 }
156 len = Math.min(len, chunkSize - pos);
157 int count = in.read(b, off, len);
158 pos += count;
159 return count;
160 }
161
162 /**
163 * Read some bytes from the stream.
164 * @param b The byte array that will hold the contents from the stream.
165 * @return The number of bytes returned or -1 if the end of stream has been
166 * reached.
167 * @see java.io.InputStream#read(byte[])
168 * @throws IOException if an IO problem occurs.
169 */
170 @Override
171 public int read (byte[] b) throws IOException {
172 return read(b, 0, b.length);
173 }
174
175 /**
176 * Read the CRLF terminator.
177 * @throws IOException If an IO error occurs.
178 */
179 private void readCRLF() throws IOException {
180 int cr = in.read();
181 int lf = in.read();
182 if ((cr != '\r') || (lf != '\n')) {
183 throw new IOException(
184 "CRLF expected at end of chunk: " + cr + "/" + lf);
185 }
186 }
187
188
189 /**
190 * Read the next chunk.
191 * @throws IOException If an IO error occurs.
192 */
193 private void nextChunk() throws IOException {
194 if (!bof) {
195 readCRLF();
196 }
197 chunkSize = getChunkSizeFromInputStream(in);
198 bof = false;
199 pos = 0;
200 if (chunkSize == 0) {
201 eof = true;
202 parseTrailerHeaders();
203 }
204 }
205
206 /**
207 * Expects the stream to start with a chunksize in hex with optional
208 * comments after a semicolon. The line must end with a CRLF: "a3; some
209 * comment\r\n" Positions the stream at the start of the next line.
210 *
211 * @param in The new input stream.
212 * @return the chunk size as integer
213 *
214 * @throws IOException when the chunk size could not be parsed
215 */
216 private static int getChunkSizeFromInputStream(final InputStream in)
217 throws IOException {
218
219 ByteArrayOutputStream baos = new ByteArrayOutputStream();
220 // States: 0=normal, 1=\r was scanned, 2=inside quoted string, -1=end
221 int state = 0;
222 while (state != -1) {
223 int b = in.read();
224 if (b == -1) {
225 throw new IOException("chunked stream ended unexpectedly");
226 }
227 switch (state) {
228 case 0:
229 switch (b) {
230 case '\r':
231 state = 1;
232 break;
233 case '\"':
234 state = 2;
235 /* fall through */
236 default:
237 baos.write(b);
238 }
239 break;
240
241 case 1:
242 if (b == '\n') {
243 state = -1;
244 } else {
245 // this was not CRLF
246 throw new IOException("Protocol violation: Unexpected"
247 + " single newline character in chunk size");
248 }
249 break;
250
251 case 2:
252 switch (b) {
253 case '\\':
254 b = in.read();
255 baos.write(b);
256 break;
257 case '\"':
258 state = 0;
259 /* fall through */
260 default:
261 baos.write(b);
262 }
263 break;
264 default: throw new RuntimeException("assertion failed");
265 }
266 }
267
268 //parse data
269 String dataString = new String(baos.toByteArray(),"US-ASCII");
270 int separator = dataString.indexOf(';');
271 dataString = (separator > 0)
272 ? dataString.substring(0, separator).trim()
273 : dataString.trim();
274
275 int result;
276 try {
277 result = Integer.parseInt(dataString.trim(), 16);
278 } catch (NumberFormatException e) {
279 throw new IOException ("Bad chunk size: " + dataString);
280 }
281 return result;
282 }
283
284 /**
285 * Reads and stores the Trailer headers.
286 * @throws IOException If an IO problem occurs
287 */
288 private void parseTrailerHeaders() throws IOException {
289 // I feel lazy. No trailing header support
290 readCRLF();
291
292 // Header[] footers = null;
293 // try {
294 // String charset = "US-ASCII";
295 // if (this.method != null) {
296 // charset = this.method.getParams().getHttpElementCharset();
297 // }
298 // footers = HttpParser.parseHeaders(in, charset);
299 // } catch(HttpException e) {
300 // LOG.error("Error parsing trailer headers", e);
301 // IOException ioe = new IOException(e.getMessage());
302 // ExceptionUtil.initCause(ioe, e);
303 // throw ioe;
304 // }
305 // if (this.method != null) {
306 // for (int i = 0; i < footers.length; i++) {
307 // this.method.addResponseFooter(footers[i]);
308 // }
309 // }
310 }
311
312 /**
313 * Upon close, this reads the remainder of the chunked message,
314 * leaving the underlying socket at a position to start reading the
315 * next response without scanning.
316 * @throws IOException If an IO problem occurs.
317 */
318 @Override
319 public void close() throws IOException {
320 if (!closed) {
321 try {
322 if (!eof) {
323 exhaustInputStream(this);
324 }
325 } finally {
326 eof = true;
327 closed = true;
328 }
329 }
330 }
331
332 /**
333 * Exhaust an input stream, reading until EOF has been encountered.
334 *
335 * <p>Note that this function is intended as a non-public utility.
336 * This is a little weird, but it seemed silly to make a utility
337 * class for this one function, so instead it is just static and
338 * shared that way.</p>
339 *
340 * @param inStream The {@link InputStream} to exhaust.
341 * @throws IOException If an IO problem occurs
342 */
343 static void exhaustInputStream(InputStream inStream) throws IOException {
344 // read and discard the remainder of the message
345 byte buffer[] = new byte[1024];
346 while (inStream.read(buffer) >= 0) {
347 ;
348 }
349 }
350 }
351