1 /**
2 *
3 * Copyright 2003-2006 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: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
42 */
43 public class InternetHeaders {
44
45 protected List headers = new ArrayList();
46
47 private transient String lastHeaderName;
48
49 /**
50 * Create an empty InternetHeaders
51 */
52 public InternetHeaders() {
53
54 addHeader("Return-path", null);
55 addHeader("Received", null);
56 addHeader("Message-ID", null);
57 addHeader("Resent-Date", null);
58 addHeader("Date", null);
59 addHeader("Resent-From", null);
60 addHeader("From", null);
61 addHeader("Reply-To", null);
62 addHeader("Sender", null);
63 addHeader("To", null);
64 addHeader("Subject", null);
65 addHeader("Cc", null);
66 addHeader("In-Reply-To", null);
67 addHeader("Resent-Message-Id", null);
68 addHeader("Errors-To", null);
69 addHeader("MIME-Version", null);
70 addHeader("Content-Type", null);
71 addHeader("Content-Transfer-Encoding", null);
72 addHeader("Content-MD5", null);
73
74 addHeader(":", null);
75 addHeader("Content-Length", null);
76 addHeader("Status", null);
77 }
78
79 /**
80 * Create a new InternetHeaders initialized by reading headers from the
81 * stream.
82 *
83 * @param in
84 * the RFC822 input stream to load from
85 * @throws MessagingException
86 * if there is a problem pasring the stream
87 */
88 public InternetHeaders(InputStream in) throws MessagingException {
89 load(in);
90 }
91
92 /**
93 * Read and parse the supplied stream and add all headers to the current
94 * set.
95 *
96 * @param in
97 * the RFC822 input stream to load from
98 * @throws MessagingException
99 * if there is a problem pasring the stream
100 */
101 public void load(InputStream in) throws MessagingException {
102 try {
103 StringBuffer name = new StringBuffer(32);
104 StringBuffer value = new StringBuffer(128);
105 done: while (true) {
106 int c = in.read();
107 char ch = (char) c;
108 if (c == -1) {
109 break;
110 } else if (c == 13) {
111
112 in.read();
113 break;
114 } else if (Character.isWhitespace(ch)) {
115
116 do {
117 c = in.read();
118 if (c == -1) {
119 break done;
120 }
121 ch = (char) c;
122 } while (Character.isWhitespace(ch));
123 } else {
124
125 if (name.length() > 0) {
126 addHeader(name.toString().trim(), value.toString().trim());
127 }
128 name.setLength(0);
129 value.setLength(0);
130 while (true) {
131 name.append((char) c);
132 c = in.read();
133 if (c == -1) {
134 break done;
135 } else if (c == ':') {
136 break;
137 }
138 }
139 c = in.read();
140 if (c == -1) {
141 break done;
142 }
143 }
144
145 while (c != 13) {
146 ch = (char) c;
147 value.append(ch);
148 c = in.read();
149 if (c == -1) {
150 break done;
151 }
152 }
153
154 c = in.read();
155 if (c == -1) {
156 break;
157 }
158 }
159 if (name.length() > 0) {
160 addHeader(name.toString().trim(), value.toString().trim());
161 }
162 } catch (IOException e) {
163 throw new MessagingException("Error loading headers", e);
164 }
165 }
166
167
168 /**
169 * Return all the values for the specified header.
170 *
171 * @param name
172 * the header to return
173 * @return the values for that header, or null if the header is not present
174 */
175 public String[] getHeader(String name) {
176 List accumulator = new ArrayList();
177
178 for (int i = 0; i < headers.size(); i++) {
179 InternetHeader header = (InternetHeader)headers.get(i);
180 if (header.getName().equalsIgnoreCase(name) && header.getValue() != null) {
181 accumulator.add(header.getValue());
182 }
183 }
184
185
186 if (accumulator.isEmpty()) {
187 return null;
188 }
189
190
191 return (String[])accumulator.toArray(new String[accumulator.size()]);
192 }
193
194 /**
195 * Return the values for the specified header as a single String. If the
196 * header has more than one value then all values are concatenated together
197 * separated by the supplied delimiter.
198 *
199 * @param name
200 * the header to return
201 * @param delimiter
202 * the delimiter used in concatenation
203 * @return the header as a single String
204 */
205 public String getHeader(String name, String delimiter) {
206
207 String[] matches = getHeader(name);
208
209
210 if (matches == null) {
211 return null;
212 }
213
214
215 if (matches.length == 1 || delimiter == null) {
216 return matches[0];
217 }
218
219
220 StringBuffer result = new StringBuffer(matches[0]);
221
222 for (int i = 1; i < matches.length; i++) {
223 result.append(delimiter);
224 result.append(matches[i]);
225 }
226
227 return result.toString();
228 }
229
230
231 /**
232 * Set the value of the header to the supplied value; any existing headers
233 * are removed.
234 *
235 * @param name
236 * the name of the header
237 * @param value
238 * the new value
239 */
240 public void setHeader(String name, String value) {
241
242 for (int i = 0; i < headers.size(); i++) {
243 InternetHeader header = (InternetHeader)headers.get(i);
244
245 if (name.equalsIgnoreCase(header.getName())) {
246
247 header.setValue(value);
248
249 removeHeaders(name, i + 1);
250 return;
251 }
252 }
253
254
255 addHeader(name, value);
256 }
257
258
259 /**
260 * Remove all headers with the given name, starting with the
261 * specified start position.
262 *
263 * @param name The target header name.
264 * @param pos The position of the first header to examine.
265 */
266 private void removeHeaders(String name, int pos) {
267
268 for (int i = pos; i < headers.size(); i++) {
269 InternetHeader header = (InternetHeader)headers.get(i);
270
271 if (name.equalsIgnoreCase(header.getName())) {
272
273 headers.remove(i);
274 i--;
275 }
276 }
277 }
278
279
280 /**
281 * Find a header in the current list by name, returning the index.
282 *
283 * @param name The target name.
284 *
285 * @return The index of the header in the list. Returns -1 for a not found
286 * condition.
287 */
288 private int findHeader(String name) {
289 return findHeader(name, 0);
290 }
291
292
293 /**
294 * Find a header in the current list, beginning with the specified
295 * start index.
296 *
297 * @param name The target header name.
298 * @param start The search start index.
299 *
300 * @return The index of the first matching header. Returns -1 if the
301 * header is not located.
302 */
303 private int findHeader(String name, int start) {
304 for (int i = start; i < headers.size(); i++) {
305 InternetHeader header = (InternetHeader)headers.get(i);
306
307 if (name.equalsIgnoreCase(header.getName())) {
308 return i;
309 }
310 }
311 return -1;
312 }
313
314 /**
315 * Add a new value to the header with the supplied name.
316 *
317 * @param name
318 * the name of the header to add a new value for
319 * @param value
320 * another value
321 */
322 public void addHeader(String name, String value) {
323 InternetHeader newHeader = new InternetHeader(name, value);
324
325
326
327 if (name.equalsIgnoreCase("Received") || name.equalsIgnoreCase("Return-Path")) {
328
329 int pos = findHeader(name);
330
331
332 if (pos != -1) {
333
334
335 InternetHeader oldHeader = (InternetHeader)headers.get(pos);
336 if (oldHeader.getValue() == null) {
337 oldHeader.setValue(value);
338 }
339 else {
340 headers.add(pos, newHeader);
341 }
342 }
343 else {
344
345 headers.add(0, newHeader);
346 }
347 }
348
349 else {
350
351 int pos = findHeader(name);
352
353
354 if (pos != -1) {
355 InternetHeader oldHeader = (InternetHeader)headers.get(pos);
356
357 if (oldHeader.getValue() == null) {
358 oldHeader.setValue(value);
359 }
360 else {
361
362
363
364 int lastPos = findHeader(name, pos + 1);
365
366 while (lastPos != -1) {
367 pos = lastPos;
368 lastPos = findHeader(name, pos + 1);
369 }
370
371
372 headers.add(pos + 1, newHeader);
373 }
374 }
375 else {
376
377 pos = findHeader(":");
378 if (pos == -1) {
379 pos = headers.size();
380 }
381 headers.add(pos, newHeader);
382 }
383 }
384 }
385
386
387 /**
388 * Remove all header entries with the supplied name
389 *
390 * @param name
391 * the header to remove
392 */
393 public void removeHeader(String name) {
394
395 int pos = findHeader(name);
396
397 if (pos != -1) {
398 InternetHeader oldHeader = (InternetHeader)headers.get(pos);
399
400 oldHeader.setValue(null);
401
402 removeHeaders(name, pos + 1);
403 }
404 }
405
406
407 /**
408 * Return all headers.
409 *
410 * @return an Enumeration<Header> containing all headers
411 */
412 public Enumeration getAllHeaders() {
413 List result = new ArrayList();
414
415 for (int i = 0; i < headers.size(); i++) {
416 InternetHeader header = (InternetHeader)headers.get(i);
417
418 if (header.getValue() != null) {
419 result.add(header);
420 }
421 }
422
423 return Collections.enumeration(result);
424 }
425
426
427 /**
428 * Test if a given header name is a match for any header in the
429 * given list.
430 *
431 * @param name The name of the current tested header.
432 * @param names The list of names to match against.
433 *
434 * @return True if this is a match for any name in the list, false
435 * for a complete mismatch.
436 */
437 private boolean matchHeader(String name, String[] names) {
438 for (int i = 0; i < names.length; i++) {
439 if (name.equalsIgnoreCase(names[i])) {
440 return true;
441 }
442 }
443 return false;
444 }
445
446
447 /**
448 * Return all matching Header objects.
449 */
450 public Enumeration getMatchingHeaders(String[] names) {
451 List result = new ArrayList();
452
453 for (int i = 0; i < headers.size(); i++) {
454 InternetHeader header = (InternetHeader)headers.get(i);
455
456 if (header.getValue() != null) {
457
458 if (matchHeader(header.getName(), names)) {
459 result.add(header);
460 }
461 }
462 }
463 return Collections.enumeration(result);
464 }
465
466
467 /**
468 * Return all non matching Header objects.
469 */
470 public Enumeration getNonMatchingHeaders(String[] names) {
471 List result = new ArrayList();
472
473 for (int i = 0; i < headers.size(); i++) {
474 InternetHeader header = (InternetHeader)headers.get(i);
475
476 if (header.getValue() != null) {
477
478 if (!matchHeader(header.getName(), names)) {
479 result.add(header);
480 }
481 }
482 }
483 return Collections.enumeration(result);
484 }
485
486
487 /**
488 * Add an RFC822 header line to the header store. If the line starts with a
489 * space or tab (a continuation line), add it to the last header line in the
490 * list. Otherwise, append the new header line to the list.
491 *
492 * Note that RFC822 headers can only contain US-ASCII characters
493 *
494 * @param line
495 * raw RFC822 header line
496 */
497 public void addHeaderLine(String line) {
498
499 if (line.length() == 0) {
500 return;
501 }
502
503
504 char ch = line.charAt(0);
505
506
507 if (ch == ' ' || ch == '\t') {
508 InternetHeader header = (InternetHeader)headers.get(headers.size() - 1);
509 header.appendValue(line);
510 }
511 else {
512
513 headers.add(new InternetHeader(line));
514 }
515 }
516
517
518 /**
519 * Return all the header lines as an Enumeration of Strings.
520 */
521 public Enumeration getAllHeaderLines() {
522 return new HeaderLineEnumeration(getAllHeaders());
523 }
524
525 /**
526 * Return all matching header lines as an Enumeration of Strings.
527 */
528 public Enumeration getMatchingHeaderLines(String[] names) {
529 return new HeaderLineEnumeration(getMatchingHeaders(names));
530 }
531
532 /**
533 * Return all non-matching header lines.
534 */
535 public Enumeration getNonMatchingHeaderLines(String[] names) {
536 return new HeaderLineEnumeration(getNonMatchingHeaders(names));
537 }
538
539
540 /**
541 * Set an internet header from a list of addresses. The
542 * first address item is set, followed by a series of addHeaders().
543 *
544 * @param name The name to set.
545 * @param addresses The list of addresses to set.
546 */
547 void setHeader(String name, Address[] addresses) {
548
549 if (addresses.length == 0) {
550 removeHeader(name);
551 }
552
553
554 setHeader(name, addresses[0].toString());
555
556
557 for (int i = 1; i < addresses.length; i++) {
558 Address address = addresses[i];
559 addHeader(name, address.toString());
560 }
561 }
562
563
564 void writeTo(OutputStream out, String[] ignore) throws IOException {
565 if (ignore == null) {
566
567 for (int i = 0; i < headers.size(); i++) {
568 InternetHeader header = (InternetHeader)headers.get(i);
569
570 if (header.getValue() != null) {
571 header.writeTo(out);
572 }
573 }
574 }
575 else {
576
577 for (int i = 0; i < headers.size(); i++) {
578 InternetHeader header = (InternetHeader)headers.get(i);
579
580 if (header.getValue() != null) {
581 if (matchHeader(header.getName(), ignore)) {
582 header.writeTo(out);
583 }
584 }
585 }
586 }
587 }
588
589 protected static final class InternetHeader extends Header {
590
591 public InternetHeader(String h) {
592
593 super("", "");
594 int separator = h.indexOf(':');
595
596 if (separator == -1) {
597 name = h.trim();
598 }
599 else {
600 name = h.substring(0, separator);
601
602 separator++;
603
604 while (separator < h.length()) {
605 char ch = h.charAt(separator);
606 if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
607 break;
608 }
609 separator++;
610 }
611
612 value = h.substring(separator);
613 }
614 }
615
616 public InternetHeader(String name, String value) {
617 super(name, value);
618 }
619
620
621 /**
622 * Package scope method for setting the header value.
623 *
624 * @param value The new header value.
625 */
626 void setValue(String value) {
627 this.value = value;
628 }
629
630 /**
631 * Package scope method for extending a header value.
632 *
633 * @param value The appended header value.
634 */
635 void appendValue(String value) {
636 if (this.value == null) {
637 this.value = value;
638 }
639 else {
640 this.value = this.value + "\r\n" + value;
641 }
642 }
643
644 void writeTo(OutputStream out) throws IOException {
645 out.write(name.getBytes());
646 out.write(':');
647 out.write(' ');
648 out.write(value.getBytes());
649 out.write('\r');
650 out.write('\n');
651 }
652 }
653
654 private static class HeaderLineEnumeration implements Enumeration {
655 private Enumeration headers;
656
657 public HeaderLineEnumeration(Enumeration headers) {
658 this.headers = headers;
659 }
660
661 public boolean hasMoreElements() {
662 return headers.hasMoreElements();
663 }
664
665 public Object nextElement() {
666 Header h = (Header) headers.nextElement();
667 return h.getName() + ": " + h.getValue();
668 }
669 }
670 }