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