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 18 package org.apache.tomcat.util.http; 19 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 26 /* XXX XXX XXX Need a major rewrite !!!! 27 */ 28 29 /** 30 * This class is used to contain standard internet message headers, 31 * used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for 32 * MIME (RFC 2045) applications such as transferring typed data and 33 * grouping related items in multipart message bodies. 34 * 35 * <P> Message headers, as specified in RFC822, include a field name 36 * and a field body. Order has no semantic significance, and several 37 * fields with the same name may exist. However, most fields do not 38 * (and should not) exist more than once in a header. 39 * 40 * <P> Many kinds of field body must conform to a specified syntax, 41 * including the standard parenthesized comment syntax. This class 42 * supports only two simple syntaxes, for dates and integers. 43 * 44 * <P> When processing headers, care must be taken to handle the case of 45 * multiple same-name fields correctly. The values of such fields are 46 * only available as strings. They may be accessed by index (treating 47 * the header as an array of fields), or by name (returning an array 48 * of string values). 49 */ 50 51 /* Headers are first parsed and stored in the order they are 52 received. This is based on the fact that most servlets will not 53 directly access all headers, and most headers are single-valued. 54 ( the alternative - a hash or similar data structure - will add 55 an overhead that is not needed in most cases ) 56 57 Apache seems to be using a similar method for storing and manipulating 58 headers. 59 60 Future enhancements: 61 - hash the headers the first time a header is requested ( i.e. if the 62 servlet needs direct access to headers). 63 - scan "common" values ( length, cookies, etc ) during the parse 64 ( addHeader hook ) 65 66 */ 67 68 69 70 /** 71 * Memory-efficient repository for Mime Headers. When the object is recycled, it 72 * will keep the allocated headers[] and all the MimeHeaderField - no GC is generated. 73 * 74 * For input headers it is possible to use the MessageByte for Fileds - so no GC 75 * will be generated. 76 * 77 * The only garbage is generated when using the String for header names/values - 78 * this can't be avoided when the servlet calls header methods, but is easy 79 * to avoid inside tomcat. The goal is to use _only_ MessageByte-based Fields, 80 * and reduce to 0 the memory overhead of tomcat. 81 * 82 * TODO: 83 * XXX one-buffer parsing - for http ( other protocols don't need that ) 84 * XXX remove unused methods 85 * XXX External enumerations, with 0 GC. 86 * XXX use HeaderName ID 87 * 88 * 89 * @author dac@eng.sun.com 90 * @author James Todd [gonzo@eng.sun.com] 91 * @author Costin Manolache 92 * @author kevin seguin 93 */ 94 public class MimeHeaders { 95 /** Initial size - should be == average number of headers per request 96 * XXX make it configurable ( fine-tuning of web-apps ) 97 */ 98 public static final int DEFAULT_HEADER_SIZE=8; 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 * Creates a new MimeHeaders object using a default buffer size. 113 */ 114 public MimeHeaders() { 115 } 116 117 /** 118 * Clears all header fields. 119 */ 120 // [seguin] added for consistency -- most other objects have recycle(). 121 public void recycle() { 122 clear(); 123 } 124 125 /** 126 * Clears all header fields. 127 */ 128 public void clear() { 129 for (int i = 0; i < count; i++) { 130 headers[i].recycle(); 131 } 132 count = 0; 133 } 134 135 /** 136 * EXPENSIVE!!! only for debugging. 137 */ 138 public String toString() { 139 StringWriter sw = new StringWriter(); 140 PrintWriter pw = new PrintWriter(sw); 141 pw.println("=== MimeHeaders ==="); 142 Enumeration e = names(); 143 while (e.hasMoreElements()) { 144 String n = (String)e.nextElement(); 145 pw.println(n + " = " + getHeader(n)); 146 } 147 return sw.toString(); 148 } 149 150 // -------------------- Idx access to headers ---------- 151 152 /** 153 * Returns the current number of header fields. 154 */ 155 public int size() { 156 return count; 157 } 158 159 /** 160 * Returns the Nth header name, or null if there is no such header. 161 * This may be used to iterate through all header fields. 162 */ 163 public MessageBytes getName(int n) { 164 return n >= 0 && n < count ? headers[n].getName() : null; 165 } 166 167 /** 168 * Returns the Nth header value, or null if there is no such header. 169 * This may be used to iterate through all header fields. 170 */ 171 public MessageBytes getValue(int n) { 172 return n >= 0 && n < count ? headers[n].getValue() : null; 173 } 174 175 /** Find the index of a header with the given name. 176 */ 177 public int findHeader( String name, int starting ) { 178 // We can use a hash - but it's not clear how much 179 // benefit you can get - there is an overhead 180 // and the number of headers is small (4-5 ?) 181 // Another problem is that we'll pay the overhead 182 // of constructing the hashtable 183 184 // A custom search tree may be better 185 for (int i = starting; i < count; i++) { 186 if (headers[i].getName().equalsIgnoreCase(name)) { 187 return i; 188 } 189 } 190 return -1; 191 } 192 193 // -------------------- -------------------- 194 195 /** 196 * Returns an enumeration of strings representing the header field names. 197 * Field names may appear multiple times in this enumeration, indicating 198 * that multiple fields with that name exist in this header. 199 */ 200 public Enumeration names() { 201 return new NamesEnumerator(this); 202 } 203 204 public Enumeration values(String name) { 205 return new ValuesEnumerator(this, name); 206 } 207 208 // -------------------- Adding headers -------------------- 209 210 211 /** 212 * Adds a partially constructed field to the header. This 213 * field has not had its name or value initialized. 214 */ 215 private MimeHeaderField createHeader() { 216 MimeHeaderField mh; 217 int len = headers.length; 218 if (count >= len) { 219 // expand header list array 220 MimeHeaderField tmp[] = new MimeHeaderField[count * 2]; 221 System.arraycopy(headers, 0, tmp, 0, len); 222 headers = tmp; 223 } 224 if ((mh = headers[count]) == null) { 225 headers[count] = mh = new MimeHeaderField(); 226 } 227 count++; 228 return mh; 229 } 230 231 /** Create a new named header , return the MessageBytes 232 container for the new value 233 */ 234 public MessageBytes addValue( String name ) { 235 MimeHeaderField mh = createHeader(); 236 mh.getName().setString(name); 237 return mh.getValue(); 238 } 239 240 /** Create a new named header using un-translated byte[]. 241 The conversion to chars can be delayed until 242 encoding is known. 243 */ 244 public MessageBytes addValue(byte b[], int startN, int len) 245 { 246 MimeHeaderField mhf=createHeader(); 247 mhf.getName().setBytes(b, startN, len); 248 return mhf.getValue(); 249 } 250 251 /** Create a new named header using translated char[]. 252 */ 253 public MessageBytes addValue(char c[], int startN, int len) 254 { 255 MimeHeaderField mhf=createHeader(); 256 mhf.getName().setChars(c, startN, len); 257 return mhf.getValue(); 258 } 259 260 /** Allow "set" operations - 261 return a MessageBytes container for the 262 header value ( existing header or new 263 if this . 264 */ 265 public MessageBytes setValue( String name ) { 266 for ( int i = 0; i < count; i++ ) { 267 if(headers[i].getName().equalsIgnoreCase(name)) { 268 for ( int j=i+1; j < count; j++ ) { 269 if(headers[j].getName().equalsIgnoreCase(name)) { 270 removeHeader(j--); 271 } 272 } 273 return headers[i].getValue(); 274 } 275 } 276 MimeHeaderField mh = createHeader(); 277 mh.getName().setString(name); 278 return mh.getValue(); 279 } 280 281 //-------------------- Getting headers -------------------- 282 /** 283 * Finds and returns a header field with the given name. If no such 284 * field exists, null is returned. If more than one such field is 285 * in the header, an arbitrary one is returned. 286 */ 287 public MessageBytes getValue(String name) { 288 for (int i = 0; i < count; i++) { 289 if (headers[i].getName().equalsIgnoreCase(name)) { 290 return headers[i].getValue(); 291 } 292 } 293 return null; 294 } 295 296 /** 297 * Finds and returns a unique header field with the given name. If no such 298 * field exists, null is returned. If the specified header field is not 299 * unique then an {@link IllegalArgumentException} is thrown. 300 */ 301 public MessageBytes getUniqueValue(String name) { 302 MessageBytes result = null; 303 for (int i = 0; i < count; i++) { 304 if (headers[i].getName().equalsIgnoreCase(name)) { 305 if (result == null) { 306 result = headers[i].getValue(); 307 } else { 308 throw new IllegalArgumentException(); 309 } 310 } 311 } 312 return result; 313 } 314 315 // bad shortcut - it'll convert to string ( too early probably, 316 // encoding is guessed very late ) 317 public String getHeader(String name) { 318 MessageBytes mh = getValue(name); 319 return mh != null ? mh.toString() : null; 320 } 321 322 // -------------------- Removing -------------------- 323 /** 324 * Removes a header field with the specified name. Does nothing 325 * if such a field could not be found. 326 * @param name the name of the header field to be removed 327 */ 328 public void removeHeader(String name) { 329 // XXX 330 // warning: rather sticky code; heavily tuned 331 332 for (int i = 0; i < count; i++) { 333 if (headers[i].getName().equalsIgnoreCase(name)) { 334 removeHeader(i--); 335 } 336 } 337 } 338 339 /** 340 * reset and swap with last header 341 * @param idx the index of the header to remove. 342 */ 343 private void removeHeader(int idx) { 344 MimeHeaderField mh = headers[idx]; 345 346 mh.recycle(); 347 headers[idx] = headers[count - 1]; 348 headers[count - 1] = mh; 349 count--; 350 } 351 352 } 353 354 /** Enumerate the distinct header names. 355 Each nextElement() is O(n) ( a comparation is 356 done with all previous elements ). 357 358 This is less frequesnt than add() - 359 we want to keep add O(1). 360 */ 361 class NamesEnumerator implements Enumeration { 362 int pos; 363 int size; 364 String next; 365 MimeHeaders headers; 366 367 public NamesEnumerator(MimeHeaders headers) { 368 this.headers=headers; 369 pos=0; 370 size = headers.size(); 371 findNext(); 372 } 373 374 private void findNext() { 375 next=null; 376 for( ; pos< size; pos++ ) { 377 next=headers.getName( pos ).toString(); 378 for( int j=0; j<pos ; j++ ) { 379 if( headers.getName( j ).equalsIgnoreCase( next )) { 380 // duplicate. 381 next=null; 382 break; 383 } 384 } 385 if( next!=null ) { 386 // it's not a duplicate 387 break; 388 } 389 } 390 // next time findNext is called it will try the 391 // next element 392 pos++; 393 } 394 395 public boolean hasMoreElements() { 396 return next!=null; 397 } 398 399 public Object nextElement() { 400 String current=next; 401 findNext(); 402 return current; 403 } 404 } 405 406 /** Enumerate the values for a (possibly ) multiple 407 value element. 408 */ 409 class ValuesEnumerator implements Enumeration { 410 int pos; 411 int size; 412 MessageBytes next; 413 MimeHeaders headers; 414 String name; 415 416 ValuesEnumerator(MimeHeaders headers, String name) { 417 this.name=name; 418 this.headers=headers; 419 pos=0; 420 size = headers.size(); 421 findNext(); 422 } 423 424 private void findNext() { 425 next=null; 426 for( ; pos< size; pos++ ) { 427 MessageBytes n1=headers.getName( pos ); 428 if( n1.equalsIgnoreCase( name )) { 429 next=headers.getValue( pos ); 430 break; 431 } 432 } 433 pos++; 434 } 435 436 public boolean hasMoreElements() { 437 return next!=null; 438 } 439 440 public Object nextElement() { 441 MessageBytes current=next; 442 findNext(); 443 return current.toString(); 444 } 445 } 446 447 class MimeHeaderField { 448 // multiple headers with same name - a linked list will 449 // speed up name enumerations and search ( both cpu and 450 // GC) 451 MimeHeaderField next; 452 MimeHeaderField prev; 453 454 protected final MessageBytes nameB = MessageBytes.newInstance(); 455 protected final MessageBytes valueB = MessageBytes.newInstance(); 456 457 /** 458 * Creates a new, uninitialized header field. 459 */ 460 public MimeHeaderField() { 461 } 462 463 public void recycle() { 464 nameB.recycle(); 465 valueB.recycle(); 466 next=null; 467 } 468 469 public MessageBytes getName() { 470 return nameB; 471 } 472 473 public MessageBytes getValue() { 474 return valueB; 475 } 476 }