1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.apache.commons.httpclient;
31
32 import java.io.IOException;
33 import java.io.InputStream;
34
35 /***
36 * Cuts the wrapped InputStream off after a specified number of bytes.
37 *
38 * <p>Implementation note: Choices abound. One approach would pass
39 * through the {@link InputStream#mark} and {@link InputStream#reset} calls to
40 * the underlying stream. That's tricky, though, because you then have to
41 * start duplicating the work of keeping track of how much a reset rewinds.
42 * Further, you have to watch out for the "readLimit", and since the semantics
43 * for the readLimit leave room for differing implementations, you might get
44 * into a lot of trouble.</p>
45 *
46 * <p>Alternatively, you could make this class extend {@link java.io.BufferedInputStream}
47 * and then use the protected members of that class to avoid duplicated effort.
48 * That solution has the side effect of adding yet another possible layer of
49 * buffering.</p>
50 *
51 * <p>Then, there is the simple choice, which this takes - simply don't
52 * support {@link InputStream#mark} and {@link InputStream#reset}. That choice
53 * has the added benefit of keeping this class very simple.</p>
54 *
55 * @author Ortwin Glueck
56 * @author Eric Johnson
57 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
58 * @since 2.0
59 */
60 public class ContentLengthInputStream extends InputStream {
61
62 /***
63 * The maximum number of bytes that can be read from the stream. Subsequent
64 * read operations will return -1.
65 */
66 private long contentLength;
67
68 /*** The current position */
69 private long pos = 0;
70
71 /*** True if the stream is closed. */
72 private boolean closed = false;
73
74 /***
75 * Wrapped input stream that all calls are delegated to.
76 */
77 private InputStream wrappedStream = null;
78
79 /***
80 * @deprecated use {@link #ContentLengthInputStream(InputStream, long)}
81 *
82 * Creates a new length limited stream
83 *
84 * @param in The stream to wrap
85 * @param contentLength The maximum number of bytes that can be read from
86 * the stream. Subsequent read operations will return -1.
87 */
88 public ContentLengthInputStream(InputStream in, int contentLength) {
89 this(in, (long)contentLength);
90 }
91
92 /***
93 * Creates a new length limited stream
94 *
95 * @param in The stream to wrap
96 * @param contentLength The maximum number of bytes that can be read from
97 * the stream. Subsequent read operations will return -1.
98 *
99 * @since 3.0
100 */
101 public ContentLengthInputStream(InputStream in, long contentLength) {
102 super();
103 this.wrappedStream = in;
104 this.contentLength = contentLength;
105 }
106
107 /***
108 * <p>Reads until the end of the known length of content.</p>
109 *
110 * <p>Does not close the underlying socket input, but instead leaves it
111 * primed to parse the next response.</p>
112 * @throws IOException If an IO problem occurs.
113 */
114 public void close() throws IOException {
115 if (!closed) {
116 try {
117 ChunkedInputStream.exhaustInputStream(this);
118 } finally {
119
120
121 closed = true;
122 }
123 }
124 }
125
126
127 /***
128 * Read the next byte from the stream
129 * @return The next byte or -1 if the end of stream has been reached.
130 * @throws IOException If an IO problem occurs
131 * @see java.io.InputStream#read()
132 */
133 public int read() throws IOException {
134 if (closed) {
135 throw new IOException("Attempted read from closed stream.");
136 }
137
138 if (pos >= contentLength) {
139 return -1;
140 }
141 pos++;
142 return this.wrappedStream.read();
143 }
144
145 /***
146 * Does standard {@link InputStream#read(byte[], int, int)} behavior, but
147 * also notifies the watcher when the contents have been consumed.
148 *
149 * @param b The byte array to fill.
150 * @param off Start filling at this position.
151 * @param len The number of bytes to attempt to read.
152 * @return The number of bytes read, or -1 if the end of content has been
153 * reached.
154 *
155 * @throws java.io.IOException Should an error occur on the wrapped stream.
156 */
157 public int read (byte[] b, int off, int len) throws java.io.IOException {
158 if (closed) {
159 throw new IOException("Attempted read from closed stream.");
160 }
161
162 if (pos >= contentLength) {
163 return -1;
164 }
165
166 if (pos + len > contentLength) {
167 len = (int) (contentLength - pos);
168 }
169 int count = this.wrappedStream.read(b, off, len);
170 pos += count;
171 return count;
172 }
173
174
175 /***
176 * Read more bytes from the stream.
177 * @param b The byte array to put the new data in.
178 * @return The number of bytes read into the buffer.
179 * @throws IOException If an IO problem occurs
180 * @see java.io.InputStream#read(byte[])
181 */
182 public int read(byte[] b) throws IOException {
183 return read(b, 0, b.length);
184 }
185
186 /***
187 * Skips and discards a number of bytes from the input stream.
188 * @param n The number of bytes to skip.
189 * @return The actual number of bytes skipped. <= 0 if no bytes
190 * are skipped.
191 * @throws IOException If an error occurs while skipping bytes.
192 * @see InputStream#skip(long)
193 */
194 public long skip(long n) throws IOException {
195
196
197 long length = Math.min(n, contentLength - pos);
198
199 length = this.wrappedStream.skip(length);
200
201
202 if (length > 0) {
203 pos += length;
204 }
205 return length;
206 }
207 }