ccs.mime
Class HeaderedEntity

java.lang.Object
  extended by ccs.mime.HeaderedEntity
Direct Known Subclasses:
MimeEntity

public class HeaderedEntity
extends java.lang.Object

This class represents a common construct in MIME and related applications: a "header" section, consisting of lines of the form "Name: value" in US-ASCII, followed by a blank line, followed by a binary content. MT-UNSAFE.

Headers come in one of two modes, collapsing (the default) and non-collapsing. In collapsing mode, where several headers have the same name, their values are concatenated, separated by a comma and a space. In non-collapsing, they stay separate. Collapsing mode is best for HTTP, but non-collapsing is required for SMTP (e.g. received: headers are best kept separate).

Headers are stored in the order they are received.


Nested Class Summary
static class HeaderedEntity.UnmarshalModes
          Body unmarshalling modes: none (no-op, existing state unchanged); headers only; full (headers + body); repeat (body is in a RepeatingSwappingBuffer.)
 
Constructor Summary
HeaderedEntity()
          Construct a HeaderedEntity with collapsing headers.
HeaderedEntity(boolean isCollapse)
          Construct a HeaderedEntity.
 
Method Summary
 void addHeader(int idx, java.lang.String name, java.lang.String value)
          In non-collapsing mode only, add a new header at the specified index.
 void addHeader(java.lang.String name, java.lang.String value)
          Add a new header.
 int bodySize()
          Return the length of the body, or -1 if the entity does not have a body.
 void cleanupRepeatableBody()
          When the body was read using readRepeatableBodyFrom, you must call this when you're done.
 void deleteHeader(int idx)
          Delete the specified header.
 void deleteHeader(java.lang.String name)
          Delete the specified header, by name, ignoring case.
 SwappingBuffer getBody()
          Gets the current body.
 byte[] getBodyBytes()
          Returns the bytes of the content (body) of the entity.
 boolean getBodyHasTrailer()
          Whether the current body has a superfluous CRLF pair on the end.
 java.lang.String getHeader(java.lang.String name)
          The value of the (first instance of the) named header.
 int getHeaderIndex(java.lang.String name)
          Returns the index of the (first occurrence of) the header with the specified name, ignoring case.
 int getHeaderInt(java.lang.String name, int defVal)
          Utility method which returns the value of the (first instance of the) named header as an int, if it exists.
 java.lang.String[] getHeaderNames()
          The set of available header names.
 java.lang.String getName(int idx)
          The name of the header with this index.
 java.lang.String getValue(int idx)
          The value of the header with this index.
 int getWireLength()
          The "wire length" of this entity is its length when transmitted onto a stream in the standard format: headers, one blank line, then the body.
protected  boolean isBoundary(byte[] boundary, byte[] wad, int wlen)
           
 void marshalTo(java.io.DataOutputStream dest)
          Marshal the HeaderedEntity for storage.
 int nHeaders()
          The total number of available headers.
protected  void out(java.lang.String s)
           
 void readBodyFrom(java.io.InputStream is)
          Read the entity body from a stream with no boundary.
 void readBodyFrom(LineInputStream lis, byte[] boundary)
          Read the entity body from the stream.
 void readFrom(LineInputStream lis, byte[] boundary)
          Read the complete entity from a stream.
 void readHeadersFrom(LineInputStream lis, byte[] boundary)
          Read only the headers from the stream.
 void readRepeatableBodyFrom(java.io.InputStream is)
          As readBodyFrom, except that the body is held in a RepeatingSwappingBuffer.
 void readRepeatableBodyFrom(LineInputStream lis, byte[] boundary)
          Read the repeatable entity body from the stream.
 void readRepeatableFrom(LineInputStream lis, byte[] boundary)
          Read the complete entity from a stream, using a repeatable body.
 void setBody(SwappingBuffer body)
          Set the body to be the content of the supplied SwappingBuffer.
 void setBodyHasTrailer(boolean hasTrailer)
          Set whether the current body has a superfluous CRLF pair on the end.
 void setHeader(int idx, java.lang.String newValue)
          Set the value of a specified header.
 void setHeader(java.lang.String name, java.lang.String value)
          Set the value of a specified header.
 void swallowPreamble(LineInputStream lis, byte[] boundary)
          RFC2046 states that an area of non-compliant guff - the preamble - can precede a multipart.
 void unmarshalFrom(java.io.DataInputStream src, HeaderedEntity.UnmarshalModes mode)
          Unmarshal the HeaderedEntity from storage.
 boolean wasLastBoundary()
          If the last read operation (readFrom, readHeadersFrom, or readBodyFrom had non-null boundary (i.e. was reading from an RFC2046 multipart MIME stream), this returns true if the encountered boundary which ended the read was of the variant type (with two hyphens as a suffix) which denotes the end of the multipart.
 void writeBodyTo(java.io.OutputStream os, boolean isClose)
          Write the body of the entity to a stream.
 void writeHeadersTo(java.io.OutputStream os)
          Write the headers of the entity to a stream.
protected  void writeln(java.lang.String s, java.io.OutputStream os)
          Utility method to write a string to the stream with at least reasonable efficiency.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Constructor Detail

HeaderedEntity

public HeaderedEntity()
Construct a HeaderedEntity with collapsing headers.


HeaderedEntity

public HeaderedEntity(boolean isCollapse)
Construct a HeaderedEntity.

Parameters:
isCollapse - Whether to collapse headers which have the same name.
Method Detail

marshalTo

public void marshalTo(java.io.DataOutputStream dest)
               throws java.io.IOException
Marshal the HeaderedEntity for storage. It cannot be used further at this point - the body SwappingBuffer has been written.

Throws:
java.io.IOException

unmarshalFrom

public void unmarshalFrom(java.io.DataInputStream src,
                          HeaderedEntity.UnmarshalModes mode)
                   throws java.io.IOException
Unmarshal the HeaderedEntity from storage.

Parameters:
src - Where to unmarshal from.
mode - The unmarshalling mode. Available modes are: none (no-op, existing state unchanged); headers only; full (headers + body); repeat (body is in a RepeatingSwappingBuffer.)
Throws:
java.io.IOException

swallowPreamble

public void swallowPreamble(LineInputStream lis,
                            byte[] boundary)
                     throws java.io.IOException
RFC2046 states that an area of non-compliant guff - the preamble - can precede a multipart. The intention is that the preamble can say something like, "dear user, if you're reading this, then your news/mail/whatever app is total pants. Consider upgrading to the current century." This utility function eats the preamble.

Parameters:
lis - The stream containing the edend.
boundary - the MIME part boundary. See readFrom. If you call this method with a null boundary, it will inefficiently eat the entire stream.
Throws:
java.io.IOException

readHeadersFrom

public void readHeadersFrom(LineInputStream lis,
                            byte[] boundary)
                     throws java.io.IOException
Read only the headers from the stream. This is especially useful when you e.g. need the headers to find out how long the body is. NB. The MIME encoding which allows non-US-ASCII to appear in headers is not supported.

Parameters:
lis - where to read from.
boundary - If lis is a MIME-style multipart stream, the part boundary. See readFrom.
Throws:
java.io.IOException

readBodyFrom

public void readBodyFrom(LineInputStream lis,
                         byte[] boundary)
                  throws java.io.IOException
Read the entity body from the stream.

Parameters:
lis - The stream to read from.
boundary - If lis is a MIME-style multipart stream, the part boundary. See readFrom.
Throws:
java.io.IOException

readRepeatableBodyFrom

public void readRepeatableBodyFrom(LineInputStream lis,
                                   byte[] boundary)
                            throws java.io.IOException
Read the repeatable entity body from the stream.

Parameters:
lis - The stream to read from.
boundary - If lis is a MIME-style multipart stream, the part boundary. See readFrom.
Throws:
java.io.IOException

readBodyFrom

public void readBodyFrom(java.io.InputStream is)
                  throws java.io.IOException
Read the entity body from a stream with no boundary. The entity is assumed to extend to the end of the stream.

Throws:
java.io.IOException

readRepeatableBodyFrom

public void readRepeatableBodyFrom(java.io.InputStream is)
                            throws java.io.IOException
As readBodyFrom, except that the body is held in a RepeatingSwappingBuffer. This means that the body can be written several times on the trot; however, you must subsequently call cleanupBody.

Throws:
java.io.IOException

setBody

public void setBody(SwappingBuffer body)
Set the body to be the content of the supplied SwappingBuffer.


getBody

public SwappingBuffer getBody()
Gets the current body. Note that, under some circumstances (specifically when it was read from a multipart) the body returned may have a superfluous CRLF pair tacked onto the end. The writeTo methods of this class take this into account.


cleanupRepeatableBody

public void cleanupRepeatableBody()
                           throws java.io.IOException
When the body was read using readRepeatableBodyFrom, you must call this when you're done.

Throws:
java.io.IOException

getBodyHasTrailer

public boolean getBodyHasTrailer()
Whether the current body has a superfluous CRLF pair on the end.


setBodyHasTrailer

public void setBodyHasTrailer(boolean hasTrailer)
Set whether the current body has a superfluous CRLF pair on the end.


readFrom

public void readFrom(LineInputStream lis,
                     byte[] boundary)
              throws java.io.IOException
Read the complete entity from a stream.

Parameters:
lis - the stream to read from. If the entity does not extend to the end of the stream, set the dam on lis (For example, this technique is required if reading the entity from an HTTP persistent connection; otherwise the read will block ad inifinitum.
boundary - If lis is a MIME-style multipart stream, the part boundary. If encountered, the method will eat it and return. It will also cope with the variant which is used for the final boundary marker. Null if not a multipart MIME stream. Note that for some bizarre reason, RFC2046 stipulates that the boundary bytestring actually used is the boundary specified in the headers which precede the multipart, but with two US-ASCII hyphens as a prefix. It is assumed that this prefix has been added by the caller.
Throws:
java.io.IOException

readRepeatableFrom

public void readRepeatableFrom(LineInputStream lis,
                               byte[] boundary)
                        throws java.io.IOException
Read the complete entity from a stream, using a repeatable body.

Parameters:
lis - the stream to read from. If the entity does not extend to the end of the stream, set the dam on lis (For example, this technique is required if reading the entity from an HTTP persistent connection; otherwise the read will block ad inifinitum.
boundary - If lis is a MIME-style multipart stream, the part boundary. If encountered, the method will eat it and return. It will also cope with the variant which is used for the final boundary marker. Null if not a multipart MIME stream. Note that for some bizarre reason, RFC2046 stipulates that the boundary bytestring actually used is the boundary specified in the headers which precede the multipart, but with two US-ASCII hyphens as a prefix. It is assumed that this prefix has been added by the caller.
Throws:
java.io.IOException

getHeaderIndex

public int getHeaderIndex(java.lang.String name)
Returns the index of the (first occurrence of) the header with the specified name, ignoring case.

Parameters:
name - The name to search for, ignoring case.
Returns:
the specified index, or -1 if there is no header by that name.

getHeader

public java.lang.String getHeader(java.lang.String name)
The value of the (first instance of the) named header.

Parameters:
name - The name to search for, ignoring case.
Returns:
The header, or null if there are no such.

setHeader

public void setHeader(int idx,
                      java.lang.String newValue)
Set the value of a specified header.

Parameters:
idx - The index of the header to change.
newValue - The value to set it to.

setHeader

public void setHeader(java.lang.String name,
                      java.lang.String value)
Set the value of a specified header.

Parameters:
name - The name of the header. The first instance, if any, will have its value set. If there is no such header, one such will be added.
value - the new value of the header.

getHeaderInt

public int getHeaderInt(java.lang.String name,
                        int defVal)
Utility method which returns the value of the (first instance of the) named header as an int, if it exists. If it doesn't, or doesn't parse as an int, returns defVal.


addHeader

public void addHeader(java.lang.String name,
                      java.lang.String value)
Add a new header. If there is already a header by that name in collapsing mode, the new value will be appended to the old with a comma / space separator pair. In non-collapsing mode, a new header by that name will be appended to the list.

Parameters:
name - The name for the header. Behaviour if the header exists already depends on whether collapsing or non-collapsing behaviour has been specified.
value - The value for the header.

addHeader

public void addHeader(int idx,
                      java.lang.String name,
                      java.lang.String value)
In non-collapsing mode only, add a new header at the specified index. The incumbent and its successors will be moved down one, as for the contract of java.util.ArrayList.add.


deleteHeader

public void deleteHeader(int idx)
Delete the specified header.


deleteHeader

public void deleteHeader(java.lang.String name)
Delete the specified header, by name, ignoring case. Does nothing if header is not present.


nHeaders

public int nHeaders()
The total number of available headers.


getName

public java.lang.String getName(int idx)
The name of the header with this index.


getValue

public java.lang.String getValue(int idx)
The value of the header with this index.


getHeaderNames

public java.lang.String[] getHeaderNames()
The set of available header names.

Returns:
The set.

getWireLength

public int getWireLength()
The "wire length" of this entity is its length when transmitted onto a stream in the standard format: headers, one blank line, then the body. This requires a certain amount of work to calculate; callers are encouraged to remember the answer.


writeHeadersTo

public void writeHeadersTo(java.io.OutputStream os)
                    throws java.io.IOException
Write the headers of the entity to a stream. Includes the terminating CRLF.

Parameters:
os - the OutputStream to write to.
Throws:
java.io.IOException

writeln

protected void writeln(java.lang.String s,
                       java.io.OutputStream os)
                throws java.io.IOException
Utility method to write a string to the stream with at least reasonable efficiency. This is only suitable for protocol, and strings containing only US-ASCII characters.

Throws:
java.io.IOException

writeBodyTo

public void writeBodyTo(java.io.OutputStream os,
                        boolean isClose)
                 throws java.io.IOException
Write the body of the entity to a stream.

Parameters:
os - The stream to write to
isClose - Whether os should be closed after the write.
Throws:
java.io.IOException

getBodyBytes

public byte[] getBodyBytes()
Returns the bytes of the content (body) of the entity. If the body is larger than the default cutoff of a SwappingBuffer, then this will return null, and writeBodyTo must be used instead. If this happens, it is not recommended to "work around" this feature by writing to a ByteArrayOutputStream - the body may be very large.


bodySize

public int bodySize()
Return the length of the body, or -1 if the entity does not have a body.


wasLastBoundary

public boolean wasLastBoundary()
If the last read operation (readFrom, readHeadersFrom, or readBodyFrom had non-null boundary (i.e. was reading from an RFC2046 multipart MIME stream), this returns true if the encountered boundary which ended the read was of the variant type (with two hyphens as a suffix) which denotes the end of the multipart.


isBoundary

protected boolean isBoundary(byte[] boundary,
                             byte[] wad,
                             int wlen)

out

protected void out(java.lang.String s)