View Javadoc

1   /*
2    * Copyright (c) 2006-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.danube.http;
14  
15  
16  import java.io.IOException;
17  import java.io.InputStream;
18  
19  /**
20   * This is buffered http input stream. It knows how to read chuncked encoding.
21   *
22   * @author Daniel Sendula
23   */
24  public class HTTPBufferedInputStream extends InputStream {
25  
26      /** Default buffer size to be used when operating in not buffered mode (close and skip methods, for instance) */
27      public static final int DEFAULT_BUFFER_SIZE = 2048;
28  
29      /** Buffer */
30      protected byte[] buffer;
31  
32      /** Pointer in the buffer */
33      protected int ptr;
34  
35      /** Amount of bytes in the buffer */
36      protected int len;
37  
38      /** Mark */
39      protected int mark = -1;
40  
41      /** Input stream */
42      protected InputStream inputStream;
43  
44      /** Buffer size */
45      protected int bufferSize;
46  
47      /** Is closed */
48      protected boolean closed = false;
49  
50      /** Content length limit */
51      protected long contentLength = 0;
52  
53      /** Number of sent bytes */
54      protected int readbytes = 0;
55  
56      /** Chunk encoding */
57      protected boolean chunkEncoding = false;
58  
59      /** Current chunk size */
60      protected int chunkSize = 0;
61  
62      /**
63       * Constructor
64       *
65       * @param intputStream wrapped input stream
66       * @param defaultBufferSize default buffer size
67       */
68      public HTTPBufferedInputStream(InputStream inputStream, int defaultBufferSize) {
69          this.inputStream = inputStream;
70          this.bufferSize = defaultBufferSize;
71      }
72  
73      /**
74       * Resets internals
75       */
76      public void resetInternals() {
77          closed = false;
78          ptr = 0;
79          contentLength = 0;
80          readbytes = 0;
81          chunkEncoding = false;
82          len = 0;
83          mark = -1;
84      }
85  
86      /**
87       * Returns buffer size
88       *
89       * @return buffer size
90       */
91      public int getBufferSize() {
92          return bufferSize;
93      }
94  
95      /**
96       * Sets buffer size
97       *
98       * @param size buffer size
99       * @throws IOException io exception
100      */
101     public void setBufferSize(int size) {
102         bufferSize = size;
103     }
104 
105     /**
106      * Returns limited content length
107      * @return limited content length
108      */
109     public long getContentLength() {
110         return contentLength;
111     }
112 
113     /**
114      * Sets content length
115      * @param contentLength content length
116      */
117     public void setContentLength(long contentLength) {
118         this.contentLength = contentLength;
119     }
120 
121     /**
122      * Returns if chunk encoding
123      * @return if chunk encoding
124      */
125     public boolean isChunkEncoding() {
126         return chunkEncoding;
127     }
128 
129     /**
130      * Sets chunk encoding
131      * @param chunkEncoding is chunk encoding
132      */
133     public void setChunkEncoding(boolean chunkEncoding) {
134         this.chunkEncoding = chunkEncoding;
135     }
136 
137     /**
138      * Read chunk size
139      * @return chunk size
140      * @throws IOException
141      */
142     protected int readChunkSize() throws IOException {
143         int size = 0;
144         int i = inputStream.read();
145         while ((i > 0)
146                 && ((i >= '0') && (i <= '9')
147                         || ((i >= 'a') && (i <= 'f'))
148                         || ((i >= 'A') && (i <= 'F'))))
149         {
150             if ((i >= '0') && (i <= '9')) {
151                 size = size * 16 + (i - '0');
152             } else if ((i >= 'a') && (i <= 'f')) {
153                 size = size * 16 + (i - 'a' + 10);
154             } else if ((i >= 'A') && (i <= 'F')) {
155                 size = size * 16 + (i - 'A' + 10);
156             }
157             i = inputStream.read();
158         }
159         if (i == 13) {
160             // CR has been read - read LF too!
161             i = inputStream.read();
162         }
163         return size;
164     }
165 
166     /**
167      * Checks if buffer is empty and fills it in if needed
168      *
169      * @throws IOException
170      */
171     protected void checkBuffer() throws IOException {
172         if (chunkEncoding) {
173             if (chunkSize >= 0) {
174                 if (ptr == len) {
175                     ptr = 0;
176                     len = 0;
177                     mark = -1;
178                     if ((buffer == null) || (bufferSize != buffer.length)) {
179                         buffer = new byte[bufferSize];
180                     }
181                     int l = 0;
182                     while ((l >= 0) && (len < buffer.length)) {
183                         if (chunkSize == 0) {
184                             chunkSize = readChunkSize();
185                             if (chunkSize == 0) {
186                                 chunkSize = -1;
187                                 l = -1;
188                             }
189                         }
190                         if (chunkSize > 0) {
191                             l = chunkSize;
192                             if (l > (buffer.length - len)) {
193                                 l = buffer.length - len;
194                             }
195                             l = inputStream.read(buffer, len, l);
196                             if (l > 0) {
197                                 chunkSize = chunkSize - l;
198                                 len = len + l;
199                             } else {
200                                 chunkSize = -1;
201                             }
202                         }
203                     }
204                 }
205             } else {
206                 if (ptr == len) {
207                     ptr = 0;
208                     len = 0;
209                     mark = -1;
210                     if ((buffer != null) && (bufferSize != buffer.length)) {
211                         buffer = null;
212                     }
213                 }
214             }
215         } else {
216             if (contentLength > 0) {
217                 if (ptr == len) {
218                     ptr = 0;
219                     len = 0;
220                     mark = -1;
221                     if ((buffer == null) || (bufferSize != buffer.length)) {
222                         buffer = new byte[bufferSize];
223                     }
224                     int l = buffer.length;
225                     if (l > contentLength) {
226                         l = (int)contentLength;
227                     }
228                     l = inputStream.read(buffer, 0, l);
229                     len = len + l;
230                     contentLength = contentLength - l;
231                 }
232             } else {
233                 if (ptr == len) {
234                     ptr = 0;
235                     len = 0;
236                     mark = -1;
237                     if ((buffer != null) && (bufferSize != buffer.length)) {
238                         buffer = null;
239                     }
240                 }
241             }
242         }
243     }
244 
245     @Override
246     public void close() throws IOException {
247         if (!closed) {
248             closed = true;
249             if (chunkEncoding) {
250                 if (bufferSize <= 0) {
251                     byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
252                     while (chunkSize >= 0) {
253                         if (chunkSize == 0) {
254                             chunkSize = readChunkSize();
255                             if (chunkSize == 0) {
256                                 chunkSize = -1;
257                             }
258                         }
259                         if (chunkSize > 0) {
260                             int r = DEFAULT_BUFFER_SIZE;
261                             if (r > chunkSize) {
262                                 r = chunkSize;
263                             }
264                             r = inputStream.read(buf, 0, r);
265                             chunkSize = chunkSize - r;
266                         }
267                     }
268                 } else {
269                     while (chunkSize >= 0) {
270                         ptr = len;
271                         checkBuffer();
272                     }
273                 }
274             } else {
275                 if (bufferSize <= 0) {
276                     if (contentLength > 0) {
277                         byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
278                         while (contentLength > 0) {
279                             int r = DEFAULT_BUFFER_SIZE;
280                             if (r > contentLength) {
281                                 r = (int)contentLength;
282                             }
283                             r = inputStream.read(buf, 0, r);
284                             contentLength = contentLength - r;
285                         }
286                     }
287                 } else {
288                     while (contentLength > 0) {
289                         contentLength = contentLength - (len - ptr);
290                         checkBuffer();
291                     }
292                 }
293             }
294         }
295     }
296 
297 
298     @Override
299     public int read() throws IOException {
300         if (closed) {
301             throw new IOException("Stream is closed");
302         }
303         if (bufferSize <= 0) {
304             if (chunkEncoding) {
305                 if (chunkSize == 0) {
306                     chunkSize = readChunkSize();
307                     if (chunkSize == 0) {
308                         chunkSize = -1;
309                     }
310                 }
311                 if (chunkSize > 0) {
312                     int r = inputStream.read();
313                     if (r >= 0) {
314                         chunkSize = chunkSize - 1;
315                     } else {
316                         chunkSize = -1;
317                     }
318                     return r;
319                 } else {
320                     chunkSize = -1;
321                     return -1;
322                 }
323             } else {
324                 if (contentLength > 0) {
325                     int r = inputStream.read();
326                     if (r >= 0) {
327                         contentLength = contentLength - 1;
328                     } else {
329                         contentLength = 0;
330                     }
331                     return r;
332                 } else {
333                     return -1;
334                 }
335             }
336         } else {
337             checkBuffer();
338             if (ptr < len) {
339                 int b = buffer[ptr];
340                 ptr = ptr + 1;
341                 return b;
342             } else {
343                 return -1;
344             }
345         }
346     }
347 
348     @Override
349     public int read(byte[] buf) throws IOException {
350         return read(buf, 0, buf.length);
351     }
352 
353     @Override
354     public int read(byte[] buf, int off, int l) throws IOException {
355         if (closed) {
356             throw new IOException("Stream is closed");
357         }
358         if (l == 0) {
359             return 0;
360         }
361         if (bufferSize <= 0) {
362             if (chunkEncoding) {
363                 int r = 0;
364                 while ((l > 0) && (chunkSize >= 0)) {
365                     if (chunkSize == 0) {
366                         chunkSize = readChunkSize();
367                         if (chunkSize == 0) {
368                             chunkSize = -1;
369                         }
370                     }
371                     int ll = l;
372                     if (ll > chunkSize) {
373                         ll = chunkSize;
374                     }
375                     ll = inputStream.read(buf, off, ll);
376                     if (ll >= 0) {
377                         r = r + ll;
378                         off = off + ll;
379                         l = l - ll;
380                         chunkSize = chunkSize - ll;
381                     } else {
382                         if (r > 0) {
383                             return r;
384                         } else {
385                             return -1;
386                         }
387                     }
388                 }
389                 return r;
390             } else {
391                 if (contentLength > 0) {
392                     if (l > contentLength) {
393                         l = (int)contentLength;
394                     }
395                     l = inputStream.read(buf, off, l);
396                     if (l >= 0) {
397                         contentLength = contentLength - l;
398                     }
399                     return l;
400                 } else {
401                     return -1;
402                 }
403             }
404         } else {
405             checkBuffer();
406             int r = 0;
407             int ll = len - ptr; // remainder in the buffer
408             while ((ll > 0) && (ll <= l)) {
409                 System.arraycopy(buffer, ptr, buf, off, ll);
410                 l = l - ll; // that less we need to add
411                 r = r + ll; // that much we have already read
412                 off = off + ll; // we need to move in the result buffer
413                 ptr = len; // internal buffer is empty now
414                 checkBuffer(); // fill in some more
415                 ll = len - ptr; // that much we have in the buffer
416             }
417             if (ll > 0) {
418                 // internal buffer is not empty and has more then we need
419                 if (((ptr + l) > buffer.length)
420                         || ((off + l) > buf.length)) {
421                     System.out.println("here");
422                 }
423                 System.arraycopy(buffer, ptr, buf, off, l);
424                 ptr = ptr + l;
425                 r = r + l;
426             }
427 
428             if (r == 0) {
429                 // We haven't read anything - so return -1 - as end of the stream!
430                 return -1;
431             } else {
432                 return r;
433             }
434         }
435     }
436 
437     @Override
438     public long skip(long l) throws IOException {
439         if (closed) {
440             throw new IOException("Stream is closed");
441         }
442         if (l == 0) {
443             return 0;
444         }
445         checkBuffer();
446         long r = 0;
447         int ll = len - ptr; // remainder in the buffer
448         while ((ll > 0) && (ll <= l)) {
449             l = l - ll; // that less we need to add
450             r = r + ll; // that much we have already read
451             ptr = len; // internal buffer is empty now
452             checkBuffer(); // fill in some more
453             ll = len - ptr; // that much we have in the buffer
454         }
455         if (ll > 0) {
456             // internal buffer is not empty and has more then we need
457             ptr = ptr + (int)l;
458             r = r + l;
459         }
460 
461         return r;
462     }
463 
464     @Override
465     public int available() throws IOException {
466         if (closed) {
467             throw new IOException("Stream is closed");
468         }
469         return ptr - len;
470     }
471 
472     @Override
473     public synchronized void mark(int i) {
474         if (!closed) {
475             if (chunkEncoding) {
476                 try {
477                     checkBuffer();
478                 } catch (IOException e) {
479                     return;
480                 }
481                 if ((chunkSize < 0) && (i > (ptr - len))) {
482                     i = ptr - len;
483                     // trim it down since we now there isn't that much
484                     // bytes in the buffer since we have already read last chunk
485                 }
486             } else {
487                 if (i > contentLength) {
488                     i = (int)contentLength;
489                 }
490 
491             }
492             if (i > bufferSize) {
493                 // extend the buffer for a required number of bytes
494                 byte[] newBuffer = new byte[i];
495                 System.arraycopy(buffer, ptr, newBuffer, 0, (ptr - len));
496                 buffer = newBuffer;
497                 len = ptr - len;
498                 ptr = 0;
499                 mark = 0;
500             } else {
501                 mark = ptr;
502             }
503         }
504     }
505 
506     @Override
507     public synchronized void reset() throws IOException {
508         if (closed) {
509             throw new IOException("Stream is closed");
510         }
511         if (mark < 0) {
512             throw new IOException("Cannot reset - stream is part asked number of bytes after the mark.");
513         }
514         ptr = mark;
515     }
516 
517     @Override
518     public boolean markSupported() {
519         return true;
520     }
521 }