001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: /*
038: * @(#)InternetHeaders.java 1.22 07/05/04
039: */
040:
041: package javax.mail.internet;
042:
043: import java.io.*;
044: import java.util.*;
045: import javax.mail.*;
046: import com.sun.mail.util.LineInputStream;
047:
048: /**
049: * InternetHeaders is a utility class that manages RFC822 style
050: * headers. Given an RFC822 format message stream, it reads lines
051: * until the blank line that indicates end of header. The input stream
052: * is positioned at the start of the body. The lines are stored
053: * within the object and can be extracted as either Strings or
054: * {@link javax.mail.Header} objects. <p>
055: *
056: * This class is mostly intended for service providers. MimeMessage
057: * and MimeBody use this class for holding their headers. <p>
058: *
059: * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
060: *
061: * RFC822 and MIME header fields <strong>must</strong> contain only
062: * US-ASCII characters. If a header contains non US-ASCII characters,
063: * it must be encoded as per the rules in RFC 2047. The MimeUtility
064: * class provided in this package can be used to to achieve this.
065: * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
066: * <code>addHeaderLine</code> methods are responsible for enforcing
067: * the MIME requirements for the specified headers. In addition, these
068: * header fields must be folded (wrapped) before being sent if they
069: * exceed the line length limitation for the transport (1000 bytes for
070: * SMTP). Received headers may have been folded. The application is
071: * responsible for folding and unfolding headers as appropriate. <p>
072: *
073: * @see javax.mail.internet.MimeUtility
074: * @author John Mani
075: * @author Bill Shannon
076: */
077:
078: public class InternetHeaders {
079: /**
080: * An individual internet header. This class is only used by
081: * subclasses of InternetHeaders. <p>
082: *
083: * An InternetHeader object with a null value is used as a placeholder
084: * for headers of that name, to preserve the order of headers.
085: * A placeholder InternetHeader object with a name of ":" marks
086: * the location in the list of headers where new headers are
087: * added by default.
088: *
089: * @since JavaMail 1.4
090: */
091: protected static final class InternetHeader extends Header {
092: /*
093: * Note that the value field from the superclass
094: * isn't used in this class. We extract the value
095: * from the line field as needed. We store the line
096: * rather than just the value to ensure that we can
097: * get back the exact original line, with the original
098: * whitespace, etc.
099: */
100: String line; // the entire RFC822 header "line",
101:
102: // or null if placeholder
103:
104: /**
105: * Constructor that takes a line and splits out
106: * the header name.
107: */
108: public InternetHeader(String l) {
109: super ("", ""); // XXX - we'll change it later
110: int i = l.indexOf(':');
111: if (i < 0) {
112: // should never happen
113: name = l.trim();
114: } else {
115: name = l.substring(0, i).trim();
116: }
117: line = l;
118: }
119:
120: /**
121: * Constructor that takes a header name and value.
122: */
123: public InternetHeader(String n, String v) {
124: super (n, "");
125: if (v != null)
126: line = n + ": " + v;
127: else
128: line = null;
129: }
130:
131: /**
132: * Return the "value" part of the header line.
133: */
134: public String getValue() {
135: int i = line.indexOf(':');
136: if (i < 0)
137: return line;
138: // skip whitespace after ':'
139: int j;
140: for (j = i + 1; j < line.length(); j++) {
141: char c = line.charAt(j);
142: if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
143: break;
144: }
145: return line.substring(j);
146: }
147: }
148:
149: /*
150: * The enumeration object used to enumerate an
151: * InternetHeaders object. Can return
152: * either a String or a Header object.
153: */
154: static class matchEnum implements Enumeration {
155: private Iterator e; // enum object of headers List
156: // XXX - is this overkill? should we step through in index
157: // order instead?
158: private String names[]; // names to match, or not
159: private boolean match; // return matching headers?
160: private boolean want_line; // return header lines?
161: private InternetHeader next_header; // the next header to be returned
162:
163: /*
164: * Constructor. Initialize the enumeration for the entire
165: * List of headers, the set of headers, whether to return
166: * matching or non-matching headers, and whether to return
167: * header lines or Header objects.
168: */
169: matchEnum(List v, String n[], boolean m, boolean l) {
170: e = v.iterator();
171: names = n;
172: match = m;
173: want_line = l;
174: next_header = null;
175: }
176:
177: /*
178: * Any more elements in this enumeration?
179: */
180: public boolean hasMoreElements() {
181: // if necessary, prefetch the next matching header,
182: // and remember it.
183: if (next_header == null)
184: next_header = nextMatch();
185: return next_header != null;
186: }
187:
188: /*
189: * Return the next element.
190: */
191: public Object nextElement() {
192: if (next_header == null)
193: next_header = nextMatch();
194:
195: if (next_header == null)
196: throw new NoSuchElementException("No more headers");
197:
198: InternetHeader h = next_header;
199: next_header = null;
200: if (want_line)
201: return h.line;
202: else
203: return new Header(h.getName(), h.getValue());
204: }
205:
206: /*
207: * Return the next Header object according to the match
208: * criteria, or null if none left.
209: */
210: private InternetHeader nextMatch() {
211: next: 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: * The actual list of Headers, including placeholder entries.
244: * Placeholder entries are Headers with a null value and
245: * are never seen by clients of the InternetHeaders class.
246: * Placeholder entries are used to keep track of the preferred
247: * order of headers. Headers are never actually removed from
248: * the list, they're converted into placeholder entries.
249: * New headers are added after existing headers of the same name
250: * (or before in the case of <code>Received</code> and
251: * <code>Return-Path</code> headers). If no existing header
252: * or placeholder for the header is found, new headers are
253: * added after the special placeholder with the name ":".
254: *
255: * @since JavaMail 1.4
256: */
257: protected List headers;
258:
259: /**
260: * Create an empty InternetHeaders object. Placeholder entries
261: * are inserted to indicate the preferred order of headers.
262: */
263: public InternetHeaders() {
264: headers = new ArrayList(40);
265: headers.add(new InternetHeader("Return-Path", null));
266: headers.add(new InternetHeader("Received", null));
267: headers.add(new InternetHeader("Resent-Date", null));
268: headers.add(new InternetHeader("Resent-From", null));
269: headers.add(new InternetHeader("Resent-Sender", null));
270: headers.add(new InternetHeader("Resent-To", null));
271: headers.add(new InternetHeader("Resent-Cc", null));
272: headers.add(new InternetHeader("Resent-Bcc", null));
273: headers.add(new InternetHeader("Resent-Message-Id", null));
274: headers.add(new InternetHeader("Date", null));
275: headers.add(new InternetHeader("From", null));
276: headers.add(new InternetHeader("Sender", null));
277: headers.add(new InternetHeader("Reply-To", null));
278: headers.add(new InternetHeader("To", null));
279: headers.add(new InternetHeader("Cc", null));
280: headers.add(new InternetHeader("Bcc", null));
281: headers.add(new InternetHeader("Message-Id", null));
282: headers.add(new InternetHeader("In-Reply-To", null));
283: headers.add(new InternetHeader("References", null));
284: headers.add(new InternetHeader("Subject", null));
285: headers.add(new InternetHeader("Comments", null));
286: headers.add(new InternetHeader("Keywords", null));
287: headers.add(new InternetHeader("Errors-To", null));
288: headers.add(new InternetHeader("MIME-Version", null));
289: headers.add(new InternetHeader("Content-Type", null));
290: headers.add(new InternetHeader("Content-Transfer-Encoding",
291: 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
345: .startsWith("\t"))) {
346: // continuation of header
347: if (prevline != null) {
348: lineBuffer.append(prevline);
349: prevline = null;
350: }
351: lineBuffer.append("\r\n");
352: lineBuffer.append(line);
353: } else {
354: // new header
355: if (prevline != null)
356: addHeaderLine(prevline);
357: else if (lineBuffer.length() > 0) {
358: // store previous header first
359: addHeaderLine(lineBuffer.toString());
360: lineBuffer.setLength(0);
361: }
362: prevline = line;
363: }
364: } while (line != null && line.length() > 0);
365: } catch (IOException ioex) {
366: throw new MessagingException("Error in input stream", ioex);
367: }
368: }
369:
370: /**
371: * Return all the values for the specified header. The
372: * values are String objects. Returns <code>null</code>
373: * if no headers with the specified name exist.
374: *
375: * @param name header name
376: * @return array of header values, or null if none
377: */
378: public String[] getHeader(String name) {
379: Iterator e = headers.iterator();
380: // XXX - should we just step through in index order?
381: List v = new ArrayList(); // accumulate return values
382:
383: while (e.hasNext()) {
384: InternetHeader h = (InternetHeader) e.next();
385: if (name.equalsIgnoreCase(h.getName()) && h.line != null) {
386: v.add(h.getValue());
387: }
388: }
389: if (v.size() == 0)
390: return (null);
391: // convert List to an array for return
392: String r[] = new String[v.size()];
393: r = (String[]) v.toArray(r);
394: return (r);
395: }
396:
397: /**
398: * Get all the headers for this header name, returned as a single
399: * String, with headers separated by the delimiter. If the
400: * delimiter is <code>null</code>, only the first header is
401: * returned. Returns <code>null</code>
402: * if no headers with the specified name exist.
403: *
404: * @param name header name
405: * @param delimiter delimiter
406: * @return the value fields for all headers with
407: * this name, or null if none
408: */
409: public String getHeader(String name, String delimiter) {
410: String s[] = getHeader(name);
411:
412: if (s == null)
413: return null;
414:
415: if ((s.length == 1) || delimiter == null)
416: return s[0];
417:
418: StringBuffer r = new StringBuffer(s[0]);
419: for (int i = 1; i < s.length; i++) {
420: r.append(delimiter);
421: r.append(s[i]);
422: }
423: return r.toString();
424: }
425:
426: /**
427: * Change the first header line that matches name
428: * to have value, adding a new header if no existing header
429: * matches. Remove all matching headers but the first. <p>
430: *
431: * Note that RFC822 headers can only contain US-ASCII characters
432: *
433: * @param name header name
434: * @param value header value
435: */
436: public void setHeader(String name, String value) {
437: boolean found = false;
438:
439: for (int i = 0; i < headers.size(); i++) {
440: InternetHeader h = (InternetHeader) headers.get(i);
441: if (name.equalsIgnoreCase(h.getName())) {
442: if (!found) {
443: int j;
444: if (h.line != null
445: && (j = h.line.indexOf(':')) >= 0) {
446: h.line = h.line.substring(0, j + 1) + " "
447: + value;
448: // preserves capitalization, spacing
449: } else {
450: h.line = name + ": " + value;
451: }
452: found = true;
453: } else {
454: headers.remove(i);
455: i--; // have to look at i again
456: }
457: }
458: }
459:
460: if (!found) {
461: addHeader(name, value);
462: }
463: }
464:
465: /**
466: * Add a header with the specified name and value to the header list. <p>
467: *
468: * The current implementation knows about the preferred order of most
469: * well-known headers and will insert headers in that order. In
470: * addition, it knows that <code>Received</code> headers should be
471: * inserted in reverse order (newest before oldest), and that they
472: * should appear at the beginning of the headers, preceeded only by
473: * a possible <code>Return-Path</code> header. <p>
474: *
475: * Note that RFC822 headers can only contain US-ASCII characters.
476: *
477: * @param name header name
478: * @param value header value
479: */
480: public void addHeader(String name, String value) {
481: int pos = headers.size();
482: boolean addReverse = name.equalsIgnoreCase("Received")
483: || name.equalsIgnoreCase("Return-Path");
484: if (addReverse)
485: pos = 0;
486: for (int i = headers.size() - 1; i >= 0; i--) {
487: InternetHeader h = (InternetHeader) headers.get(i);
488: if (name.equalsIgnoreCase(h.getName())) {
489: if (addReverse) {
490: pos = i;
491: } else {
492: headers.add(i + 1, new InternetHeader(name, value));
493: return;
494: }
495: }
496: // marker for default place to add new headers
497: if (h.getName().equals(":"))
498: pos = i;
499: }
500: headers.add(pos, new InternetHeader(name, value));
501: }
502:
503: /**
504: * Remove all header entries that match the given name
505: * @param name header name
506: */
507: public void removeHeader(String name) {
508: for (int i = 0; i < headers.size(); i++) {
509: InternetHeader h = (InternetHeader) headers.get(i);
510: if (name.equalsIgnoreCase(h.getName())) {
511: h.line = null;
512: //headers.remove(i);
513: //i--; // have to look at i again
514: }
515: }
516: }
517:
518: /**
519: * Return all the headers as an Enumeration of
520: * {@link javax.mail.Header} objects.
521: *
522: * @return Header objects
523: */
524: public Enumeration getAllHeaders() {
525: return (new matchEnum(headers, null, false, false));
526: }
527:
528: /**
529: * Return all matching {@link javax.mail.Header} objects.
530: *
531: * @return matching Header objects
532: */
533: public Enumeration getMatchingHeaders(String[] names) {
534: return (new matchEnum(headers, names, true, false));
535: }
536:
537: /**
538: * Return all non-matching {@link javax.mail.Header} objects.
539: *
540: * @return non-matching Header objects
541: */
542: public Enumeration getNonMatchingHeaders(String[] names) {
543: return (new matchEnum(headers, names, false, false));
544: }
545:
546: /**
547: * Add an RFC822 header line to the header store.
548: * If the line starts with a space or tab (a continuation line),
549: * add it to the last header line in the list. Otherwise,
550: * append the new header line to the list. <p>
551: *
552: * Note that RFC822 headers can only contain US-ASCII characters
553: *
554: * @param line raw RFC822 header line
555: */
556: public void addHeaderLine(String line) {
557: try {
558: char c = line.charAt(0);
559: if (c == ' ' || c == '\t') {
560: InternetHeader h = (InternetHeader) headers.get(headers
561: .size() - 1);
562: h.line += "\r\n" + line;
563: } else
564: headers.add(new InternetHeader(line));
565: } catch (StringIndexOutOfBoundsException e) {
566: // line is empty, ignore it
567: return;
568: } catch (NoSuchElementException e) {
569: // XXX - vector is empty?
570: }
571: }
572:
573: /**
574: * Return all the header lines as an Enumeration of Strings.
575: */
576: public Enumeration getAllHeaderLines() {
577: return (getNonMatchingHeaderLines(null));
578: }
579:
580: /**
581: * Return all matching header lines as an Enumeration of Strings.
582: */
583: public Enumeration getMatchingHeaderLines(String[] names) {
584: return (new matchEnum(headers, names, true, true));
585: }
586:
587: /**
588: * Return all non-matching header lines
589: */
590: public Enumeration getNonMatchingHeaderLines(String[] names) {
591: return (new matchEnum(headers, names, false, true));
592: }
593: }
|