001: package org.enhydra.shark.webclient.spec;
002:
003: import java.io.ByteArrayInputStream;
004: import java.io.InputStream;
005: import java.util.ArrayList;
006: import java.util.HashMap;
007:
008: //import com.eosrisq.versmath.business.utils.Display;
009:
010: /**
011: * Representation of single entity part from multipart/form-data http
012: * request.
013: */
014: public class EntityPart {
015:
016: // Standard multipart/form-data HTTP headers
017: public static final String ALLOW = "Allow";
018: public static final String CONTENT_ENCODING = "Content-Encoding";
019: public static final String CONTENY_LANGUAGE = "Content-Language";
020: public static final String CONTENT_LENGTH = "Content-Length";
021: public static final String CONTENT_LOCATION = "Content-Location";
022: public static final String CONTENT_MD5 = "Content-MD5";
023: public static final String CONTENT_RANGE = "Content-Range";
024: public static final String CONTENT_TYPE = "Content-Type";
025: public static final String EXPIRES = "Expires";
026: public static final String LAST_MODIFIED = "Last-Modified";
027: // Non-standard HTTP headers
028: public static final String CONTENT_DISPOSITION = "Content-Disposition";
029:
030: /**
031: * Byte aray constant which represents CR and LF character combination.
032: * From RFC 1867:
033: * As with all MIME transmissions, CRLF is used as the separator for
034: * lines in a POST of the data in multipart/form-data.
035: */
036: public final static byte[] CRLF = { 0x0D, 0x0A };
037:
038: /**
039: * Byte aray constant which represents CR LF CR and LF character combination.
040: */
041: public final static byte[] CRLF_DOUBLE = { 0x0D, 0x0A, 0x0D, 0x0A };
042:
043: /**
044: * Storage for body part of multipart/form-data entity
045: */
046: private byte[] body = null;
047:
048: /**
049: * Storage for multipart/form-data entity header lines. Note that original
050: * header lines which were spred to multiply lines are joined into single
051: * String array element.
052: */
053: private String[] headerLines = null;
054:
055: /**
056: * Storage for multipart/form-data entity header fields
057: */
058: private HashMap headerFields = new HashMap();
059:
060: /**
061: * Construction with multipart/form-data given as byte array, and encoding.
062: * @param data data given as byte array
063: * @param encoding is passed encoding used when byte array to string
064: * transformation is necessery. If encoding is null, the UTF-8 will be used
065: * as default.
066: */
067: public EntityPart(byte[] data, String encoding) {
068: int index = ByteArrayUtility.firstIndex(EntityPart.CRLF_DOUBLE,
069: data, 0);
070: if (index == -1)
071: return;
072: // every header line should be finished with CRLF, and this is the reason
073: // why array data will be cut to the index + 2 position
074: byte[] header = ByteArrayUtility.cutArray(data, 0, index + 2);
075: this .body = ByteArrayUtility.cutArray(data, index + 4,
076: data.length);
077: this .headerLines = makeHeaderLines(header, encoding);
078: }
079:
080: /**
081: * Splits array of bytes which represent the header part of Entity into header lines and
082: * transform them into Strings.
083: * @param header
084: * @param encoding
085: * @return
086: */
087: private String[] makeHeaderLines(byte[] header, String encoding) {
088: ArrayList linesList = new ArrayList();
089:
090: int[] endIndexes = ByteArrayUtility.findAllIndexes(
091: EntityPart.CRLF, header);
092: int startIndex = 0;
093: for (int i = 0; i < endIndexes.length; i++) {
094: String line = "";
095: byte[] temp = ByteArrayUtility.cutArray(header, startIndex,
096: endIndexes[i]);
097: // If any HT or space character starts the new header line it means that that line should be
098: // concatenated to previous line
099: if (temp[0] == 0x09 || temp[0] == 0x20) {
100: temp = ByteArrayUtility.replaceAll(temp, (byte) 0x20,
101: (byte) 0x09);
102: line = ByteArrayUtility.toString(temp, encoding).trim();
103: if (linesList.size() != 0
104: && (line != null || line.length() != 0))
105: line = (String) (linesList
106: .remove(linesList.size() - 1))
107: + " " + line;
108: } else
109: line = ByteArrayUtility.toString(temp, encoding).trim();
110:
111: linesList.add(line);
112: startIndex = startIndex + 2;
113: }
114:
115: String[] retStrings = new String[linesList.size()];
116: for (int i = 0; i < retStrings.length; i++) {
117: retStrings[i] = (String) linesList.get(i);
118: this .addHeaderField(retStrings[i]);
119: }
120:
121: return retStrings;
122: }
123:
124: /**
125: * Return header lines. Note that lines which originaly in http were split
126: * into more then one line
127: * @return
128: */
129: public String[] getHeaderLines() {
130: return this .headerLines;
131: }
132:
133: /**
134: * Adds Entity header fields into storage.
135: * @param line
136: */
137: private void addHeaderField(String line) {
138: int colon = line.indexOf(":");
139: if (colon > 0 && colon != line.length() - 1) {
140: String entity = line.substring(0, colon).trim();
141: String entityVal = line.substring(colon + 1).trim();
142: // Checking because potential upper/smoler case inconsistency
143: if (entity.equalsIgnoreCase(EntityPart.CONTENT_DISPOSITION))
144: this .headerFields.put(EntityPart.CONTENT_DISPOSITION,
145: entityVal);
146: else if (entity.equalsIgnoreCase(EntityPart.CONTENT_TYPE))
147: this .headerFields.put(EntityPart.CONTENT_TYPE,
148: entityVal);
149: // For other field names which are not focused in file download process
150: // in VersMath application, case restriction is not checking (it can be
151: // implemented if there is need for that).
152: else
153: this .headerFields.put(entity, entityVal);
154: }
155: }
156:
157: public String getHeaderFieldValue(String headerName) {
158: return (String) this .headerFields.get(headerName);
159: }
160:
161: public String getContentDisposition() {
162: return (String) this .headerFields
163: .get(EntityPart.CONTENT_DISPOSITION);
164: }
165:
166: public String getContentType() {
167: return (String) this .headerFields.get(EntityPart.CONTENT_TYPE);
168: }
169:
170: public byte[] getBody() {
171: return this .body;
172: }
173:
174: public String getBody(String encoding) {
175: return ByteArrayUtility.toString(this .body, encoding);
176: }
177:
178: public InputStream getBodyStream() {
179: return new ByteArrayInputStream(this .body);
180: }
181:
182: public String getFieldValue(String fieldName) {
183: String value = (String) this .headerFields.get(fieldName
184: + ".$_value");
185: if (value == null) {
186: value = (String) this .headerFields.get(fieldName);
187: if (value != null) {
188: value = this .removeComments(value);
189: int stopIndex = value.indexOf(";");
190: if (stopIndex != -1)
191: value = value.substring(0, stopIndex).trim();
192: else
193: value = value.trim();
194: } else
195: return null;
196: } else
197: return value;
198:
199: value = this .removeQuotes(value);
200: this .headerFields.put(fieldName + ".$_value", value);
201: return value;
202: }
203:
204: public String getContentDispositionValue() {
205: return this .getFieldValue(EntityPart.CONTENT_DISPOSITION);
206: }
207:
208: public String getContentTypeValue() {
209: return this .getFieldValue(EntityPart.CONTENT_TYPE);
210: }
211:
212: public String getFieldParameter(String fieldName, String paramName) {
213: String value = (String) this .headerFields.get(fieldName + "."
214: + paramName);
215: if (value == null) {
216: value = (String) this .headerFields.get(fieldName);
217: if (value != null) {
218: value = this .removeComments(value);
219: int startIndex = value.indexOf(paramName);
220: if (startIndex != -1) {
221: int equalIndex = value.indexOf("=", startIndex + 1);
222: if (equalIndex == -1)
223: return null;
224: int stopIndex = value.indexOf(";", equalIndex + 1);
225: if (stopIndex != -1)
226: value = value.substring(equalIndex + 1,
227: stopIndex).trim();
228: else
229: value = value.substring(equalIndex + 1).trim();
230: } else
231: return null;
232: } else
233: return null;
234: } else
235: return value;
236:
237: value = this .removeQuotes(value);
238: this .headerFields.put(fieldName + "." + paramName, value);
239: return value;
240: }
241:
242: public String getContentDispositionName() {
243: return this .getFieldParameter(EntityPart.CONTENT_DISPOSITION,
244: "name");
245: }
246:
247: public String getContentDispositionFilename() {
248: return this .getFieldParameter(EntityPart.CONTENT_DISPOSITION,
249: "filename");
250: }
251:
252: private String removeQuotes(String in) {
253: if (in.startsWith("\""))
254: in = in.substring(1);
255: if (in.endsWith("\""))
256: in = in.substring(0, in.length() - 1);
257: return in;
258: }
259:
260: /**
261: * Removes comments from header field-s. Note that this method could work
262: * wrong in cases when '(' and ')' does not represent comments but they
263: * are parts of parameter value strings or part of field value.
264: * @param in
265: * @return
266: */
267: private String removeComments(String in) {
268: String out = in;
269: int stopComment = 0;
270: while (stopComment < in.length()) {
271: int startComment = in.indexOf("(", stopComment);
272: if (startComment == -1)
273: return out;
274: stopComment = in.indexOf(")", startComment);
275: if (stopComment == -1)
276: return out;
277:
278: out = out.substring(0, startComment)
279: + out.substring(stopComment + 1);
280: }
281: return out;
282: }
283:
284: }
|