init
This commit is contained in:
530
java/org/apache/tomcat/util/http/MimeHeaders.java
Normal file
530
java/org/apache/tomcat/util/http/MimeHeaders.java
Normal file
@@ -0,0 +1,530 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.util.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import org.apache.tomcat.util.buf.MessageBytes;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* This class is used to contain standard internet message headers,
|
||||
* used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for
|
||||
* MIME (RFC 2045) applications such as transferring typed data and
|
||||
* grouping related items in multipart message bodies.
|
||||
*
|
||||
* <P> Message headers, as specified in RFC822, include a field name
|
||||
* and a field body. Order has no semantic significance, and several
|
||||
* fields with the same name may exist. However, most fields do not
|
||||
* (and should not) exist more than once in a header.
|
||||
*
|
||||
* <P> Many kinds of field body must conform to a specified syntax,
|
||||
* including the standard parenthesized comment syntax. This class
|
||||
* supports only two simple syntaxes, for dates and integers.
|
||||
*
|
||||
* <P> When processing headers, care must be taken to handle the case of
|
||||
* multiple same-name fields correctly. The values of such fields are
|
||||
* only available as strings. They may be accessed by index (treating
|
||||
* the header as an array of fields), or by name (returning an array
|
||||
* of string values).
|
||||
*/
|
||||
|
||||
/* Headers are first parsed and stored in the order they are
|
||||
received. This is based on the fact that most servlets will not
|
||||
directly access all headers, and most headers are single-valued.
|
||||
( the alternative - a hash or similar data structure - will add
|
||||
an overhead that is not needed in most cases )
|
||||
|
||||
Apache seems to be using a similar method for storing and manipulating
|
||||
headers.
|
||||
|
||||
Future enhancements:
|
||||
- hash the headers the first time a header is requested ( i.e. if the
|
||||
servlet needs direct access to headers).
|
||||
- scan "common" values ( length, cookies, etc ) during the parse
|
||||
( addHeader hook )
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Memory-efficient repository for Mime Headers. When the object is recycled, it
|
||||
* will keep the allocated headers[] and all the MimeHeaderField - no GC is generated.
|
||||
*
|
||||
* For input headers it is possible to use the MessageByte for Fields - so no GC
|
||||
* will be generated.
|
||||
*
|
||||
* The only garbage is generated when using the String for header names/values -
|
||||
* this can't be avoided when the servlet calls header methods, but is easy
|
||||
* to avoid inside tomcat. The goal is to use _only_ MessageByte-based Fields,
|
||||
* and reduce to 0 the memory overhead of tomcat.
|
||||
*
|
||||
* TODO:
|
||||
* XXX one-buffer parsing - for http ( other protocols don't need that )
|
||||
* XXX remove unused methods
|
||||
* XXX External enumerations, with 0 GC.
|
||||
* XXX use HeaderName ID
|
||||
*
|
||||
*
|
||||
* @author dac@eng.sun.com
|
||||
* @author James Todd [gonzo@eng.sun.com]
|
||||
* @author Costin Manolache
|
||||
* @author kevin seguin
|
||||
*/
|
||||
public class MimeHeaders {
|
||||
/** Initial size - should be == average number of headers per request
|
||||
* XXX make it configurable ( fine-tuning of web-apps )
|
||||
*/
|
||||
public static final int DEFAULT_HEADER_SIZE=8;
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager("org.apache.tomcat.util.http");
|
||||
|
||||
/**
|
||||
* The header fields.
|
||||
*/
|
||||
private MimeHeaderField[] headers = new
|
||||
MimeHeaderField[DEFAULT_HEADER_SIZE];
|
||||
|
||||
/**
|
||||
* The current number of header fields.
|
||||
*/
|
||||
private int count;
|
||||
|
||||
/**
|
||||
* The limit on the number of header fields.
|
||||
*/
|
||||
private int limit = -1;
|
||||
|
||||
/**
|
||||
* Creates a new MimeHeaders object using a default buffer size.
|
||||
*/
|
||||
public MimeHeaders() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
/**
|
||||
* Set limit on the number of header fields.
|
||||
* @param limit The new limit
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
if (limit > 0 && headers.length > limit && count < limit) {
|
||||
// shrink header list array
|
||||
MimeHeaderField tmp[] = new MimeHeaderField[limit];
|
||||
System.arraycopy(headers, 0, tmp, 0, count);
|
||||
headers = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all header fields.
|
||||
*/
|
||||
// [seguin] added for consistency -- most other objects have recycle().
|
||||
public void recycle() {
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all header fields.
|
||||
*/
|
||||
public void clear() {
|
||||
for (int i = 0; i < count; i++) {
|
||||
headers[i].recycle();
|
||||
}
|
||||
count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPENSIVE!!! only for debugging.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
pw.println("=== MimeHeaders ===");
|
||||
Enumeration<String> e = names();
|
||||
while (e.hasMoreElements()) {
|
||||
String n = e.nextElement();
|
||||
Enumeration<String> ev = values(n);
|
||||
while (ev.hasMoreElements()) {
|
||||
pw.print(n);
|
||||
pw.print(" = ");
|
||||
pw.println(ev.nextElement());
|
||||
}
|
||||
}
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
|
||||
public void duplicate(MimeHeaders source) throws IOException {
|
||||
for (int i = 0; i < source.size(); i++) {
|
||||
MimeHeaderField mhf = createHeader();
|
||||
mhf.getName().duplicate(source.getName(i));
|
||||
mhf.getValue().duplicate(source.getValue(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Idx access to headers ----------
|
||||
|
||||
/**
|
||||
* @return the current number of header fields.
|
||||
*/
|
||||
public int size() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param n The header index
|
||||
* @return the Nth header name, or null if there is no such header.
|
||||
* This may be used to iterate through all header fields.
|
||||
*/
|
||||
public MessageBytes getName(int n) {
|
||||
return n >= 0 && n < count ? headers[n].getName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param n The header index
|
||||
* @return the Nth header value, or null if there is no such header.
|
||||
* This may be used to iterate through all header fields.
|
||||
*/
|
||||
public MessageBytes getValue(int n) {
|
||||
return n >= 0 && n < count ? headers[n].getValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of a header with the given name.
|
||||
* @param name The header name
|
||||
* @param starting Index on which to start looking
|
||||
* @return the header index
|
||||
*/
|
||||
public int findHeader( String name, int starting ) {
|
||||
// We can use a hash - but it's not clear how much
|
||||
// benefit you can get - there is an overhead
|
||||
// and the number of headers is small (4-5 ?)
|
||||
// Another problem is that we'll pay the overhead
|
||||
// of constructing the hashtable
|
||||
|
||||
// A custom search tree may be better
|
||||
for (int i = starting; i < count; i++) {
|
||||
if (headers[i].getName().equalsIgnoreCase(name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// -------------------- --------------------
|
||||
|
||||
/**
|
||||
* Returns an enumeration of strings representing the header field names.
|
||||
* Field names may appear multiple times in this enumeration, indicating
|
||||
* that multiple fields with that name exist in this header.
|
||||
* @return the enumeration
|
||||
*/
|
||||
public Enumeration<String> names() {
|
||||
return new NamesEnumerator(this);
|
||||
}
|
||||
|
||||
public Enumeration<String> values(String name) {
|
||||
return new ValuesEnumerator(this, name);
|
||||
}
|
||||
|
||||
// -------------------- Adding headers --------------------
|
||||
|
||||
|
||||
/**
|
||||
* Adds a partially constructed field to the header. This
|
||||
* field has not had its name or value initialized.
|
||||
*/
|
||||
private MimeHeaderField createHeader() {
|
||||
if (limit > -1 && count >= limit) {
|
||||
throw new IllegalStateException(sm.getString(
|
||||
"headers.maxCountFail", Integer.valueOf(limit)));
|
||||
}
|
||||
MimeHeaderField mh;
|
||||
int len = headers.length;
|
||||
if (count >= len) {
|
||||
// expand header list array
|
||||
int newLength = count * 2;
|
||||
if (limit > 0 && newLength > limit) {
|
||||
newLength = limit;
|
||||
}
|
||||
MimeHeaderField tmp[] = new MimeHeaderField[newLength];
|
||||
System.arraycopy(headers, 0, tmp, 0, len);
|
||||
headers = tmp;
|
||||
}
|
||||
if ((mh = headers[count]) == null) {
|
||||
headers[count] = mh = new MimeHeaderField();
|
||||
}
|
||||
count++;
|
||||
return mh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new named header , return the MessageBytes
|
||||
* container for the new value
|
||||
* @param name The header name
|
||||
* @return the message bytes container for the value
|
||||
*/
|
||||
public MessageBytes addValue( String name ) {
|
||||
MimeHeaderField mh = createHeader();
|
||||
mh.getName().setString(name);
|
||||
return mh.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new named header using un-translated byte[].
|
||||
* The conversion to chars can be delayed until
|
||||
* encoding is known.
|
||||
* @param b The header name bytes
|
||||
* @param startN Offset
|
||||
* @param len Length
|
||||
* @return the message bytes container for the value
|
||||
*/
|
||||
public MessageBytes addValue(byte b[], int startN, int len) {
|
||||
MimeHeaderField mhf=createHeader();
|
||||
mhf.getName().setBytes(b, startN, len);
|
||||
return mhf.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow "set" operations, which removes all current values
|
||||
* for this header.
|
||||
* @param name The header name
|
||||
* @return the message bytes container for the value
|
||||
*/
|
||||
public MessageBytes setValue( String name ) {
|
||||
for ( int i = 0; i < count; i++ ) {
|
||||
if(headers[i].getName().equalsIgnoreCase(name)) {
|
||||
for ( int j=i+1; j < count; j++ ) {
|
||||
if(headers[j].getName().equalsIgnoreCase(name)) {
|
||||
removeHeader(j--);
|
||||
}
|
||||
}
|
||||
return headers[i].getValue();
|
||||
}
|
||||
}
|
||||
MimeHeaderField mh = createHeader();
|
||||
mh.getName().setString(name);
|
||||
return mh.getValue();
|
||||
}
|
||||
|
||||
//-------------------- Getting headers --------------------
|
||||
/**
|
||||
* Finds and returns a header field with the given name. If no such
|
||||
* field exists, null is returned. If more than one such field is
|
||||
* in the header, an arbitrary one is returned.
|
||||
* @param name The header name
|
||||
* @return the value
|
||||
*/
|
||||
public MessageBytes getValue(String name) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (headers[i].getName().equalsIgnoreCase(name)) {
|
||||
return headers[i].getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns a unique header field with the given name. If no such
|
||||
* field exists, null is returned. If the specified header field is not
|
||||
* unique then an {@link IllegalArgumentException} is thrown.
|
||||
* @param name The header name
|
||||
* @return the value if unique
|
||||
* @throws IllegalArgumentException if the header has multiple values
|
||||
*/
|
||||
public MessageBytes getUniqueValue(String name) {
|
||||
MessageBytes result = null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (headers[i].getName().equalsIgnoreCase(name)) {
|
||||
if (result == null) {
|
||||
result = headers[i].getValue();
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// bad shortcut - it'll convert to string ( too early probably,
|
||||
// encoding is guessed very late )
|
||||
public String getHeader(String name) {
|
||||
MessageBytes mh = getValue(name);
|
||||
return mh != null ? mh.toString() : null;
|
||||
}
|
||||
|
||||
// -------------------- Removing --------------------
|
||||
/**
|
||||
* Removes a header field with the specified name. Does nothing
|
||||
* if such a field could not be found.
|
||||
* @param name the name of the header field to be removed
|
||||
*/
|
||||
public void removeHeader(String name) {
|
||||
// XXX
|
||||
// warning: rather sticky code; heavily tuned
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (headers[i].getName().equalsIgnoreCase(name)) {
|
||||
removeHeader(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* reset and swap with last header
|
||||
* @param idx the index of the header to remove.
|
||||
*/
|
||||
public void removeHeader(int idx) {
|
||||
MimeHeaderField mh = headers[idx];
|
||||
|
||||
mh.recycle();
|
||||
headers[idx] = headers[count - 1];
|
||||
headers[count - 1] = mh;
|
||||
count--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Enumerate the distinct header names.
|
||||
Each nextElement() is O(n) ( a comparison is
|
||||
done with all previous elements ).
|
||||
|
||||
This is less frequent than add() -
|
||||
we want to keep add O(1).
|
||||
*/
|
||||
class NamesEnumerator implements Enumeration<String> {
|
||||
private int pos;
|
||||
private final int size;
|
||||
private String next;
|
||||
private final MimeHeaders headers;
|
||||
|
||||
public NamesEnumerator(MimeHeaders headers) {
|
||||
this.headers=headers;
|
||||
pos=0;
|
||||
size = headers.size();
|
||||
findNext();
|
||||
}
|
||||
|
||||
private void findNext() {
|
||||
next=null;
|
||||
for(; pos< size; pos++ ) {
|
||||
next=headers.getName( pos ).toString();
|
||||
for( int j=0; j<pos ; j++ ) {
|
||||
if( headers.getName( j ).equalsIgnoreCase( next )) {
|
||||
// duplicate.
|
||||
next=null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( next!=null ) {
|
||||
// it's not a duplicate
|
||||
break;
|
||||
}
|
||||
}
|
||||
// next time findNext is called it will try the
|
||||
// next element
|
||||
pos++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return next!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextElement() {
|
||||
String current=next;
|
||||
findNext();
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
/** Enumerate the values for a (possibly ) multiple
|
||||
value element.
|
||||
*/
|
||||
class ValuesEnumerator implements Enumeration<String> {
|
||||
private int pos;
|
||||
private final int size;
|
||||
private MessageBytes next;
|
||||
private final MimeHeaders headers;
|
||||
private final String name;
|
||||
|
||||
ValuesEnumerator(MimeHeaders headers, String name) {
|
||||
this.name=name;
|
||||
this.headers=headers;
|
||||
pos=0;
|
||||
size = headers.size();
|
||||
findNext();
|
||||
}
|
||||
|
||||
private void findNext() {
|
||||
next=null;
|
||||
for(; pos< size; pos++ ) {
|
||||
MessageBytes n1=headers.getName( pos );
|
||||
if( n1.equalsIgnoreCase( name )) {
|
||||
next=headers.getValue( pos );
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return next!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextElement() {
|
||||
MessageBytes current=next;
|
||||
findNext();
|
||||
return current.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class MimeHeaderField {
|
||||
|
||||
private final MessageBytes nameB = MessageBytes.newInstance();
|
||||
private final MessageBytes valueB = MessageBytes.newInstance();
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized header field.
|
||||
*/
|
||||
public MimeHeaderField() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
nameB.recycle();
|
||||
valueB.recycle();
|
||||
}
|
||||
|
||||
public MessageBytes getName() {
|
||||
return nameB;
|
||||
}
|
||||
|
||||
public MessageBytes getValue() {
|
||||
return valueB;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user