001 /**
002 *
003 * Copyright 2003-2004 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package javax.mail.internet;
019
020 import java.io.IOException;
021 import java.io.InputStream;
022 import java.io.OutputStream;
023 import java.util.ArrayList;
024 import java.util.Arrays;
025 import java.util.Collections;
026 import java.util.Enumeration;
027 import java.util.HashSet;
028 import java.util.Iterator;
029 import java.util.LinkedHashMap;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.Set;
033
034 import javax.mail.Address;
035 import javax.mail.Header;
036 import javax.mail.MessagingException;
037
038 /**
039 * Class that represents the RFC822 headers associated with a message.
040 *
041 * @version $Rev: 398618 $ $Date: 2006-05-01 08:18:57 -0700 (Mon, 01 May 2006) $
042 */
043 public class InternetHeaders {
044 // RFC822 imposes an ordering on its headers so we use a LinkedHashedMap
045 private final LinkedHashMap headers = new LinkedHashMap();
046
047 private transient String lastHeaderName;
048
049 /**
050 * Create an empty InternetHeaders
051 */
052 public InternetHeaders() {
053 // we need to initialize the headers in the correct order
054 // fields: dates source destination optional-field
055 // dates:
056 setHeaderList("Date", null);
057 setHeaderList("Resent-Date", null);
058 // source: trace originator resent
059 // trace: return received
060 setHeaderList("Return-path", null);
061 setHeaderList("Received", null);
062 // originator: authentic reply-to
063 setHeaderList("Sender", null);
064 setHeaderList("From", null);
065 setHeaderList("Reply-To", null);
066 // resent: resent-authentic resent-reply-to
067 setHeaderList("Resent-Sender", null);
068 setHeaderList("Resent-From", null);
069 setHeaderList("Resent-Reply-To", null);
070 // destination:
071 setHeaderList("To", null);
072 setHeaderList("Resent-To", null);
073 setHeaderList("cc", null);
074 setHeaderList("Resent-cc", null);
075 setHeaderList("bcc", null);
076 setHeaderList("Resent-bcc", null);
077 // optional-field:
078 setHeaderList("Message-ID", null);
079 setHeaderList("Resent-Message-ID", null);
080 setHeaderList("In-Reply-To", null);
081 setHeaderList("References", null);
082 setHeaderList("Keywords", null);
083 setHeaderList("Subject", null);
084 setHeaderList("Comments", null);
085 setHeaderList("Encrypted", null);
086 }
087
088 /**
089 * Create a new InternetHeaders initialized by reading headers from the
090 * stream.
091 *
092 * @param in
093 * the RFC822 input stream to load from
094 * @throws MessagingException
095 * if there is a problem pasring the stream
096 */
097 public InternetHeaders(InputStream in) throws MessagingException {
098 load(in);
099 }
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 }