Source code: com/lutris/mime/MimeHeader.java
1 /*
2 * Enhydra Java Application Server Project
3 *
4 * The contents of this file are subject to the Enhydra Public License
5 * Version 1.1 (the "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy of the License on
7 * the Enhydra web site ( http://www.enhydra.org/ ).
8 *
9 * Software distributed under the License is distributed on an "AS IS"
10 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11 * the License for the specific terms governing rights and limitations
12 * under the License.
13 *
14 * The Initial Developer of the Enhydra Application Server is Lutris
15 * Technologies, Inc. The Enhydra Application Server and portions created
16 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17 * All Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * $Id: MimeHeader.java,v 1.8.12.1 2000/10/19 17:58:53 jasona Exp $
22 */
23
24
25
26
27 package com.lutris.mime;
28 import java.util.*;
29 import java.io.*;
30
31 /**
32 * Represents a generic parsed Mime header. Specific header types, such
33 * as <code>Content-Type</code> are represented by classes derived from
34 * this header. The constructor for this class parses only parameters
35 * according to the rules specified in RFC2045 Section 5.1 Page 11.
36 * It is left to derived classes to parse type-specifiec information
37 * such as the Mime type (<code>Content-Type</code>) or content disposition
38 * (<code>Content-Disposition</code>).
39 */
40 public class MimeHeader {
41 /**
42 * Contains the parsed parameters for this hash table.
43 */
44 private Hashtable myParams;
45
46 /**
47 * Contains the type-independent value (if any) for the header.
48 */
49 private String myValue;
50
51 /**
52 * Contains the Mime header type value, defined as the characters
53 * before the first colon (':') character.
54 */
55 private String myHeaderType;
56
57 /**
58 * The raw, unparsed header line passed to the constructor.
59 */
60 private String rawHeaderLine;
61
62 /**
63 * Constructs a generic <code>MimeHeader</code> object and initializes
64 * its internal parameter list.
65 */
66 public
67 MimeHeader(String headerLine)
68 {
69 myParams = new Hashtable();
70 rawHeaderLine = headerLine;
71 int pos = headerLine.indexOf(':');
72 if (pos < 0) {
73 myHeaderType = null;
74 } else {
75 myHeaderType = headerLine.substring(0, pos).trim().toLowerCase();
76 headerLine = headerLine.substring(pos+1);
77 }
78 pos = headerLine.indexOf(';');
79 if (pos < 0) {
80 // Handle potential comment.
81 pos = headerLine.indexOf('(');
82 if (pos < 0) {
83 myValue = headerLine.trim();
84 } else {
85 myValue = headerLine.substring(0, pos).trim();
86 }
87 } else {
88 myValue = headerLine.substring(0, pos).trim();
89 }
90 }
91
92 /**
93 * Used by derived classes to put a parameter into the internal
94 * parameter value holder.
95 *
96 * @param name The name of the parameter value to add.
97 * @param value The value to add.
98 */
99 protected
100 void putParameter(String name, String value)
101 {
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
117 * trimmed substring between the first colon (':') character and
118 * the first semicolon (';') character. For example, observe the
119 * following header line:
120 * <PRE>
121 * Content-Type: multipart/form-data; boundary="000572A9AD4"
122 * </PRE>
123 * The <b>value</b> of this header is <i>multipart/form-data</i>,
124 * the <b>header type</b> is <i>Content-Type</i>, and the first
125 * <b>parameter</b> is named <i>boundary</i> and has the value
126 * <i>00572A9AD4</i>.
127 *
128 * @return The <b>value</b> of this Mime header.
129 */
130 public
131 String getValue()
132 {
133 return myValue;
134 }
135
136 /**
137 * Returns the <b>header type</b> of this header. This is defined to
138 * be the substring from the first character up to first colon (':')
139 * character. For example, observe the following header line:
140 * <PRE>
141 * Content-Type: multipart/form-data; boundary="000572A9AD4"
142 * </PRE>
143 * The <b>value</b> of this header is <i>multipart/form-data</i>,
144 * the <b>header type</b> is <i>Content-Type</i>, and the first
145 * <b>parameter</b> is named <i>boundary</i> and has the value
146 * <i>00572A9AD4</i>.
147 *
148 * @return The <b>header type</b> of this Mime header.
149 */
150 public
151 String getHeaderType()
152 {
153 return myHeaderType;
154 }
155
156 /**
157 * Returns the string passed to the constructor without any parsing.
158 *
159 * @returns The unparsed Mime header line in its entirety.
160 */
161 public
162 String getRawHeaderLine()
163 {
164 return rawHeaderLine;
165 }
166
167 /**
168 * Returns a single <code>String</code> value representing the
169 * last occurence of the <b>parameter</b> named by <code>name</code>.
170 * For example, observe the following header line:
171 * <PRE>
172 * Content-Type: multipart/form-data; boundary="000572A9AD4"
173 * </PRE>
174 * The <b>value</b> of this header is <i>multipart/form-data</i>,
175 * the <b>header type</b> is <i>Content-Type</i>, and the first
176 * <b>parameter</b> is named <i>boundary</i> and has the value
177 * <i>00572A9AD4</i>.
178 *
179 * @param name The name of the <b>parameter</b> to return.
180 * @return The value of the last occurence of the named
181 * <b>parameter</b> or <code>null</code> if not found.
182 */
183 public
184 String getParameter(String name)
185 {
186 name = name.toLowerCase();
187 String[] vals = (String[]) myParams.get(name);
188 if (vals == null) return null;
189 if (vals.length < 1) return null;
190 return vals[vals.length - 1];
191 }
192
193 /**
194 * Returns an array of <code>String</code> containing all occurences
195 * of values named by <code>name</code>.
196 * For example, observe the following header line:
197 * <PRE>
198 * Content-Type: multipart/form-data; boundary="000572A9AD4"
199 * </PRE>
200 * The <b>value</b> of this header is <i>multipart/form-data</i>,
201 * the <b>header type</b> is <i>Content-Type</i>, and the first
202 * <b>parameter</b> is named <i>boundary</i> and has the value
203 * <i>00572A9AD4</i>.
204 *
205 * @param name The name of the <b>parameter</b> to return.
206 * @return Array containing all values for the named
207 * <b>parameter</b> or <code>null</code> if not found.
208 */
209 public String[] getParameters(String name)
210 {
211 name = name.toLowerCase();
212 String[] vals = (String[]) myParams.get(name);
213 if (vals == null) return null;
214 if (vals.length < 1) return null;
215 return vals;
216 }
217
218 /**
219 * Returns an enumeration of all of the parameter names parsed from
220 * the current Mime header.
221 *
222 * @return An enumeration of the names of all parameters parsed from
223 * the Mime header.
224 */
225 public Enumeration getParameterNames()
226 {
227 return myParams.keys();
228 }
229
230 private static
231 void skipComment(PushbackReader input)
232 throws IOException
233 {
234 int ch;
235 while ((ch = input.read()) >= 0) if (ch == ')') return;
236 }
237
238 private final
239 String parseToken(PushbackReader input, boolean trim)
240 throws IOException
241 {
242 StringBuffer buf = new StringBuffer();
243 int ch;
244 if (trim) {
245 while ((ch = input.read()) >= 0) if ((ch > ' ') && (ch < 0xff)) {
246 input.unread(ch);
247 if (ch == '(') {
248 skipComment(input);
249 }
250 else
251 break;
252 }
253 }
254 while ((ch = input.read()) >= 0) {
255 switch (ch) {
256 case '=': case ';':
257 input.unread(ch);
258 return buf.toString();
259 case '(':
260 skipComment(input);
261 break;
262 default:
263 if ((ch > ' ') && (ch < 0xff))
264 buf.append((char)ch);
265 else {
266 input.unread(ch);
267 return buf.toString();
268 }
269 break;
270 }
271 }
272 return buf.toString();
273 }
274
275 private final
276 String parseQuotedString(PushbackReader input)
277 throws IOException
278 {
279 StringBuffer buf = new StringBuffer();
280 int ch;
281 while ((ch = input.read()) >= 0) {
282 switch (ch) {
283 case '"':
284 return buf.toString();
285
286 case '\\':
287 if ((ch = input.read()) >= 0) {
288 if ((ch!='\\')&&(ch!='"')) {
289 buf.append('\\');
290 }
291 buf.append((char)ch);
292 } else {
293 buf.append('\\');
294 return buf.toString();
295 }
296 break;
297
298 default:
299 buf.append((char)ch);
300 break;
301 }
302 }
303 return buf.toString();
304 }
305
306 /**
307 * Parses the a given string into <i>key=parameter</i> pairs in
308 * accordance with RFC2045 Section 5.1 Page 11. Quoted strings
309 * are parsed in accordance with the RFC822 definition of the
310 * "<code>quoted-string</code>" grammatic construct. According
311 * to RFC822, quoted strings are strings, enclosed in double-quote
312 * marks, which contain any character except LF or double-quote.
313 * The backslash character is used to literally quote the following
314 * character. Octal or hex code backquoting is not supported.
315 */
316 protected
317 void parseParameters(String headerLine)
318 {
319 try {
320 StringReader reader = new StringReader(headerLine);
321 PushbackReader input = new PushbackReader(reader);
322 int ch;
323 String key, value;
324 while (true) {
325 key = parseToken(input, true);
326 ch = input.read();
327 if (ch < 0) break;
328 switch (ch) {
329 case '=':
330 switch (ch = input.read()) {
331 case '"':
332 value = parseQuotedString(input);
333 if (key.length() > 0) {
334 putParameter(key, value);
335 }
336 break;
337 default:
338 input.unread(ch);
339 value = parseToken(input, false);
340 if (key.length() > 0) {
341 putParameter(key, value);
342 }
343 break;
344 }
345 while (((ch = input.read()) >= 0) && (ch != ';')) {
346 if (ch == '(') {
347 skipComment(input);
348 }
349 }
350 break;
351 case ';':
352 // Not a key=value pair.
353 break;
354 default:
355 // Garbled input.
356 return;
357 } // outer switch
358 } // outer while
359 return;
360 }
361 catch (IOException e) {
362 return;
363 }
364 }
365 }
366