- /*
- * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
- package javax.mail.internet;
-
- import java.io.*;
- import java.util.*;
- import javax.mail.*;
- import com.sun.mail.util.LineInputStream;
-
- /**
- * InternetHeaders is a utility class that manages RFC822 style
- * headers. Given a rfc822 format message stream, it reads lines
- * till the blank line that indicates end of header. The input stream
- * is positioned at the start of the body. The lines are stored
- * within the object and can be extracted as either Strings or
- * Header objects. <p>
- *
- * This class is mostly intended for service providers. MimeMessage
- * and MimeBody use this class for holding their headers. <p>
- *
- * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
- *
- * RFC822 and MIME header fields <strong>must</strong> contain only
- * US-ASCII characters. If a header contains non US-ASCII characters,
- * it must be encoded as per the rules in RFC 2047. The MimeUtility
- * class provided in this package can be used to to achieve this.
- * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
- * <code>addHeaderLine</code> methods are responsible for enforcing
- * the MIME requirements for the specified headers. In addition, these
- * header fields must be folded (wrapped) before being sent if they
- * exceed the line length limitation for the transport (1000 bytes for
- * SMTP). Received headers may have been folded. The application is
- * responsible for folding and unfolding headers as appropriate. <p>
- *
- * @see javax.mail.internet.MimeUtility
- * @author John Mani
- * @author Bill Shannon
- */
-
- public class InternetHeaders {
-
- private Vector headers;
-
- /**
- * Create an empty InternetHeaders object.
- */
- public InternetHeaders() {
- headers = new Vector();
- headers.addElement(new hdr("Return-Path", null));
- headers.addElement(new hdr("Received", null));
- headers.addElement(new hdr("Message-Id", null));
- headers.addElement(new hdr("Resent-Date", null));
- headers.addElement(new hdr("Date", null));
- headers.addElement(new hdr("Resent-From", null));
- headers.addElement(new hdr("From", null));
- headers.addElement(new hdr("Reply-To", null));
- headers.addElement(new hdr("To", null));
- headers.addElement(new hdr("Subject", null));
- headers.addElement(new hdr("Cc", null));
- headers.addElement(new hdr("In-Reply-To", null));
- headers.addElement(new hdr("Resent-Message-Id", null));
- headers.addElement(new hdr("Errors-To", null));
- headers.addElement(new hdr("Mime-Version", null));
- headers.addElement(new hdr("Content-Type", null));
- headers.addElement(new hdr("Content-Transfer-Encoding", null));
- headers.addElement(new hdr("Content-MD5", null));
- headers.addElement(new hdr(":", null));
- headers.addElement(new hdr("Content-Length", null));
- headers.addElement(new hdr("Status", null));
- }
-
- /**
- * Read and parse the given rfc822 message stream till the
- * blank line separating the header from the body. The input
- * stream is left positioned at the start of the body. The
- * header lines are stored internally. <p>
- *
- * For efficiency, wrap a BufferedInputStream around the actual
- * input stream and pass it as the parameter.
- *
- * @param is rfc822 input stream
- */
- public InternetHeaders(InputStream is) throws MessagingException {
- headers = new Vector();
- load(is);
- }
-
- /**
- * Read and parse the given rfc822 message stream till the
- * blank line separating the header from the body. Store the
- * header lines inside this InternetHeaders object. <p>
- *
- * Note that the header lines are added into this InternetHeaders
- * object, so any existing headers in this object will not be
- * affected.
- *
- * @param is rfc822 input stream
- */
- public void load(InputStream is) throws MessagingException {
- // Read header lines until a blank line. It is valid
- // to have BodyParts with no header lines.
- String line;
- LineInputStream lis = new LineInputStream(is);
-
- try {
- while ((line = lis.readLine()) != null) {
- if (line.length() == 0)
- // blank line, end of header
- break;
-
- addHeaderLine(line);
- }
- } catch (IOException ioex) {
- throw new MessagingException("Error in input stream", ioex);
- }
- }
-
- /**
- * Return all the values for the specified header. The
- * values are String objects.
- *
- * @param name header name
- */
- public String[] getHeader(String name) {
- Enumeration e = headers.elements();
- // XXX - should we just step through in index order?
- Vector v = new Vector(); // accumulate return values
-
- while (e.hasMoreElements()) {
- hdr h = (hdr)e.nextElement();
- if (name.equalsIgnoreCase(h.name) && h.line != null) {
- v.addElement(h.getValue());
- }
- }
- if (v.size() == 0)
- return (null);
- // convert Vector to an array for return
- String r[] = new String[v.size()];
- v.copyInto(r);
- return (r);
- }
-
- /**
- * Get all the headers for this header name, returned as a single
- * String, with headers separated by the delimiter. If the
- * delimiter is <code>null</code>, only the first header is
- * returned.
- *
- * @param name header name
- * @param delimiter delimiter
- * @return the value fields for all headers with
- * this name
- */
- public String getHeader(String name, String delimiter) {
- String s[] = getHeader(name);
-
- if (s == null)
- return null;
-
- if ((s.length == 1) || delimiter == null)
- return s[0];
-
- StringBuffer r = new StringBuffer(s[0]);
- for (int i = 1; i < s.length; i++) {
- r.append(delimiter);
- r.append(s[i]);
- }
- return r.toString();
- }
-
- /**
- * Change the first header line that matches name
- * to have value, adding a new header if no existing header
- * matches. Remove all matching headers but the first. <p>
- *
- * Note that RFC822 headers can only contain US-ASCII characters
- *
- * @param name header name
- * @param value header value
- */
- public void setHeader(String name, String value) {
- boolean found = false;
-
- for (int i = 0; i < headers.size(); i++) {
- hdr h = (hdr)headers.elementAt(i);
- if (name.equalsIgnoreCase(h.name)) {
- if (!found) {
- int j;
- if (h.line != null && (j = h.line.indexOf(':')) >= 0) {
- h.line = h.line.substring(0, j + 1) + " " + value;
- } else {
- h.line = name + ": " + value;
- }
- found = true;
- } else {
- headers.removeElementAt(i);
- i--; // have to look at i again
- }
- }
- }
-
- if (!found) {
- addHeader(name, value);
- }
- }
-
- /**
- * Add a header with the specified name and value to the header list. <p>
- *
- * Note that RFC822 headers can only contain US-ASCII characters.
- *
- * @param name header name
- * @param value header value
- */
- public void addHeader(String name, String value) {
- int pos = headers.size();
- for (int i = pos - 1; i >= 0; i--) {
- hdr h = (hdr)headers.elementAt(i);
- if (name.equalsIgnoreCase(h.name)) {
- headers.insertElementAt(new hdr(name, value), i + 1);
- return;
- }
- // marker for default place to add new headers
- if (h.name.equals(":"))
- pos = i;
- }
- headers.insertElementAt(new hdr(name, value), pos);
- }
-
- /**
- * Remove all header entries that match the given name
- * @param name header name
- */
- public void removeHeader(String name) {
- for (int i = 0; i < headers.size(); i++) {
- hdr h = (hdr)headers.elementAt(i);
- if (name.equalsIgnoreCase(h.name)) {
- h.line = null;
- //headers.removeElementAt(i);
- //i--; // have to look at i again
- }
- }
- }
-
- /**
- * Return all the headers as an Enumeration of Header objects
- * @return Header objects
- */
- public Enumeration getAllHeaders() {
- return (new matchEnum(headers, null, false, false));
- }
-
- /**
- * Return all matching Header objects
- * @return matching Header objects
- */
- public Enumeration getMatchingHeaders(String[] names) {
- return (new matchEnum(headers, names, true, false));
- }
-
- /**
- * Return all non-matching Header objects
- * @return non-matching Header objects
- */
- public Enumeration getNonMatchingHeaders(String[] names) {
- return (new matchEnum(headers, names, false, false));
- }
-
- /**
- * Add an RFC822 header line to the header store.
- * If the line starts with a space or tab (a continuation line),
- * add it to the last header line in the list. <p>
- *
- * Note that RFC822 headers can only contain US-ASCII characters
- *
- * @param line raw rfc822 header line
- */
- public void addHeaderLine(String line) {
- try {
- char c = line.charAt(0);
- if (c == ' ' || c == '\t') {
- hdr h = (hdr)headers.lastElement();
- h.line += "\r\n" + line;
- } else
- headers.addElement(new hdr(line));
- } catch (StringIndexOutOfBoundsException e) {
- // line is empty, ignore it
- return;
- } catch (NoSuchElementException e) {
- // XXX - vector is empty?
- }
- }
-
- /**
- * Return all the header lines as an Enumeration of Strings.
- */
- public Enumeration getAllHeaderLines() {
- return (getNonMatchingHeaderLines(null));
- }
-
- /**
- * Return all matching header lines as an Enumeration of Strings.
- */
- public Enumeration getMatchingHeaderLines(String[] names) {
- return (new matchEnum(headers, names, true, true));
- }
-
- /**
- * Return all non-matching header lines
- */
- public Enumeration getNonMatchingHeaderLines(String[] names) {
- return (new matchEnum(headers, names, false, true));
- }
- }
-
- /*
- * A private utility class to represent an individual header.
- * A hdr object with a null line field is used as a "place holder"
- * for headers of that name, to preserve order of headers.
- */
- class hdr {
- // XXX - should these be private?
- String name; // the canonicalized (trimmed) name of this header
- // XXX - should name be stored in lower case?
- String line; // the entire RFC822 header "line", or null if place holder
-
- /*
- * Constructor that takes a line and splits out
- * the header name.
- */
- hdr(String l) {
- int i = l.indexOf(':');
- if (i < 0) {
- // should never happen
- name = l.trim();
- } else {
- name = l.substring(0, i).trim();
- }
- line = l;
- }
-
- /*
- * Constructor that takes a header name and value.
- */
- hdr(String n, String v) {
- name = n;
- if (v != null)
- line = n + ": " + v;
- else
- line = null;
- }
-
- /*
- * Return the "name" part of the header line.
- */
- String getName() {
- return (name);
- }
-
- /*
- * Return the "value" part of the header line.
- */
- String getValue() {
- int i = line.indexOf(':');
- if (i < 0)
- return (line);
- // skip whitespace after ':'
- int j;
- for (j = i + 1; j < line.length(); j++) {
- char c = line.charAt(j);
- if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
- break;
- }
- return (line.substring(j));
- }
- }
-
- /*
- * The enumeration object used to enumerate an
- * InternetHeaders object. Can return
- * either a String or a Header object.
- */
- class matchEnum implements Enumeration {
- private Enumeration e; // enum object of headers Vector
- // XXX - is this overkill? should we step through in index
- // order instead?
- private String names[]; // names to match, or not
- private boolean match; // return matching headers?
- private boolean want_line; // return header lines?
- private hdr next_header; // the next header to be returned
-
- /*
- * Constructor. Initialize the enumeration for the entire
- * Vector of headers, the set of headers, whether to return
- * matching or non-matching headers, and whether to return
- * header lines or Header objects.
- */
- matchEnum(Vector v, String n[], boolean m, boolean l) {
- e = v.elements();
- names = n;
- match = m;
- want_line = l;
- next_header = null;
- }
-
- /*
- * Any more elements in this enumeration?
- */
- public boolean hasMoreElements() {
- // if necessary, prefetch the next matching header,
- // and remember it.
- if (next_header == null)
- next_header = nextMatch();
- return (next_header != null);
- }
-
- /*
- * Return the next element.
- */
- public Object nextElement() {
- if (next_header == null)
- next_header = nextMatch();
-
- if (next_header == null)
- throw new NoSuchElementException("No more headers");
-
- hdr h = next_header;
- next_header = null;
- if (want_line)
- return (h.line);
- else
- return (new Header(h.getName(),
- h.getValue()));
- }
-
- /*
- * Return the next hdr object according to the match
- * criteria, or null if none left.
- */
- private hdr nextMatch() {
- next:
- while (e.hasMoreElements()) {
- hdr h = (hdr)e.nextElement();
-
- // skip "place holder" headers
- if (h.line == null)
- continue;
-
- // if no names to match against, return appropriately
- if (names == null)
- return (match ? null : h);
-
- // check whether this header matches any of the names
- for (int i = 0; i < names.length; i++) {
- if (names[i].equalsIgnoreCase(h.name)) {
- if (match)
- return (h);
- else
- // found a match, but we're
- // looking for non-matches.
- // try next header.
- continue next;
- }
- }
- // found no matches. if that's what we wanted, return it.
- if (!match)
- return (h);
- }
- return (null);
- }
- }