001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: MimeHeader.java,v 1.2 2006-06-15 13:47:00 sinisa Exp $
022: */
023:
024: package com.lutris.mime;
025:
026: import java.io.IOException;
027: import java.io.PushbackReader;
028: import java.io.StringReader;
029: import java.util.Enumeration;
030: import java.util.Hashtable;
031:
032: /**
033: * Represents a generic parsed Mime header. Specific header types, such as
034: * <code>Content-Type</code> are represented by classes derived from this
035: * header. The constructor for this class parses only parameters according to
036: * the rules specified in RFC2045 Section 5.1 Page 11. It is left to derived
037: * classes to parse type-specifiec information such as the Mime type (
038: * <code>Content-Type</code>) or content disposition (
039: * <code>Content-Disposition</code>).
040: */
041: public class MimeHeader {
042: /**
043: * Contains the parsed parameters for this hash table.
044: */
045: private Hashtable myParams;
046:
047: /**
048: * Contains the type-independent value (if any) for the header.
049: */
050: private String myValue;
051:
052: /**
053: * Contains the Mime header type value, defined as the characters before the
054: * first colon (':') character.
055: */
056: private String myHeaderType;
057:
058: /**
059: * The raw, unparsed header line passed to the constructor.
060: */
061: private String rawHeaderLine;
062:
063: /**
064: * Constructs a generic <code>MimeHeader</code> object and initializes its
065: * internal parameter list.
066: */
067: public MimeHeader(String headerLine) {
068: myParams = new Hashtable();
069: rawHeaderLine = headerLine;
070: int pos = headerLine.indexOf(':');
071: if (pos < 0) {
072: myHeaderType = null;
073: } else {
074: myHeaderType = headerLine.substring(0, pos).trim()
075: .toLowerCase();
076: headerLine = headerLine.substring(pos + 1);
077: }
078: pos = headerLine.indexOf(';');
079: if (pos < 0) {
080: // Handle potential comment.
081: pos = headerLine.indexOf('(');
082: if (pos < 0) {
083: myValue = headerLine.trim();
084: } else {
085: myValue = headerLine.substring(0, pos).trim();
086: }
087: } else {
088: myValue = headerLine.substring(0, pos).trim();
089: }
090: }
091:
092: /**
093: * Used by derived classes to put a parameter into the internal parameter
094: * value holder.
095: *
096: * @param name
097: * The name of the parameter value to add.
098: * @param value
099: * The value to add.
100: */
101: protected void putParameter(String name, String value) {
102: name = name.toLowerCase();
103: String[] oldvals = (String[]) myParams.get(name);
104: int num = oldvals == null ? 0 : oldvals.length;
105: String[] newvals = new String[num + 1];
106: for (int i = 0; i < num; i++) {
107: newvals[i] = oldvals[i];
108: oldvals[i] = null;
109: }
110: newvals[num] = value;
111: oldvals = null;
112: myParams.put(name, newvals);
113: }
114:
115: /**
116: * Returns the "value" of this header. This is defined to be the trimmed
117: * substring between the first colon (':') character and the first semicolon
118: * (';') character. For example, observe the following header line:
119: *
120: * <PRE>
121: *
122: * Content-Type: multipart/form-data; boundary="000572A9AD4"
123: *
124: * </PRE>
125: *
126: * The <b>value </b> of this header is <i>multipart/form-data </i>, the
127: * <b>header type </b> is <i>Content-Type </i>, and the first <b>parameter
128: * </b> is named <i>boundary </i> and has the value <i>00572A9AD4 </i>.
129: *
130: * @return The <b>value </b> of this Mime header.
131: */
132: public String getValue() {
133: return myValue;
134: }
135:
136: /**
137: * Returns the <b>header type </b> of this header. This is defined to be the
138: * substring from the first character up to first colon (':') character. For
139: * example, observe the following header line:
140: *
141: * <PRE>
142: *
143: * Content-Type: multipart/form-data; boundary="000572A9AD4"
144: *
145: * </PRE>
146: *
147: * The <b>value </b> of this header is <i>multipart/form-data </i>, the
148: * <b>header type </b> is <i>Content-Type </i>, and the first <b>parameter
149: * </b> is named <i>boundary </i> and has the value <i>00572A9AD4 </i>.
150: *
151: * @return The <b>header type </b> of this Mime header.
152: */
153: public String getHeaderType() {
154: return myHeaderType;
155: }
156:
157: /**
158: * Returns the string passed to the constructor without any parsing.
159: *
160: * @returns The unparsed Mime header line in its entirety.
161: */
162: public String getRawHeaderLine() {
163: return rawHeaderLine;
164: }
165:
166: /**
167: * Returns a single <code>String</code> value representing the last
168: * occurence of the <b>parameter </b> named by <code>name</code>. For
169: * example, observe the following header line:
170: *
171: * <PRE>
172: *
173: * Content-Type: multipart/form-data; boundary="000572A9AD4"
174: *
175: * </PRE>
176: *
177: * The <b>value </b> of this header is <i>multipart/form-data </i>, the
178: * <b>header type </b> is <i>Content-Type </i>, and the first <b>parameter
179: * </b> is named <i>boundary </i> and has the value <i>00572A9AD4 </i>.
180: *
181: * @param name
182: * The name of the <b>parameter </b> to return.
183: * @return The value of the last occurence of the named <b>parameter </b> or
184: * <code>null</code> if not found.
185: */
186: public String getParameter(String name) {
187: name = name.toLowerCase();
188: String[] vals = (String[]) myParams.get(name);
189: if (vals == null)
190: return null;
191: if (vals.length < 1)
192: return null;
193: return vals[vals.length - 1];
194: }
195:
196: /**
197: * Returns an array of <code>String</code> containing all occurences of
198: * values named by <code>name</code>. For example, observe the following
199: * header line:
200: *
201: * <PRE>
202: *
203: * Content-Type: multipart/form-data; boundary="000572A9AD4"
204: *
205: * </PRE>
206: *
207: * The <b>value </b> of this header is <i>multipart/form-data </i>, the
208: * <b>header type </b> is <i>Content-Type </i>, and the first <b>parameter
209: * </b> is named <i>boundary </i> and has the value <i>00572A9AD4 </i>.
210: *
211: * @param name
212: * The name of the <b>parameter </b> to return.
213: * @return Array containing all values for the named <b>parameter </b> or
214: * <code>null</code> if not found.
215: */
216: public String[] getParameters(String name) {
217: name = name.toLowerCase();
218: String[] vals = (String[]) myParams.get(name);
219: if (vals == null)
220: return null;
221: if (vals.length < 1)
222: return null;
223: return vals;
224: }
225:
226: /**
227: * Returns an enumeration of all of the parameter names parsed from the
228: * current Mime header.
229: *
230: * @return An enumeration of the names of all parameters parsed from the
231: * Mime header.
232: */
233: public Enumeration getParameterNames() {
234: return myParams.keys();
235: }
236:
237: private static void skipComment(PushbackReader input)
238: throws IOException {
239: int ch;
240: while ((ch = input.read()) >= 0)
241: if (ch == ')')
242: return;
243: }
244:
245: private final String parseToken(PushbackReader input, boolean trim)
246: throws IOException {
247: StringBuffer buf = new StringBuffer();
248: int ch;
249: if (trim) {
250: while ((ch = input.read()) >= 0)
251: if ((ch > ' ') && (ch < 0xff)) {
252: input.unread(ch);
253: if (ch == '(') {
254: skipComment(input);
255: } else
256: break;
257: }
258: }
259: while ((ch = input.read()) >= 0) {
260: switch (ch) {
261: case '=':
262: case ';':
263: input.unread(ch);
264: return buf.toString();
265: case '(':
266: skipComment(input);
267: break;
268: default:
269: if ((ch > ' ') && (ch < 0xff))
270: buf.append((char) ch);
271: else {
272: input.unread(ch);
273: return buf.toString();
274: }
275: break;
276: }
277: }
278: return buf.toString();
279: }
280:
281: private final String parseQuotedString(PushbackReader input)
282: throws IOException {
283: StringBuffer buf = new StringBuffer();
284: int ch;
285: while ((ch = input.read()) >= 0) {
286: switch (ch) {
287: case '"':
288: return buf.toString();
289:
290: case '\\':
291: if ((ch = input.read()) >= 0) {
292: if ((ch != '\\') && (ch != '"')) {
293: buf.append('\\');
294: }
295: buf.append((char) ch);
296: } else {
297: buf.append('\\');
298: return buf.toString();
299: }
300: break;
301:
302: default:
303: buf.append((char) ch);
304: break;
305: }
306: }
307: return buf.toString();
308: }
309:
310: /**
311: * Parses the a given string into <i>key=parameter </i> pairs in accordance
312: * with RFC2045 Section 5.1 Page 11. Quoted strings are parsed in accordance
313: * with the RFC822 definition of the "<code>quoted-string</code>"
314: * grammatic construct. According to RFC822, quoted strings are strings,
315: * enclosed in double-quote marks, which contain any character except LF or
316: * double-quote. The backslash character is used to literally quote the
317: * following character. Octal or hex code backquoting is not supported.
318: */
319: protected void parseParameters(String headerLine) {
320: try {
321: StringReader reader = new StringReader(headerLine);
322: PushbackReader input = new PushbackReader(reader);
323: int ch;
324: String key, value;
325: while (true) {
326: key = parseToken(input, true);
327: ch = input.read();
328: if (ch < 0)
329: break;
330: switch (ch) {
331: case '=':
332: switch (ch = input.read()) {
333: case '"':
334: value = parseQuotedString(input);
335: if (key.length() > 0) {
336: putParameter(key, value);
337: }
338: break;
339: default:
340: input.unread(ch);
341: value = parseToken(input, false);
342: if (key.length() > 0) {
343: putParameter(key, value);
344: }
345: break;
346: }
347: while (((ch = input.read()) >= 0) && (ch != ';')) {
348: if (ch == '(') {
349: skipComment(input);
350: }
351: }
352: break;
353: case ';':
354: // Not a key=value pair.
355: break;
356: default:
357: // Garbled input.
358: return;
359: } // outer switch
360: } // outer while
361: return;
362: } catch (IOException e) {
363: return;
364: }
365: }
366: }
|