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