001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.tomcat.util.http;
018:
019: import java.io.PrintWriter;
020: import java.io.StringWriter;
021: import java.util.Enumeration;
022:
023: import org.apache.tomcat.util.buf.MessageBytes;
024:
025: /* XXX XXX XXX Need a major rewrite !!!!
026: */
027:
028: /**
029: * This class is used to contain standard internet message headers,
030: * used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for
031: * MIME (RFC 2045) applications such as transferring typed data and
032: * grouping related items in multipart message bodies.
033: *
034: * <P> Message headers, as specified in RFC822, include a field name
035: * and a field body. Order has no semantic significance, and several
036: * fields with the same name may exist. However, most fields do not
037: * (and should not) exist more than once in a header.
038: *
039: * <P> Many kinds of field body must conform to a specified syntax,
040: * including the standard parenthesized comment syntax. This class
041: * supports only two simple syntaxes, for dates and integers.
042: *
043: * <P> When processing headers, care must be taken to handle the case of
044: * multiple same-name fields correctly. The values of such fields are
045: * only available as strings. They may be accessed by index (treating
046: * the header as an array of fields), or by name (returning an array
047: * of string values).
048: */
049:
050: /* Headers are first parsed and stored in the order they are
051: received. This is based on the fact that most servlets will not
052: directly access all headers, and most headers are single-valued.
053: ( the alternative - a hash or similar data structure - will add
054: an overhead that is not needed in most cases )
055:
056: Apache seems to be using a similar method for storing and manipulating
057: headers.
058:
059: Future enhancements:
060: - hash the headers the first time a header is requested ( i.e. if the
061: servlet needs direct access to headers).
062: - scan "common" values ( length, cookies, etc ) during the parse
063: ( addHeader hook )
064:
065: */
066:
067: /**
068: * Memory-efficient repository for Mime Headers. When the object is recycled, it
069: * will keep the allocated headers[] and all the MimeHeaderField - no GC is generated.
070: *
071: * For input headers it is possible to use the MessageByte for Fileds - so no GC
072: * will be generated.
073: *
074: * The only garbage is generated when using the String for header names/values -
075: * this can't be avoided when the servlet calls header methods, but is easy
076: * to avoid inside tomcat. The goal is to use _only_ MessageByte-based Fields,
077: * and reduce to 0 the memory overhead of tomcat.
078: *
079: * TODO:
080: * XXX one-buffer parsing - for http ( other protocols don't need that )
081: * XXX remove unused methods
082: * XXX External enumerations, with 0 GC.
083: * XXX use HeaderName ID
084: *
085: *
086: * @author dac@eng.sun.com
087: * @author James Todd [gonzo@eng.sun.com]
088: * @author Costin Manolache
089: * @author kevin seguin
090: */
091: public class MimeHeaders {
092: /** Initial size - should be == average number of headers per request
093: * XXX make it configurable ( fine-tuning of web-apps )
094: */
095: public static final int DEFAULT_HEADER_SIZE = 8;
096:
097: /**
098: * The header fields.
099: */
100: private MimeHeaderField[] headers = new MimeHeaderField[DEFAULT_HEADER_SIZE];
101:
102: /**
103: * The current number of header fields.
104: */
105: private int count;
106:
107: /**
108: * Creates a new MimeHeaders object using a default buffer size.
109: */
110: public MimeHeaders() {
111: }
112:
113: /**
114: * Clears all header fields.
115: */
116: // [seguin] added for consistency -- most other objects have recycle().
117: public void recycle() {
118: clear();
119: }
120:
121: /**
122: * Clears all header fields.
123: */
124: public void clear() {
125: for (int i = 0; i < count; i++) {
126: headers[i].recycle();
127: }
128: count = 0;
129: }
130:
131: /**
132: * EXPENSIVE!!! only for debugging.
133: */
134: public String toString() {
135: StringWriter sw = new StringWriter();
136: PrintWriter pw = new PrintWriter(sw);
137: pw.println("=== MimeHeaders ===");
138: Enumeration e = names();
139: while (e.hasMoreElements()) {
140: String n = (String) e.nextElement();
141: pw.println(n + " = " + getHeader(n));
142: }
143: return sw.toString();
144: }
145:
146: // -------------------- Idx access to headers ----------
147:
148: /**
149: * Returns the current number of header fields.
150: */
151: public int size() {
152: return count;
153: }
154:
155: /**
156: * Returns the Nth header name, or null if there is no such header.
157: * This may be used to iterate through all header fields.
158: */
159: public MessageBytes getName(int n) {
160: return n >= 0 && n < count ? headers[n].getName() : null;
161: }
162:
163: /**
164: * Returns the Nth header value, or null if there is no such header.
165: * This may be used to iterate through all header fields.
166: */
167: public MessageBytes getValue(int n) {
168: return n >= 0 && n < count ? headers[n].getValue() : null;
169: }
170:
171: /** Find the index of a header with the given name.
172: */
173: public int findHeader(String name, int starting) {
174: // We can use a hash - but it's not clear how much
175: // benefit you can get - there is an overhead
176: // and the number of headers is small (4-5 ?)
177: // Another problem is that we'll pay the overhead
178: // of constructing the hashtable
179:
180: // A custom search tree may be better
181: for (int i = starting; i < count; i++) {
182: if (headers[i].getName().equalsIgnoreCase(name)) {
183: return i;
184: }
185: }
186: return -1;
187: }
188:
189: // -------------------- --------------------
190:
191: /**
192: * Returns an enumeration of strings representing the header field names.
193: * Field names may appear multiple times in this enumeration, indicating
194: * that multiple fields with that name exist in this header.
195: */
196: public Enumeration names() {
197: return new NamesEnumerator(this );
198: }
199:
200: public Enumeration values(String name) {
201: return new ValuesEnumerator(this , name);
202: }
203:
204: // -------------------- Adding headers --------------------
205:
206: /**
207: * Adds a partially constructed field to the header. This
208: * field has not had its name or value initialized.
209: */
210: private MimeHeaderField createHeader() {
211: MimeHeaderField mh;
212: int len = headers.length;
213: if (count >= len) {
214: // expand header list array
215: MimeHeaderField tmp[] = new MimeHeaderField[count * 2];
216: System.arraycopy(headers, 0, tmp, 0, len);
217: headers = tmp;
218: }
219: if ((mh = headers[count]) == null) {
220: headers[count] = mh = new MimeHeaderField();
221: }
222: count++;
223: return mh;
224: }
225:
226: /** Create a new named header , return the MessageBytes
227: container for the new value
228: */
229: public MessageBytes addValue(String name) {
230: MimeHeaderField mh = createHeader();
231: mh.getName().setString(name);
232: return mh.getValue();
233: }
234:
235: /** Create a new named header using un-translated byte[].
236: The conversion to chars can be delayed until
237: encoding is known.
238: */
239: public MessageBytes addValue(byte b[], int startN, int len) {
240: MimeHeaderField mhf = createHeader();
241: mhf.getName().setBytes(b, startN, len);
242: return mhf.getValue();
243: }
244:
245: /** Create a new named header using translated char[].
246: */
247: public MessageBytes addValue(char c[], int startN, int len) {
248: MimeHeaderField mhf = createHeader();
249: mhf.getName().setChars(c, startN, len);
250: return mhf.getValue();
251: }
252:
253: /** Allow "set" operations -
254: return a MessageBytes container for the
255: header value ( existing header or new
256: if this .
257: */
258: public MessageBytes setValue(String name) {
259: MessageBytes value = getValue(name);
260: if (value == null) {
261: MimeHeaderField mh = createHeader();
262: mh.getName().setString(name);
263: value = mh.getValue();
264: }
265: return value;
266: }
267:
268: //-------------------- Getting headers --------------------
269: /**
270: * Finds and returns a header field with the given name. If no such
271: * field exists, null is returned. If more than one such field is
272: * in the header, an arbitrary one is returned.
273: */
274: public MessageBytes getValue(String name) {
275: for (int i = 0; i < count; i++) {
276: if (headers[i].getName().equalsIgnoreCase(name)) {
277: return headers[i].getValue();
278: }
279: }
280: return null;
281: }
282:
283: // bad shortcut - it'll convert to string ( too early probably,
284: // encoding is guessed very late )
285: public String getHeader(String name) {
286: MessageBytes mh = getValue(name);
287: return mh != null ? mh.toString() : null;
288: }
289:
290: // -------------------- Removing --------------------
291: /**
292: * Removes a header field with the specified name. Does nothing
293: * if such a field could not be found.
294: * @param name the name of the header field to be removed
295: */
296: public void removeHeader(String name) {
297: // XXX
298: // warning: rather sticky code; heavily tuned
299:
300: for (int i = 0; i < count; i++) {
301: if (headers[i].getName().equalsIgnoreCase(name)) {
302: // reset and swap with last header
303: MimeHeaderField mh = headers[i];
304:
305: mh.recycle();
306: headers[i] = headers[count - 1];
307: headers[count - 1] = mh;
308:
309: count--;
310: i--;
311: }
312: }
313: }
314: }
315:
316: /** Enumerate the distinct header names.
317: Each nextElement() is O(n) ( a comparation is
318: done with all previous elements ).
319:
320: This is less frequesnt than add() -
321: we want to keep add O(1).
322: */
323: class NamesEnumerator implements Enumeration {
324: int pos;
325: int size;
326: String next;
327: MimeHeaders headers;
328:
329: NamesEnumerator(MimeHeaders headers) {
330: this .headers = headers;
331: pos = 0;
332: size = headers.size();
333: findNext();
334: }
335:
336: private void findNext() {
337: next = null;
338: for (; pos < size; pos++) {
339: next = headers.getName(pos).toString();
340: for (int j = 0; j < pos; j++) {
341: if (headers.getName(j).equalsIgnoreCase(next)) {
342: // duplicate.
343: next = null;
344: break;
345: }
346: }
347: if (next != null) {
348: // it's not a duplicate
349: break;
350: }
351: }
352: // next time findNext is called it will try the
353: // next element
354: pos++;
355: }
356:
357: public boolean hasMoreElements() {
358: return next != null;
359: }
360:
361: public Object nextElement() {
362: String current = next;
363: findNext();
364: return current;
365: }
366: }
367:
368: /** Enumerate the values for a (possibly ) multiple
369: value element.
370: */
371: class ValuesEnumerator implements Enumeration {
372: int pos;
373: int size;
374: MessageBytes next;
375: MimeHeaders headers;
376: String name;
377:
378: ValuesEnumerator(MimeHeaders headers, String name) {
379: this .name = name;
380: this .headers = headers;
381: pos = 0;
382: size = headers.size();
383: findNext();
384: }
385:
386: private void findNext() {
387: next = null;
388: for (; pos < size; pos++) {
389: MessageBytes n1 = headers.getName(pos);
390: if (n1.equalsIgnoreCase(name)) {
391: next = headers.getValue(pos);
392: break;
393: }
394: }
395: pos++;
396: }
397:
398: public boolean hasMoreElements() {
399: return next != null;
400: }
401:
402: public Object nextElement() {
403: MessageBytes current = next;
404: findNext();
405: return current.toString();
406: }
407: }
408:
409: class MimeHeaderField {
410: // multiple headers with same name - a linked list will
411: // speed up name enumerations and search ( both cpu and
412: // GC)
413: MimeHeaderField next;
414: MimeHeaderField prev;
415:
416: protected final MessageBytes nameB = MessageBytes.newInstance();
417: protected final MessageBytes valueB = MessageBytes.newInstance();
418:
419: /**
420: * Creates a new, uninitialized header field.
421: */
422: public MimeHeaderField() {
423: }
424:
425: public void recycle() {
426: nameB.recycle();
427: valueB.recycle();
428: next = null;
429: }
430:
431: public MessageBytes getName() {
432: return nameB;
433: }
434:
435: public MessageBytes getValue() {
436: return valueB;
437: }
438: }
|