0001: /*
0002: * @(#)Codecs.java 0.3-2 18/06/1999
0003: *
0004: * This file is part of the HTTPClient package
0005: * Copyright (C) 1996-1999 Ronald Tschalär
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free
0019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
0020: * MA 02111-1307, USA
0021: *
0022: * For questions, suggestions, bug-reports, enhancement-requests etc.
0023: * I may be contacted at:
0024: *
0025: * ronald@innovation.ch
0026: *
0027: */
0028:
0029: package HTTPClient;
0030:
0031: import java.util.BitSet;
0032: import java.util.Vector;
0033: import java.util.StringTokenizer;
0034: import java.io.IOException;
0035: import java.io.InputStream;
0036: import java.io.DataInputStream;
0037: import java.io.File;
0038: import java.io.FileInputStream;
0039: import java.io.FileOutputStream;
0040: import java.io.BufferedReader;
0041:
0042: /**
0043: * This class collects various encoders and decoders.
0044: *
0045: * @version 0.3-2 18/06/1999
0046: * @author Ronald Tschalär
0047: */
0048:
0049: public class Codecs {
0050: private static BitSet BoundChar;
0051: private static BitSet EBCDICUnsafeChar;
0052: private static byte[] Base64EncMap, Base64DecMap;
0053: private static char[] UUEncMap;
0054: private static byte[] UUDecMap;
0055:
0056: private final static String ContDisp = "\r\nContent-Disposition: form-data; name=\"";
0057: private final static String FileName = "\"; filename=\"";
0058: private final static String Boundary = "\r\n-----ieoau._._+2_8_GoodLuck8.3-dskdfJwSJKlrWLr0234324jfLdsjfdAuaoei-----";
0059:
0060: // Class Initializer
0061:
0062: static {
0063: // rfc-2046 & rfc-2045: (bcharsnospace & token)
0064: // used for multipart codings
0065: BoundChar = new BitSet(256);
0066: for (int ch = '0'; ch <= '9'; ch++)
0067: BoundChar.set(ch);
0068: for (int ch = 'A'; ch <= 'Z'; ch++)
0069: BoundChar.set(ch);
0070: for (int ch = 'a'; ch <= 'z'; ch++)
0071: BoundChar.set(ch);
0072: BoundChar.set('+');
0073: BoundChar.set('_');
0074: BoundChar.set('-');
0075: BoundChar.set('.');
0076:
0077: // EBCDIC unsafe characters to be quoted in quoted-printable
0078: // See first NOTE in section 6.7 of rfc-2045
0079: EBCDICUnsafeChar = new BitSet(256);
0080: EBCDICUnsafeChar.set('!');
0081: EBCDICUnsafeChar.set('"');
0082: EBCDICUnsafeChar.set('#');
0083: EBCDICUnsafeChar.set('$');
0084: EBCDICUnsafeChar.set('@');
0085: EBCDICUnsafeChar.set('[');
0086: EBCDICUnsafeChar.set('\\');
0087: EBCDICUnsafeChar.set(']');
0088: EBCDICUnsafeChar.set('^');
0089: EBCDICUnsafeChar.set('`');
0090: EBCDICUnsafeChar.set('{');
0091: EBCDICUnsafeChar.set('|');
0092: EBCDICUnsafeChar.set('}');
0093: EBCDICUnsafeChar.set('~');
0094:
0095: // rfc-2045: Base64 Alphabet
0096: byte[] map = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D',
0097: (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H',
0098: (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L',
0099: (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
0100: (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T',
0101: (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X',
0102: (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b',
0103: (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',
0104: (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
0105: (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',
0106: (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r',
0107: (byte) 's', (byte) 't', (byte) 'u', (byte) 'v',
0108: (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z',
0109: (byte) '0', (byte) '1', (byte) '2', (byte) '3',
0110: (byte) '4', (byte) '5', (byte) '6', (byte) '7',
0111: (byte) '8', (byte) '9', (byte) '+', (byte) '/' };
0112: Base64EncMap = map;
0113: Base64DecMap = new byte[128];
0114: for (int idx = 0; idx < Base64EncMap.length; idx++)
0115: Base64DecMap[Base64EncMap[idx]] = (byte) idx;
0116:
0117: // uuencode'ing maps
0118: UUEncMap = new char[64];
0119: for (int idx = 0; idx < UUEncMap.length; idx++)
0120: UUEncMap[idx] = (char) (idx + 0x20);
0121: UUDecMap = new byte[128];
0122: for (int idx = 0; idx < UUEncMap.length; idx++)
0123: UUDecMap[UUEncMap[idx]] = (byte) idx;
0124: }
0125:
0126: // Constructors
0127:
0128: /**
0129: * This class isn't meant to be instantiated.
0130: */
0131: private Codecs() {
0132: }
0133:
0134: // Methods
0135:
0136: /**
0137: * This method encodes the given string using the base64-encoding
0138: * specified in RFC-2045 (Section 6.8). It's used for example in the
0139: * "Basic" authorization scheme.
0140: *
0141: * @param str the string
0142: * @return the base64-encoded <var>str</var>
0143: */
0144: public final static String base64Encode(String str) {
0145: if (str == null)
0146: return null;
0147:
0148: byte data[] = new byte[str.length()];
0149: str.getBytes(0, str.length(), data, 0);
0150:
0151: return new String(base64Encode(data), 0);
0152: }
0153:
0154: /**
0155: * This method encodes the given byte[] using the base64-encoding
0156: * specified in RFC-2045 (Section 6.8).
0157: *
0158: * @param data the data
0159: * @return the base64-encoded <var>data</var>
0160: */
0161: public final static byte[] base64Encode(byte[] data) {
0162: if (data == null)
0163: return null;
0164:
0165: int sidx, didx;
0166: byte dest[] = new byte[((data.length + 2) / 3) * 4];
0167:
0168: // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
0169: for (sidx = 0, didx = 0; sidx < data.length - 2; sidx += 3) {
0170: dest[didx++] = Base64EncMap[(data[sidx] >>> 2) & 077];
0171: dest[didx++] = Base64EncMap[(data[sidx + 1] >>> 4) & 017
0172: | (data[sidx] << 4) & 077];
0173: dest[didx++] = Base64EncMap[(data[sidx + 2] >>> 6) & 003
0174: | (data[sidx + 1] << 2) & 077];
0175: dest[didx++] = Base64EncMap[data[sidx + 2] & 077];
0176: }
0177: if (sidx < data.length) {
0178: dest[didx++] = Base64EncMap[(data[sidx] >>> 2) & 077];
0179: if (sidx < data.length - 1) {
0180: dest[didx++] = Base64EncMap[(data[sidx + 1] >>> 4)
0181: & 017 | (data[sidx] << 4) & 077];
0182: dest[didx++] = Base64EncMap[(data[sidx + 1] << 2) & 077];
0183: } else
0184: dest[didx++] = Base64EncMap[(data[sidx] << 4) & 077];
0185: }
0186:
0187: // add padding
0188: for (; didx < dest.length; didx++)
0189: dest[didx] = (byte) '=';
0190:
0191: return dest;
0192: }
0193:
0194: /**
0195: * This method decodes the given string using the base64-encoding
0196: * specified in RFC-2045 (Section 6.8).
0197: *
0198: * @param str the base64-encoded string.
0199: * @return the decoded <var>str</var>.
0200: */
0201: public final static String base64Decode(String str) {
0202: if (str == null)
0203: return null;
0204:
0205: byte data[] = new byte[str.length()];
0206: str.getBytes(0, str.length(), data, 0);
0207:
0208: return new String(base64Decode(data), 0);
0209: }
0210:
0211: /**
0212: * This method decodes the given byte[] using the base64-encoding
0213: * specified in RFC-2045 (Section 6.8).
0214: *
0215: * @param data the base64-encoded data.
0216: * @return the decoded <var>data</var>.
0217: */
0218: public final static byte[] base64Decode(byte[] data) {
0219: if (data == null)
0220: return null;
0221:
0222: int tail = data.length;
0223: while (data[tail - 1] == '=')
0224: tail--;
0225:
0226: byte dest[] = new byte[tail - data.length / 4];
0227:
0228: // ascii printable to 0-63 conversion
0229: for (int idx = 0; idx < data.length; idx++)
0230: data[idx] = Base64DecMap[data[idx]];
0231:
0232: // 4-byte to 3-byte conversion
0233: int sidx, didx;
0234: for (sidx = 0, didx = 0; didx < dest.length - 2; sidx += 4, didx += 3) {
0235: dest[didx] = (byte) (((data[sidx] << 2) & 255) | ((data[sidx + 1] >>> 4) & 003));
0236: dest[didx + 1] = (byte) (((data[sidx + 1] << 4) & 255) | ((data[sidx + 2] >>> 2) & 017));
0237: dest[didx + 2] = (byte) (((data[sidx + 2] << 6) & 255) | (data[sidx + 3] & 077));
0238: }
0239: if (didx < dest.length)
0240: dest[didx] = (byte) (((data[sidx] << 2) & 255) | ((data[sidx + 1] >>> 4) & 003));
0241: if (++didx < dest.length)
0242: dest[didx] = (byte) (((data[sidx + 1] << 4) & 255) | ((data[sidx + 2] >>> 2) & 017));
0243:
0244: return dest;
0245: }
0246:
0247: /**
0248: * This method encodes the given byte[] using the unix uuencode
0249: * encding. The output is split into lines starting with the encoded
0250: * number of encoded octets in the line and ending with a newline.
0251: * No line is longer than 45 octets (60 characters), not including
0252: * length and newline.
0253: *
0254: * <P><em>Note:</em> just the raw data is encoded; no 'begin' and 'end'
0255: * lines are added as is done by the unix <code>uuencode</code> utility.
0256: *
0257: * @param data the data
0258: * @return the uuencoded <var>data</var>
0259: */
0260: public final static char[] uuencode(byte[] data) {
0261: if (data == null)
0262: return null;
0263: if (data.length == 0)
0264: return new char[0];
0265:
0266: int line_len = 45; // line length, in octets
0267:
0268: int sidx, didx;
0269: char nl[] = System.getProperty("line.separator", "\n")
0270: .toCharArray(), dest[] = new char[(data.length + 2) / 3
0271: * 4 + ((data.length + line_len - 1) / line_len)
0272: * (nl.length + 1)];
0273:
0274: // split into lines, adding line-length and line terminator
0275: for (sidx = 0, didx = 0; sidx + line_len < data.length;) {
0276: // line length
0277: dest[didx++] = UUEncMap[line_len];
0278:
0279: // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
0280: for (int end = sidx + line_len; sidx < end; sidx += 3) {
0281: dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
0282: dest[didx++] = UUEncMap[(data[sidx + 1] >>> 4) & 017
0283: | (data[sidx] << 4) & 077];
0284: dest[didx++] = UUEncMap[(data[sidx + 2] >>> 6) & 003
0285: | (data[sidx + 1] << 2) & 077];
0286: dest[didx++] = UUEncMap[data[sidx + 2] & 077];
0287: }
0288:
0289: // line terminator
0290: for (int idx = 0; idx < nl.length; idx++)
0291: dest[didx++] = nl[idx];
0292: }
0293:
0294: // last line
0295:
0296: // line length
0297: dest[didx++] = UUEncMap[data.length - sidx];
0298:
0299: // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
0300: for (; sidx + 2 < data.length; sidx += 3) {
0301: dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
0302: dest[didx++] = UUEncMap[(data[sidx + 1] >>> 4) & 017
0303: | (data[sidx] << 4) & 077];
0304: dest[didx++] = UUEncMap[(data[sidx + 2] >>> 6) & 003
0305: | (data[sidx + 1] << 2) & 077];
0306: dest[didx++] = UUEncMap[data[sidx + 2] & 077];
0307: }
0308:
0309: if (sidx < data.length - 1) {
0310: dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
0311: dest[didx++] = UUEncMap[(data[sidx + 1] >>> 4) & 017
0312: | (data[sidx] << 4) & 077];
0313: dest[didx++] = UUEncMap[(data[sidx + 1] << 2) & 077];
0314: dest[didx++] = UUEncMap[0];
0315: } else if (sidx < data.length) {
0316: dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
0317: dest[didx++] = UUEncMap[(data[sidx] << 4) & 077];
0318: dest[didx++] = UUEncMap[0];
0319: dest[didx++] = UUEncMap[0];
0320: }
0321:
0322: // line terminator
0323: for (int idx = 0; idx < nl.length; idx++)
0324: dest[didx++] = nl[idx];
0325:
0326: // sanity check
0327: if (didx != dest.length)
0328: throw new Error("Calculated " + dest.length
0329: + " chars but wrote " + didx + " chars!");
0330:
0331: return dest;
0332: }
0333:
0334: /**
0335: * TBD! How to return file name and mode?
0336: *
0337: * @param rdr the reader from which to read and decode the data
0338: * @exception ParseException if either the "begin" or "end" line are not
0339: * found, or the "begin" is incorrect
0340: * @exception IOException if the <var>rdr</var> throws an IOException
0341: */
0342: private final static byte[] uudecode(BufferedReader rdr)
0343: throws ParseException, IOException {
0344: String line, file_name;
0345: int file_mode;
0346:
0347: // search for beginning
0348:
0349: while ((line = rdr.readLine()) != null
0350: && !line.startsWith("begin "))
0351: ;
0352: if (line == null)
0353: throw new ParseException("'begin' line not found");
0354:
0355: // parse 'begin' line
0356:
0357: StringTokenizer tok = new StringTokenizer(line);
0358: tok.nextToken(); // throw away 'begin'
0359: try // extract mode
0360: {
0361: file_mode = Integer.parseInt(tok.nextToken(), 8);
0362: } catch (Exception e) {
0363: throw new ParseException("Invalid mode on line: " + line);
0364: }
0365: try // extract name
0366: {
0367: file_name = tok.nextToken();
0368: } catch (java.util.NoSuchElementException e) {
0369: throw new ParseException("No file name found on line: "
0370: + line);
0371: }
0372:
0373: // read and parse body
0374:
0375: byte[] body = new byte[1000];
0376: int off = 0;
0377:
0378: while ((line = rdr.readLine()) != null && !line.equals("end")) {
0379: byte[] tmp = uudecode(line.toCharArray());
0380: if (off + tmp.length > body.length)
0381: body = Util.resizeArray(body, off + 1000);
0382: System.arraycopy(tmp, 0, body, off, tmp.length);
0383: off += tmp.length;
0384: }
0385:
0386: if (line == null)
0387: throw new ParseException("'end' line not found");
0388:
0389: return Util.resizeArray(body, off);
0390: }
0391:
0392: /**
0393: * This method decodes the given uuencoded char[].
0394: *
0395: * <P><em>Note:</em> just the actual data is decoded; any 'begin' and
0396: * 'end' lines such as those generated by the unix <code>uuencode</code>
0397: * utility must not be included.
0398: *
0399: * @param data the uuencode-encoded data.
0400: * @return the decoded <var>data</var>.
0401: */
0402: public final static byte[] uudecode(char[] data) {
0403: if (data == null)
0404: return null;
0405:
0406: int sidx, didx;
0407: byte dest[] = new byte[data.length / 4 * 3];
0408:
0409: for (sidx = 0, didx = 0; sidx < data.length;) {
0410: // get line length (in number of encoded octets)
0411: int len = UUDecMap[data[sidx++]];
0412:
0413: // ascii printable to 0-63 and 4-byte to 3-byte conversion
0414: int end = didx + len;
0415: for (; didx < end - 2; sidx += 4) {
0416: byte A = UUDecMap[data[sidx]], B = UUDecMap[data[sidx + 1]], C = UUDecMap[data[sidx + 2]], D = UUDecMap[data[sidx + 3]];
0417: dest[didx++] = (byte) (((A << 2) & 255) | ((B >>> 4) & 003));
0418: dest[didx++] = (byte) (((B << 4) & 255) | ((C >>> 2) & 017));
0419: dest[didx++] = (byte) (((C << 6) & 255) | (D & 077));
0420: }
0421:
0422: if (didx < end) {
0423: byte A = UUDecMap[data[sidx]], B = UUDecMap[data[sidx + 1]];
0424: dest[didx++] = (byte) (((A << 2) & 255) | ((B >>> 4) & 003));
0425: }
0426: if (didx < end) {
0427: byte B = UUDecMap[data[sidx + 1]], C = UUDecMap[data[sidx + 2]];
0428: dest[didx++] = (byte) (((B << 4) & 255) | ((C >>> 2) & 017));
0429: }
0430:
0431: // skip padding
0432: while (sidx < data.length && data[sidx] != '\n'
0433: && data[sidx] != '\r')
0434: sidx++;
0435:
0436: // skip end of line
0437: while (sidx < data.length
0438: && (data[sidx] == '\n' || data[sidx] == '\r'))
0439: sidx++;
0440: }
0441:
0442: return Util.resizeArray(dest, didx);
0443: }
0444:
0445: /**
0446: * This method does a quoted-printable encoding of the given string
0447: * according to RFC-2045 (Section 6.7). <em>Note:</em> this assumes
0448: * 8-bit characters.
0449: *
0450: * @param str the string
0451: * @return the quoted-printable encoded string
0452: */
0453: public final static String quotedPrintableEncode(String str) {
0454: if (str == null)
0455: return null;
0456:
0457: char map[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
0458: '9', 'A', 'B', 'C', 'D', 'E', 'F' }, nl[] = System
0459: .getProperty("line.separator", "\n").toCharArray(), res[] = new char[(int) (str
0460: .length() * 1.5)], src[] = str.toCharArray();
0461: char ch;
0462: int cnt = 0, didx = 1, last = 0, slen = str.length();
0463:
0464: for (int sidx = 0; sidx < slen; sidx++) {
0465: ch = src[sidx];
0466:
0467: if (ch == nl[0] && match(src, sidx, nl)) // Rule #4
0468: {
0469: if (res[didx - 1] == ' ') // Rule #3
0470: {
0471: res[didx - 1] = '=';
0472: res[didx++] = '2';
0473: res[didx++] = '0';
0474: } else if (res[didx - 1] == '\t') // Rule #3
0475: {
0476: res[didx - 1] = '=';
0477: res[didx++] = '0';
0478: res[didx++] = '9';
0479: }
0480:
0481: res[didx++] = '\r';
0482: res[didx++] = '\n';
0483: sidx += nl.length - 1;
0484: cnt = didx;
0485: } else if (ch > 126 || (ch < 32 && ch != '\t') || ch == '='
0486: || EBCDICUnsafeChar.get((int) ch)) { // Rule #1, #2
0487: res[didx++] = '=';
0488: res[didx++] = map[(ch & 0xf0) >>> 4];
0489: res[didx++] = map[ch & 0x0f];
0490: } else // Rule #1
0491: {
0492: res[didx++] = ch;
0493: }
0494:
0495: if (didx > cnt + 70) // Rule #5
0496: {
0497: res[didx++] = '=';
0498: res[didx++] = '\r';
0499: res[didx++] = '\n';
0500: cnt = didx;
0501: }
0502:
0503: if (didx > res.length - 5)
0504: res = Util.resizeArray(res, res.length + 500);
0505: }
0506:
0507: return String.valueOf(res, 1, didx - 1);
0508: }
0509:
0510: private final static boolean match(char[] str, int start, char[] arr) {
0511: if (str.length < start + arr.length)
0512: return false;
0513:
0514: for (int idx = 1; idx < arr.length; idx++)
0515: if (str[start + idx] != arr[idx])
0516: return false;
0517: return true;
0518: }
0519:
0520: /**
0521: * This method does a quoted-printable decoding of the given string
0522: * according to RFC-2045 (Section 6.7). <em>Note:</em> this method
0523: * expects the whole message in one chunk, not line by line.
0524: *
0525: * @param str the message
0526: * @return the decoded message
0527: * @exception ParseException If a '=' is not followed by a valid
0528: * 2-digit hex number or '\r\n'.
0529: */
0530: public final static String quotedPrintableDecode(String str)
0531: throws ParseException {
0532: if (str == null)
0533: return null;
0534:
0535: char res[] = new char[(int) (str.length() * 1.1)], src[] = str
0536: .toCharArray(), nl[] = System.getProperty(
0537: "line.separator", "\n").toCharArray();
0538: int last = 0, didx = 0, slen = str.length();
0539:
0540: for (int sidx = 0; sidx < slen;) {
0541: char ch = src[sidx++];
0542:
0543: if (ch == '=') {
0544: if (sidx >= slen - 1)
0545: throw new ParseException(
0546: "Premature end of input detected");
0547:
0548: if (src[sidx] == '\n' || src[sidx] == '\r') { // Rule #5
0549: sidx++;
0550:
0551: if (src[sidx - 1] == '\r' && src[sidx] == '\n')
0552: sidx++;
0553: } else // Rule #1
0554: {
0555: char repl;
0556: int hi = Character.digit(src[sidx], 16), lo = Character
0557: .digit(src[sidx + 1], 16);
0558:
0559: if ((hi | lo) < 0)
0560: throw new ParseException(new String(src,
0561: sidx - 1, 3)
0562: + " is an invalid code");
0563: else {
0564: repl = (char) (hi << 4 | lo);
0565: sidx += 2;
0566: }
0567:
0568: res[didx++] = repl;
0569: }
0570: last = didx;
0571: } else if (ch == '\n' || ch == '\r') // Rule #4
0572: {
0573: if (ch == '\r' && sidx < slen && src[sidx] == '\n')
0574: sidx++;
0575: for (int idx = 0; idx < nl.length; idx++)
0576: res[last++] = nl[idx];
0577: didx = last;
0578: } else // Rule #1, #2
0579: {
0580: res[didx++] = ch;
0581: if (ch != ' ' && ch != '\t') // Rule #3
0582: last = didx;
0583: }
0584:
0585: if (didx > res.length - nl.length - 2)
0586: res = Util.resizeArray(res, res.length + 500);
0587: }
0588:
0589: return new String(res, 0, didx);
0590: }
0591:
0592: /**
0593: * This method urlencodes the given string. This method is here for
0594: * symmetry reasons and just calls java.net.URLEncoder.encode().
0595: *
0596: * @param str the string
0597: * @return the url-encoded string
0598: */
0599: public final static String URLEncode(String str) {
0600: if (str == null)
0601: return null;
0602:
0603: return java.net.URLEncoder.encode(str);
0604: }
0605:
0606: /**
0607: * This method decodes the given urlencoded string.
0608: *
0609: * @param str the url-encoded string
0610: * @return the decoded string
0611: * @exception ParseException If a '%' is not followed by a valid
0612: * 2-digit hex number.
0613: */
0614: public final static String URLDecode(String str)
0615: throws ParseException {
0616: if (str == null)
0617: return null;
0618:
0619: char[] res = new char[str.length()];
0620: int didx = 0;
0621:
0622: for (int sidx = 0; sidx < str.length(); sidx++) {
0623: char ch = str.charAt(sidx);
0624: if (ch == '+')
0625: res[didx++] = ' ';
0626: else if (ch == '%') {
0627: try {
0628: res[didx++] = (char) Integer.parseInt(str
0629: .substring(sidx + 1, sidx + 3), 16);
0630: sidx += 2;
0631: } catch (NumberFormatException e) {
0632: throw new ParseException(str.substring(sidx,
0633: sidx + 3)
0634: + " is an invalid code");
0635: }
0636: } else
0637: res[didx++] = ch;
0638: }
0639:
0640: return String.valueOf(res, 0, didx);
0641: }
0642:
0643: /**
0644: * This method decodes a multipart/form-data encoded string.
0645: *
0646: * @param data the form-data to decode.
0647: * @param cont_type the content type header (must contain the
0648: * boundary string).
0649: * @param dir the directory to create the files in.
0650: * @return an array of name/value pairs, one for each part;
0651: * the name is the 'name' attribute given in the
0652: * Content-Disposition header; the value is either
0653: * the name of the file if a filename attribute was
0654: * found, or the contents of the part.
0655: * @exception IOException If any file operation fails.
0656: * @exception ParseException If an error during parsing occurs.
0657: * @see #mpFormDataDecode(byte[], java.lang.String, java.lang.String, HTTPClient.FilenameMangler)
0658: */
0659: public final static NVPair[] mpFormDataDecode(byte[] data,
0660: String cont_type, String dir) throws IOException,
0661: ParseException {
0662: return mpFormDataDecode(data, cont_type, dir, null);
0663: }
0664:
0665: /**
0666: * This method decodes a multipart/form-data encoded string. The boundary
0667: * is parsed from the <var>cont_type</var> parameter, which must be of the
0668: * form 'multipart/form-data; boundary=...'. Any encoded files are created
0669: * in the directory specified by <var>dir</var> using the encoded filename.
0670: *
0671: * <P><em>Note:</em> Does not handle nested encodings (yet).
0672: *
0673: * <P>Examples: If you're receiving a multipart/form-data encoded response
0674: * from a server you could use something like:
0675: * <PRE>
0676: * NVPair[] opts = Codecs.mpFormDataDecode(resp.getData(),
0677: * resp.getHeader("Content-type"), ".");
0678: * </PRE>
0679: * If you're using this in a Servlet to decode the body of a request from
0680: * a client you could use something like:
0681: * <PRE>
0682: * byte[] body = new byte[req.getContentLength()];
0683: * new DataInputStream(req.getInputStream()).readFully(body);
0684: * NVPair[] opts = Codecs.mpFormDataDecode(body, req.getContentType(),
0685: * ".");
0686: * </PRE>
0687: * Assuming the data received looked something like:
0688: * <PRE>
0689: * -----------------------------114975832116442893661388290519
0690: * Content-Disposition: form-data; name="option"
0691: *
0692: * doit
0693: * -----------------------------114975832116442893661388290519
0694: * Content-Disposition: form-data; name="comment"; filename="comment.txt"
0695: *
0696: * Gnus and Gnats are not Gnomes.
0697: * -----------------------------114975832116442893661388290519--
0698: * </PRE>
0699: * you would get one file called <VAR>comment.txt</VAR> in the current
0700: * directory, and opts would contain two elements: {"option", "doit"}
0701: * and {"comment", "comment.txt"}
0702: *
0703: * @param data the form-data to decode.
0704: * @param cont_type the content type header (must contain the
0705: * boundary string).
0706: * @param dir the directory to create the files in.
0707: * @param mangler the filename mangler, or null if no mangling is
0708: * to be done. This is invoked just before each
0709: * file is created and written, thereby allowing
0710: * you to control the names of the files.
0711: * @return an array of name/value pairs, one for each part;
0712: * the name is the 'name' attribute given in the
0713: * Content-Disposition header; the value is either
0714: * the name of the file if a filename attribute was
0715: * found, or the contents of the part.
0716: * @exception IOException If any file operation fails.
0717: * @exception ParseException If an error during parsing occurs.
0718: */
0719: public final static NVPair[] mpFormDataDecode(byte[] data,
0720: String cont_type, String dir, FilenameMangler mangler)
0721: throws IOException, ParseException {
0722: // Find and extract boundary string
0723:
0724: String bndstr = Util.getParameter("boundary", cont_type);
0725: if (bndstr == null)
0726: throw new ParseException(
0727: "'boundary' parameter not found in Content-type: "
0728: + cont_type);
0729:
0730: byte[] srtbndry = new byte[bndstr.length() + 4], boundary = new byte[bndstr
0731: .length() + 6], endbndry = new byte[bndstr.length() + 6];
0732:
0733: ("--" + bndstr + "\r\n").getBytes(0, srtbndry.length, srtbndry,
0734: 0);
0735: ("\r\n--" + bndstr + "\r\n").getBytes(0, boundary.length,
0736: boundary, 0);
0737: ("\r\n--" + bndstr + "--").getBytes(0, endbndry.length,
0738: endbndry, 0);
0739:
0740: // setup search routines
0741:
0742: int[] bs = Util.compile_search(srtbndry), bc = Util
0743: .compile_search(boundary), be = Util
0744: .compile_search(endbndry);
0745:
0746: // let's start parsing the actual data
0747:
0748: int start = Util.findStr(srtbndry, bs, data, 0, data.length);
0749: if (start == -1) // didn't even find the start
0750: throw new ParseException("Starting boundary not found: "
0751: + new String(srtbndry, 0));
0752: start += srtbndry.length;
0753:
0754: NVPair[] res = new NVPair[10];
0755: boolean done = false;
0756: int idx;
0757:
0758: for (idx = 0; !done; idx++) {
0759: // find end of this part
0760:
0761: int end = Util.findStr(boundary, bc, data, start,
0762: data.length);
0763: if (end == -1) // must be the last part
0764: {
0765: end = Util.findStr(endbndry, be, data, start,
0766: data.length);
0767: if (end == -1)
0768: throw new ParseException(
0769: "Ending boundary not found: "
0770: + new String(endbndry, 0));
0771: done = true;
0772: }
0773:
0774: // parse header(s)
0775:
0776: String hdr, name = null, value, filename = null, cont_disp = null;
0777:
0778: while (true) {
0779: int next = findEOL(data, start) + 2;
0780: if (next - 2 <= start)
0781: break; // empty line -> end of headers
0782: hdr = new String(data, 0, start, next - 2 - start);
0783: start = next;
0784:
0785: // handle line continuation
0786: byte ch;
0787: while (next < data.length - 1
0788: && ((ch = data[next]) == ' ' || ch == '\t')) {
0789: next = findEOL(data, start) + 2;
0790: hdr += new String(data, 0, start, next - 2 - start);
0791: start = next;
0792: }
0793:
0794: if (!hdr.regionMatches(true, 0, "Content-Disposition",
0795: 0, 19))
0796: continue;
0797: Vector pcd = Util.parseHeader(hdr.substring(hdr
0798: .indexOf(':') + 1));
0799: HttpHeaderElement elem = Util.getElement(pcd,
0800: "form-data");
0801:
0802: if (elem == null)
0803: throw new ParseException(
0804: "Expected 'Content-Disposition: form-data' in line: "
0805: + hdr);
0806:
0807: NVPair[] params = elem.getParams();
0808: name = filename = null;
0809: for (int pidx = 0; pidx < params.length; pidx++) {
0810: if (params[pidx].getName().equalsIgnoreCase("name"))
0811: name = params[pidx].getValue();
0812: if (params[pidx].getName().equalsIgnoreCase(
0813: "filename"))
0814: filename = params[pidx].getValue();
0815: }
0816: if (name == null)
0817: throw new ParseException(
0818: "'name' parameter not found in header: "
0819: + hdr);
0820:
0821: cont_disp = hdr;
0822: }
0823:
0824: start += 2;
0825: if (start > end)
0826: throw new ParseException(
0827: "End of header not found at offset " + end);
0828:
0829: if (cont_disp == null)
0830: throw new ParseException(
0831: "Missing 'Content-Disposition' header at offset "
0832: + start);
0833:
0834: // handle data for this part
0835:
0836: if (filename != null) // It's a file
0837: {
0838: if (mangler != null)
0839: filename = mangler.mangleFilename(filename, name);
0840: if (filename != null) {
0841: File file = new File(dir, filename);
0842: FileOutputStream out = new FileOutputStream(file);
0843:
0844: out.write(data, start, end - start);
0845: out.close();
0846: }
0847:
0848: value = filename;
0849: } else // It's simple data
0850: {
0851: value = new String(data, 0, start, end - start);
0852: }
0853:
0854: if (idx >= res.length)
0855: res = Util.resizeArray(res, idx + 10);
0856: res[idx] = new NVPair(name, value);
0857:
0858: start = end + boundary.length;
0859: }
0860:
0861: return Util.resizeArray(res, idx);
0862: }
0863:
0864: /**
0865: * Searches for the next CRLF in an array.
0866: *
0867: * @param arr the byte array to search.
0868: * @param off the offset at which to start the search.
0869: * @return the position of the CR or (arr.length-2) if not found
0870: */
0871: private final static int findEOL(byte[] arr, int off) {
0872: while (off < arr.length - 1
0873: && !(arr[off++] == '\r' && arr[off] == '\n'))
0874: ;
0875: return off - 1;
0876: }
0877:
0878: /**
0879: * This method encodes name/value pairs and files into a byte array
0880: * using the multipart/form-data encoding.
0881: *
0882: * @param opts the simple form-data to encode (may be null);
0883: * for each NVPair the name refers to the 'name'
0884: * attribute to be used in the header of the part,
0885: * and the value is contents of the part.
0886: * @param files the files to encode (may be null); for each
0887: * NVPair the name refers to the 'name' attribute
0888: * to be used in the header of the part, and the
0889: * value is the actual filename (the file will be
0890: * read and it's contents put in the body of that
0891: * part).
0892: * @param cont_type this returns a new NVPair in the 0'th element
0893: * which contains
0894: * name = "Content-Type",
0895: * value = "multipart/form-data; boundary=..."
0896: * (the reason this parameter is an array is
0897: * because a) that's the only way to simulate
0898: * pass-by-reference and b) you need an array for
0899: * the headers parameter to the Post() or Put()
0900: * anyway).
0901: * @return an encoded byte array containing all the opts
0902: * and files.
0903: * @exception IOException If any file operation fails.
0904: * @see #mpFormDataEncode(HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.FilenameMangler)
0905: */
0906: public final static byte[] mpFormDataEncode(NVPair[] opts,
0907: NVPair[] files, NVPair[] cont_type) throws IOException {
0908: return mpFormDataEncode(opts, files, cont_type, null);
0909: }
0910:
0911: private static NVPair[] dummy = new NVPair[0];
0912:
0913: /**
0914: * This method encodes name/value pairs and files into a byte array
0915: * using the multipart/form-data encoding. The boundary is returned
0916: * as part of <var>cont_type</var>.
0917: * <BR>Example:
0918: * <PRE>
0919: * NVPair[] opts = { new NVPair("option", "doit") };
0920: * NVPair[] file = { new NVPair("comment", "comment.txt") };
0921: * NVPair[] hdrs = new NVPair[1];
0922: * byte[] data = Codecs.mpFormDataEncode(opts, file, hdrs);
0923: * con.Post("/cgi-bin/handle-it", data, hdrs);
0924: * </PRE>
0925: * <VAR>data</VAR> will look something like the following:
0926: * <PRE>
0927: * -----------------------------114975832116442893661388290519
0928: * Content-Disposition: form-data; name="option"
0929: *
0930: * doit
0931: * -----------------------------114975832116442893661388290519
0932: * Content-Disposition: form-data; name="comment"; filename="comment.txt"
0933: *
0934: * Gnus and Gnats are not Gnomes.
0935: * -----------------------------114975832116442893661388290519--
0936: * </PRE>
0937: * where the "Gnus and Gnats ..." is the contents of the file
0938: * <VAR>comment.txt</VAR> in the current directory.
0939: *
0940: * @param opts the simple form-data to encode (may be null);
0941: * for each NVPair the name refers to the 'name'
0942: * attribute to be used in the header of the part,
0943: * and the value is contents of the part.
0944: * @param files the files to encode (may be null); for each
0945: * NVPair the name refers to the 'name' attribute
0946: * to be used in the header of the part, and the
0947: * value is the actual filename (the file will be
0948: * read and it's contents put in the body of that
0949: * part).
0950: * @param cont_type this returns a new NVPair in the 0'th element
0951: * which contains
0952: * name = "Content-Type",
0953: * value = "multipart/form-data; boundary=..."
0954: * (the reason this parameter is an array is
0955: * because a) that's the only way to simulate
0956: * pass-by-reference and b) you need an array for
0957: * the headers parameter to the Post() or Put()
0958: * anyway).
0959: * @param mangler the filename mangler, or null if no mangling is
0960: * to be done. This allows you to change the name
0961: * used in the <var>filename</var> attribute of the
0962: * Content-Disposition header. Note: the mangler
0963: * will be invoked twice for each filename.
0964: * @return an encoded byte array containing all the opts
0965: * and files.
0966: * @exception IOException If any file operation fails.
0967: */
0968: public final static byte[] mpFormDataEncode(NVPair[] opts,
0969: NVPair[] files, NVPair[] cont_type, FilenameMangler mangler)
0970: throws IOException {
0971: int len = 0, hdr_len = 2 + 2 + 70 + 2 + 39 + 2 + 2;
0972: // \r\n -- bnd \r\n C.. \r\n \r\n
0973: byte[] boundary = new byte[74], cont_disp = new byte[40], filename = new byte[13];
0974:
0975: ContDisp.getBytes(0, ContDisp.length(), cont_disp, 0);
0976: FileName.getBytes(0, FileName.length(), filename, 0);
0977: Boundary.getBytes(0, Boundary.length(), boundary, 0);
0978:
0979: if (opts == null)
0980: opts = dummy;
0981: if (files == null)
0982: files = dummy;
0983:
0984: // Calculate the length of the data
0985:
0986: for (int idx = 0; idx < opts.length; idx++)
0987: len += hdr_len + opts[idx].getName().length()
0988: + opts[idx].getValue().length();
0989:
0990: for (int idx = 0; idx < files.length; idx++) {
0991: File file = new File(files[idx].getValue());
0992: String fname = file.getName();
0993: if (mangler != null)
0994: fname = mangler.mangleFilename(fname, files[idx]
0995: .getName());
0996: if (fname != null) {
0997: len += hdr_len + files[idx].getName().length() + 13;
0998: len += fname.length() + file.length();
0999: }
1000: }
1001:
1002: len -= 2; // first CR LF is not written
1003: len += 2 + 2 + 70 + 2 + 2; // \r\n -- bnd -- \r\n
1004:
1005: // Now fill array
1006:
1007: byte[] res = new byte[len];
1008: int pos = 0;
1009:
1010: NewBound: for (int new_c = 0x30303030; new_c != 0x7A7A7A7A; new_c++) {
1011: pos = 0;
1012:
1013: // modify boundary in hopes that it will be unique
1014: while (!BoundChar.get(new_c & 0xff))
1015: new_c += 0x00000001;
1016: while (!BoundChar.get(new_c >> 8 & 0xff))
1017: new_c += 0x00000100;
1018: while (!BoundChar.get(new_c >> 16 & 0xff))
1019: new_c += 0x00010000;
1020: while (!BoundChar.get(new_c >> 24 & 0xff))
1021: new_c += 0x01000000;
1022: boundary[40] = (byte) (new_c & 0xff);
1023: boundary[42] = (byte) (new_c >> 8 & 0xff);
1024: boundary[44] = (byte) (new_c >> 16 & 0xff);
1025: boundary[46] = (byte) (new_c >> 24 & 0xff);
1026:
1027: int off = 2;
1028: int[] bnd_cmp = Util.compile_search(boundary);
1029:
1030: for (int idx = 0; idx < opts.length; idx++) {
1031: System.arraycopy(boundary, off, res, pos,
1032: boundary.length - off);
1033: pos += boundary.length - off;
1034: off = 0;
1035: System.arraycopy(cont_disp, 0, res, pos,
1036: cont_disp.length);
1037: pos += cont_disp.length;
1038:
1039: int nlen = opts[idx].getName().length();
1040: opts[idx].getName().getBytes(0, nlen, res, pos);
1041: if (nlen >= boundary.length
1042: && Util.findStr(boundary, bnd_cmp, res, pos,
1043: pos + nlen) != -1)
1044: continue NewBound;
1045: pos += nlen;
1046:
1047: res[pos++] = (byte) '"';
1048: res[pos++] = (byte) '\r';
1049: res[pos++] = (byte) '\n';
1050: res[pos++] = (byte) '\r';
1051: res[pos++] = (byte) '\n';
1052:
1053: int vlen = opts[idx].getValue().length();
1054: opts[idx].getValue().getBytes(0, vlen, res, pos);
1055: if (vlen >= boundary.length
1056: && Util.findStr(boundary, bnd_cmp, res, pos,
1057: pos + vlen) != -1)
1058: continue NewBound;
1059: pos += vlen;
1060: }
1061:
1062: for (int idx = 0; idx < files.length; idx++) {
1063: File file = new File(files[idx].getValue());
1064: String fname = file.getName();
1065: if (mangler != null)
1066: fname = mangler.mangleFilename(fname, files[idx]
1067: .getName());
1068: if (fname == null)
1069: continue;
1070:
1071: System.arraycopy(boundary, off, res, pos,
1072: boundary.length - off);
1073: pos += boundary.length - off;
1074: off = 0;
1075: System.arraycopy(cont_disp, 0, res, pos,
1076: cont_disp.length);
1077: pos += cont_disp.length;
1078:
1079: int nlen = files[idx].getName().length();
1080: files[idx].getName().getBytes(0, nlen, res, pos);
1081: if (nlen >= boundary.length
1082: && Util.findStr(boundary, bnd_cmp, res, pos,
1083: pos + nlen) != -1)
1084: continue NewBound;
1085: pos += nlen;
1086:
1087: System
1088: .arraycopy(filename, 0, res, pos,
1089: filename.length);
1090: pos += filename.length;
1091:
1092: nlen = fname.length();
1093: fname.getBytes(0, nlen, res, pos);
1094: if (nlen >= boundary.length
1095: && Util.findStr(boundary, bnd_cmp, res, pos,
1096: pos + nlen) != -1)
1097: continue NewBound;
1098: pos += nlen;
1099:
1100: res[pos++] = (byte) '"';
1101: res[pos++] = (byte) '\r';
1102: res[pos++] = (byte) '\n';
1103: res[pos++] = (byte) '\r';
1104: res[pos++] = (byte) '\n';
1105:
1106: nlen = (int) file.length();
1107: int opos = pos;
1108: FileInputStream fin = new FileInputStream(file);
1109: while (nlen > 0) {
1110: int got = fin.read(res, pos, nlen);
1111: nlen -= got;
1112: pos += got;
1113: }
1114: if (Util.findStr(boundary, bnd_cmp, res, opos, pos) != -1)
1115: continue NewBound;
1116: }
1117:
1118: break NewBound;
1119: }
1120:
1121: System.arraycopy(boundary, 0, res, pos, boundary.length);
1122: pos += boundary.length;
1123: res[pos++] = (byte) '-';
1124: res[pos++] = (byte) '-';
1125: res[pos++] = (byte) '\r';
1126: res[pos++] = (byte) '\n';
1127:
1128: if (pos != len)
1129: throw new Error("Calculated " + len + " bytes but wrote "
1130: + pos + " bytes!");
1131:
1132: /* the boundary parameter should be quoted (rfc-2046, section 5.1.1)
1133: * but too many script authors are not capable of reading specs...
1134: * So, I give up and don't quote it.
1135: */
1136: cont_type[0] = new NVPair("Content-Type",
1137: "multipart/form-data; boundary="
1138: + new String(boundary, 0, 4, 70));
1139:
1140: return res;
1141: }
1142:
1143: /**
1144: * Turns an array of name/value pairs into the string
1145: * "name1=value1&name2=value2&name3=value3". The names and values are
1146: * first urlencoded. This is the form in which form-data is passed to
1147: * a cgi script.
1148: *
1149: * @param pairs the array of name/value pairs
1150: * @return a string containg the encoded name/value pairs
1151: */
1152: public final static String nv2query(NVPair pairs[]) {
1153: if (pairs == null)
1154: return null;
1155:
1156: int idx;
1157: StringBuffer qbuf = new StringBuffer();
1158:
1159: for (idx = 0; idx < pairs.length; idx++) {
1160: qbuf.append(URLEncode(pairs[idx].getName()) + "="
1161: + URLEncode(pairs[idx].getValue()) + "&");
1162: }
1163:
1164: if (idx > 0)
1165: qbuf.setLength(qbuf.length() - 1); // remove trailing '&'
1166:
1167: return qbuf.toString();
1168: }
1169:
1170: /**
1171: * Turns a string of the form "name1=value1&name2=value2&name3=value3"
1172: * into an array of name/value pairs. The names and values are
1173: * urldecoded. The query string is in the form in which form-data is
1174: * received in a cgi script.
1175: *
1176: * @param query the query string containing the encoded name/value pairs
1177: * @return an array of NVPairs
1178: * @exception ParseException If the '=' is missing in any field, or if
1179: * the urldecoding of the name or value fails
1180: */
1181: public final static NVPair[] query2nv(String query)
1182: throws ParseException {
1183: if (query == null)
1184: return null;
1185:
1186: int idx = -1, cnt = 1;
1187: while ((idx = query.indexOf('&', idx + 1)) != -1)
1188: cnt++;
1189: NVPair[] pairs = new NVPair[cnt];
1190:
1191: for (idx = 0, cnt = 0; cnt < pairs.length; cnt++) {
1192: int eq = query.indexOf('=', idx);
1193: int end = query.indexOf('&', idx);
1194:
1195: if (end == -1)
1196: end = query.length();
1197:
1198: if (eq == -1 || eq >= end)
1199: throw new ParseException("'=' missing in "
1200: + query.substring(idx, end));
1201:
1202: pairs[cnt] = new NVPair(
1203: URLDecode(query.substring(idx, eq)),
1204: URLDecode(query.substring(eq + 1, end)));
1205:
1206: idx = end + 1;
1207: }
1208:
1209: return pairs;
1210: }
1211:
1212: /**
1213: * Encodes data used the chunked encoding. <var>last</var> signales if
1214: * this is the last chunk, in which case the appropriate footer is
1215: * generated.
1216: *
1217: * @param data the data to be encoded; may be null.
1218: * @param ftrs optional headers to include in the footer (ignored if
1219: * not last); may be null.
1220: * @param last whether this is the last chunk.
1221: * @return an array of bytes containing the chunk
1222: */
1223: public final static byte[] chunkedEncode(byte[] data,
1224: NVPair[] ftrs, boolean last) {
1225: return chunkedEncode(data, 0, data == null ? 0 : data.length,
1226: ftrs, last);
1227: }
1228:
1229: /**
1230: * Encodes data used the chunked encoding. <var>last</var> signales if
1231: * this is the last chunk, in which case the appropriate footer is
1232: * generated.
1233: *
1234: * @param data the data to be encoded; may be null.
1235: * @param off an offset into the <var>data</var>
1236: * @param len the number of bytes to take from <var>data</var>
1237: * @param ftrs optional headers to include in the footer (ignored if
1238: * not last); may be null.
1239: * @param last whether this is the last chunk.
1240: * @return an array of bytes containing the chunk
1241: */
1242: public final static byte[] chunkedEncode(byte[] data, int off,
1243: int len, NVPair[] ftrs, boolean last) {
1244: if (data == null) {
1245: data = new byte[0];
1246: len = 0;
1247: }
1248: if (last && ftrs == null)
1249: ftrs = new NVPair[0];
1250:
1251: // get length of data as hex-string
1252: String hex_len = Integer.toString(len, 16);
1253:
1254: // calculate length of chunk
1255:
1256: int res_len = 0;
1257: if (len > 0) // len CRLF data CRLF
1258: res_len += hex_len.length() + 2 + len + 2;
1259:
1260: if (last) {
1261: res_len += 1 + 2; // 0 CRLF
1262: for (int idx = 0; idx < ftrs.length; idx++)
1263: res_len += ftrs[idx].getName().length() + 2 + // name ": "
1264: ftrs[idx].getValue().length() + 2; // value CRLF
1265: res_len += 2; // CRLF
1266: }
1267:
1268: // allocate result
1269:
1270: byte[] res = new byte[res_len];
1271: int r_off = 0;
1272:
1273: // fill result
1274:
1275: if (len > 0) {
1276: hex_len.getBytes(0, hex_len.length(), res, r_off);
1277: r_off += hex_len.length();
1278: res[r_off++] = (byte) '\r';
1279: res[r_off++] = (byte) '\n';
1280:
1281: System.arraycopy(data, off, res, r_off, len);
1282: r_off += len;
1283: res[r_off++] = (byte) '\r';
1284: res[r_off++] = (byte) '\n';
1285: }
1286:
1287: if (last) {
1288: res[r_off++] = (byte) '0';
1289: res[r_off++] = (byte) '\r';
1290: res[r_off++] = (byte) '\n';
1291:
1292: for (int idx = 0; idx < ftrs.length; idx++) {
1293: ftrs[idx].getName().getBytes(0,
1294: ftrs[idx].getName().length(), res, r_off);
1295: r_off += ftrs[idx].getName().length();
1296:
1297: res[r_off++] = (byte) ':';
1298: res[r_off++] = (byte) ' ';
1299:
1300: ftrs[idx].getValue().getBytes(0,
1301: ftrs[idx].getValue().length(), res, r_off);
1302: r_off += ftrs[idx].getValue().length();
1303:
1304: res[r_off++] = (byte) '\r';
1305: res[r_off++] = (byte) '\n';
1306: }
1307:
1308: res[r_off++] = (byte) '\r';
1309: res[r_off++] = (byte) '\n';
1310: }
1311:
1312: if (r_off != res.length)
1313: throw new Error("Calculated " + res.length
1314: + " bytes but wrote " + r_off + " bytes!");
1315:
1316: return res;
1317: }
1318:
1319: /**
1320: * Decodes chunked data. The chunks are read from an InputStream, which
1321: * is assumed to be correctly positioned. Use 'xxx instanceof byte[]'
1322: * and 'xxx instanceof NVPair[]' to determine if this was data or the
1323: * last chunk.
1324: *
1325: * @param input the stream from which to read the next chunk.
1326: * @return If this was a data chunk then it returns a byte[]; else
1327: * it's the footer and it returns a NVPair[] containing the
1328: * footers.
1329: * @exception ParseException If any exception during parsing occured.
1330: * @exception IOException If any exception during reading occured.
1331: */
1332: public final static Object chunkedDecode(InputStream input)
1333: throws ParseException, IOException {
1334: int len = getChunkLength(input);
1335:
1336: if (len > 0) // it's a chunk
1337: {
1338: byte[] res = new byte[len];
1339:
1340: int off = 0;
1341: while (len != -1 && off < res.length) {
1342: len = input.read(res, off, res.length - off);
1343: off += len;
1344: }
1345:
1346: if (len == -1)
1347: throw new ParseException(
1348: "Premature EOF while reading chunk;"
1349: + "Expected: " + res.length
1350: + " Bytes, " + "Received: " + (off + 1)
1351: + " Bytes");
1352:
1353: input.read(); // CR
1354: input.read(); // LF
1355:
1356: return res;
1357: } else // it's the end
1358: {
1359: NVPair[] res = new NVPair[0];
1360:
1361: DataInputStream datain = new DataInputStream(input);
1362: String line;
1363:
1364: // read and parse footer
1365: while ((line = datain.readLine()) != null
1366: && line.length() > 0) {
1367: int colon = line.indexOf(':');
1368: if (colon == -1)
1369: throw new ParseException(
1370: "Error in Footer format: no "
1371: + "':' found in '" + line + "'");
1372: res = Util.resizeArray(res, res.length + 1);
1373: res[res.length - 1] = new NVPair(line.substring(0,
1374: colon).trim(), line.substring(colon + 1).trim());
1375: }
1376:
1377: return res;
1378: }
1379:
1380: }
1381:
1382: /**
1383: * Gets the length of the chunk.
1384: *
1385: * @param input the stream from which to read the next chunk.
1386: * @return the length of chunk to follow (w/o trailing CR LF).
1387: * @exception ParseException If any exception during parsing occured.
1388: * @exception IOException If any exception during reading occured.
1389: */
1390: final static int getChunkLength(InputStream input)
1391: throws ParseException, IOException {
1392: byte[] hex_len = new byte[8]; // if they send more than 2GB chunks...
1393: int off = 0, ch;
1394:
1395: // read chunk length
1396:
1397: while ((ch = input.read()) != '\r' && ch != '\n' && ch != ';'
1398: && off < hex_len.length)
1399: hex_len[off++] = (byte) ch;
1400:
1401: if (ch == ';') // chunk-ext (ignore it)
1402: while ((ch = input.read()) != '\r' && ch != '\n')
1403: ;
1404:
1405: if (ch != '\n' && (ch != '\r' || input.read() != '\n'))
1406: throw new ParseException("Didn't find valid chunk length: "
1407: + new String(hex_len, 0, 0, off));
1408:
1409: // parse chunk length
1410:
1411: int len;
1412: try {
1413: len = Integer.parseInt(new String(hex_len, 0, 0, off)
1414: .trim(), 16);
1415: } catch (NumberFormatException nfe) {
1416: throw new ParseException("Didn't find valid chunk length: "
1417: + new String(hex_len, 0, 0, off));
1418: }
1419:
1420: return len;
1421: }
1422:
1423: }
|