KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > HTTPClient > Codecs


1 /*
2  * @(#)Codecs.java 0.3-2 18/06/1999
3  *
4  * This file is part of the HTTPClient package
5  * Copyright (C) 1996-1999 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  */

28
29 package HTTPClient;
30
31
32 import java.util.BitSet JavaDoc;
33 import java.util.Vector JavaDoc;
34 import java.util.StringTokenizer JavaDoc;
35 import java.io.IOException JavaDoc;
36 import java.io.InputStream JavaDoc;
37 import java.io.DataInputStream JavaDoc;
38 import java.io.File JavaDoc;
39 import java.io.FileInputStream JavaDoc;
40 import java.io.FileOutputStream JavaDoc;
41 import java.io.BufferedReader JavaDoc;
42
43
44 /**
45  * This class collects various encoders and decoders.
46  *
47  * @version 0.3-2 18/06/1999
48  * @author Ronald Tschalär
49  */

50
51 public class Codecs
52 {
53     private static BitSet JavaDoc BoundChar;
54     private static BitSet JavaDoc EBCDICUnsafeChar;
55     private static byte[] Base64EncMap, Base64DecMap;
56     private static char[] UUEncMap;
57     private static byte[] UUDecMap;
58
59
60     private final static String JavaDoc ContDisp = "\r\nContent-Disposition: form-data; name=\"";
61     private final static String JavaDoc FileName = "\"; filename=\"";
62     private final static String JavaDoc Boundary = "\r\n-----ieoau._._+2_8_GoodLuck8.3-dskdfJwSJKlrWLr0234324jfLdsjfdAuaoei-----";
63
64
65     // Class Initializer
66

67     static
68     {
69     // rfc-2046 & rfc-2045: (bcharsnospace & token)
70
// used for multipart codings
71
BoundChar = new BitSet JavaDoc(256);
72     for (int ch='0'; ch <= '9'; ch++) BoundChar.set(ch);
73     for (int ch='A'; ch <= 'Z'; ch++) BoundChar.set(ch);
74     for (int ch='a'; ch <= 'z'; ch++) BoundChar.set(ch);
75     BoundChar.set('+');
76     BoundChar.set('_');
77     BoundChar.set('-');
78     BoundChar.set('.');
79
80     // EBCDIC unsafe characters to be quoted in quoted-printable
81
// See first NOTE in section 6.7 of rfc-2045
82
EBCDICUnsafeChar = new BitSet JavaDoc(256);
83     EBCDICUnsafeChar.set('!');
84     EBCDICUnsafeChar.set('"');
85     EBCDICUnsafeChar.set('#');
86     EBCDICUnsafeChar.set('$');
87     EBCDICUnsafeChar.set('@');
88     EBCDICUnsafeChar.set('[');
89     EBCDICUnsafeChar.set('\\');
90     EBCDICUnsafeChar.set(']');
91     EBCDICUnsafeChar.set('^');
92     EBCDICUnsafeChar.set('`');
93     EBCDICUnsafeChar.set('{');
94     EBCDICUnsafeChar.set('|');
95     EBCDICUnsafeChar.set('}');
96     EBCDICUnsafeChar.set('~');
97
98     // rfc-2045: Base64 Alphabet
99
byte[] map = {
100         (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
101         (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L',
102         (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q', (byte)'R',
103         (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X',
104         (byte)'Y', (byte)'Z',
105         (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f',
106         (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l',
107         (byte)'m', (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r',
108         (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x',
109         (byte)'y', (byte)'z',
110         (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
111         (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' };
112     Base64EncMap = map;
113     Base64DecMap = new byte[128];
114     for (int idx=0; idx<Base64EncMap.length; idx++)
115         Base64DecMap[Base64EncMap[idx]] = (byte) idx;
116
117     // uuencode'ing maps
118
UUEncMap = new char[64];
119     for (int idx=0; idx<UUEncMap.length; idx++)
120         UUEncMap[idx] = (char) (idx + 0x20);
121     UUDecMap = new byte[128];
122     for (int idx=0; idx<UUEncMap.length; idx++)
123         UUDecMap[UUEncMap[idx]] = (byte) idx;
124     }
125
126
127     // Constructors
128

129     /**
130      * This class isn't meant to be instantiated.
131      */

132     private Codecs() {}
133
134
135     // Methods
136

137     /**
138      * This method encodes the given string using the base64-encoding
139      * specified in RFC-2045 (Section 6.8). It's used for example in the
140      * "Basic" authorization scheme.
141      *
142      * @param str the string
143      * @return the base64-encoded <var>str</var>
144      */

145     public final static String JavaDoc base64Encode(String JavaDoc str)
146     {
147     if (str == null) return null;
148
149     byte data[] = new byte[str.length()];
150     str.getBytes(0, str.length(), data, 0);
151
152     return new String JavaDoc(base64Encode(data), 0);
153     }
154
155
156     /**
157      * This method encodes the given byte[] using the base64-encoding
158      * specified in RFC-2045 (Section 6.8).
159      *
160      * @param data the data
161      * @return the base64-encoded <var>data</var>
162      */

163     public final static byte[] base64Encode(byte[] data)
164     {
165     if (data == null) return null;
166
167     int sidx, didx;
168     byte dest[] = new byte[((data.length+2)/3)*4];
169
170
171     // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
172
for (sidx=0, didx=0; sidx < data.length-2; sidx += 3)
173     {
174         dest[didx++] = Base64EncMap[(data[sidx] >>> 2) & 077];
175         dest[didx++] = Base64EncMap[(data[sidx+1] >>> 4) & 017 |
176                     (data[sidx] << 4) & 077];
177         dest[didx++] = Base64EncMap[(data[sidx+2] >>> 6) & 003 |
178                     (data[sidx+1] << 2) & 077];
179         dest[didx++] = Base64EncMap[data[sidx+2] & 077];
180     }
181     if (sidx < data.length)
182     {
183         dest[didx++] = Base64EncMap[(data[sidx] >>> 2) & 077];
184         if (sidx < data.length-1)
185         {
186         dest[didx++] = Base64EncMap[(data[sidx+1] >>> 4) & 017 |
187                         (data[sidx] << 4) & 077];
188         dest[didx++] = Base64EncMap[(data[sidx+1] << 2) & 077];
189         }
190         else
191         dest[didx++] = Base64EncMap[(data[sidx] << 4) & 077];
192     }
193
194     // add padding
195
for ( ; didx < dest.length; didx++)
196         dest[didx] = (byte) '=';
197
198     return dest;
199     }
200
201
202     /**
203      * This method decodes the given string using the base64-encoding
204      * specified in RFC-2045 (Section 6.8).
205      *
206      * @param str the base64-encoded string.
207      * @return the decoded <var>str</var>.
208      */

209     public final static String JavaDoc base64Decode(String JavaDoc str)
210     {
211     if (str == null) return null;
212
213     byte data[] = new byte[str.length()];
214     str.getBytes(0, str.length(), data, 0);
215
216     return new String JavaDoc(base64Decode(data), 0);
217     }
218
219
220     /**
221      * This method decodes the given byte[] using the base64-encoding
222      * specified in RFC-2045 (Section 6.8).
223      *
224      * @param data the base64-encoded data.
225      * @return the decoded <var>data</var>.
226      */

227     public final static byte[] base64Decode(byte[] data)
228     {
229     if (data == null) return null;
230
231     int tail = data.length;
232     while (data[tail-1] == '=') tail--;
233
234     byte dest[] = new byte[tail - data.length/4];
235
236
237     // ascii printable to 0-63 conversion
238
for (int idx = 0; idx <data.length; idx++)
239         data[idx] = Base64DecMap[data[idx]];
240
241     // 4-byte to 3-byte conversion
242
int sidx, didx;
243     for (sidx = 0, didx=0; didx < dest.length-2; sidx += 4, didx += 3)
244     {
245         dest[didx] = (byte) ( ((data[sidx] << 2) & 255) |
246                 ((data[sidx+1] >>> 4) & 003) );
247         dest[didx+1] = (byte) ( ((data[sidx+1] << 4) & 255) |
248                 ((data[sidx+2] >>> 2) & 017) );
249         dest[didx+2] = (byte) ( ((data[sidx+2] << 6) & 255) |
250                 (data[sidx+3] & 077) );
251     }
252     if (didx < dest.length)
253         dest[didx] = (byte) ( ((data[sidx] << 2) & 255) |
254                 ((data[sidx+1] >>> 4) & 003) );
255     if (++didx < dest.length)
256         dest[didx] = (byte) ( ((data[sidx+1] << 4) & 255) |
257                 ((data[sidx+2] >>> 2) & 017) );
258
259     return dest;
260     }
261
262
263     /**
264      * This method encodes the given byte[] using the unix uuencode
265      * encding. The output is split into lines starting with the encoded
266      * number of encoded octets in the line and ending with a newline.
267      * No line is longer than 45 octets (60 characters), not including
268      * length and newline.
269      *
270      * <P><em>Note:</em> just the raw data is encoded; no 'begin' and 'end'
271      * lines are added as is done by the unix <code>uuencode</code> utility.
272      *
273      * @param data the data
274      * @return the uuencoded <var>data</var>
275      */

276     public final static char[] uuencode(byte[] data)
277     {
278     if (data == null) return null;
279     if (data.length == 0) return new char[0];
280
281     int line_len = 45; // line length, in octets
282

283     int sidx, didx;
284     char nl[] = System.getProperty("line.separator", "\n").toCharArray(),
285          dest[] = new char[(data.length+2)/3*4 +
286                 ((data.length+line_len-1)/line_len)*(nl.length+1)];
287
288     // split into lines, adding line-length and line terminator
289
for (sidx=0, didx=0; sidx+line_len < data.length; )
290     {
291         // line length
292
dest[didx++] = UUEncMap[line_len];
293
294         // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
295
for (int end = sidx+line_len; sidx < end; sidx += 3)
296         {
297         dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
298         dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
299                         (data[sidx] << 4) & 077];
300         dest[didx++] = UUEncMap[(data[sidx+2] >>> 6) & 003 |
301                         (data[sidx+1] << 2) & 077];
302         dest[didx++] = UUEncMap[data[sidx+2] & 077];
303         }
304
305         // line terminator
306
for (int idx=0; idx<nl.length; idx++) dest[didx++] = nl[idx];
307     }
308
309     // last line
310

311     // line length
312
dest[didx++] = UUEncMap[data.length-sidx];
313
314     // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
315
for (; sidx+2 < data.length; sidx += 3)
316     {
317         dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
318         dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
319                     (data[sidx] << 4) & 077];
320         dest[didx++] = UUEncMap[(data[sidx+2] >>> 6) & 003 |
321                     (data[sidx+1] << 2) & 077];
322         dest[didx++] = UUEncMap[data[sidx+2] & 077];
323     }
324
325     if (sidx < data.length-1)
326     {
327         dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
328         dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
329                     (data[sidx] << 4) & 077];
330         dest[didx++] = UUEncMap[(data[sidx+1] << 2) & 077];
331         dest[didx++] = UUEncMap[0];
332     }
333     else if (sidx < data.length)
334     {
335         dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
336         dest[didx++] = UUEncMap[(data[sidx] << 4) & 077];
337         dest[didx++] = UUEncMap[0];
338         dest[didx++] = UUEncMap[0];
339     }
340
341     // line terminator
342
for (int idx=0; idx<nl.length; idx++) dest[didx++] = nl[idx];
343
344     // sanity check
345
if (didx != dest.length)
346         throw new Error JavaDoc("Calculated "+dest.length+" chars but wrote "+didx+" chars!");
347
348     return dest;
349     }
350
351
352     /**
353      * TBD! How to return file name and mode?
354      *
355      * @param rdr the reader from which to read and decode the data
356      * @exception ParseException if either the "begin" or "end" line are not
357      * found, or the "begin" is incorrect
358      * @exception IOException if the <var>rdr</var> throws an IOException
359      */

360     private final static byte[] uudecode(BufferedReader JavaDoc rdr)
361         throws ParseException, IOException JavaDoc
362     {
363     String JavaDoc line, file_name;
364     int file_mode;
365
366
367     // search for beginning
368

369     while ((line = rdr.readLine()) != null && !line.startsWith("begin "))
370         ;
371     if (line == null)
372         throw new ParseException("'begin' line not found");
373
374
375     // parse 'begin' line
376

377     StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(line);
378     tok.nextToken(); // throw away 'begin'
379
try // extract mode
380
{ file_mode = Integer.parseInt(tok.nextToken(), 8); }
381     catch (Exception JavaDoc e)
382         { throw new ParseException("Invalid mode on line: " + line); }
383     try // extract name
384
{ file_name = tok.nextToken(); }
385     catch (java.util.NoSuchElementException JavaDoc e)
386         { throw new ParseException("No file name found on line: " + line); }
387
388
389     // read and parse body
390

391     byte[] body = new byte[1000];
392     int off = 0;
393
394     while ((line = rdr.readLine()) != null && !line.equals("end"))
395     {
396         byte[] tmp = uudecode(line.toCharArray());
397         if (off + tmp.length > body.length)
398         body = Util.resizeArray(body, off+1000);
399         System.arraycopy(tmp, 0, body, off, tmp.length);
400         off += tmp.length;
401     }
402
403     if (line == null)
404         throw new ParseException("'end' line not found");
405
406
407     return Util.resizeArray(body, off);
408     }
409
410
411     /**
412      * This method decodes the given uuencoded char[].
413      *
414      * <P><em>Note:</em> just the actual data is decoded; any 'begin' and
415      * 'end' lines such as those generated by the unix <code>uuencode</code>
416      * utility must not be included.
417      *
418      * @param data the uuencode-encoded data.
419      * @return the decoded <var>data</var>.
420      */

421     public final static byte[] uudecode(char[] data)
422     {
423     if (data == null) return null;
424
425     int sidx, didx;
426     byte dest[] = new byte[data.length/4*3];
427
428
429     for (sidx=0, didx=0; sidx < data.length; )
430     {
431         // get line length (in number of encoded octets)
432
int len = UUDecMap[data[sidx++]];
433
434         // ascii printable to 0-63 and 4-byte to 3-byte conversion
435
int end = didx+len;
436         for (; didx < end-2; sidx += 4)
437         {
438         byte A = UUDecMap[data[sidx]],
439              B = UUDecMap[data[sidx+1]],
440              C = UUDecMap[data[sidx+2]],
441              D = UUDecMap[data[sidx+3]];
442         dest[didx++] = (byte) ( ((A << 2) & 255) | ((B >>> 4) & 003) );
443         dest[didx++] = (byte) ( ((B << 4) & 255) | ((C >>> 2) & 017) );
444         dest[didx++] = (byte) ( ((C << 6) & 255) | (D & 077) );
445         }
446
447         if (didx < end)
448         {
449         byte A = UUDecMap[data[sidx]],
450              B = UUDecMap[data[sidx+1]];
451         dest[didx++] = (byte) ( ((A << 2) & 255) | ((B >>> 4) & 003) );
452         }
453         if (didx < end)
454         {
455         byte B = UUDecMap[data[sidx+1]],
456              C = UUDecMap[data[sidx+2]];
457         dest[didx++] = (byte) ( ((B << 4) & 255) | ((C >>> 2) & 017) );
458         }
459
460         // skip padding
461
while (sidx < data.length &&
462            data[sidx] != '\n' && data[sidx] != '\r')
463         sidx++;
464
465         // skip end of line
466
while (sidx < data.length &&
467            (data[sidx] == '\n' || data[sidx] == '\r'))
468         sidx++;
469     }
470
471     return Util.resizeArray(dest, didx);
472     }
473
474
475     /**
476      * This method does a quoted-printable encoding of the given string
477      * according to RFC-2045 (Section 6.7). <em>Note:</em> this assumes
478      * 8-bit characters.
479      *
480      * @param str the string
481      * @return the quoted-printable encoded string
482      */

483     public final static String JavaDoc quotedPrintableEncode(String JavaDoc str)
484     {
485     if (str == null) return null;
486
487     char map[] =
488         {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'},
489          nl[] = System.getProperty("line.separator", "\n").toCharArray(),
490          res[] = new char[(int) (str.length()*1.5)],
491          src[] = str.toCharArray();
492     char ch;
493     int cnt = 0,
494          didx = 1,
495          last = 0,
496          slen = str.length();
497
498
499     for (int sidx=0; sidx < slen; sidx++)
500     {
501         ch = src[sidx];
502
503         if (ch == nl[0] && match(src, sidx, nl)) // Rule #4
504
{
505         if (res[didx-1] == ' ') // Rule #3
506
{
507             res[didx-1] = '=';
508             res[didx++] = '2';
509             res[didx++] = '0';
510         }
511         else if (res[didx-1] == '\t') // Rule #3
512
{
513             res[didx-1] = '=';
514             res[didx++] = '0';
515             res[didx++] = '9';
516         }
517
518         res[didx++] = '\r';
519         res[didx++] = '\n';
520         sidx += nl.length - 1;
521         cnt = didx;
522         }
523         else if (ch > 126 || (ch < 32 && ch != '\t') || ch == '=' ||
524              EBCDICUnsafeChar.get((int) ch))
525         { // Rule #1, #2
526
res[didx++] = '=';
527         res[didx++] = map[(ch & 0xf0) >>> 4];
528         res[didx++] = map[ch & 0x0f];
529         }
530         else // Rule #1
531
{
532         res[didx++] = ch;
533         }
534
535         if (didx > cnt+70) // Rule #5
536
{
537         res[didx++] = '=';
538         res[didx++] = '\r';
539         res[didx++] = '\n';
540         cnt = didx;
541         }
542
543         if (didx > res.length-5)
544         res = Util.resizeArray(res, res.length+500);
545     }
546
547     return String.valueOf(res, 1, didx-1);
548     }
549
550     private final static boolean match(char[] str, int start, char[] arr)
551     {
552     if (str.length < start + arr.length) return false;
553
554     for (int idx=1; idx < arr.length; idx++)
555         if (str[start+idx] != arr[idx]) return false;
556     return true;
557     }
558
559
560     /**
561      * This method does a quoted-printable decoding of the given string
562      * according to RFC-2045 (Section 6.7). <em>Note:</em> this method
563      * expects the whole message in one chunk, not line by line.
564      *
565      * @param str the message
566      * @return the decoded message
567      * @exception ParseException If a '=' is not followed by a valid
568      * 2-digit hex number or '\r\n'.
569      */

570     public final static String JavaDoc quotedPrintableDecode(String JavaDoc str)
571         throws ParseException
572     {
573     if (str == null) return null;
574
575     char res[] = new char[(int) (str.length()*1.1)],
576          src[] = str.toCharArray(),
577          nl[] = System.getProperty("line.separator", "\n").toCharArray();
578     int last = 0,
579         didx = 0,
580         slen = str.length();
581
582
583     for (int sidx=0; sidx<slen; )
584     {
585         char ch = src[sidx++];
586
587         if (ch == '=')
588         {
589         if (sidx >= slen-1)
590             throw new ParseException("Premature end of input detected");
591
592         if (src[sidx] == '\n' || src[sidx] == '\r')
593         { // Rule #5
594
sidx++;
595
596             if (src[sidx-1] == '\r' &&
597             src[sidx] == '\n')
598             sidx++;
599         }
600         else // Rule #1
601
{
602             char repl;
603             int hi = Character.digit(src[sidx], 16),
604             lo = Character.digit(src[sidx+1], 16);
605
606             if ((hi | lo) < 0)
607             throw new ParseException(new String JavaDoc(src, sidx-1, 3) +
608                         " is an invalid code");
609             else
610             {
611             repl = (char) (hi << 4 | lo);
612             sidx += 2;
613             }
614
615             res[didx++] = repl;
616         }
617         last = didx;
618         }
619         else if (ch == '\n' || ch == '\r') // Rule #4
620
{
621         if (ch == '\r' && sidx < slen && src[sidx] == '\n')
622             sidx++;
623         for (int idx=0; idx<nl.length; idx++)
624             res[last++] = nl[idx];
625         didx = last;
626         }
627         else // Rule #1, #2
628
{
629         res[didx++] = ch;
630         if (ch != ' ' && ch != '\t') // Rule #3
631
last = didx;
632         }
633
634         if (didx > res.length-nl.length-2)
635         res = Util.resizeArray(res, res.length+500);
636     }
637
638     return new String JavaDoc(res, 0, didx);
639     }
640
641
642     /**
643      * This method urlencodes the given string. This method is here for
644      * symmetry reasons and just calls java.net.URLEncoder.encode().
645      *
646      * @param str the string
647      * @return the url-encoded string
648      */

649     public final static String JavaDoc URLEncode(String JavaDoc str)
650     {
651     if (str == null) return null;
652
653     return java.net.URLEncoder.encode(str);
654     }
655
656
657     /**
658      * This method decodes the given urlencoded string.
659      *
660      * @param str the url-encoded string
661      * @return the decoded string
662      * @exception ParseException If a '%' is not followed by a valid
663      * 2-digit hex number.
664      */

665     public final static String JavaDoc URLDecode(String JavaDoc str) throws ParseException
666     {
667     if (str == null) return null;
668
669     char[] res = new char[str.length()];
670     int didx = 0;
671
672     for (int sidx=0; sidx<str.length(); sidx++)
673     {
674         char ch = str.charAt(sidx);
675         if (ch == '+')
676         res[didx++] = ' ';
677         else if (ch == '%')
678         {
679         try
680         {
681             res[didx++] = (char)
682             Integer.parseInt(str.substring(sidx+1,sidx+3), 16);
683             sidx += 2;
684         }
685         catch (NumberFormatException JavaDoc e)
686         {
687             throw new ParseException(str.substring(sidx,sidx+3) +
688                         " is an invalid code");
689         }
690         }
691         else
692         res[didx++] = ch;
693     }
694
695     return String.valueOf(res, 0, didx);
696     }
697
698
699     /**
700      * This method decodes a multipart/form-data encoded string.
701      *
702      * @param data the form-data to decode.
703      * @param cont_type the content type header (must contain the
704      * boundary string).
705      * @param dir the directory to create the files in.
706      * @return an array of name/value pairs, one for each part;
707      * the name is the 'name' attribute given in the
708      * Content-Disposition header; the value is either
709      * the name of the file if a filename attribute was
710      * found, or the contents of the part.
711      * @exception IOException If any file operation fails.
712      * @exception ParseException If an error during parsing occurs.
713      * @see #mpFormDataDecode(byte[], java.lang.String, java.lang.String, HTTPClient.FilenameMangler)
714      */

715     public final static NVPair[] mpFormDataDecode(byte[] data, String JavaDoc cont_type,
716                           String JavaDoc dir)
717         throws IOException JavaDoc, ParseException
718     {
719     return mpFormDataDecode(data, cont_type, dir, null);
720     }
721
722
723     /**
724      * This method decodes a multipart/form-data encoded string. The boundary
725      * is parsed from the <var>cont_type</var> parameter, which must be of the
726      * form 'multipart/form-data; boundary=...'. Any encoded files are created
727      * in the directory specified by <var>dir</var> using the encoded filename.
728      *
729      * <P><em>Note:</em> Does not handle nested encodings (yet).
730      *
731      * <P>Examples: If you're receiving a multipart/form-data encoded response
732      * from a server you could use something like:
733      * <PRE>
734      * NVPair[] opts = Codecs.mpFormDataDecode(resp.getData(),
735      * resp.getHeader("Content-type"), ".");
736      * </PRE>
737      * If you're using this in a Servlet to decode the body of a request from
738      * a client you could use something like:
739      * <PRE>
740      * byte[] body = new byte[req.getContentLength()];
741      * new DataInputStream(req.getInputStream()).readFully(body);
742      * NVPair[] opts = Codecs.mpFormDataDecode(body, req.getContentType(),
743      * ".");
744      * </PRE>
745      * Assuming the data received looked something like:
746      * <PRE>
747      * -----------------------------114975832116442893661388290519
748      * Content-Disposition: form-data; name="option"
749      * &nbsp;
750      * doit
751      * -----------------------------114975832116442893661388290519
752      * Content-Disposition: form-data; name="comment"; filename="comment.txt"
753      * &nbsp;
754      * Gnus and Gnats are not Gnomes.
755      * -----------------------------114975832116442893661388290519--
756      * </PRE>
757      * you would get one file called <VAR>comment.txt</VAR> in the current
758      * directory, and opts would contain two elements: {"option", "doit"}
759      * and {"comment", "comment.txt"}
760      *
761      * @param data the form-data to decode.
762      * @param cont_type the content type header (must contain the
763      * boundary string).
764      * @param dir the directory to create the files in.
765      * @param mangler the filename mangler, or null if no mangling is
766      * to be done. This is invoked just before each
767      * file is created and written, thereby allowing
768      * you to control the names of the files.
769      * @return an array of name/value pairs, one for each part;
770      * the name is the 'name' attribute given in the
771      * Content-Disposition header; the value is either
772      * the name of the file if a filename attribute was
773      * found, or the contents of the part.
774      * @exception IOException If any file operation fails.
775      * @exception ParseException If an error during parsing occurs.
776      */

777     public final static NVPair[] mpFormDataDecode(byte[] data, String JavaDoc cont_type,
778                           String JavaDoc dir,
779                           FilenameMangler mangler)
780         throws IOException JavaDoc, ParseException
781     {
782     // Find and extract boundary string
783

784     String JavaDoc bndstr = Util.getParameter("boundary", cont_type);
785     if (bndstr == null)
786         throw new ParseException("'boundary' parameter not found in Content-type: " + cont_type);
787
788     byte[] srtbndry = new byte[bndstr.length()+4],
789            boundary = new byte[bndstr.length()+6],
790            endbndry = new byte[bndstr.length()+6];
791
792     ( "--" + bndstr + "\r\n").getBytes(0, srtbndry.length, srtbndry, 0);
793     ("\r\n--" + bndstr + "\r\n").getBytes(0, boundary.length, boundary, 0);
794     ("\r\n--" + bndstr + "--" ).getBytes(0, endbndry.length, endbndry, 0);
795
796
797     // setup search routines
798

799     int[] bs = Util.compile_search(srtbndry),
800           bc = Util.compile_search(boundary),
801           be = Util.compile_search(endbndry);
802
803
804     // let's start parsing the actual data
805

806     int start = Util.findStr(srtbndry, bs, data, 0, data.length);
807     if (start == -1) // didn't even find the start
808
throw new ParseException("Starting boundary not found: " +
809                      new String JavaDoc(srtbndry,0));
810     start += srtbndry.length;
811
812     NVPair[] res = new NVPair[10];
813     boolean done = false;
814     int idx;
815
816     for (idx=0; !done; idx++)
817     {
818         // find end of this part
819

820         int end = Util.findStr(boundary, bc, data, start, data.length);
821         if (end == -1) // must be the last part
822
{
823         end = Util.findStr(endbndry, be, data, start, data.length);
824         if (end == -1)
825             throw new ParseException("Ending boundary not found: " +
826                          new String JavaDoc(endbndry,0));
827         done = true;
828         }
829
830         // parse header(s)
831

832         String JavaDoc hdr, name=null, value, filename=null, cont_disp = null;
833
834         while (true)
835         {
836         int next = findEOL(data, start) + 2;
837         if (next-2 <= start) break; // empty line -> end of headers
838
hdr = new String JavaDoc(data, 0, start, next-2-start);
839         start = next;
840
841         // handle line continuation
842
byte ch;
843         while (next < data.length-1 &&
844                ((ch = data[next]) == ' ' || ch == '\t'))
845         {
846             next = findEOL(data, start) + 2;
847             hdr += new String JavaDoc(data, 0, start, next-2-start);
848             start = next;
849         }
850
851         if (!hdr.regionMatches(true, 0, "Content-Disposition", 0, 19))
852             continue;
853         Vector JavaDoc pcd =
854             Util.parseHeader(hdr.substring(hdr.indexOf(':')+1));
855         HttpHeaderElement elem = Util.getElement(pcd, "form-data");
856
857         if (elem == null)
858             throw new ParseException("Expected 'Content-Disposition: form-data' in line: "+hdr);
859
860         NVPair[] params = elem.getParams();
861         name = filename = null;
862         for (int pidx=0; pidx<params.length; pidx++)
863         {
864             if (params[pidx].getName().equalsIgnoreCase("name"))
865             name = params[pidx].getValue();
866             if (params[pidx].getName().equalsIgnoreCase("filename"))
867             filename = params[pidx].getValue();
868         }
869         if (name == null)
870             throw new ParseException("'name' parameter not found in header: "+hdr);
871
872         cont_disp = hdr;
873         }
874
875         start += 2;
876         if (start > end)
877         throw new ParseException("End of header not found at offset "+end);
878
879         if (cont_disp == null)
880         throw new ParseException("Missing 'Content-Disposition' header at offset "+start);
881
882         // handle data for this part
883

884         if (filename != null) // It's a file
885
{
886         if (mangler != null)
887             filename = mangler.mangleFilename(filename, name);
888         if (filename != null)
889         {
890             File JavaDoc file = new File JavaDoc(dir, filename);
891             FileOutputStream JavaDoc out = new FileOutputStream JavaDoc(file);
892
893             out.write(data, start, end-start);
894             out.close();
895         }
896
897         value = filename;
898         }
899         else // It's simple data
900
{
901         value = new String JavaDoc(data, 0, start, end-start);
902         }
903
904         if (idx >= res.length)
905         res = Util.resizeArray(res, idx+10);
906         res[idx] = new NVPair(name, value);
907
908         start = end + boundary.length;
909     }
910
911     return Util.resizeArray(res, idx);
912     }
913
914
915     /**
916      * Searches for the next CRLF in an array.
917      *
918      * @param arr the byte array to search.
919      * @param off the offset at which to start the search.
920      * @return the position of the CR or (arr.length-2) if not found
921      */

922     private final static int findEOL(byte[] arr, int off)
923     {
924     while (off < arr.length-1 &&
925            !(arr[off++] == '\r' && arr[off] == '\n'));
926     return off-1;
927     }
928
929     /**
930      * This method encodes name/value pairs and files into a byte array
931      * using the multipart/form-data encoding.
932      *
933      * @param opts the simple form-data to encode (may be null);
934      * for each NVPair the name refers to the 'name'
935      * attribute to be used in the header of the part,
936      * and the value is contents of the part.
937      * @param files the files to encode (may be null); for each
938      * NVPair the name refers to the 'name' attribute
939      * to be used in the header of the part, and the
940      * value is the actual filename (the file will be
941      * read and it's contents put in the body of that
942      * part).
943      * @param cont_type this returns a new NVPair in the 0'th element
944      * which contains
945      * name = "Content-Type",
946      * value = "multipart/form-data; boundary=..."
947      * (the reason this parameter is an array is
948      * because a) that's the only way to simulate
949      * pass-by-reference and b) you need an array for
950      * the headers parameter to the Post() or Put()
951      * anyway).
952      * @return an encoded byte array containing all the opts
953      * and files.
954      * @exception IOException If any file operation fails.
955      * @see #mpFormDataEncode(HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.FilenameMangler)
956      */

957     public final static byte[] mpFormDataEncode(NVPair[] opts, NVPair[] files,
958                         NVPair[] cont_type)
959         throws IOException JavaDoc
960     {
961     return mpFormDataEncode(opts, files, cont_type, null);
962     }
963
964
965     private static NVPair[] dummy = new NVPair[0];
966
967     /**
968      * This method encodes name/value pairs and files into a byte array
969      * using the multipart/form-data encoding. The boundary is returned
970      * as part of <var>cont_type</var>.
971      * <BR>Example:
972      * <PRE>
973      * NVPair[] opts = { new NVPair("option", "doit") };
974      * NVPair[] file = { new NVPair("comment", "comment.txt") };
975      * NVPair[] hdrs = new NVPair[1];
976      * byte[] data = Codecs.mpFormDataEncode(opts, file, hdrs);
977      * con.Post("/cgi-bin/handle-it", data, hdrs);
978      * </PRE>
979      * <VAR>data</VAR> will look something like the following:
980      * <PRE>
981      * -----------------------------114975832116442893661388290519
982      * Content-Disposition: form-data; name="option"
983      * &nbsp;
984      * doit
985      * -----------------------------114975832116442893661388290519
986      * Content-Disposition: form-data; name="comment"; filename="comment.txt"
987      * &nbsp;
988      * Gnus and Gnats are not Gnomes.
989      * -----------------------------114975832116442893661388290519--
990      * </PRE>
991      * where the "Gnus and Gnats ..." is the contents of the file
992      * <VAR>comment.txt</VAR> in the current directory.
993      *
994      * @param opts the simple form-data to encode (may be null);
995      * for each NVPair the name refers to the 'name'
996      * attribute to be used in the header of the part,
997      * and the value is contents of the part.
998      * @param files the files to encode (may be null); for each
999      * NVPair the name refers to the 'name' attribute
1000     * to be used in the header of the part, and the
1001     * value is the actual filename (the file will be
1002     * read and it's contents put in the body of that
1003     * part).
1004     * @param cont_type this returns a new NVPair in the 0'th element
1005     * which contains
1006     * name = "Content-Type",
1007     * value = "multipart/form-data; boundary=..."
1008     * (the reason this parameter is an array is
1009     * because a) that's the only way to simulate
1010     * pass-by-reference and b) you need an array for
1011     * the headers parameter to the Post() or Put()
1012     * anyway).
1013     * @param mangler the filename mangler, or null if no mangling is
1014     * to be done. This allows you to change the name
1015     * used in the <var>filename</var> attribute of the
1016     * Content-Disposition header. Note: the mangler
1017     * will be invoked twice for each filename.
1018     * @return an encoded byte array containing all the opts
1019     * and files.
1020     * @exception IOException If any file operation fails.
1021     */

1022    public final static byte[] mpFormDataEncode(NVPair[] opts, NVPair[] files,
1023                        NVPair[] cont_type,
1024                        FilenameMangler mangler)
1025        throws IOException JavaDoc
1026    {
1027    int len = 0,
1028        hdr_len = 2 + 2 + 70 + 2 + 39 + 2 + 2;
1029         // \r\n -- bnd \r\n C.. \r\n \r\n
1030
byte[] boundary = new byte[74],
1031           cont_disp = new byte[40],
1032           filename = new byte[13];
1033
1034        ContDisp.getBytes(0, ContDisp.length(), cont_disp, 0);
1035    FileName.getBytes(0, FileName.length(), filename, 0);
1036    Boundary.getBytes(0, Boundary.length(), boundary, 0);
1037
1038    if (opts == null) opts = dummy;
1039    if (files == null) files = dummy;
1040
1041
1042    // Calculate the length of the data
1043

1044    for (int idx=0; idx<opts.length; idx++)
1045        len += hdr_len + opts[idx].getName().length() +
1046           opts[idx].getValue().length();
1047
1048    for (int idx=0; idx<files.length; idx++)
1049    {
1050        File JavaDoc file = new File JavaDoc(files[idx].getValue());
1051        String JavaDoc fname = file.getName();
1052        if (mangler != null)
1053        fname = mangler.mangleFilename(fname, files[idx].getName());
1054        if (fname != null)
1055        {
1056        len += hdr_len + files[idx].getName().length() + 13;
1057        len += fname.length() + file.length();
1058        }
1059    }
1060
1061    len -= 2; // first CR LF is not written
1062
len += 2 + 2 + 70 + 2 + 2; // \r\n -- bnd -- \r\n
1063

1064
1065    // Now fill array
1066

1067    byte[] res = new byte[len];
1068    int pos = 0;
1069
1070    NewBound: for (int new_c=0x30303030; new_c!=0x7A7A7A7A; new_c++)
1071    {
1072        pos = 0;
1073
1074        // modify boundary in hopes that it will be unique
1075
while (!BoundChar.get(new_c & 0xff)) new_c += 0x00000001;
1076        while (!BoundChar.get(new_c>>8 & 0xff)) new_c += 0x00000100;
1077        while (!BoundChar.get(new_c>>16 & 0xff)) new_c += 0x00010000;
1078        while (!BoundChar.get(new_c>>24 & 0xff)) new_c += 0x01000000;
1079        boundary[40] = (byte) (new_c & 0xff);
1080        boundary[42] = (byte) (new_c>>8 & 0xff);
1081        boundary[44] = (byte) (new_c>>16 & 0xff);
1082        boundary[46] = (byte) (new_c>>24 & 0xff);
1083
1084        int off = 2;
1085        int[] bnd_cmp = Util.compile_search(boundary);
1086
1087        for (int idx=0; idx<opts.length; idx++)
1088        {
1089        System.arraycopy(boundary, off, res, pos, boundary.length-off);
1090        pos += boundary.length - off;
1091        off = 0;
1092        System.arraycopy(cont_disp, 0, res, pos, cont_disp.length);
1093        pos += cont_disp.length;
1094
1095        int nlen = opts[idx].getName().length();
1096        opts[idx].getName().getBytes(0, nlen, res, pos);
1097        if (nlen >= boundary.length &&
1098            Util.findStr(boundary, bnd_cmp, res, pos, pos+nlen) != -1)
1099            continue NewBound;
1100        pos += nlen;
1101
1102        res[pos++] = (byte) '"';
1103        res[pos++] = (byte) '\r';
1104        res[pos++] = (byte) '\n';
1105        res[pos++] = (byte) '\r';
1106        res[pos++] = (byte) '\n';
1107
1108        int vlen = opts[idx].getValue().length();
1109        opts[idx].getValue().getBytes(0, vlen, res, pos);
1110        if (vlen >= boundary.length &&
1111            Util.findStr(boundary, bnd_cmp, res, pos, pos+vlen) != -1)
1112            continue NewBound;
1113        pos += vlen;
1114        }
1115
1116        for (int idx=0; idx<files.length; idx++)
1117        {
1118        File JavaDoc file = new File JavaDoc(files[idx].getValue());
1119        String JavaDoc fname = file.getName();
1120        if (mangler != null)
1121            fname = mangler.mangleFilename(fname, files[idx].getName());
1122        if (fname == null) continue;
1123
1124        System.arraycopy(boundary, off, res, pos, boundary.length-off);
1125        pos += boundary.length - off;
1126        off = 0;
1127        System.arraycopy(cont_disp, 0, res, pos, cont_disp.length);
1128        pos += cont_disp.length;
1129
1130        int nlen = files[idx].getName().length();
1131        files[idx].getName().getBytes(0, nlen, res, pos);
1132        if (nlen >= boundary.length &&
1133            Util.findStr(boundary, bnd_cmp, res, pos, pos+nlen) != -1)
1134            continue NewBound;
1135        pos += nlen;
1136
1137        System.arraycopy(filename, 0, res, pos, filename.length);
1138        pos += filename.length;
1139
1140        nlen = fname.length();
1141        fname.getBytes(0, nlen, res, pos);
1142        if (nlen >= boundary.length &&
1143            Util.findStr(boundary, bnd_cmp, res, pos, pos+nlen) != -1)
1144            continue NewBound;
1145        pos += nlen;
1146
1147        res[pos++] = (byte) '"';
1148        res[pos++] = (byte) '\r';
1149        res[pos++] = (byte) '\n';
1150        res[pos++] = (byte) '\r';
1151        res[pos++] = (byte) '\n';
1152
1153        nlen = (int) file.length();
1154        int opos = pos;
1155        FileInputStream JavaDoc fin = new FileInputStream JavaDoc(file);
1156        while (nlen > 0)
1157        {
1158            int got = fin.read(res, pos, nlen);
1159            nlen -= got;
1160            pos += got;
1161        }
1162        if (Util.findStr(boundary, bnd_cmp, res, opos, pos) != -1)
1163            continue NewBound;
1164        }
1165
1166        break NewBound;
1167    }
1168
1169    System.arraycopy(boundary, 0, res, pos, boundary.length);
1170    pos += boundary.length;
1171    res[pos++] = (byte) '-';
1172    res[pos++] = (byte) '-';
1173    res[pos++] = (byte) '\r';
1174    res[pos++] = (byte) '\n';
1175
1176    if (pos != len)
1177        throw new Error JavaDoc("Calculated "+len+" bytes but wrote "+pos+" bytes!");
1178
1179    /* the boundary parameter should be quoted (rfc-2046, section 5.1.1)
1180     * but too many script authors are not capable of reading specs...
1181     * So, I give up and don't quote it.
1182     */

1183    cont_type[0] = new NVPair("Content-Type",
1184                  "multipart/form-data; boundary=" +
1185                  new String JavaDoc(boundary, 0, 4, 70));
1186
1187    return res;
1188    }
1189
1190
1191    /**
1192     * Turns an array of name/value pairs into the string
1193     * "name1=value1&name2=value2&name3=value3". The names and values are
1194     * first urlencoded. This is the form in which form-data is passed to
1195     * a cgi script.
1196     *
1197     * @param pairs the array of name/value pairs
1198     * @return a string containg the encoded name/value pairs
1199     */

1200    public final static String JavaDoc nv2query(NVPair pairs[])
1201    {
1202    if (pairs == null)
1203        return null;
1204
1205
1206    int idx;
1207    StringBuffer JavaDoc qbuf = new StringBuffer JavaDoc();
1208
1209    for (idx = 0; idx < pairs.length; idx++)
1210    {
1211        qbuf.append(URLEncode(pairs[idx].getName()) + "=" +
1212            URLEncode(pairs[idx].getValue()) + "&");
1213    }
1214
1215    if (idx > 0)
1216        qbuf.setLength(qbuf.length()-1); // remove trailing '&'
1217

1218    return qbuf.toString();
1219    }
1220
1221
1222    /**
1223     * Turns a string of the form "name1=value1&name2=value2&name3=value3"
1224     * into an array of name/value pairs. The names and values are
1225     * urldecoded. The query string is in the form in which form-data is
1226     * received in a cgi script.
1227     *
1228     * @param query the query string containing the encoded name/value pairs
1229     * @return an array of NVPairs
1230     * @exception ParseException If the '=' is missing in any field, or if
1231     * the urldecoding of the name or value fails
1232     */

1233    public final static NVPair[] query2nv(String JavaDoc query) throws ParseException
1234    {
1235    if (query == null) return null;
1236
1237    int idx = -1,
1238        cnt = 1;
1239    while ((idx = query.indexOf('&', idx+1)) != -1) cnt ++;
1240    NVPair[] pairs = new NVPair[cnt];
1241
1242    for (idx=0, cnt=0; cnt<pairs.length; cnt++)
1243    {
1244        int eq = query.indexOf('=', idx);
1245        int end = query.indexOf('&', idx);
1246
1247        if (end == -1) end = query.length();
1248
1249        if (eq == -1 || eq >= end)
1250        throw new ParseException("'=' missing in " +
1251                     query.substring(idx, end));
1252
1253        pairs[cnt] =
1254            new NVPair(URLDecode(query.substring(idx,eq)),
1255                URLDecode(query.substring(eq+1,end)));
1256
1257        idx = end + 1;
1258    }
1259
1260    return pairs;
1261    }
1262
1263
1264    /**
1265     * Encodes data used the chunked encoding. <var>last</var> signales if
1266     * this is the last chunk, in which case the appropriate footer is
1267     * generated.
1268     *
1269     * @param data the data to be encoded; may be null.
1270     * @param ftrs optional headers to include in the footer (ignored if
1271     * not last); may be null.
1272     * @param last whether this is the last chunk.
1273     * @return an array of bytes containing the chunk
1274     */

1275    public final static byte[] chunkedEncode(byte[] data, NVPair[] ftrs,
1276                         boolean last)
1277    {
1278    return
1279        chunkedEncode(data, 0, data == null ? 0 : data.length, ftrs, last);
1280    }
1281
1282
1283    /**
1284     * Encodes data used the chunked encoding. <var>last</var> signales if
1285     * this is the last chunk, in which case the appropriate footer is
1286     * generated.
1287     *
1288     * @param data the data to be encoded; may be null.
1289     * @param off an offset into the <var>data</var>
1290     * @param len the number of bytes to take from <var>data</var>
1291     * @param ftrs optional headers to include in the footer (ignored if
1292     * not last); may be null.
1293     * @param last whether this is the last chunk.
1294     * @return an array of bytes containing the chunk
1295     */

1296    public final static byte[] chunkedEncode(byte[] data, int off, int len,
1297                         NVPair[] ftrs, boolean last)
1298    {
1299    if (data == null)
1300    {
1301        data = new byte[0];
1302        len = 0;
1303    }
1304    if (last && ftrs == null) ftrs = new NVPair[0];
1305
1306    // get length of data as hex-string
1307
String JavaDoc hex_len = Integer.toString(len, 16);
1308
1309
1310    // calculate length of chunk
1311

1312    int res_len = 0;
1313    if (len > 0) // len CRLF data CRLF
1314
res_len += hex_len.length() + 2 + len + 2;
1315
1316    if (last)
1317    {
1318        res_len += 1 + 2; // 0 CRLF
1319
for (int idx=0; idx<ftrs.length; idx++)
1320        res_len += ftrs[idx].getName().length() + 2 + // name ": "
1321
ftrs[idx].getValue().length() + 2; // value CRLF
1322
res_len += 2; // CRLF
1323
}
1324
1325    // allocate result
1326

1327    byte[] res = new byte[res_len];
1328    int r_off = 0;
1329
1330
1331    // fill result
1332

1333    if (len > 0)
1334    {
1335        hex_len.getBytes(0, hex_len.length(), res, r_off);
1336        r_off += hex_len.length();
1337        res[r_off++] = (byte) '\r';
1338        res[r_off++] = (byte) '\n';
1339
1340        System.arraycopy(data, off, res, r_off, len);
1341        r_off += len;
1342        res[r_off++] = (byte) '\r';
1343        res[r_off++] = (byte) '\n';
1344    }
1345
1346    if (last)
1347    {
1348        res[r_off++] = (byte) '0';
1349        res[r_off++] = (byte) '\r';
1350        res[r_off++] = (byte) '\n';
1351
1352        for (int idx=0; idx<ftrs.length; idx++)
1353        {
1354        ftrs[idx].getName().getBytes(0, ftrs[idx].getName().length(),
1355                         res, r_off);
1356        r_off += ftrs[idx].getName().length();
1357
1358        res[r_off++] = (byte) ':';
1359        res[r_off++] = (byte) ' ';
1360
1361        ftrs[idx].getValue().getBytes(0, ftrs[idx].getValue().length(),
1362                          res, r_off);
1363        r_off += ftrs[idx].getValue().length();
1364
1365        res[r_off++] = (byte) '\r';
1366        res[r_off++] = (byte) '\n';
1367        }
1368
1369        res[r_off++] = (byte) '\r';
1370        res[r_off++] = (byte) '\n';
1371    }
1372
1373    if (r_off != res.length)
1374        throw new Error JavaDoc("Calculated "+res.length+" bytes but wrote "+r_off+" bytes!");
1375
1376    return res;
1377    }
1378
1379
1380    /**
1381     * Decodes chunked data. The chunks are read from an InputStream, which
1382     * is assumed to be correctly positioned. Use 'xxx instanceof byte[]'
1383     * and 'xxx instanceof NVPair[]' to determine if this was data or the
1384     * last chunk.
1385     *
1386     * @param input the stream from which to read the next chunk.
1387     * @return If this was a data chunk then it returns a byte[]; else
1388     * it's the footer and it returns a NVPair[] containing the
1389     * footers.
1390     * @exception ParseException If any exception during parsing occured.
1391     * @exception IOException If any exception during reading occured.
1392     */

1393    public final static Object JavaDoc chunkedDecode(InputStream JavaDoc input)
1394        throws ParseException, IOException JavaDoc
1395    {
1396    int len = getChunkLength(input);
1397
1398    if (len > 0) // it's a chunk
1399
{
1400        byte[] res = new byte[len];
1401
1402        int off = 0;
1403        while (len != -1 && off < res.length)
1404        {
1405        len = input.read(res, off, res.length-off);
1406        off += len;
1407        }
1408
1409        if (len == -1)
1410        throw new ParseException("Premature EOF while reading chunk;" +
1411                     "Expected: "+res.length+" Bytes, " +
1412                     "Received: "+(off+1)+" Bytes");
1413
1414        input.read(); // CR
1415
input.read(); // LF
1416

1417        return res;
1418    }
1419    else // it's the end
1420
{
1421        NVPair[] res = new NVPair[0];
1422
1423        DataInputStream JavaDoc datain = new DataInputStream JavaDoc(input);
1424        String JavaDoc line;
1425
1426        // read and parse footer
1427
while ((line = datain.readLine()) != null && line.length() > 0)
1428        {
1429        int colon = line.indexOf(':');
1430        if (colon == -1)
1431            throw new ParseException("Error in Footer format: no "+
1432                         "':' found in '" + line + "'");
1433        res = Util.resizeArray(res, res.length+1);
1434        res[res.length-1] = new NVPair(line.substring(0, colon).trim(),
1435                           line.substring(colon+1).trim());
1436        }
1437
1438        return res;
1439    }
1440
1441    }
1442
1443
1444    /**
1445     * Gets the length of the chunk.
1446     *
1447     * @param input the stream from which to read the next chunk.
1448     * @return the length of chunk to follow (w/o trailing CR LF).
1449     * @exception ParseException If any exception during parsing occured.
1450     * @exception IOException If any exception during reading occured.
1451     */

1452    final static int getChunkLength(InputStream JavaDoc input)
1453        throws ParseException, IOException JavaDoc
1454    {
1455    byte[] hex_len = new byte[8]; // if they send more than 2GB chunks...
1456
int off = 0,
1457           ch;
1458
1459
1460    // read chunk length
1461

1462    while ((ch = input.read()) != '\r' && ch != '\n' &&
1463        ch != ';' && off < hex_len.length)
1464        hex_len[off++] = (byte) ch;
1465
1466    if (ch == ';') // chunk-ext (ignore it)
1467
while ((ch = input.read()) != '\r' && ch != '\n') ;
1468
1469    if (ch != '\n' && (ch != '\r' || input.read() != '\n'))
1470        throw new ParseException("Didn't find valid chunk length: " +
1471                     new String JavaDoc(hex_len, 0, 0, off));
1472
1473    // parse chunk length
1474

1475    int len;
1476    try
1477        { len = Integer.parseInt(new String JavaDoc(hex_len, 0, 0, off).trim(),
1478                     16); }
1479    catch (NumberFormatException JavaDoc nfe)
1480        { throw new ParseException("Didn't find valid chunk length: " +
1481                    new String JavaDoc(hex_len, 0, 0, off) ); }
1482
1483    return len;
1484    }
1485
1486}
1487
1488
Popular Tags