Codecs.java
上传用户:demmber
上传日期:2007-12-22
资源大小:717k
文件大小:48k
源码类别:

Java编程

开发平台:

Java

  1. /*
  2.  * @(#)Codecs.java 0.3-3 06/05/2001
  3.  *
  4.  *  This file is part of the HTTPClient package
  5.  *  Copyright (C) 1996-2001 Ronald Tschal鋜
  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.  *  The HTTPClient's home page is located at:
  28.  *
  29.  *  http://www.innovation.ch/java/HTTPClient/ 
  30.  *
  31.  */
  32. package HTTPClient;
  33. import java.util.BitSet;
  34. import java.util.Vector;
  35. import java.util.StringTokenizer;
  36. import java.io.IOException;
  37. import java.io.EOFException;
  38. import java.io.InputStream;
  39. import java.io.File;
  40. import java.io.FileInputStream;
  41. import java.io.FileOutputStream;
  42. import java.io.BufferedReader;
  43. import java.io.InputStreamReader;
  44. import java.io.UnsupportedEncodingException;
  45. import java.net.URLConnection;
  46. /**
  47.  * This class collects various encoders and decoders.
  48.  *
  49.  * @version 0.3-3  06/05/2001
  50.  * @author Ronald Tschal鋜
  51.  */
  52. public class Codecs
  53. {
  54.     private static BitSet  BoundChar;
  55.     private static BitSet  EBCDICUnsafeChar;
  56.     private static byte[]  Base64EncMap, Base64DecMap;
  57.     private static char[]  UUEncMap;
  58.     private static byte[]  UUDecMap;
  59.     private final static String ContDisp = "rnContent-Disposition: form-data; name="";
  60.     private final static String FileName = ""; filename="";
  61.     private final static String ContType = "rnContent-Type: ";
  62.     private final static String Boundary = "rn----------ieoau._._+2_8_GoodLuck8.3-dskdfJwSJKl234324jfLdsjfdAuaoei-----";
  63.     // Class Initializer
  64.     static
  65.     {
  66. // rfc-2046 & rfc-2045: (bcharsnospace & token)
  67. // used for multipart codings
  68. BoundChar = new BitSet(256);
  69. for (int ch='0'; ch <= '9'; ch++)  BoundChar.set(ch);
  70. for (int ch='A'; ch <= 'Z'; ch++)  BoundChar.set(ch);
  71. for (int ch='a'; ch <= 'z'; ch++)  BoundChar.set(ch);
  72. BoundChar.set('+');
  73. BoundChar.set('_');
  74. BoundChar.set('-');
  75. BoundChar.set('.');
  76. // EBCDIC unsafe characters to be quoted in quoted-printable
  77. // See first NOTE in section 6.7 of rfc-2045
  78. EBCDICUnsafeChar = new BitSet(256);
  79. EBCDICUnsafeChar.set('!');
  80. EBCDICUnsafeChar.set('"');
  81. EBCDICUnsafeChar.set('#');
  82. EBCDICUnsafeChar.set('$');
  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. // rfc-2045: Base64 Alphabet
  94. byte[] map = {
  95.     (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
  96.     (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L',
  97.     (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q', (byte)'R',
  98.     (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X',
  99.     (byte)'Y', (byte)'Z',
  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)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
  106.     (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' };
  107. Base64EncMap = map;
  108. Base64DecMap = new byte[128];
  109. for (int idx=0; idx<Base64EncMap.length; idx++)
  110.     Base64DecMap[Base64EncMap[idx]] = (byte) idx;
  111. // uuencode'ing maps
  112. UUEncMap = new char[64];
  113. for (int idx=0; idx<UUEncMap.length; idx++)
  114.     UUEncMap[idx] = (char) (idx + 0x20);
  115. UUDecMap = new byte[128];
  116. for (int idx=0; idx<UUEncMap.length; idx++)
  117.     UUDecMap[UUEncMap[idx]] = (byte) idx;
  118.     }
  119.     // Constructors
  120.     /**
  121.      * This class isn't meant to be instantiated.
  122.      */
  123.     private Codecs() {}
  124.     // Methods
  125.     /**
  126.      * This method encodes the given string using the base64-encoding
  127.      * specified in RFC-2045 (Section 6.8). It's used for example in the
  128.      * "Basic" authorization scheme.
  129.      *
  130.      * @param  str the string
  131.      * @return the base64-encoded <var>str</var>
  132.      */
  133.     public final static String base64Encode(String str)
  134.     {
  135. if (str == null)  return  null;
  136. try
  137.     { return new String(base64Encode(str.getBytes("8859_1")), "8859_1"); }
  138. catch (UnsupportedEncodingException uee)
  139.     { throw new Error(uee.toString()); }
  140.     }
  141.     /**
  142.      * This method encodes the given byte[] using the base64-encoding
  143.      * specified in RFC-2045 (Section 6.8).
  144.      *
  145.      * @param  data the data
  146.      * @return the base64-encoded <var>data</var>
  147.      */
  148.     public final static byte[] base64Encode(byte[] data)
  149.     {
  150. if (data == null)  return  null;
  151. int sidx, didx;
  152. byte dest[] = new byte[((data.length+2)/3)*4];
  153. // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
  154. for (sidx=0, didx=0; sidx < data.length-2; sidx += 3)
  155. {
  156.     dest[didx++] = Base64EncMap[(data[sidx] >>> 2) & 077];
  157.     dest[didx++] = Base64EncMap[(data[sidx+1] >>> 4) & 017 |
  158. (data[sidx] << 4) & 077];
  159.     dest[didx++] = Base64EncMap[(data[sidx+2] >>> 6) & 003 |
  160. (data[sidx+1] << 2) & 077];
  161.     dest[didx++] = Base64EncMap[data[sidx+2] & 077];
  162. }
  163. if (sidx < data.length)
  164. {
  165.     dest[didx++] = Base64EncMap[(data[sidx] >>> 2) & 077];
  166.     if (sidx < data.length-1)
  167.     {
  168. dest[didx++] = Base64EncMap[(data[sidx+1] >>> 4) & 017 |
  169.     (data[sidx] << 4) & 077];
  170. dest[didx++] = Base64EncMap[(data[sidx+1] << 2) & 077];
  171.     }
  172.     else
  173. dest[didx++] = Base64EncMap[(data[sidx] << 4) & 077];
  174. }
  175. // add padding
  176. for ( ; didx < dest.length; didx++)
  177.     dest[didx] = (byte) '=';
  178. return dest;
  179.     }
  180.     /**
  181.      * This method decodes the given string using the base64-encoding
  182.      * specified in RFC-2045 (Section 6.8).
  183.      *
  184.      * @param  str the base64-encoded string.
  185.      * @return the decoded <var>str</var>.
  186.      */
  187.     public final static String base64Decode(String str)
  188.     {
  189. if (str == null)  return  null;
  190. try
  191.     { return new String(base64Decode(str.getBytes("8859_1")), "8859_1"); }
  192. catch (UnsupportedEncodingException uee)
  193.     { throw new Error(uee.toString()); }
  194.     }
  195.     /**
  196.      * This method decodes the given byte[] using the base64-encoding
  197.      * specified in RFC-2045 (Section 6.8).
  198.      *
  199.      * @param  data the base64-encoded data.
  200.      * @return the decoded <var>data</var>.
  201.      */
  202.     public final static byte[] base64Decode(byte[] data)
  203.     {
  204. if (data == null)  return  null;
  205. int tail = data.length;
  206. while (data[tail-1] == '=')  tail--;
  207. byte dest[] = new byte[tail - data.length/4];
  208. // ascii printable to 0-63 conversion
  209. for (int idx = 0; idx <data.length; idx++)
  210.     data[idx] = Base64DecMap[data[idx]];
  211. // 4-byte to 3-byte conversion
  212. int sidx, didx;
  213. for (sidx = 0, didx=0; didx < dest.length-2; sidx += 4, didx += 3)
  214. {
  215.     dest[didx]   = (byte) ( ((data[sidx] << 2) & 255) |
  216.     ((data[sidx+1] >>> 4) & 003) );
  217.     dest[didx+1] = (byte) ( ((data[sidx+1] << 4) & 255) |
  218.     ((data[sidx+2] >>> 2) & 017) );
  219.     dest[didx+2] = (byte) ( ((data[sidx+2] << 6) & 255) |
  220.     (data[sidx+3] & 077) );
  221. }
  222. if (didx < dest.length)
  223.     dest[didx]   = (byte) ( ((data[sidx] << 2) & 255) |
  224.     ((data[sidx+1] >>> 4) & 003) );
  225. if (++didx < dest.length)
  226.     dest[didx]   = (byte) ( ((data[sidx+1] << 4) & 255) |
  227.     ((data[sidx+2] >>> 2) & 017) );
  228. return dest;
  229.     }
  230.     /**
  231.      * This method encodes the given byte[] using the unix uuencode
  232.      * encding. The output is split into lines starting with the encoded
  233.      * number of encoded octets in the line and ending with a newline.
  234.      * No line is longer than 45 octets (60 characters), not including
  235.      * length and newline.
  236.      *
  237.      * <P><em>Note:</em> just the raw data is encoded; no 'begin' and 'end'
  238.      * lines are added as is done by the unix <code>uuencode</code> utility.
  239.      *
  240.      * @param  data the data
  241.      * @return the uuencoded <var>data</var>
  242.      */
  243.     public final static char[] uuencode(byte[] data)
  244.     {
  245. if (data == null)      return  null;
  246. if (data.length == 0)  return  new char[0];
  247. int line_len = 45; // line length, in octets
  248. int sidx, didx;
  249. char nl[]   = System.getProperty("line.separator", "n").toCharArray(),
  250.      dest[] = new char[(data.length+2)/3*4 +
  251.     ((data.length+line_len-1)/line_len)*(nl.length+1)];
  252. // split into lines, adding line-length and line terminator
  253. for (sidx=0, didx=0; sidx+line_len < data.length; )
  254. {
  255.     // line length
  256.     dest[didx++] = UUEncMap[line_len];
  257.     // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
  258.     for (int end = sidx+line_len; sidx < end; sidx += 3)
  259.     {
  260. dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
  261. dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
  262.     (data[sidx] << 4) & 077];
  263. dest[didx++] = UUEncMap[(data[sidx+2] >>> 6) & 003 |
  264.     (data[sidx+1] << 2) & 077];
  265. dest[didx++] = UUEncMap[data[sidx+2] & 077];
  266.     }
  267.     // line terminator
  268.     for (int idx=0; idx<nl.length; idx++)  dest[didx++] = nl[idx];
  269. }
  270. // last line
  271. // line length
  272. dest[didx++] = UUEncMap[data.length-sidx];
  273. // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
  274. for (; sidx+2 < data.length; sidx += 3)
  275. {
  276.     dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
  277.     dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
  278. (data[sidx] << 4) & 077];
  279.     dest[didx++] = UUEncMap[(data[sidx+2] >>> 6) & 003 |
  280. (data[sidx+1] << 2) & 077];
  281.     dest[didx++] = UUEncMap[data[sidx+2] & 077];
  282. }
  283. if (sidx < data.length-1)
  284. {
  285.     dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
  286.     dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
  287. (data[sidx] << 4) & 077];
  288.     dest[didx++] = UUEncMap[(data[sidx+1] << 2) & 077];
  289.     dest[didx++] = UUEncMap[0];
  290. }
  291. else if (sidx < data.length)
  292. {
  293.     dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
  294.     dest[didx++] = UUEncMap[(data[sidx] << 4) & 077];
  295.     dest[didx++] = UUEncMap[0];
  296.     dest[didx++] = UUEncMap[0];
  297. }
  298. // line terminator
  299. for (int idx=0; idx<nl.length; idx++)  dest[didx++] = nl[idx];
  300. // sanity check
  301. if (didx != dest.length)
  302.     throw new Error("Calculated "+dest.length+" chars but wrote "+didx+" chars!");
  303. return dest;
  304.     }
  305.     /**
  306.      * TBD! How to return file name and mode?
  307.      *
  308.      * @param rdr the reader from which to read and decode the data
  309.      * @exception ParseException if either the "begin" or "end" line are not
  310.      *                           found, or the "begin" is incorrect
  311.      * @exception IOException if the <var>rdr</var> throws an IOException
  312.      */
  313.     private final static byte[] uudecode(BufferedReader rdr)
  314. throws ParseException, IOException
  315.     {
  316. String line, file_name;
  317. int    file_mode;
  318. // search for beginning
  319. while ((line = rdr.readLine()) != null  &&  !line.startsWith("begin "))
  320.     ;
  321. if (line == null)
  322.     throw new ParseException("'begin' line not found");
  323. // parse 'begin' line
  324. StringTokenizer tok = new StringTokenizer(line);
  325. tok.nextToken(); // throw away 'begin'
  326. try // extract mode
  327.     { file_mode = Integer.parseInt(tok.nextToken(), 8); }
  328. catch (Exception e)
  329.     { throw new ParseException("Invalid mode on line: " + line); }
  330. try // extract name
  331.     { file_name = tok.nextToken(); }
  332. catch (java.util.NoSuchElementException e)
  333.     { throw new ParseException("No file name found on line: " + line); }
  334. // read and parse body
  335. byte[] body = new byte[1000];
  336. int    off  = 0;
  337. while ((line = rdr.readLine()) != null  &&  !line.equals("end"))
  338. {
  339.     byte[] tmp = uudecode(line.toCharArray());
  340.     if (off + tmp.length > body.length)
  341. body = Util.resizeArray(body, off+1000);
  342.     System.arraycopy(tmp, 0, body, off, tmp.length);
  343.     off += tmp.length;
  344. }
  345. if (line == null)
  346.     throw new ParseException("'end' line not found");
  347. return Util.resizeArray(body, off);
  348.     }
  349.     /**
  350.      * This method decodes the given uuencoded char[].
  351.      *
  352.      * <P><em>Note:</em> just the actual data is decoded; any 'begin' and
  353.      * 'end' lines such as those generated by the unix <code>uuencode</code>
  354.      * utility must not be included.
  355.      *
  356.      * @param  data the uuencode-encoded data.
  357.      * @return the decoded <var>data</var>.
  358.      */
  359.     public final static byte[] uudecode(char[] data)
  360.     {
  361. if (data == null)  return  null;
  362. int sidx, didx;
  363. byte dest[] = new byte[data.length/4*3];
  364. for (sidx=0, didx=0; sidx < data.length; )
  365. {
  366.     // get line length (in number of encoded octets)
  367.     int len = UUDecMap[data[sidx++]];
  368.     // ascii printable to 0-63 and 4-byte to 3-byte conversion
  369.     int end = didx+len;
  370.     for (; didx < end-2; sidx += 4)
  371.     {
  372. byte A = UUDecMap[data[sidx]],
  373.      B = UUDecMap[data[sidx+1]],
  374.      C = UUDecMap[data[sidx+2]],
  375.      D = UUDecMap[data[sidx+3]];
  376. dest[didx++] = (byte) ( ((A << 2) & 255) | ((B >>> 4) & 003) );
  377. dest[didx++] = (byte) ( ((B << 4) & 255) | ((C >>> 2) & 017) );
  378. dest[didx++] = (byte) ( ((C << 6) & 255) | (D & 077) );
  379.     }
  380.     if (didx < end)
  381.     {
  382. byte A = UUDecMap[data[sidx]],
  383.      B = UUDecMap[data[sidx+1]];
  384. dest[didx++] = (byte) ( ((A << 2) & 255) | ((B >>> 4) & 003) );
  385.     }
  386.     if (didx < end)
  387.     {
  388. byte B = UUDecMap[data[sidx+1]],
  389.      C = UUDecMap[data[sidx+2]];
  390. dest[didx++] = (byte) ( ((B << 4) & 255) | ((C >>> 2) & 017) );
  391.     }
  392.     // skip padding
  393.     while (sidx < data.length  &&
  394.    data[sidx] != 'n'  &&  data[sidx] != 'r')
  395. sidx++;
  396.     // skip end of line
  397.     while (sidx < data.length  &&
  398.    (data[sidx] == 'n'  ||  data[sidx] == 'r'))
  399. sidx++;
  400. }
  401. return Util.resizeArray(dest, didx);
  402.     }
  403.     /**
  404.      * This method does a quoted-printable encoding of the given string
  405.      * according to RFC-2045 (Section 6.7). <em>Note:</em> this assumes
  406.      * 8-bit characters.
  407.      *
  408.      * @param  str the string
  409.      * @return the quoted-printable encoded string
  410.      */
  411.     public final static String quotedPrintableEncode(String str)
  412.     {
  413. if (str == null)  return  null;
  414. char map[] =
  415.     {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'},
  416.      nl[]  = System.getProperty("line.separator", "n").toCharArray(),
  417.      res[] = new char[(int) (str.length()*1.5)],
  418.      src[] = str.toCharArray();
  419. char ch;
  420. int  cnt  = 0,
  421.      didx = 1,
  422.      last = 0,
  423.      slen = str.length();
  424. for (int sidx=0; sidx < slen; sidx++)
  425. {
  426.     ch = src[sidx];
  427.     if (ch == nl[0]   &&  match(src, sidx, nl)) // Rule #4
  428.     {
  429. if (res[didx-1] == ' ') // Rule #3
  430. {
  431.     res[didx-1] = '=';
  432.     res[didx++] = '2';
  433.     res[didx++] = '0';
  434. }
  435. else if (res[didx-1] == 't') // Rule #3
  436. {
  437.     res[didx-1] = '=';
  438.     res[didx++] = '0';
  439.     res[didx++] = '9';
  440. }
  441. res[didx++] = 'r';
  442. res[didx++] = 'n';
  443. sidx += nl.length - 1;
  444. cnt = didx;
  445.     }
  446.     else if (ch > 126  ||  (ch < 32  &&  ch != 't')  ||  ch == '='  ||
  447.      EBCDICUnsafeChar.get((int) ch))
  448.     { // Rule #1, #2
  449. res[didx++] = '=';
  450. res[didx++] = map[(ch & 0xf0) >>> 4];
  451. res[didx++] = map[ch & 0x0f];
  452.     }
  453.     else // Rule #1
  454.     {
  455. res[didx++] = ch;
  456.     }
  457.     if (didx > cnt+70) // Rule #5
  458.     {
  459. res[didx++] = '=';
  460. res[didx++] = 'r';
  461. res[didx++] = 'n';
  462. cnt = didx;
  463.     }
  464.     if (didx > res.length-5)
  465. res = Util.resizeArray(res, res.length+500);
  466. }
  467. return String.valueOf(res, 1, didx-1);
  468.     }
  469.     private final static boolean match(char[] str, int start, char[] arr)
  470.     {
  471. if (str.length < start + arr.length)  return false;
  472. for (int idx=1; idx < arr.length; idx++)
  473.     if (str[start+idx] != arr[idx])  return false;
  474. return true;
  475.     }
  476.     /**
  477.      * This method does a quoted-printable decoding of the given string
  478.      * according to RFC-2045 (Section 6.7). <em>Note:</em> this method
  479.      * expects the whole message in one chunk, not line by line.
  480.      *
  481.      * @param  str the message
  482.      * @return the decoded message
  483.      * @exception ParseException If a '=' is not followed by a valid
  484.      *                           2-digit hex number or 'rn'.
  485.      */
  486.     public final static String quotedPrintableDecode(String str)
  487.     throws ParseException
  488.     {
  489. if (str == null)  return  null;
  490. char res[] = new char[(int) (str.length()*1.1)],
  491.      src[] = str.toCharArray(),
  492.      nl[]  = System.getProperty("line.separator", "n").toCharArray();
  493. int last   = 0,
  494.     didx   = 0,
  495.     slen   = str.length();
  496. for (int sidx=0; sidx<slen; )
  497. {
  498.     char ch = src[sidx++];
  499.     if (ch == '=')
  500.     {
  501. if (sidx >= slen-1)
  502.     throw new ParseException("Premature end of input detected");
  503. if (src[sidx] == 'n'  ||  src[sidx] == 'r')
  504. { // Rule #5
  505.     sidx++;
  506.     if (src[sidx-1] == 'r'  &&
  507. src[sidx] == 'n')
  508. sidx++;
  509. }
  510. else // Rule #1
  511. {
  512.     char repl;
  513.     int hi = Character.digit(src[sidx], 16),
  514. lo = Character.digit(src[sidx+1], 16);
  515.     if ((hi | lo) < 0)
  516. throw new ParseException(new String(src, sidx-1, 3) +
  517. " is an invalid code");
  518.     else
  519.     {
  520. repl = (char) (hi << 4 | lo);
  521. sidx += 2;
  522.     }
  523.     res[didx++] = repl;
  524. }
  525. last = didx;
  526.     }
  527.     else if (ch == 'n'  ||  ch == 'r') // Rule #4
  528.     {
  529. if (ch == 'r'  &&  sidx < slen  &&  src[sidx] == 'n')
  530.     sidx++;
  531. for (int idx=0; idx<nl.length; idx++)
  532.     res[last++] = nl[idx];
  533. didx = last;
  534.     }
  535.     else // Rule #1, #2
  536.     {
  537. res[didx++] = ch;
  538. if (ch != ' '  &&  ch != 't') // Rule #3
  539.     last = didx;
  540.     }
  541.     if (didx > res.length-nl.length-2)
  542. res = Util.resizeArray(res, res.length+500);
  543. }
  544. return new String(res, 0, didx);
  545.     }
  546.     /**
  547.      * This method urlencodes the given string. This method is here for
  548.      * symmetry reasons and just calls java.net.URLEncoder.encode().
  549.      *
  550.      * @param  str the string
  551.      * @return the url-encoded string
  552.      */
  553.     public final static String URLEncode(String str)
  554.     {
  555. if (str == null)  return  null;
  556. return java.net.URLEncoder.encode(str);
  557.     }
  558.     /**
  559.      * This method decodes the given urlencoded string.
  560.      *
  561.      * @param  str the url-encoded string
  562.      * @return the decoded string
  563.      * @exception ParseException If a '%' is not followed by a valid
  564.      *                           2-digit hex number.
  565.      */
  566.     public final static String URLDecode(String str) throws ParseException
  567.     {
  568. if (str == null)  return  null;
  569. char[] res  = new char[str.length()];
  570. int    didx = 0;
  571. for (int sidx=0; sidx<str.length(); sidx++)
  572. {
  573.     char ch = str.charAt(sidx);
  574.     if (ch == '+')
  575. res[didx++] = ' ';
  576.     else if (ch == '%')
  577.     {
  578. try
  579. {
  580.     res[didx++] = (char)
  581. Integer.parseInt(str.substring(sidx+1,sidx+3), 16);
  582.     sidx += 2;
  583. }
  584. catch (NumberFormatException e)
  585. {
  586.     throw new ParseException(str.substring(sidx,sidx+3) +
  587.     " is an invalid code");
  588. }
  589.     }
  590.     else
  591. res[didx++] = ch;
  592. }
  593. return String.valueOf(res, 0, didx);
  594.     }
  595.     /**
  596.      * This method decodes a multipart/form-data encoded string.
  597.      *
  598.      * @param     data        the form-data to decode.
  599.      * @param     cont_type   the content type header (must contain the
  600.      *       boundary string).
  601.      * @param     dir         the directory to create the files in.
  602.      * @return                an array of name/value pairs, one for each part;
  603.      *                        the name is the 'name' attribute given in the
  604.      *                        Content-Disposition header; the value is either
  605.      *                        the name of the file if a filename attribute was
  606.      *                        found, or the contents of the part.
  607.      * @exception IOException If any file operation fails.
  608.      * @exception ParseException If an error during parsing occurs.
  609.      * @see #mpFormDataDecode(byte[], java.lang.String, java.lang.String, HTTPClient.FilenameMangler)
  610.      */
  611.     public final static NVPair[] mpFormDataDecode(byte[] data, String cont_type,
  612.   String dir)
  613.     throws IOException, ParseException
  614.     {
  615. return mpFormDataDecode(data, cont_type, dir, null);
  616.     }
  617.     /**
  618.      * This method decodes a multipart/form-data encoded string. The boundary
  619.      * is parsed from the <var>cont_type</var> parameter, which must be of the
  620.      * form 'multipart/form-data; boundary=...'. Any encoded files are created
  621.      * in the directory specified by <var>dir</var> using the encoded filename.
  622.      *
  623.      * <P><em>Note:</em> Does not handle nested encodings (yet).
  624.      *
  625.      * <P>Examples: If you're receiving a multipart/form-data encoded response
  626.      * from a server you could use something like:
  627.      * <PRE>
  628.      *     NVPair[] opts = Codecs.mpFormDataDecode(resp.getData(),
  629.      *                                  resp.getHeader("Content-type"), ".");
  630.      * </PRE>
  631.      * If you're using this in a Servlet to decode the body of a request from
  632.      * a client you could use something like:
  633.      * <PRE>
  634.      *     byte[] body = new byte[req.getContentLength()];
  635.      *     new DataInputStream(req.getInputStream()).readFully(body);
  636.      *     NVPair[] opts = Codecs.mpFormDataDecode(body, req.getContentType(),
  637.      *                                             ".");
  638.      * </PRE>
  639.      * (where 'req' is the HttpServletRequest).
  640.      *
  641.      * <P>Assuming the data received looked something like:
  642.      * <PRE>
  643.      * -----------------------------114975832116442893661388290519
  644.      * Content-Disposition: form-data; name="option"
  645.      *                                                         &nbsp;
  646.      * doit
  647.      * -----------------------------114975832116442893661388290519
  648.      * Content-Disposition: form-data; name="comment"; filename="comment.txt"
  649.      *                                                         &nbsp;
  650.      * Gnus and Gnats are not Gnomes.
  651.      * -----------------------------114975832116442893661388290519--
  652.      * </PRE>
  653.      * you would get one file called <VAR>comment.txt</VAR> in the current
  654.      * directory, and opts would contain two elements: {"option", "doit"}
  655.      * and {"comment", "comment.txt"}
  656.      *
  657.      * @param     data        the form-data to decode.
  658.      * @param     cont_type   the content type header (must contain the
  659.      *       boundary string).
  660.      * @param     dir         the directory to create the files in.
  661.      * @param     mangler     the filename mangler, or null if no mangling is
  662.      *                        to be done. This is invoked just before each
  663.      *                        file is created and written, thereby allowing
  664.      *                        you to control the names of the files.
  665.      * @return                an array of name/value pairs, one for each part;
  666.      *                        the name is the 'name' attribute given in the
  667.      *                        Content-Disposition header; the value is either
  668.      *                        the name of the file if a filename attribute was
  669.      *                        found, or the contents of the part.
  670.      * @exception IOException If any file operation fails.
  671.      * @exception ParseException If an error during parsing occurs.
  672.      */
  673.     public final static NVPair[] mpFormDataDecode(byte[] data, String cont_type,
  674.   String dir,
  675.   FilenameMangler mangler)
  676.     throws IOException, ParseException
  677.     {
  678. // Find and extract boundary string
  679. String bndstr = Util.getParameter("boundary", cont_type);
  680. if (bndstr == null)
  681.     throw new ParseException("'boundary' parameter not found in Content-type: " + cont_type);
  682. byte[] srtbndry = (    "--" + bndstr + "rn").getBytes("8859_1"),
  683.        boundary = ("rn--" + bndstr + "rn").getBytes("8859_1"),
  684.        endbndry = ("rn--" + bndstr + "--"  ).getBytes("8859_1");
  685. // setup search routines
  686. int[] bs = Util.compile_search(srtbndry),
  687.       bc = Util.compile_search(boundary),
  688.       be = Util.compile_search(endbndry);
  689. // let's start parsing the actual data
  690. int start = Util.findStr(srtbndry, bs, data, 0, data.length);
  691. if (start == -1) // didn't even find the start
  692.     throw new ParseException("Starting boundary not found: " +
  693.      new String(srtbndry, "8859_1"));
  694. start += srtbndry.length;
  695. NVPair[] res  = new NVPair[10];
  696. boolean  done = false;
  697. int      idx;
  698. for (idx=0; !done; idx++)
  699. {
  700.     // find end of this part
  701.     int end = Util.findStr(boundary, bc, data, start, data.length);
  702.     if (end == -1) // must be the last part
  703.     {
  704. end = Util.findStr(endbndry, be, data, start, data.length);
  705. if (end == -1)
  706.     throw new ParseException("Ending boundary not found: " +
  707.      new String(endbndry, "8859_1"));
  708. done = true;
  709.     }
  710.     // parse header(s)
  711.     String hdr, name=null, value, filename=null, cont_disp = null;
  712.     while (true)
  713.     {
  714. int next = findEOL(data, start) + 2;
  715. if (next-2 <= start)  break; // empty line -> end of headers
  716. hdr      = new String(data, start, next-2-start, "8859_1");
  717. start    = next;
  718. // handle line continuation
  719. byte ch;
  720. while (next < data.length-1  &&
  721.        ((ch = data[next]) == ' '  ||  ch == 't'))
  722. {
  723.     next   = findEOL(data, start) + 2;
  724.     hdr   += new String(data, start, next-2-start, "8859_1");
  725.     start  = next;
  726. }
  727. if (!hdr.regionMatches(true, 0, "Content-Disposition", 0, 19))
  728.     continue;
  729. Vector pcd =
  730. Util.parseHeader(hdr.substring(hdr.indexOf(':')+1));
  731. HttpHeaderElement elem = Util.getElement(pcd, "form-data");
  732. if (elem == null)
  733.     throw new ParseException("Expected 'Content-Disposition: form-data' in line: "+hdr);
  734. NVPair[] params = elem.getParams();
  735. name = filename = null;
  736. for (int pidx=0; pidx<params.length; pidx++)
  737. {
  738.     if (params[pidx].getName().equalsIgnoreCase("name"))
  739. name = params[pidx].getValue();
  740.     if (params[pidx].getName().equalsIgnoreCase("filename"))
  741. filename = params[pidx].getValue();
  742. }
  743. if (name == null)
  744.     throw new ParseException("'name' parameter not found in header: "+hdr);
  745. cont_disp = hdr;
  746.     }
  747.     start += 2;
  748.     if (start > end)
  749. throw new ParseException("End of header not found at offset "+end);
  750.     if (cont_disp == null)
  751. throw new ParseException("Missing 'Content-Disposition' header at offset "+start);
  752.     // handle data for this part
  753.     if (filename != null) // It's a file
  754.     {
  755. if (mangler != null)
  756.     filename = mangler.mangleFilename(filename, name);
  757. if (filename != null  &&  filename.length() > 0)
  758. {
  759.     File file = new File(dir, filename);
  760.     FileOutputStream out = new FileOutputStream(file);
  761.     out.write(data, start, end-start);
  762.     out.close();
  763. }
  764. value = filename;
  765.     }
  766.     else // It's simple data
  767.     {
  768. value = new String(data, start, end-start, "8859_1");
  769.     }
  770.     if (idx >= res.length)
  771. res = Util.resizeArray(res, idx+10);
  772.     res[idx] = new NVPair(name, value);
  773.     start = end + boundary.length;
  774. }
  775. return Util.resizeArray(res, idx);
  776.     }
  777.     /**
  778.      * Searches for the next CRLF in an array.
  779.      *
  780.      * @param  arr the byte array to search.
  781.      * @param  off the offset at which to start the search.
  782.      * @return the position of the CR or (arr.length-2) if not found
  783.      */
  784.     private final static int findEOL(byte[] arr, int off)
  785.     {
  786. while (off < arr.length-1  &&
  787.        !(arr[off++] == 'r'  &&  arr[off] == 'n'));
  788. return off-1;
  789.     }
  790.     /**
  791.      * This method encodes name/value pairs and files into a byte array
  792.      * using the multipart/form-data encoding.
  793.      *
  794.      * @param     opts        the simple form-data to encode (may be null);
  795.      *                        for each NVPair the name refers to the 'name'
  796.      *                        attribute to be used in the header of the part,
  797.      *                        and the value is contents of the part.
  798.      * @param     files       the files to encode (may be null); for each
  799.      *                        NVPair the name refers to the 'name' attribute
  800.      *                        to be used in the header of the part, and the
  801.      *                        value is the actual filename (the file will be
  802.      *                        read and it's contents put in the body of that
  803.      *                        part).
  804.      * @param     ct_hdr      this returns a new NVPair in the 0'th element
  805.      *                        which contains name = "Content-Type",
  806.      *       value = "multipart/form-data; boundary=..."
  807.      *                        (the reason this parameter is an array is
  808.      *                        because a) that's the only way to simulate
  809.      *                        pass-by-reference and b) you need an array for
  810.      *                        the headers parameter to the Post() or Put()
  811.      *                        anyway).
  812.      * @return                an encoded byte array containing all the opts
  813.      *       and files.
  814.      * @exception IOException If any file operation fails.
  815.      * @see #mpFormDataEncode(HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.FilenameMangler)
  816.      */
  817.     public final static byte[] mpFormDataEncode(NVPair[] opts, NVPair[] files,
  818. NVPair[] ct_hdr)
  819.     throws IOException
  820.     {
  821. return mpFormDataEncode(opts, files, ct_hdr, null);
  822.     }
  823.     private static NVPair[] dummy = new NVPair[0];
  824.     /**
  825.      * This method encodes name/value pairs and files into a byte array
  826.      * using the multipart/form-data encoding. The boundary is returned
  827.      * as part of <var>ct_hdr</var>.
  828.      * <BR>Example:
  829.      * <PRE>
  830.      *     NVPair[] opts = { new NVPair("option", "doit") };
  831.      *     NVPair[] file = { new NVPair("comment", "comment.txt") };
  832.      *     NVPair[] hdrs = new NVPair[1];
  833.      *     byte[]   data = Codecs.mpFormDataEncode(opts, file, hdrs);
  834.      *     con.Post("/cgi-bin/handle-it", data, hdrs);
  835.      * </PRE>
  836.      * <VAR>data</VAR> will look something like the following:
  837.      * <PRE>
  838.      * -----------------------------114975832116442893661388290519
  839.      * Content-Disposition: form-data; name="option"
  840.      *                                                         &nbsp;
  841.      * doit
  842.      * -----------------------------114975832116442893661388290519
  843.      * Content-Disposition: form-data; name="comment"; filename="comment.txt"
  844.      * Content-Type: text/plain
  845.      *                                                         &nbsp;
  846.      * Gnus and Gnats are not Gnomes.
  847.      * -----------------------------114975832116442893661388290519--
  848.      * </PRE>
  849.      * where the "Gnus and Gnats ..." is the contents of the file
  850.      * <VAR>comment.txt</VAR> in the current directory.
  851.      *
  852.      * <P>If no elements are found in the parameters then a zero-length
  853.      * byte[] is returned and the content-type is set to
  854.      * <var>application/octet-string</var> (because a multipart must
  855.      * always have at least one part.
  856.      *
  857.      * <P>For files an attempt is made to discover the content-type, and if
  858.      * found a Content-Type header will be added to that part. The content type
  859.      * is retrieved using java.net.URLConnection.guessContentTypeFromName() -
  860.      * see java.net.URLConnection.setFileNameMap() for how to modify that map.
  861.      * Note that under JDK 1.1 by default the map seems to be empty. If you
  862.      * experience troubles getting the server to accept the data then make
  863.      * sure the fileNameMap is returning a content-type for each file (this
  864.      * may mean you'll have to set your own).
  865.      *
  866.      * @param     opts        the simple form-data to encode (may be null);
  867.      *                        for each NVPair the name refers to the 'name'
  868.      *                        attribute to be used in the header of the part,
  869.      *                        and the value is contents of the part.
  870.      *                        null elements in the array are ingored.
  871.      * @param     files       the files to encode (may be null); for each
  872.      *                        NVPair the name refers to the 'name' attribute
  873.      *                        to be used in the header of the part, and the
  874.      *                        value is the actual filename (the file will be
  875.      *                        read and it's contents put in the body of
  876.      *                        that part). null elements in the array
  877.      *                        are ingored.
  878.      * @param     ct_hdr      this returns a new NVPair in the 0'th element
  879.      *                        which contains name = "Content-Type",
  880.      *       value = "multipart/form-data; boundary=..."
  881.      *                        (the reason this parameter is an array is
  882.      *                        because a) that's the only way to simulate
  883.      *                        pass-by-reference and b) you need an array for
  884.      *                        the headers parameter to the Post() or Put()
  885.      *                        anyway). The exception to this is that if no
  886.      *                        opts or files are given the type is set to
  887.      *                        "application/octet-stream" instead.
  888.      * @param     mangler     the filename mangler, or null if no mangling is
  889.      *                        to be done. This allows you to change the name
  890.      *                        used in the <var>filename</var> attribute of the
  891.      *                        Content-Disposition header. Note: the mangler
  892.      *                        will be invoked twice for each filename.
  893.      * @return                an encoded byte array containing all the opts
  894.      *       and files.
  895.      * @exception IOException If any file operation fails.
  896.      */
  897.     public final static byte[] mpFormDataEncode(NVPair[] opts, NVPair[] files,
  898. NVPair[] ct_hdr,
  899. FilenameMangler mangler)
  900.     throws IOException
  901.     {
  902. byte[] boundary  = Boundary.getBytes("8859_1"),
  903.        cont_disp = ContDisp.getBytes("8859_1"),
  904.        cont_type = ContType.getBytes("8859_1"),
  905.        filename  = FileName.getBytes("8859_1");
  906. int len = 0,
  907.     hdr_len = boundary.length + cont_disp.length+1 + 2 +  2;
  908.     //        rn --  bnd      rn C-D: ..; n=".." rn rn
  909. if (opts == null)   opts  = dummy;
  910. if (files == null)  files = dummy;
  911. // Calculate the length of the data
  912. for (int idx=0; idx<opts.length; idx++)
  913. {
  914.     if (opts[idx] == null)  continue;
  915.     len += hdr_len + opts[idx].getName().length() +
  916.    opts[idx].getValue().length();
  917. }
  918. for (int idx=0; idx<files.length; idx++)
  919. {
  920.     if (files[idx] == null)  continue;
  921.     File file = new File(files[idx].getValue());
  922.     String fname = file.getName();
  923.     if (mangler != null)
  924. fname = mangler.mangleFilename(fname, files[idx].getName());
  925.     if (fname != null)
  926.     {
  927. len += hdr_len + files[idx].getName().length() + filename.length;
  928. len += fname.length() + file.length();
  929. String ct = CT.getContentType(file.getName());
  930. if (ct != null)
  931.     len += cont_type.length + ct.length();
  932.     }
  933. }
  934. if (len == 0)
  935. {
  936.     ct_hdr[0] = new NVPair("Content-Type", "application/octet-stream");
  937.     return new byte[0];
  938. }
  939. len -= 2; // first CR LF is not written
  940. len += boundary.length + 2 + 2; // rn -- bnd -- rn
  941. // Now fill array
  942. byte[] res = new byte[len];
  943. int    pos = 0;
  944. NewBound: for (int new_c=0x30303030; new_c!=0x7A7A7A7A; new_c++)
  945. {
  946.     pos = 0;
  947.     // modify boundary in hopes that it will be unique
  948.     while (!BoundChar.get(new_c     & 0xff)) new_c += 0x00000001;
  949.     while (!BoundChar.get(new_c>>8  & 0xff)) new_c += 0x00000100;
  950.     while (!BoundChar.get(new_c>>16 & 0xff)) new_c += 0x00010000;
  951.     while (!BoundChar.get(new_c>>24 & 0xff)) new_c += 0x01000000;
  952.     boundary[40] = (byte) (new_c     & 0xff);
  953.     boundary[42] = (byte) (new_c>>8  & 0xff);
  954.     boundary[44] = (byte) (new_c>>16 & 0xff);
  955.     boundary[46] = (byte) (new_c>>24 & 0xff);
  956.     int off = 2;
  957.     int[] bnd_cmp = Util.compile_search(boundary);
  958.     for (int idx=0; idx<opts.length; idx++)
  959.     {
  960. if (opts[idx] == null)  continue;
  961. System.arraycopy(boundary, off, res, pos, boundary.length-off);
  962. pos += boundary.length - off;
  963. off  = 0;
  964. int  start = pos;
  965. System.arraycopy(cont_disp, 0, res, pos, cont_disp.length);
  966. pos += cont_disp.length;
  967. int nlen = opts[idx].getName().length();
  968. System.arraycopy(opts[idx].getName().getBytes("8859_1"), 0, res, pos, nlen);
  969. pos += nlen;
  970. res[pos++] = (byte) '"';
  971. res[pos++] = (byte) 'r';
  972. res[pos++] = (byte) 'n';
  973. res[pos++] = (byte) 'r';
  974. res[pos++] = (byte) 'n';
  975. int vlen = opts[idx].getValue().length();
  976. System.arraycopy(opts[idx].getValue().getBytes("8859_1"), 0, res, pos, vlen);
  977. pos += vlen;
  978. if ((pos-start) >= boundary.length  &&
  979.     Util.findStr(boundary, bnd_cmp, res, start, pos) != -1)
  980.     continue NewBound;
  981.     }
  982.     for (int idx=0; idx<files.length; idx++)
  983.     {
  984. if (files[idx] == null)  continue;
  985. File file = new File(files[idx].getValue());
  986. String fname = file.getName();
  987. if (mangler != null)
  988.     fname = mangler.mangleFilename(fname, files[idx].getName());
  989. if (fname == null)  continue;
  990. System.arraycopy(boundary, off, res, pos, boundary.length-off);
  991. pos += boundary.length - off;
  992. off  = 0;
  993. int start = pos;
  994. System.arraycopy(cont_disp, 0, res, pos, cont_disp.length);
  995. pos += cont_disp.length;
  996. int nlen = files[idx].getName().length();
  997. System.arraycopy(files[idx].getName().getBytes("8859_1"), 0, res, pos, nlen);
  998. pos += nlen;
  999. System.arraycopy(filename, 0, res, pos, filename.length);
  1000. pos += filename.length;
  1001. nlen = fname.length();
  1002. System.arraycopy(fname.getBytes("8859_1"), 0, res, pos, nlen);
  1003. pos += nlen;
  1004. res[pos++] = (byte) '"';
  1005. String ct = CT.getContentType(file.getName());
  1006. if (ct != null)
  1007. {
  1008.     System.arraycopy(cont_type, 0, res, pos, cont_type.length);
  1009.     pos += cont_type.length;
  1010.     System.arraycopy(ct.getBytes("8859_1"), 0, res, pos, ct.length());
  1011.     pos += ct.length();
  1012. }
  1013. res[pos++] = (byte) 'r';
  1014. res[pos++] = (byte) 'n';
  1015. res[pos++] = (byte) 'r';
  1016. res[pos++] = (byte) 'n';
  1017. nlen = (int) file.length();
  1018. FileInputStream fin = new FileInputStream(file);
  1019. while (nlen > 0)
  1020. {
  1021.     int got = fin.read(res, pos, nlen);
  1022.     nlen -= got;
  1023.     pos += got;
  1024. }
  1025. fin.close();
  1026. if ((pos-start) >= boundary.length  &&
  1027.     Util.findStr(boundary, bnd_cmp, res, start, pos) != -1)
  1028.     continue NewBound;
  1029.     }
  1030.     break NewBound;
  1031. }
  1032. System.arraycopy(boundary, 0, res, pos, boundary.length);
  1033. pos += boundary.length;
  1034. res[pos++] = (byte) '-';
  1035. res[pos++] = (byte) '-';
  1036. res[pos++] = (byte) 'r';
  1037. res[pos++] = (byte) 'n';
  1038. if (pos != len)
  1039.     throw new Error("Calculated "+len+" bytes but wrote "+pos+" bytes!");
  1040. /* the boundary parameter should be quoted (rfc-2046, section 5.1.1)
  1041.  * but too many script authors are not capable of reading specs...
  1042.  * So, I give up and don't quote it.
  1043.  */
  1044. ct_hdr[0] = new NVPair("Content-Type",
  1045.        "multipart/form-data; boundary=" +
  1046.        new String(boundary, 4, boundary.length-4, "8859_1"));
  1047. return res;
  1048.     }
  1049.     private static class CT extends URLConnection
  1050.     {
  1051. protected static final String getContentType(String fname)
  1052. {
  1053.     return guessContentTypeFromName(fname);
  1054. }
  1055. private CT() { super(null); }
  1056. public void connect() { }
  1057.     }
  1058.     /**
  1059.      * Turns an array of name/value pairs into the string
  1060.      * "name1=value1&name2=value2&name3=value3". The names and values are
  1061.      * first urlencoded. This is the form in which form-data is passed to
  1062.      * a cgi script.
  1063.      *
  1064.      * @param pairs the array of name/value pairs
  1065.      * @return a string containg the encoded name/value pairs
  1066.      */
  1067.     public final static String nv2query(NVPair pairs[])
  1068.     {
  1069. if (pairs == null)
  1070.     return null;
  1071. int          idx;
  1072. StringBuffer qbuf = new StringBuffer();
  1073. for (idx = 0; idx < pairs.length; idx++)
  1074. {
  1075.     if (pairs[idx] != null)
  1076. qbuf.append(URLEncode(pairs[idx].getName()) + "=" +
  1077.     URLEncode(pairs[idx].getValue()) + "&");
  1078. }
  1079. if (qbuf.length() > 0)
  1080.     qbuf.setLength(qbuf.length()-1); // remove trailing '&'
  1081. return qbuf.toString();
  1082.     }
  1083.     /**
  1084.      * Turns a string of the form "name1=value1&name2=value2&name3=value3"
  1085.      * into an array of name/value pairs. The names and values are
  1086.      * urldecoded. The query string is in the form in which form-data is
  1087.      * received in a cgi script.
  1088.      *
  1089.      * @param query the query string containing the encoded name/value pairs
  1090.      * @return an array of NVPairs
  1091.      * @exception ParseException If the '=' is missing in any field, or if
  1092.      *  the urldecoding of the name or value fails
  1093.      */
  1094.     public final static NVPair[] query2nv(String query)  throws ParseException
  1095.     {
  1096. if (query == null) return null;
  1097. int idx = -1,
  1098.     cnt = 1;
  1099. while ((idx = query.indexOf('&', idx+1)) != -1)  cnt ++;
  1100. NVPair[] pairs = new NVPair[cnt];
  1101. for (idx=0, cnt=0; cnt<pairs.length; cnt++)
  1102. {
  1103.     int eq  = query.indexOf('=', idx);
  1104.     int end = query.indexOf('&', idx);
  1105.     if (end == -1)  end = query.length();
  1106.     if (eq == -1  ||  eq >= end)
  1107. throw new ParseException("'=' missing in " +
  1108.  query.substring(idx, end));
  1109.     pairs[cnt] =
  1110.     new  NVPair(URLDecode(query.substring(idx,eq)),
  1111. URLDecode(query.substring(eq+1,end)));
  1112.     idx = end + 1;
  1113. }
  1114. return pairs;
  1115.     }
  1116.     /**
  1117.      * Encodes data used the chunked encoding. <var>last</var> signales if
  1118.      * this is the last chunk, in which case the appropriate footer is
  1119.      * generated.
  1120.      *
  1121.      * @param data  the data to be encoded; may be null.
  1122.      * @param ftrs  optional headers to include in the footer (ignored if
  1123.      *              not last); may be null.
  1124.      * @param last  whether this is the last chunk.
  1125.      * @return an array of bytes containing the chunk
  1126.      */
  1127.     public final static byte[] chunkedEncode(byte[] data, NVPair[] ftrs,
  1128.      boolean last)
  1129.     {
  1130. return
  1131.     chunkedEncode(data, 0, data == null ? 0 : data.length, ftrs, last);
  1132.     }
  1133.     /**
  1134.      * Encodes data used the chunked encoding. <var>last</var> signales if
  1135.      * this is the last chunk, in which case the appropriate footer is
  1136.      * generated.
  1137.      *
  1138.      * @param data  the data to be encoded; may be null.
  1139.      * @param off   an offset into the <var>data</var>
  1140.      * @param len   the number of bytes to take from <var>data</var>
  1141.      * @param ftrs  optional headers to include in the footer (ignored if
  1142.      *              not last); may be null.
  1143.      * @param last  whether this is the last chunk.
  1144.      * @return an array of bytes containing the chunk
  1145.      */
  1146.     public final static byte[] chunkedEncode(byte[] data, int off, int len,
  1147.      NVPair[] ftrs, boolean last)
  1148.     {
  1149. if (data == null)
  1150. {
  1151.     data = new byte[0];
  1152.     len  = 0;
  1153. }
  1154. if (last  &&  ftrs == null) ftrs = new NVPair[0];
  1155. // get length of data as hex-string
  1156. String hex_len = Integer.toString(len, 16);
  1157. // calculate length of chunk
  1158. int res_len = 0;
  1159. if (len > 0) // len CRLF data CRLF
  1160.     res_len += hex_len.length() + 2 + len + 2;
  1161. if (last)
  1162. {
  1163.     res_len += 1 + 2; // 0 CRLF
  1164.     for (int idx=0; idx<ftrs.length; idx++)
  1165. res_len += ftrs[idx].getName().length() + 2 + // name ": "
  1166.    ftrs[idx].getValue().length() + 2; // value CRLF
  1167.     res_len += 2; // CRLF
  1168. }
  1169. // allocate result
  1170. byte[] res = new byte[res_len];
  1171. int    r_off = 0;
  1172. // fill result
  1173. if (len > 0)
  1174. {
  1175.     int hlen = hex_len.length();
  1176.     try
  1177. { System.arraycopy(hex_len.getBytes("8859_1"), 0, res, r_off, hlen); }
  1178.     catch (UnsupportedEncodingException uee)
  1179. { throw new Error(uee.toString()); }
  1180.     r_off += hlen;
  1181.     res[r_off++] = (byte) 'r';
  1182.     res[r_off++] = (byte) 'n';
  1183.     System.arraycopy(data, off, res, r_off, len);
  1184.     r_off += len;
  1185.     res[r_off++] = (byte) 'r';
  1186.     res[r_off++] = (byte) 'n';
  1187. }
  1188. if (last)
  1189. {
  1190.     res[r_off++] = (byte) '0';
  1191.     res[r_off++] = (byte) 'r';
  1192.     res[r_off++] = (byte) 'n';
  1193.     for (int idx=0; idx<ftrs.length; idx++)
  1194.     {
  1195. int nlen = ftrs[idx].getName().length();
  1196. try
  1197.     { System.arraycopy(ftrs[idx].getName().getBytes("8859_1"),
  1198.        0, res, r_off, nlen); }
  1199. catch (UnsupportedEncodingException uee)
  1200.     { throw new Error(uee.toString()); }
  1201. r_off += nlen;
  1202. res[r_off++] = (byte) ':';
  1203. res[r_off++] = (byte) ' ';
  1204. int vlen = ftrs[idx].getValue().length();
  1205. try
  1206.     { System.arraycopy(ftrs[idx].getValue().getBytes("8859_1"),
  1207.        0, res, r_off, vlen); }
  1208. catch (UnsupportedEncodingException uee)
  1209.     { throw new Error(uee.toString()); }
  1210. r_off += vlen;
  1211. res[r_off++] = (byte) 'r';
  1212. res[r_off++] = (byte) 'n';
  1213.     }
  1214.     res[r_off++] = (byte) 'r';
  1215.     res[r_off++] = (byte) 'n';
  1216. }
  1217. if (r_off != res.length)
  1218.     throw new Error("Calculated "+res.length+" bytes but wrote "+r_off+" bytes!");
  1219. return res;
  1220.     }
  1221.     /**
  1222.      * Decodes chunked data. The chunks are read from an InputStream, which
  1223.      * is assumed to be correctly positioned. Use 'xxx instanceof byte[]'
  1224.      * and 'xxx instanceof NVPair[]' to determine if this was data or the
  1225.      * last chunk.
  1226.      *
  1227.      * @param  input  the stream from which to read the next chunk.
  1228.      * @return If this was a data chunk then it returns a byte[]; else
  1229.      *         it's the footer and it returns a NVPair[] containing the
  1230.      *         footers.
  1231.      * @exception ParseException If any exception during parsing occured.
  1232.      * @exception IOException    If any exception during reading occured.
  1233.      */
  1234.     public final static Object chunkedDecode(InputStream input)
  1235.     throws ParseException, IOException
  1236.     {
  1237. long clen = getChunkLength(input);
  1238. if (clen > Integer.MAX_VALUE) // Huston, what the hell are you sending?
  1239.     throw new ParseException("Can't deal with chunk lengths greater " +
  1240.      "Integer.MAX_VALUE: " + clen + " > " +
  1241.      Integer.MAX_VALUE);
  1242. if (clen > 0) // it's a chunk
  1243. {
  1244.     byte[] res = new byte[(int) clen];
  1245.     int off = 0, len = 0;
  1246.     while (len != -1  &&  off < res.length)
  1247.     {
  1248. len  = input.read(res, off, res.length-off);
  1249. off += len;
  1250.     }
  1251.     if (len == -1)
  1252. throw new ParseException("Premature EOF while reading chunk;" +
  1253.  "Expected: "+res.length+" Bytes, " +
  1254.  "Received: "+(off+1)+" Bytes");
  1255.     input.read(); // CR
  1256.     input.read(); // LF
  1257.     return res;
  1258. }
  1259. else // it's the end
  1260. {
  1261.     NVPair[] res = new NVPair[0];
  1262.     BufferedReader reader = new BufferedReader(new InputStreamReader(input, "8859_1"));
  1263.     String line;
  1264.     // read and parse footer
  1265.     while ((line = reader.readLine()) != null  &&  line.length() > 0)
  1266.     {
  1267. int colon = line.indexOf(':');
  1268. if (colon == -1)
  1269.     throw new ParseException("Error in Footer format: no "+
  1270.      "':' found in '" + line + "'");
  1271. res = Util.resizeArray(res, res.length+1);
  1272. res[res.length-1] = new NVPair(line.substring(0, colon).trim(),
  1273.        line.substring(colon+1).trim());
  1274.     }
  1275.     return res;
  1276. }
  1277.     }
  1278.     /**
  1279.      * Gets the length of the chunk.
  1280.      *
  1281.      * @param  input  the stream from which to read the next chunk.
  1282.      * @return  the length of chunk to follow (w/o trailing CR LF).
  1283.      * @exception ParseException If any exception during parsing occured.
  1284.      * @exception IOException    If any exception during reading occured.
  1285.      */
  1286.     final static long getChunkLength(InputStream input)
  1287.     throws ParseException, IOException
  1288.     {
  1289. byte[] hex_len = new byte[16]; // if they send more than 8EB chunks...
  1290. int    off     = 0,
  1291.        ch;
  1292. // read chunk length
  1293. while ((ch = input.read()) > 0  &&  (ch == ' '  ||  ch == 't')) ;
  1294. if (ch < 0)
  1295.     throw new EOFException("Premature EOF while reading chunk length");
  1296. hex_len[off++] = (byte) ch;
  1297. while ((ch = input.read()) > 0  &&  ch != 'r'  &&  ch != 'n'  &&
  1298. ch != ' '  &&  ch != 't'  &&  ch != ';'  &&
  1299. off < hex_len.length)
  1300.     hex_len[off++] = (byte) ch;
  1301. while ((ch == ' '  ||  ch == 't')  &&  (ch = input.read()) > 0) ;
  1302. if (ch == ';') // chunk-ext (ignore it)
  1303.     while ((ch = input.read()) > 0  &&  ch != 'r'  &&  ch != 'n') ;
  1304. if (ch < 0)
  1305.     throw new EOFException("Premature EOF while reading chunk length");
  1306. if (ch != 'n'  &&  (ch != 'r'  ||  input.read() != 'n'))
  1307.     throw new ParseException("Didn't find valid chunk length: " +
  1308.      new String(hex_len, 0, off, "8859_1"));
  1309. // parse chunk length
  1310. try
  1311.     { return Long.parseLong(new String(hex_len, 0, off, "8859_1").trim(),
  1312.     16); }
  1313. catch (NumberFormatException nfe)
  1314.     { throw new ParseException("Didn't find valid chunk length: " +
  1315. new String(hex_len, 0, off, "8859_1") ); }
  1316.     }
  1317. }