1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can obtain
10 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
11 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
12 * language governing permissions and limitations under the License.
13 *
14 * When distributing the software, include this License Header Notice in each
15 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
16 * Sun designates this particular file as subject to the "Classpath" exception
17 * as provided by Sun in the GPL Version 2 section of the License file that
18 * accompanied this code. If applicable, add the following below the License
19 * Header, with the fields enclosed by brackets [] replaced by your own
20 * identifying information: "Portions Copyrighted [year]
21 * [name of copyright owner]"
22 *
23 * Contributor(s):
24 *
25 * If you wish your version of this file to be governed by only the CDDL or
26 * only the GPL Version 2, indicate your decision by adding "[Contributor]
27 * elects to include this software in this distribution under the [CDDL or GPL
28 * Version 2] license." If you don't indicate a single choice of license, a
29 * recipient has the option to distribute your version of this file under
30 * either the CDDL, the GPL Version 2 or to extend the choice of license to
31 * its licensees as provided above. However, if you add GPL Version 2 code
32 * and therefore, elected the GPL Version 2 license, then the option applies
33 * only if the new code is made subject to such option by the copyright
34 * holder.
35 */
36
37 /*
38 * @(#)InternetHeaders.java 1.22 07/05/04
39 */
40
41 package javax.mail.internet;
42
43 import java.io;
44 import java.util;
45 import javax.mail;
46 import com.sun.mail.util.LineInputStream;
47
48 /**
49 * InternetHeaders is a utility class that manages RFC822 style
50 * headers. Given an RFC822 format message stream, it reads lines
51 * until the blank line that indicates end of header. The input stream
52 * is positioned at the start of the body. The lines are stored
53 * within the object and can be extracted as either Strings or
54 * {@link javax.mail.Header} objects. <p>
55 *
56 * This class is mostly intended for service providers. MimeMessage
57 * and MimeBody use this class for holding their headers. <p>
58 *
59 * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
60 *
61 * RFC822 and MIME header fields <strong>must</strong> contain only
62 * US-ASCII characters. If a header contains non US-ASCII characters,
63 * it must be encoded as per the rules in RFC 2047. The MimeUtility
64 * class provided in this package can be used to to achieve this.
65 * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
66 * <code>addHeaderLine</code> methods are responsible for enforcing
67 * the MIME requirements for the specified headers. In addition, these
68 * header fields must be folded (wrapped) before being sent if they
69 * exceed the line length limitation for the transport (1000 bytes for
70 * SMTP). Received headers may have been folded. The application is
71 * responsible for folding and unfolding headers as appropriate. <p>
72 *
73 * @see javax.mail.internet.MimeUtility
74 * @author John Mani
75 * @author Bill Shannon
76 */
77
78 public class InternetHeaders {
79 /**
80 * An individual internet header. This class is only used by
81 * subclasses of InternetHeaders. <p>
82 *
83 * An InternetHeader object with a null value is used as a placeholder
84 * for headers of that name, to preserve the order of headers.
85 * A placeholder InternetHeader object with a name of ":" marks
86 * the location in the list of headers where new headers are
87 * added by default.
88 *
89 * @since JavaMail 1.4
90 */
91 protected static final class InternetHeader extends Header {
92 /*
93 * Note that the value field from the superclass
94 * isn't used in this class. We extract the value
95 * from the line field as needed. We store the line
96 * rather than just the value to ensure that we can
97 * get back the exact original line, with the original
98 * whitespace, etc.
99 */
100 String line; // the entire RFC822 header "line",
101 // or null if placeholder
102
103 /**
104 * Constructor that takes a line and splits out
105 * the header name.
106 */
107 public InternetHeader(String l) {
108 super("", ""); // XXX - we'll change it later
109 int i = l.indexOf(':');
110 if (i < 0) {
111 // should never happen
112 name = l.trim();
113 } else {
114 name = l.substring(0, i).trim();
115 }
116 line = l;
117 }
118
119 /**
120 * Constructor that takes a header name and value.
121 */
122 public InternetHeader(String n, String v) {
123 super(n, "");
124 if (v != null)
125 line = n + ": " + v;
126 else
127 line = null;
128 }
129
130 /**
131 * Return the "value" part of the header line.
132 */
133 public String getValue() {
134 int i = line.indexOf(':');
135 if (i < 0)
136 return line;
137 // skip whitespace after ':'
138 int j;
139 for (j = i + 1; j < line.length(); j++) {
140 char c = line.charAt(j);
141 if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
142 break;
143 }
144 return line.substring(j);
145 }
146 }
147
148 /*
149 * The enumeration object used to enumerate an
150 * InternetHeaders object. Can return
151 * either a String or a Header object.
152 */
153 static class matchEnum implements Enumeration {
154 private Iterator e; // enum object of headers List
155 // XXX - is this overkill? should we step through in index
156 // order instead?
157 private String names[]; // names to match, or not
158 private boolean match; // return matching headers?
159 private boolean want_line; // return header lines?
160 private InternetHeader next_header; // the next header to be returned
161
162 /*
163 * Constructor. Initialize the enumeration for the entire
164 * List of headers, the set of headers, whether to return
165 * matching or non-matching headers, and whether to return
166 * header lines or Header objects.
167 */
168 matchEnum(List v, String n[], boolean m, boolean l) {
169 e = v.iterator();
170 names = n;
171 match = m;
172 want_line = l;
173 next_header = null;
174 }
175
176 /*
177 * Any more elements in this enumeration?
178 */
179 public boolean hasMoreElements() {
180 // if necessary, prefetch the next matching header,
181 // and remember it.
182 if (next_header == null)
183 next_header = nextMatch();
184 return next_header != null;
185 }
186
187 /*
188 * Return the next element.
189 */
190 public Object nextElement() {
191 if (next_header == null)
192 next_header = nextMatch();
193
194 if (next_header == null)
195 throw new NoSuchElementException("No more headers");
196
197 InternetHeader h = next_header;
198 next_header = null;
199 if (want_line)
200 return h.line;
201 else
202 return new Header(h.getName(), h.getValue());
203 }
204
205 /*
206 * Return the next Header object according to the match
207 * criteria, or null if none left.
208 */
209 private InternetHeader nextMatch() {
210 next:
211 while (e.hasNext()) {
212 InternetHeader h = (InternetHeader)e.next();
213
214 // skip "place holder" headers
215 if (h.line == null)
216 continue;
217
218 // if no names to match against, return appropriately
219 if (names == null)
220 return match ? null : h;
221
222 // check whether this header matches any of the names
223 for (int i = 0; i < names.length; i++) {
224 if (names[i].equalsIgnoreCase(h.getName())) {
225 if (match)
226 return h;
227 else
228 // found a match, but we're
229 // looking for non-matches.
230 // try next header.
231 continue next;
232 }
233 }
234 // found no matches. if that's what we wanted, return it.
235 if (!match)
236 return h;
237 }
238 return null;
239 }
240 }
241
242
243 /**
244 * The actual list of Headers, including placeholder entries.
245 * Placeholder entries are Headers with a null value and
246 * are never seen by clients of the InternetHeaders class.
247 * Placeholder entries are used to keep track of the preferred
248 * order of headers. Headers are never actually removed from
249 * the list, they're converted into placeholder entries.
250 * New headers are added after existing headers of the same name
251 * (or before in the case of <code>Received</code> and
252 * <code>Return-Path</code> headers). If no existing header
253 * or placeholder for the header is found, new headers are
254 * added after the special placeholder with the name ":".
255 *
256 * @since JavaMail 1.4
257 */
258 protected List headers;
259
260 /**
261 * Create an empty InternetHeaders object. Placeholder entries
262 * are inserted to indicate the preferred order of headers.
263 */
264 public InternetHeaders() {
265 headers = new ArrayList(40);
266 headers.add(new InternetHeader("Return-Path", null));
267 headers.add(new InternetHeader("Received", null));
268 headers.add(new InternetHeader("Resent-Date", null));
269 headers.add(new InternetHeader("Resent-From", null));
270 headers.add(new InternetHeader("Resent-Sender", null));
271 headers.add(new InternetHeader("Resent-To", null));
272 headers.add(new InternetHeader("Resent-Cc", null));
273 headers.add(new InternetHeader("Resent-Bcc", null));
274 headers.add(new InternetHeader("Resent-Message-Id", null));
275 headers.add(new InternetHeader("Date", null));
276 headers.add(new InternetHeader("From", null));
277 headers.add(new InternetHeader("Sender", null));
278 headers.add(new InternetHeader("Reply-To", null));
279 headers.add(new InternetHeader("To", null));
280 headers.add(new InternetHeader("Cc", null));
281 headers.add(new InternetHeader("Bcc", null));
282 headers.add(new InternetHeader("Message-Id", null));
283 headers.add(new InternetHeader("In-Reply-To", null));
284 headers.add(new InternetHeader("References", null));
285 headers.add(new InternetHeader("Subject", null));
286 headers.add(new InternetHeader("Comments", null));
287 headers.add(new InternetHeader("Keywords", null));
288 headers.add(new InternetHeader("Errors-To", null));
289 headers.add(new InternetHeader("MIME-Version", null));
290 headers.add(new InternetHeader("Content-Type", null));
291 headers.add(new InternetHeader("Content-Transfer-Encoding", null));
292 headers.add(new InternetHeader("Content-MD5", null));
293 headers.add(new InternetHeader(":", null));
294 headers.add(new InternetHeader("Content-Length", null));
295 headers.add(new InternetHeader("Status", null));
296 }
297
298 /**
299 * Read and parse the given RFC822 message stream till the
300 * blank line separating the header from the body. The input
301 * stream is left positioned at the start of the body. The
302 * header lines are stored internally. <p>
303 *
304 * For efficiency, wrap a BufferedInputStream around the actual
305 * input stream and pass it as the parameter. <p>
306 *
307 * No placeholder entries are inserted; the original order of
308 * the headers is preserved.
309 *
310 * @param is RFC822 input stream
311 */
312 public InternetHeaders(InputStream is) throws MessagingException {
313 headers = new ArrayList(40);
314 load(is);
315 }
316
317 /**
318 * Read and parse the given RFC822 message stream till the
319 * blank line separating the header from the body. Store the
320 * header lines inside this InternetHeaders object. The order
321 * of header lines is preserved. <p>
322 *
323 * Note that the header lines are added into this InternetHeaders
324 * object, so any existing headers in this object will not be
325 * affected. Headers are added to the end of the existing list
326 * of headers, in order.
327 *
328 * @param is RFC822 input stream
329 */
330 public void load(InputStream is) throws MessagingException {
331 // Read header lines until a blank line. It is valid
332 // to have BodyParts with no header lines.
333 String line;
334 LineInputStream lis = new LineInputStream(is);
335 String prevline = null; // the previous header line, as a string
336 // a buffer to accumulate the header in, when we know it's needed
337 StringBuffer lineBuffer = new StringBuffer();
338
339 try {
340 //while ((line = lis.readLine()) != null) {
341 do {
342 line = lis.readLine();
343 if (line != null &&
344 (line.startsWith(" ") || line.startsWith("\t"))) {
345 // continuation of header
346 if (prevline != null) {
347 lineBuffer.append(prevline);
348 prevline = null;
349 }
350 lineBuffer.append("\r\n");
351 lineBuffer.append(line);
352 } else {
353 // new header
354 if (prevline != null)
355 addHeaderLine(prevline);
356 else if (lineBuffer.length() > 0) {
357 // store previous header first
358 addHeaderLine(lineBuffer.toString());
359 lineBuffer.setLength(0);
360 }
361 prevline = line;
362 }
363 } while (line != null && line.length() > 0);
364 } catch (IOException ioex) {
365 throw new MessagingException("Error in input stream", ioex);
366 }
367 }
368
369 /**
370 * Return all the values for the specified header. The
371 * values are String objects. Returns <code>null</code>
372 * if no headers with the specified name exist.
373 *
374 * @param name header name
375 * @return array of header values, or null if none
376 */
377 public String[] getHeader(String name) {
378 Iterator e = headers.iterator();
379 // XXX - should we just step through in index order?
380 List v = new ArrayList(); // accumulate return values
381
382 while (e.hasNext()) {
383 InternetHeader h = (InternetHeader)e.next();
384 if (name.equalsIgnoreCase(h.getName()) && h.line != null) {
385 v.add(h.getValue());
386 }
387 }
388 if (v.size() == 0)
389 return (null);
390 // convert List to an array for return
391 String r[] = new String[v.size()];
392 r = (String[])v.toArray(r);
393 return (r);
394 }
395
396 /**
397 * Get all the headers for this header name, returned as a single
398 * String, with headers separated by the delimiter. If the
399 * delimiter is <code>null</code>, only the first header is
400 * returned. Returns <code>null</code>
401 * if no headers with the specified name exist.
402 *
403 * @param name header name
404 * @param delimiter delimiter
405 * @return the value fields for all headers with
406 * this name, or null if none
407 */
408 public String getHeader(String name, String delimiter) {
409 String s[] = getHeader(name);
410
411 if (s == null)
412 return null;
413
414 if ((s.length == 1) || delimiter == null)
415 return s[0];
416
417 StringBuffer r = new StringBuffer(s[0]);
418 for (int i = 1; i < s.length; i++) {
419 r.append(delimiter);
420 r.append(s[i]);
421 }
422 return r.toString();
423 }
424
425 /**
426 * Change the first header line that matches name
427 * to have value, adding a new header if no existing header
428 * matches. Remove all matching headers but the first. <p>
429 *
430 * Note that RFC822 headers can only contain US-ASCII characters
431 *
432 * @param name header name
433 * @param value header value
434 */
435 public void setHeader(String name, String value) {
436 boolean found = false;
437
438 for (int i = 0; i < headers.size(); i++) {
439 InternetHeader h = (InternetHeader)headers.get(i);
440 if (name.equalsIgnoreCase(h.getName())) {
441 if (!found) {
442 int j;
443 if (h.line != null && (j = h.line.indexOf(':')) >= 0) {
444 h.line = h.line.substring(0, j + 1) + " " + value;
445 // preserves capitalization, spacing
446 } else {
447 h.line = name + ": " + value;
448 }
449 found = true;
450 } else {
451 headers.remove(i);
452 i--; // have to look at i again
453 }
454 }
455 }
456
457 if (!found) {
458 addHeader(name, value);
459 }
460 }
461
462 /**
463 * Add a header with the specified name and value to the header list. <p>
464 *
465 * The current implementation knows about the preferred order of most
466 * well-known headers and will insert headers in that order. In
467 * addition, it knows that <code>Received</code> headers should be
468 * inserted in reverse order (newest before oldest), and that they
469 * should appear at the beginning of the headers, preceeded only by
470 * a possible <code>Return-Path</code> header. <p>
471 *
472 * Note that RFC822 headers can only contain US-ASCII characters.
473 *
474 * @param name header name
475 * @param value header value
476 */
477 public void addHeader(String name, String value) {
478 int pos = headers.size();
479 boolean addReverse =
480 name.equalsIgnoreCase("Received") ||
481 name.equalsIgnoreCase("Return-Path");
482 if (addReverse)
483 pos = 0;
484 for (int i = headers.size() - 1; i >= 0; i--) {
485 InternetHeader h = (InternetHeader)headers.get(i);
486 if (name.equalsIgnoreCase(h.getName())) {
487 if (addReverse) {
488 pos = i;
489 } else {
490 headers.add(i + 1, new InternetHeader(name, value));
491 return;
492 }
493 }
494 // marker for default place to add new headers
495 if (h.getName().equals(":"))
496 pos = i;
497 }
498 headers.add(pos, new InternetHeader(name, value));
499 }
500
501 /**
502 * Remove all header entries that match the given name
503 * @param name header name
504 */
505 public void removeHeader(String name) {
506 for (int i = 0; i < headers.size(); i++) {
507 InternetHeader h = (InternetHeader)headers.get(i);
508 if (name.equalsIgnoreCase(h.getName())) {
509 h.line = null;
510 //headers.remove(i);
511 //i--; // have to look at i again
512 }
513 }
514 }
515
516 /**
517 * Return all the headers as an Enumeration of
518 * {@link javax.mail.Header} objects.
519 *
520 * @return Header objects
521 */
522 public Enumeration getAllHeaders() {
523 return (new matchEnum(headers, null, false, false));
524 }
525
526 /**
527 * Return all matching {@link javax.mail.Header} objects.
528 *
529 * @return matching Header objects
530 */
531 public Enumeration getMatchingHeaders(String[] names) {
532 return (new matchEnum(headers, names, true, false));
533 }
534
535 /**
536 * Return all non-matching {@link javax.mail.Header} objects.
537 *
538 * @return non-matching Header objects
539 */
540 public Enumeration getNonMatchingHeaders(String[] names) {
541 return (new matchEnum(headers, names, false, false));
542 }
543
544 /**
545 * Add an RFC822 header line to the header store.
546 * If the line starts with a space or tab (a continuation line),
547 * add it to the last header line in the list. Otherwise,
548 * append the new header line to the list. <p>
549 *
550 * Note that RFC822 headers can only contain US-ASCII characters
551 *
552 * @param line raw RFC822 header line
553 */
554 public void addHeaderLine(String line) {
555 try {
556 char c = line.charAt(0);
557 if (c == ' ' || c == '\t') {
558 InternetHeader h =
559 (InternetHeader)headers.get(headers.size() - 1);
560 h.line += "\r\n" + line;
561 } else
562 headers.add(new InternetHeader(line));
563 } catch (StringIndexOutOfBoundsException e) {
564 // line is empty, ignore it
565 return;
566 } catch (NoSuchElementException e) {
567 // XXX - vector is empty?
568 }
569 }
570
571 /**
572 * Return all the header lines as an Enumeration of Strings.
573 */
574 public Enumeration getAllHeaderLines() {
575 return (getNonMatchingHeaderLines(null));
576 }
577
578 /**
579 * Return all matching header lines as an Enumeration of Strings.
580 */
581 public Enumeration getMatchingHeaderLines(String[] names) {
582 return (new matchEnum(headers, names, true, true));
583 }
584
585 /**
586 * Return all non-matching header lines
587 */
588 public Enumeration getNonMatchingHeaderLines(String[] names) {
589 return (new matchEnum(headers, names, false, true));
590 }
591 }