1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */

17 package org.apache.tomcat.util.http;
18
19 import java.io.IOException;
20 import java.io.PrintWriter;
21 import java.io.StringWriter;
22 import java.util.Enumeration;
23
24 import org.apache.tomcat.util.buf.MessageBytes;
25 import org.apache.tomcat.util.res.StringManager;
26
27 /**
28  * This class is used to contain standard internet message headers,
29  * used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for
30  * MIME (RFC 2045) applications such as transferring typed data and
31  * grouping related items in multipart message bodies.
32  *
33  * <P> Message headers, as specified in RFC822, include a field name
34  * and a field body.  Order has no semantic significance, and several
35  * fields with the same name may exist.  However, most fields do not
36  * (and should not) exist more than once in a header.
37  *
38  * <P> Many kinds of field body must conform to a specified syntax,
39  * including the standard parenthesized comment syntax.  This class
40  * supports only two simple syntaxes, for dates and integers.
41  *
42  * <P> When processing headers, care must be taken to handle the case of
43  * multiple same-name fields correctly.  The values of such fields are
44  * only available as strings.  They may be accessed by index (treating
45  * the header as an array of fields), or by name (returning an array
46  * of string values).
47  */

48
49 /* Headers are first parsed and stored in the order they are
50    received. This is based on the fact that most servlets will not
51    directly access all headers, and most headers are single-valued.
52    ( the alternative - a hash or similar data structure - will add
53    an overhead that is not needed in most cases )
54
55    Apache seems to be using a similar method for storing and manipulating
56    headers.
57
58    Future enhancements:
59    - hash the headers the first time a header is requested ( i.e. if the
60    servlet needs direct access to headers).
61    - scan "common" values ( length, cookies, etc ) during the parse
62    ( addHeader hook )
63
64 */

65
66
67 /**
68  *  Memory-efficient repository for Mime Headers. When the object is recycled, it
69  *  will keep the allocated headers[] and all the MimeHeaderField - no GC is generated.
70  *
71  *  For input headers it is possible to use the MessageByte for Fields - so no GC
72  *  will be generated.
73  *
74  *  The only garbage is generated when using the String for header names/values -
75  *  this can't be avoided when the servlet calls header methods, but is easy
76  *  to avoid inside tomcat. The goal is to use _only_ MessageByte-based Fields,
77  *  and reduce to 0 the memory overhead of tomcat.
78  *
79  *  TODO:
80  *  XXX one-buffer parsing - for http ( other protocols don't need that )
81  *  XXX remove unused methods
82  *  XXX External enumerations, with 0 GC.
83  *  XXX use HeaderName ID
84  *
85  *
86  * @author dac@eng.sun.com
87  * @author James Todd [gonzo@eng.sun.com]
88  * @author Costin Manolache
89  * @author kevin seguin
90  */

91 public class MimeHeaders {
92     /** Initial size - should be == average number of headers per request
93      *  XXX  make it configurable ( fine-tuning of web-apps )
94      */

95     public static final int DEFAULT_HEADER_SIZE=8;
96
97     private static final StringManager sm =
98             StringManager.getManager("org.apache.tomcat.util.http");
99
100     /**
101      * The header fields.
102      */

103     private MimeHeaderField[] headers = new
104         MimeHeaderField[DEFAULT_HEADER_SIZE];
105
106     /**
107      * The current number of header fields.
108      */

109     private int count;
110
111     /**
112      * The limit on the number of header fields.
113      */

114     private int limit = -1;
115
116     /**
117      * Creates a new MimeHeaders object using a default buffer size.
118      */

119     public MimeHeaders() {
120         // NO-OP
121     }
122
123     /**
124      * Set limit on the number of header fields.
125      * @param limit The new limit
126      */

127     public void setLimit(int limit) {
128         this.limit = limit;
129         if (limit > 0 && headers.length > limit && count < limit) {
130             // shrink header list array
131             MimeHeaderField tmp[] = new MimeHeaderField[limit];
132             System.arraycopy(headers, 0, tmp, 0, count);
133             headers = tmp;
134         }
135     }
136
137     /**
138      * Clears all header fields.
139      */

140     // [seguin] added for consistency -- most other objects have recycle().
141     public void recycle() {
142         clear();
143     }
144
145     /**
146      * Clears all header fields.
147      */

148     public void clear() {
149         for (int i = 0; i < count; i++) {
150             headers[i].recycle();
151         }
152         count = 0;
153     }
154
155     /**
156      * EXPENSIVE!!!  only for debugging.
157      */

158     @Override
159     public String toString() {
160         StringWriter sw = new StringWriter();
161         PrintWriter pw = new PrintWriter(sw);
162         pw.println("=== MimeHeaders ===");
163         Enumeration<String> e = names();
164         while (e.hasMoreElements()) {
165             String n = e.nextElement();
166             Enumeration<String> ev = values(n);
167             while (ev.hasMoreElements()) {
168                 pw.print(n);
169                 pw.print(" = ");
170                 pw.println(ev.nextElement());
171             }
172         }
173         return sw.toString();
174     }
175
176
177     public void duplicate(MimeHeaders source) throws IOException {
178         for (int i = 0; i < source.size(); i++) {
179             MimeHeaderField mhf = createHeader();
180             mhf.getName().duplicate(source.getName(i));
181             mhf.getValue().duplicate(source.getValue(i));
182         }
183     }
184
185
186     // -------------------- Idx access to headers ----------
187
188     /**
189      * @return the current number of header fields.
190      */

191     public int size() {
192         return count;
193     }
194
195     /**
196      * @param n The header index
197      * @return the Nth header name, or null if there is no such header.
198      * This may be used to iterate through all header fields.
199      */

200     public MessageBytes getName(int n) {
201         return n >= 0 && n < count ? headers[n].getName() : null;
202     }
203
204     /**
205      * @param n The header index
206      * @return the Nth header value, or null if there is no such header.
207      * This may be used to iterate through all header fields.
208      */

209     public MessageBytes getValue(int n) {
210         return n >= 0 && n < count ? headers[n].getValue() : null;
211     }
212
213     /**
214      * Find the index of a header with the given name.
215      * @param name The header name
216      * @param starting Index on which to start looking
217      * @return the header index
218      */

219     public int findHeader( String name, int starting ) {
220         // We can use a hash - but it's not clear how much
221         // benefit you can get - there is an  overhead
222         // and the number of headers is small (4-5 ?)
223         // Another problem is that we'll pay the overhead
224         // of constructing the hashtable
225
226         // A custom search tree may be better
227         for (int i = starting; i < count; i++) {
228             if (headers[i].getName().equalsIgnoreCase(name)) {
229                 return i;
230             }
231         }
232         return -1;
233     }
234
235     // -------------------- --------------------
236
237     /**
238      * Returns an enumeration of strings representing the header field names.
239      * Field names may appear multiple times in this enumeration, indicating
240      * that multiple fields with that name exist in this header.
241      * @return the enumeration
242      */

243     public Enumeration<String> names() {
244         return new NamesEnumerator(this);
245     }
246
247     public Enumeration<String> values(String name) {
248         return new ValuesEnumerator(this, name);
249     }
250
251     // -------------------- Adding headers --------------------
252
253
254     /**
255      * Adds a partially constructed field to the header.  This
256      * field has not had its name or value initialized.
257      */

258     private MimeHeaderField createHeader() {
259         if (limit > -1 && count >= limit) {
260             throw new IllegalStateException(sm.getString(
261                     "headers.maxCountFail", Integer.valueOf(limit)));
262         }
263         MimeHeaderField mh;
264         int len = headers.length;
265         if (count >= len) {
266             // expand header list array
267             int newLength = count * 2;
268             if (limit > 0 && newLength > limit) {
269                 newLength = limit;
270             }
271             MimeHeaderField tmp[] = new MimeHeaderField[newLength];
272             System.arraycopy(headers, 0, tmp, 0, len);
273             headers = tmp;
274         }
275         if ((mh = headers[count]) == null) {
276             headers[count] = mh = new MimeHeaderField();
277         }
278         count++;
279         return mh;
280     }
281
282     /**
283      * Create a new named header , return the MessageBytes
284      * container for the new value
285      * @param name The header name
286      * @return the message bytes container for the value
287      */

288     public MessageBytes addValue( String name ) {
289         MimeHeaderField mh = createHeader();
290         mh.getName().setString(name);
291         return mh.getValue();
292     }
293
294     /**
295      * Create a new named header using un-translated byte[].
296      * The conversion to chars can be delayed until
297      * encoding is known.
298      * @param b The header name bytes
299      * @param startN Offset
300      * @param len Length
301      * @return the message bytes container for the value
302      */

303     public MessageBytes addValue(byte b[], int startN, int len) {
304         MimeHeaderField mhf=createHeader();
305         mhf.getName().setBytes(b, startN, len);
306         return mhf.getValue();
307     }
308
309     /**
310      * Allow "set" operations, which removes all current values
311      * for this header.
312      * @param name The header name
313      * @return the message bytes container for the value
314      */

315     public MessageBytes setValue( String name ) {
316         for ( int i = 0; i < count; i++ ) {
317             if(headers[i].getName().equalsIgnoreCase(name)) {
318                 for ( int j=i+1; j < count; j++ ) {
319                     if(headers[j].getName().equalsIgnoreCase(name)) {
320                         removeHeader(j--);
321                     }
322                 }
323                 return headers[i].getValue();
324             }
325         }
326         MimeHeaderField mh = createHeader();
327         mh.getName().setString(name);
328         return mh.getValue();
329     }
330
331     //-------------------- Getting headers --------------------
332     /**
333      * Finds and returns a header field with the given name.  If no such
334      * field exists, null is returned.  If more than one such field is
335      * in the header, an arbitrary one is returned.
336      * @param name The header name
337      * @return the value
338      */

339     public MessageBytes getValue(String name) {
340         for (int i = 0; i < count; i++) {
341             if (headers[i].getName().equalsIgnoreCase(name)) {
342                 return headers[i].getValue();
343             }
344         }
345         return null;
346     }
347
348     /**
349      * Finds and returns a unique header field with the given name. If no such
350      * field exists, null is returned. If the specified header field is not
351      * unique then an {@link IllegalArgumentException} is thrown.
352      * @param name The header name
353      * @return the value if unique
354      * @throws IllegalArgumentException if the header has multiple values
355      */

356     public MessageBytes getUniqueValue(String name) {
357         MessageBytes result = null;
358         for (int i = 0; i < count; i++) {
359             if (headers[i].getName().equalsIgnoreCase(name)) {
360                 if (result == null) {
361                     result = headers[i].getValue();
362                 } else {
363                     throw new IllegalArgumentException();
364                 }
365             }
366         }
367         return result;
368     }
369
370     // bad shortcut - it'll convert to string ( too early probably,
371     // encoding is guessed very late )
372     public String getHeader(String name) {
373         MessageBytes mh = getValue(name);
374         return mh != null ? mh.toString() : null;
375     }
376
377     // -------------------- Removing --------------------
378     /**
379      * Removes a header field with the specified name.  Does nothing
380      * if such a field could not be found.
381      * @param name the name of the header field to be removed
382      */

383     public void removeHeader(String name) {
384         // XXX
385         // warning: rather sticky code; heavily tuned
386
387         for (int i = 0; i < count; i++) {
388             if (headers[i].getName().equalsIgnoreCase(name)) {
389                 removeHeader(i--);
390             }
391         }
392     }
393
394     /**
395      * reset and swap with last header
396      * @param idx the index of the header to remove.
397      */

398     public void removeHeader(int idx) {
399         MimeHeaderField mh = headers[idx];
400
401         mh.recycle();
402         headers[idx] = headers[count - 1];
403         headers[count - 1] = mh;
404         count--;
405     }
406
407 }
408
409 /** Enumerate the distinct header names.
410     Each nextElement() is O(n) ( a comparison is
411     done with all previous elements ).
412
413     This is less frequent than add() -
414     we want to keep add O(1).
415 */

416 class NamesEnumerator implements Enumeration<String> {
417     private int pos;
418     private final int size;
419     private String next;
420     private final MimeHeaders headers;
421
422     public NamesEnumerator(MimeHeaders headers) {
423         this.headers=headers;
424         pos=0;
425         size = headers.size();
426         findNext();
427     }
428
429     private void findNext() {
430         next=null;
431         for(; pos< size; pos++ ) {
432             next=headers.getName( pos ).toString();
433             forint j=0; j<pos ; j++ ) {
434                 if( headers.getName( j ).equalsIgnoreCase( next )) {
435                     // duplicate.
436                     next=null;
437                     break;
438                 }
439             }
440             if( next!=null ) {
441                 // it's not a duplicate
442                 break;
443             }
444         }
445         // next time findNext is called it will try the
446         // next element
447         pos++;
448     }
449
450     @Override
451     public boolean hasMoreElements() {
452         return next!=null;
453     }
454
455     @Override
456     public String nextElement() {
457         String current=next;
458         findNext();
459         return current;
460     }
461 }
462
463 /** Enumerate the values for a (possibly ) multiple
464     value element.
465 */

466 class ValuesEnumerator implements Enumeration<String> {
467     private int pos;
468     private final int size;
469     private MessageBytes next;
470     private final MimeHeaders headers;
471     private final String name;
472
473     ValuesEnumerator(MimeHeaders headers, String name) {
474         this.name=name;
475         this.headers=headers;
476         pos=0;
477         size = headers.size();
478         findNext();
479     }
480
481     private void findNext() {
482         next=null;
483         for(; pos< size; pos++ ) {
484             MessageBytes n1=headers.getName( pos );
485             if( n1.equalsIgnoreCase( name )) {
486                 next=headers.getValue( pos );
487                 break;
488             }
489         }
490         pos++;
491     }
492
493     @Override
494     public boolean hasMoreElements() {
495         return next!=null;
496     }
497
498     @Override
499     public String nextElement() {
500         MessageBytes current=next;
501         findNext();
502         return current.toString();
503     }
504 }
505
506 class MimeHeaderField {
507
508     private final MessageBytes nameB = MessageBytes.newInstance();
509     private final MessageBytes valueB = MessageBytes.newInstance();
510
511     /**
512      * Creates a new, uninitialized header field.
513      */

514     public MimeHeaderField() {
515         // NO-OP
516     }
517
518     public void recycle() {
519         nameB.recycle();
520         valueB.recycle();
521     }
522
523     public MessageBytes getName() {
524         return nameB;
525     }
526
527     public MessageBytes getValue() {
528         return valueB;
529     }
530 }
531