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 for( int 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