View Javadoc

1   /**
2    *
3    * Copyright 2003-2004 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  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 javax.mail.internet;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.Enumeration;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import javax.mail.Address;
35  import javax.mail.Header;
36  import javax.mail.MessagingException;
37  
38  /**
39   * Class that represents the RFC822 headers associated with a message.
40   *
41   * @version $Rev: 398618 $ $Date: 2006-05-01 08:18:57 -0700 (Mon, 01 May 2006) $
42   */
43  public class InternetHeaders {
44      // RFC822 imposes an ordering on its headers so we use a LinkedHashedMap
45      private final LinkedHashMap headers = new LinkedHashMap();
46  
47      private transient String lastHeaderName;
48  
49      /**
50       * Create an empty InternetHeaders
51       */
52      public InternetHeaders() {
53          // we need to initialize the headers in the correct order
54          // fields: dates source destination optional-field
55          // dates:
56          setHeaderList("Date", null);
57          setHeaderList("Resent-Date", null);
58          // source: trace originator resent
59          // trace: return received
60          setHeaderList("Return-path", null);
61          setHeaderList("Received", null);
62          // originator: authentic reply-to
63          setHeaderList("Sender", null);
64          setHeaderList("From", null);
65          setHeaderList("Reply-To", null);
66          // resent: resent-authentic resent-reply-to
67          setHeaderList("Resent-Sender", null);
68          setHeaderList("Resent-From", null);
69          setHeaderList("Resent-Reply-To", null);
70          // destination:
71          setHeaderList("To", null);
72          setHeaderList("Resent-To", null);
73          setHeaderList("cc", null);
74          setHeaderList("Resent-cc", null);
75          setHeaderList("bcc", null);
76          setHeaderList("Resent-bcc", null);
77          // optional-field:
78          setHeaderList("Message-ID", null);
79          setHeaderList("Resent-Message-ID", null);
80          setHeaderList("In-Reply-To", null);
81          setHeaderList("References", null);
82          setHeaderList("Keywords", null);
83          setHeaderList("Subject", null);
84          setHeaderList("Comments", null);
85          setHeaderList("Encrypted", null);
86      }
87  
88      /**
89       * Create a new InternetHeaders initialized by reading headers from the
90       * stream.
91       *
92       * @param in
93       *            the RFC822 input stream to load from
94       * @throws MessagingException
95       *             if there is a problem pasring the stream
96       */
97      public InternetHeaders(InputStream in) throws MessagingException {
98          load(in);
99      }
100 
101     /**
102      * Read and parse the supplied stream and add all headers to the current
103      * set.
104      *
105      * @param in
106      *            the RFC822 input stream to load from
107      * @throws MessagingException
108      *             if there is a problem pasring the stream
109      */
110     public void load(InputStream in) throws MessagingException {
111         try {
112             StringBuffer name = new StringBuffer(32);
113             StringBuffer value = new StringBuffer(128);
114             done: while (true) {
115                 int c = in.read();
116                 char ch = (char) c;
117                 if (c == -1) {
118                     break;
119                 } else if (c == 13) {
120                     // empty line terminates header
121                     in.read(); // skip LF
122                     break;
123                 } else if (Character.isWhitespace(ch)) {
124                     // handle continuation
125                     do {
126                         c = in.read();
127                         if (c == -1) {
128                             break done;
129                         }
130                         ch = (char) c;
131                     } while (Character.isWhitespace(ch));
132                 } else {
133                     // new header
134                     if (name.length() > 0) {
135                         addHeader(name.toString().trim(), value.toString().trim());
136                     }
137                     name.setLength(0);
138                     value.setLength(0);
139                     while (true) {
140                         name.append((char) c);
141                         c = in.read();
142                         if (c == -1) {
143                             break done;
144                         } else if (c == ':') {
145                             break;
146                         }
147                     }
148                     c = in.read();
149                     if (c == -1) {
150                         break done;
151                     }
152                 }
153 
154                 while (c != 13) {
155                     ch = (char) c;
156                     value.append(ch);
157                     c = in.read();
158                     if (c == -1) {
159                         break done;
160                     }
161                 }
162                 // skip LF
163                 c = in.read();
164                 if (c == -1) {
165                     break;
166                 }
167             }
168             if (name.length() > 0) {
169                 addHeader(name.toString().trim(), value.toString().trim());
170             }
171         } catch (IOException e) {
172             throw new MessagingException("Error loading headers", e);
173         }
174     }
175 
176     /**
177      * Return all the values for the specified header.
178      *
179      * @param name
180      *            the header to return
181      * @return the values for that header, or null if the header is not present
182      */
183     public String[] getHeader(String name) {
184         List headers = getHeaderList(name);
185         if (headers == null) {
186             return null;
187         } else {
188             String[] result = new String[headers.size()];
189             for (int i = 0; i < headers.size(); i++) {
190                 InternetHeader header = (InternetHeader) headers.get(i);
191                 result[i] = header.getValue();
192             }
193             return result;
194         }
195     }
196 
197     /**
198      * Return the values for the specified header as a single String. If the
199      * header has more than one value then all values are concatenated together
200      * separated by the supplied delimiter.
201      *
202      * @param name
203      *            the header to return
204      * @param delimiter
205      *            the delimiter used in concatenation
206      * @return the header as a single String
207      */
208     public String getHeader(String name, String delimiter) {
209         List list = getHeaderList(name);
210         if (list == null) {
211             return null;
212         } else if (list.isEmpty()) {
213             return "";
214         } else if (list.size() == 1 || delimiter == null) {
215             return ((InternetHeader) list.get(0)).getValue();
216         } else {
217             StringBuffer buf = new StringBuffer(20 * list.size());
218             buf.append(((InternetHeader) list.get(0)).getValue());
219             for (int i = 1; i < list.size(); i++) {
220                 buf.append(delimiter);
221                 buf.append(((InternetHeader) list.get(i)).getValue());
222             }
223             return buf.toString();
224         }
225     }
226 
227     /**
228      * Set the value of the header to the supplied value; any existing headers
229      * are removed.
230      *
231      * @param name
232      *            the name of the header
233      * @param value
234      *            the new value
235      */
236     public void setHeader(String name, String value) {
237         List list = new ArrayList();
238         list.add(new InternetHeader(name, value));
239         setHeaderList(name, list);
240     }
241 
242     /**
243      * Add a new value to the header with the supplied name.
244      *
245      * @param name
246      *            the name of the header to add a new value for
247      * @param value
248      *            another value
249      */
250     public void addHeader(String name, String value) {
251         List list = getHeaderList(name);
252         if (list == null) {
253             list = new ArrayList();
254             headers.put(name.toLowerCase(), list);
255         }
256         list.add(new InternetHeader(name, value));
257     }
258 
259     /**
260      * Remove all header entries with the supplied name
261      *
262      * @param name
263      *            the header to remove
264      */
265     public void removeHeader(String name) {
266         List list = getHeaderList(name);
267         // it's possible we might not have the named header.  This is a nop if the header doesn't exist.
268         if (list != null) {
269             list.clear();
270         }
271     }
272 
273     /**
274      * Return all headers.
275      *
276      * @return an Enumeration<Header> containing all headers
277      */
278     public Enumeration getAllHeaders() {
279         List result = new ArrayList(headers.size() * 2);
280         Iterator it = headers.values().iterator();
281         while (it.hasNext()) {
282             List list = (List) it.next();
283             if (list != null) {
284                 result.addAll(list);
285             }
286         }
287         return Collections.enumeration(result);
288     }
289 
290     /**
291      * Return all matching Header objects.
292      */
293     public Enumeration getMatchingHeaders(String[] names) {
294         Set include = new HashSet(names.length);
295         for (int i = 0; i < names.length; i++) {
296             String name = names[i];
297             include.add(name.toLowerCase());
298         }
299         List result = new ArrayList(headers.size());
300         for (Iterator i = headers.entrySet().iterator(); i.hasNext();) {
301             Map.Entry entry = (Map.Entry) i.next();
302             if (entry.getValue() != null && include.contains(((String) entry.getKey()).toLowerCase())) {
303                 result.addAll((List) entry.getValue());
304             }
305         }
306         return Collections.enumeration(result);
307     }
308 
309     /**
310      * Return all non matching Header objects.
311      */
312     public Enumeration getNonMatchingHeaders(String[] names) {
313         Set exclude = new HashSet(names.length);
314         for (int i = 0; i < names.length; i++) {
315             String name = names[i];
316             exclude.add(name.toLowerCase());
317         }
318         List result = new ArrayList(headers.size());
319         for (Iterator i = headers.entrySet().iterator(); i.hasNext();) {
320             Map.Entry entry = (Map.Entry) i.next();
321             if (entry.getValue() != null && !exclude.contains(((String) entry.getKey()).toLowerCase())) {
322                 result.addAll((List) entry.getValue());
323             }
324         }
325         return Collections.enumeration(result);
326     }
327 
328     /**
329      * Add an RFC822 header line to the header store. If the line starts with a
330      * space or tab (a continuation line), add it to the last header line in the
331      * list. Otherwise, append the new header line to the list.
332      *
333      * Note that RFC822 headers can only contain US-ASCII characters
334      *
335      * @param line
336      *            raw RFC822 header line
337      */
338     public void addHeaderLine(String line) {
339         StringBuffer name = new StringBuffer(32);
340         StringBuffer value = new StringBuffer(128);
341         boolean inName = true;
342         boolean continuation = false;
343         if (Character.isWhitespace(line.charAt(0))) {
344             continuation = true;
345             inName = false;
346         }
347         for (int i = 0; i < line.length(); i++) {
348             char c = line.charAt(i);
349             if (inName && c == ':') {
350                 inName = false;
351             } else if (inName) {
352                 name.append(c);
353             } else {
354                 value.append(c);
355             }
356         }
357         if (continuation) {
358             List list = getHeaderList(lastHeaderName);
359             Header h = (Header) list.remove(list.size() - 1);
360             list.add(new InternetHeader(lastHeaderName, (h.getValue() + value.toString()).trim()));
361         } else {
362             lastHeaderName = name.toString().trim();
363             addHeader(lastHeaderName, value.toString().trim());
364         }
365     }
366 
367     /**
368      * Return all the header lines as an Enumeration of Strings.
369      */
370     public Enumeration getAllHeaderLines() {
371         return new HeaderLineEnumeration(getAllHeaders());
372     }
373 
374     /**
375      * Return all matching header lines as an Enumeration of Strings.
376      */
377     public Enumeration getMatchingHeaderLines(String[] names) {
378         return new HeaderLineEnumeration(getMatchingHeaders(names));
379     }
380 
381     /**
382      * Return all non-matching header lines.
383      */
384     public Enumeration getNonMatchingHeaderLines(String[] names) {
385         return new HeaderLineEnumeration(getNonMatchingHeaders(names));
386     }
387 
388     void setHeader(String name, Address[] addresses) {
389         List list = new ArrayList(addresses.length);
390         for (int i = 0; i < addresses.length; i++) {
391             Address address = addresses[i];
392             list.add(new InternetHeader(name, address.toString()));
393         }
394         headers.put(name.toLowerCase(), list);
395     }
396 
397     private List getHeaderList(String name) {
398         return (List) headers.get(name.toLowerCase());
399     }
400 
401     private void setHeaderList(String name, List list) {
402         headers.put(name.toLowerCase(), list);
403     }
404 
405     void writeTo(OutputStream out, String[] ignore) throws IOException {
406         Map map = new LinkedHashMap(headers);
407         if (ignore != null) {
408             // remove each of these from the header list (note, they are stored as lower case keys).
409             for (int i = 0; i < ignore.length; i++) {
410                 String key = ignore[i].toLowerCase();
411                 map.remove(key);
412             }
413         }
414         for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
415             Map.Entry entry = (Map.Entry) i.next();
416             String name = (String) entry.getKey();
417             List headers = (List) entry.getValue();
418             if (headers != null) {
419                 for (int j = 0; j < headers.size(); j++) {
420                     InternetHeader header = (InternetHeader) headers.get(j);
421                     out.write(header.getName().getBytes());
422                     out.write(':');
423                     out.write(' ');
424                     out.write(header.getValue().getBytes());
425                     out.write(13);
426                     out.write(10);
427                 }
428             }
429         }
430     }
431 
432     private static class InternetHeader extends Header {
433         public InternetHeader(String name, String value) {
434             super(name, value);
435         }
436 
437         public boolean equals(Object obj) {
438             if (this == obj)
439                 return true;
440             if (obj instanceof InternetHeader == false)
441                 return false;
442             final InternetHeader other = (InternetHeader) obj;
443             return getName().equalsIgnoreCase(other.getName());
444         }
445 
446         public int hashCode() {
447             return getName().toLowerCase().hashCode();
448         }
449     }
450 
451     private static class HeaderLineEnumeration implements Enumeration {
452         private Enumeration headers;
453 
454         public HeaderLineEnumeration(Enumeration headers) {
455             this.headers = headers;
456         }
457 
458         public boolean hasMoreElements() {
459             return headers.hasMoreElements();
460         }
461 
462         public Object nextElement() {
463             Header h = (Header) headers.nextElement();
464             return h.getName() + ": " + h.getValue();
465         }
466     }
467 }