//
// $Id: Codecs.java,v 1.7 2011/08/22 06:50:47 ylafon Exp $
// From Philippe Le Hegaret (Philippe.Le_Hegaret@sophia.inria.fr)
//
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html
/*
* Be Careful this version is not the original version.
* I modified some sources. Philippe Le Hegaret
*
* @(#)Codecs.java 0.2-2 23/03/1997
*
* This file is part of the HTTPClient package
* Copyright (C) 1996,1997 Ronald Tschalaer
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA
*
* For questions, suggestions, bug-reports, enhancement-requests etc.
* I may be contacted at:
*
* ronald@innovation.ch
* Ronald.Tschalaer@psi.ch
*
*/
package org.w3c.css.util;
import java.io.IOException;
/**
* This class collects various encoders and decoders.
*
* @version 0.2 (bug fix 2) 23/03/1997
* @author Ronald Tschalär
*/
public class Codecs {
/**
* I have problems with multipart/form-data so I integrated auto-debug here
*/
private static boolean debugMode = false;
// Constructors
/**
* This class isn't meant to be instantiated.
*/
private Codecs() {
}
// Methods
/**
* This method decodes a multipart/form-data encoded string. The boundary
* is parsed from the cont_type parameter, which must be of the
* form 'multipart/form-data; boundary=...'.
*
Any encoded files are created in the directory specified by
* dir using the encoded filename.
*
Note: Does not handle nested encodings (yet).
*
Example:
*
* NVPair[] opts = Codecs.mpFormDataDecode(resp.getData(),
* resp.getHeader("Content-length"),
* ".");
*
* Assuming the data received looked something like:
*
* -----------------------------114975832116442893661388290519
* Content-Disposition: form-data; name="option"
*
* doit
* -----------------------------114975832116442893661388290519
* Content-Disposition: form-data; name="comment"; filename="comment.txt"
*
* Gnus and Gnats are not Gnomes.
* -----------------------------114975832116442893661388290519--
*
* you would get one file called comment.txt in the current
* directory, and opts would contain two elements: {"option", "doit"}
* and {"comment", "comment.txt"}
*
* @param data the form-data to decode.
* @param cont_type the content type header (must contain the
* boundary string).
* @return an array of name/value pairs, one for each part;
* the name is the 'name' attribute given in the
* Content-Disposition header; the value is either
* the name of the file if a filename attribute was
* found, or the contents of the part.
* @throws IOException If any file operation fails.
*/
public final static synchronized NVPair[] mpFormDataDecode(byte[] data,
String cont_type)
throws IOException {
// Find and extract boundary string
String bndstr = getParameter("boundary", cont_type);
if (bndstr == null) {
throw new IOException("\'boundary\' parameter " +
"not found in Content-type: " + cont_type);
}
byte[] srtbndry = new byte[bndstr.length() + 4],
boundary = new byte[bndstr.length() + 6],
endbndry = new byte[bndstr.length() + 6];
srtbndry = ("--" + bndstr + "\n").getBytes();
boundary = ("\n--" + bndstr + "\n").getBytes();
endbndry = ("\n--" + bndstr + "--").getBytes();
if (debugMode) {
System.err.println("[START OF DATA]");
printData(data);
System.err.println("[END OF DATA]");
System.err.print("boundary : ");
printData(srtbndry);
System.err.println();
printData(boundary);
System.err.println();
printData(endbndry);
System.err.println();
}
// slurp
// setup search routines
int[] bs = Util.compile_search(srtbndry);
int[] bc = Util.compile_search(boundary);
int[] be = Util.compile_search(endbndry);
// let's start parsing the actual data
int start = Util.findStr(srtbndry, bs, data, 0, data.length);
if (start == -1) { // didn't even find the start
if (!debugMode) {
debugMode = true;
mpFormDataDecode(data, cont_type);
return null;
} else {
debugMode = false;
throw new IOException("Starting boundary not found: " +
new String(srtbndry));
}
}
start += srtbndry.length;
NVPair[] res = new NVPair[10];
boolean done = false;
int idx;
for (idx = 0; !done; idx++) {
// find end of this part
int end = Util.findStr(boundary, bc, data, start, data.length);
if (end == -1) { // must be the last part
end = Util.findStr(endbndry, be, data, start, data.length);
if (end == -1) {
/* if (!debugMode) {
debugMode = true;
mpFormDataDecode(data, cont_type);
return null;
} else {
debugMode = false;
System.err.println( "[Ending boundary not found in]" );
printData(data, start);
System.err.println( "[END DATA BOUNDARY SEARCH]");
throw new IOException("Ending boundary not found: " +
new String(endbndry));
*/
end = data.length - 1;
while (end >= 0 && (data[end] == '\n' || data[end] == ' ')) {
end--;
}
end++;
/* } */
}
done = true;
}
// parse header(s)
String hdr, lchdr, name = null, filename = null, cont_disp = null, mimeType = null;
Object value;
while (true) {
int next = findEOL(data, start) + 1;
if (next - 1 <= start) break; // empty line -> end of headers
hdr = new String(data, start, next - 1 - start);
if (debugMode) {
System.err.println(" start = " + start +
" end = " + next);
}
// handle line continuation
byte ch;
while (next < data.length - 1 &&
((ch = data[next]) == ' ' || ch == '\t')) {
next = findEOL(data, start) + 1;
String result = new String(data, start, next - 1 - start);
hdr += result;
start = next;
}
start = next;
if (debugMode) {
System.err.println("hdr=" + hdr);
System.err.println("(New) start = " + start +
" end = " + next);
}
lchdr = hdr.toLowerCase();
if (lchdr.startsWith("content-type")) {
mimeType = lchdr.substring("content-type: ".length());
continue;
} else if (!lchdr.startsWith("content-disposition")) continue;
int off = lchdr.indexOf("form-data", 20);
if (off == -1) {
if (!debugMode) {
debugMode = true;
mpFormDataDecode(data, cont_type);
return null;
} else {
debugMode = false;
throw new IOException("Expected \'Content-Disposition: form-data\' in line: " + hdr);
}
}
name = getParameter("name", hdr);
if (debugMode) {
System.err.println("[ADD name is " + name + ']');
}
if (name == null) {
if (!debugMode) {
debugMode = true;
mpFormDataDecode(data, cont_type);
return null;
} else {
debugMode = false;
throw new IOException("\'name\' parameter not found in header: " + hdr);
}
}
filename = getParameter("filename", hdr);
if (debugMode) {
System.err.println("[ADD filename is " + filename + ']');
}
cont_disp = hdr;
}
start += 1;
if (debugMode) {
System.err.println("(End) start = " + start +
" end = " + end);
}
if (start > end) {
if (!debugMode) {
debugMode = true;
mpFormDataDecode(data, cont_type);
return null;
} else {
debugMode = false;
throw new IOException("End of header not found at offset " + end);
}
}
if (cont_disp == null) {
if (!debugMode) {
debugMode = true;
mpFormDataDecode(data, cont_type);
return null;
} else {
debugMode = false;
throw new IOException("Missing \'Content-Disposition\' header at offset " + start);
}
}
// handle data for this part
if (filename != null) { // It's a file
FakeFile file = new FakeFile(filename);
file.write(data, start, end - start);
file.setContentType(mimeType);
value = file;
} else { // It's simple data
value = new String(data, start, end - start);
}
if (idx >= res.length) {
res = Util.resizeArray(res, idx + 10);
}
res[idx] = new NVPair(name, value);
if (debugMode) {
System.err.println("[ADD " + name + ',' + value + ','
+ value.getClass() + ']');
}
start = end + boundary.length;
}
return Util.resizeArray(res, idx);
}
/**
* retrieves the value associated with the parameter param in
* a given header string. This is used especially in headers like
* 'Content-type' and 'Content-Disposition'. Here is the syntax it
* expects: