001: /*
002: * The contents of this file are subject to the terms
003: * of the Common Development and Distribution License
004: * (the "License"). You may not use this file except
005: * in compliance with the License.
006: *
007: * You can obtain a copy of the license at
008: * https://jwsdp.dev.java.net/CDDLv1.0.html
009: * See the License for the specific language governing
010: * permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL
013: * HEADER in each file and include the License file at
014: * https://jwsdp.dev.java.net/CDDLv1.0.html If applicable,
015: * add the following below this CDDL HEADER, with the
016: * fields enclosed by brackets "[]" replaced with your
017: * own identifying information: Portions Copyright [yyyy]
018: * [name of copyright owner]
019: */
020: /*
021: * @(#)InternetHeaders.java 1.16 02/08/08
022: */
023:
024: /*
025: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
026: *
027: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
028: *
029: * The contents of this file are subject to the terms of either the GNU
030: * General Public License Version 2 only ("GPL") or the Common Development
031: * and Distribution License("CDDL") (collectively, the "License"). You
032: * may not use this file except in compliance with the License. You can obtain
033: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
034: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
035: * language governing permissions and limitations under the License.
036: *
037: * When distributing the software, include this License Header Notice in each
038: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
039: * Sun designates this particular file as subject to the "Classpath" exception
040: * as provided by Sun in the GPL Version 2 section of the License file that
041: * accompanied this code. If applicable, add the following below the License
042: * Header, with the fields enclosed by brackets [] replaced by your own
043: * identifying information: "Portions Copyrighted [year]
044: * [name of copyright owner]"
045: *
046: * Contributor(s):
047: *
048: * If you wish your version of this file to be governed by only the CDDL or
049: * only the GPL Version 2, indicate your decision by adding "[Contributor]
050: * elects to include this software in this distribution under the [CDDL or GPL
051: * Version 2] license." If you don't indicate a single choice of license, a
052: * recipient has the option to distribute your version of this file under
053: * either the CDDL, the GPL Version 2 or to extend the choice of license to
054: * its licensees as provided above. However, if you add GPL Version 2 code
055: * and therefore, elected the GPL Version 2 license, then the option applies
056: * only if the new code is made subject to such option by the copyright
057: * holder.
058: */
059:
060: package com.sun.xml.messaging.saaj.packaging.mime.internet;
061:
062: import com.sun.xml.messaging.saaj.packaging.mime.Header;
063: import com.sun.xml.messaging.saaj.packaging.mime.MessagingException;
064: import com.sun.xml.messaging.saaj.packaging.mime.util.LineInputStream;
065: import com.sun.xml.messaging.saaj.util.FinalArrayList;
066:
067: import java.io.IOException;
068: import java.io.InputStream;
069: import java.util.AbstractList;
070: import java.util.List;
071: import java.util.NoSuchElementException;
072:
073: /**
074: * InternetHeaders is a utility class that manages RFC822 style
075: * headers. Given an RFC822 format message stream, it reads lines
076: * until the blank line that indicates end of header. The input stream
077: * is positioned at the start of the body. The lines are stored
078: * within the object and can be extracted as either Strings or
079: * {@link Header} objects. <p>
080: * <p/>
081: * This class is mostly intended for service providers. MimeMessage
082: * and MimeBody use this class for holding their headers. <p>
083: * <p/>
084: * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
085: * <p/>
086: * RFC822 and MIME header fields <strong>must</strong> contain only
087: * US-ASCII characters. If a header contains non US-ASCII characters,
088: * it must be encoded as per the rules in RFC 2047. The MimeUtility
089: * class provided in this package can be used to to achieve this.
090: * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
091: * <code>addHeaderLine</code> methods are responsible for enforcing
092: * the MIME requirements for the specified headers. In addition, these
093: * header fields must be folded (wrapped) before being sent if they
094: * exceed the line length limitation for the transport (1000 bytes for
095: * SMTP). Received headers may have been folded. The application is
096: * responsible for folding and unfolding headers as appropriate. <p>
097: *
098: * @author John Mani
099: * @author Bill Shannon
100: * @see MimeUtility
101: */
102: public final class InternetHeaders {
103:
104: private final FinalArrayList headers = new FinalArrayList();
105:
106: /**
107: * Lazily cerated view of header lines (Strings).
108: */
109: private List headerValueView;
110:
111: /**
112: * Create an empty InternetHeaders object.
113: */
114: public InternetHeaders() {
115: }
116:
117: /**
118: * Read and parse the given RFC822 message stream till the
119: * blank line separating the header from the body. The input
120: * stream is left positioned at the start of the body. The
121: * header lines are stored internally. <p>
122: * <p/>
123: * For efficiency, wrap a BufferedInputStream around the actual
124: * input stream and pass it as the parameter.
125: *
126: * @param is RFC822 input stream
127: */
128: public InternetHeaders(InputStream is) throws MessagingException {
129: load(is);
130: }
131:
132: /**
133: * Read and parse the given RFC822 message stream till the
134: * blank line separating the header from the body. Store the
135: * header lines inside this InternetHeaders object. <p>
136: * <p/>
137: * Note that the header lines are added into this InternetHeaders
138: * object, so any existing headers in this object will not be
139: * affected.
140: *
141: * @param is RFC822 input stream
142: */
143: public void load(InputStream is) throws MessagingException {
144: // Read header lines until a blank line. It is valid
145: // to have BodyParts with no header lines.
146: String line;
147: LineInputStream lis = new LineInputStream(is);
148: String prevline = null; // the previous header line, as a string
149: // a buffer to accumulate the header in, when we know it's needed
150: StringBuffer lineBuffer = new StringBuffer();
151:
152: try {
153: //while ((line = lis.readLine()) != null) {
154: do {
155: line = lis.readLine();
156: if (line != null
157: && (line.startsWith(" ") || line
158: .startsWith("\t"))) {
159: // continuation of header
160: if (prevline != null) {
161: lineBuffer.append(prevline);
162: prevline = null;
163: }
164: lineBuffer.append("\r\n");
165: lineBuffer.append(line);
166: } else {
167: // new header
168: if (prevline != null)
169: addHeaderLine(prevline);
170: else if (lineBuffer.length() > 0) {
171: // store previous header first
172: addHeaderLine(lineBuffer.toString());
173: lineBuffer.setLength(0);
174: }
175: prevline = line;
176: }
177: } while (line != null && line.length() > 0);
178: } catch (IOException ioex) {
179: throw new MessagingException("Error in input stream", ioex);
180: }
181: }
182:
183: /**
184: * Return all the values for the specified header. The
185: * values are String objects. Returns <code>null</code>
186: * if no headers with the specified name exist.
187: *
188: * @param name header name
189: * @return array of header values, or null if none
190: */
191: public String[] getHeader(String name) {
192: // XXX - should we just step through in index order?
193: FinalArrayList v = new FinalArrayList(); // accumulate return values
194:
195: int len = headers.size();
196: for (int i = 0; i < len; i++) {
197: hdr h = (hdr) headers.get(i);
198: if (name.equalsIgnoreCase(h.name)) {
199: v.add(h.getValue());
200: }
201: }
202: if (v.size() == 0)
203: return (null);
204: // convert Vector to an array for return
205: return (String[]) v.toArray(new String[v.size()]);
206: }
207:
208: /**
209: * Get all the headers for this header name, returned as a single
210: * String, with headers separated by the delimiter. If the
211: * delimiter is <code>null</code>, only the first header is
212: * returned. Returns <code>null</code>
213: * if no headers with the specified name exist.
214: *
215: * @param delimiter delimiter
216: * @return the value fields for all headers with
217: * this name, or null if none
218: * @param name header name
219: */
220: public String getHeader(String name, String delimiter) {
221: String[] s = getHeader(name);
222:
223: if (s == null)
224: return null;
225:
226: if ((s.length == 1) || delimiter == null)
227: return s[0];
228:
229: StringBuffer r = new StringBuffer(s[0]);
230: for (int i = 1; i < s.length; i++) {
231: r.append(delimiter);
232: r.append(s[i]);
233: }
234: return r.toString();
235: }
236:
237: /**
238: * Change the first header line that matches name
239: * to have value, adding a new header if no existing header
240: * matches. Remove all matching headers but the first. <p>
241: * <p/>
242: * Note that RFC822 headers can only contain US-ASCII characters
243: *
244: * @param name header name
245: * @param value header value
246: */
247: public void setHeader(String name, String value) {
248: boolean found = false;
249:
250: for (int i = 0; i < headers.size(); i++) {
251: hdr h = (hdr) headers.get(i);
252: if (name.equalsIgnoreCase(h.name)) {
253: if (!found) {
254: int j;
255: if (h.line != null
256: && (j = h.line.indexOf(':')) >= 0) {
257: h.line = h.line.substring(0, j + 1) + " "
258: + value;
259: } else {
260: h.line = name + ": " + value;
261: }
262: found = true;
263: } else {
264: headers.remove(i);
265: i--; // have to look at i again
266: }
267: }
268: }
269:
270: if (!found) {
271: addHeader(name, value);
272: }
273: }
274:
275: /**
276: * Add a header with the specified name and value to the header list. <p>
277: * <p/>
278: * Note that RFC822 headers can only contain US-ASCII characters.
279: *
280: * @param name header name
281: * @param value header value
282: */
283: public void addHeader(String name, String value) {
284: int pos = headers.size();
285: for (int i = headers.size() - 1; i >= 0; i--) {
286: hdr h = (hdr) headers.get(i);
287: if (name.equalsIgnoreCase(h.name)) {
288: headers.add(i + 1, new hdr(name, value));
289: return;
290: }
291: // marker for default place to add new headers
292: if (h.name.equals(":"))
293: pos = i;
294: }
295: headers.add(pos, new hdr(name, value));
296: }
297:
298: /**
299: * Remove all header entries that match the given name
300: *
301: * @param name header name
302: */
303: public void removeHeader(String name) {
304: for (int i = 0; i < headers.size(); i++) {
305: hdr h = (hdr) headers.get(i);
306: if (name.equalsIgnoreCase(h.name)) {
307: headers.remove(i);
308: i--; // have to look at i again
309: }
310: }
311: }
312:
313: /**
314: * Return all the headers as an Enumeration of
315: * {@link Header} objects.
316: *
317: * @return Header objects
318: */
319: public FinalArrayList getAllHeaders() {
320: return headers; // conceptually it should be read-only, but for performance reason I'm not wrapping it here
321: }
322:
323: /**
324: * Add an RFC822 header line to the header store.
325: * If the line starts with a space or tab (a continuation line),
326: * add it to the last header line in the list. <p>
327: * <p/>
328: * Note that RFC822 headers can only contain US-ASCII characters
329: *
330: * @param line raw RFC822 header line
331: */
332: public void addHeaderLine(String line) {
333: try {
334: char c = line.charAt(0);
335: if (c == ' ' || c == '\t') {
336: hdr h = (hdr) headers.get(headers.size() - 1);
337: h.line += "\r\n" + line;
338: } else
339: headers.add(new hdr(line));
340: } catch (StringIndexOutOfBoundsException e) {
341: // line is empty, ignore it
342: return;
343: } catch (NoSuchElementException e) {
344: // XXX - vector is empty?
345: }
346: }
347:
348: /**
349: * Return all the header lines as a collection
350: */
351: public List getAllHeaderLines() {
352: if (headerValueView == null)
353: headerValueView = new AbstractList() {
354: public Object get(int index) {
355: return ((hdr) headers.get(index)).line;
356: }
357:
358: public int size() {
359: return headers.size();
360: }
361: };
362: return headerValueView;
363: }
364: }
365:
366: /*
367: * A private utility class to represent an individual header.
368: */
369:
370: class hdr implements Header {
371: // XXX - should these be private?
372: String name; // the canonicalized (trimmed) name of this header
373: // XXX - should name be stored in lower case?
374: String line; // the entire RFC822 header "line"
375:
376: /*
377: * Constructor that takes a line and splits out
378: * the header name.
379: */
380: hdr(String l) {
381: int i = l.indexOf(':');
382: if (i < 0) {
383: // should never happen
384: name = l.trim();
385: } else {
386: name = l.substring(0, i).trim();
387: }
388: line = l;
389: }
390:
391: /*
392: * Constructor that takes a header name and value.
393: */
394: hdr(String n, String v) {
395: name = n;
396: line = n + ": " + v;
397: }
398:
399: /*
400: * Return the "name" part of the header line.
401: */
402: public String getName() {
403: return name;
404: }
405:
406: /*
407: * Return the "value" part of the header line.
408: */
409: public String getValue() {
410: int i = line.indexOf(':');
411: if (i < 0)
412: return line;
413:
414: int j;
415: if (name.equalsIgnoreCase("Content-Description")) {
416: // Content-Description should retain the folded whitespace after header unfolding -
417: // rf. RFC2822 section 2.2.3, rf. RFC2822 section 3.2.3
418: for (j = i + 1; j < line.length(); j++) {
419: char c = line.charAt(j);
420: if (!(/*c == ' ' ||*/c == '\t' || c == '\r' || c == '\n'))
421: break;
422: }
423: } else {
424: // skip whitespace after ':'
425: for (j = i + 1; j < line.length(); j++) {
426: char c = line.charAt(j);
427: if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
428: break;
429: }
430: }
431: return line.substring(j);
432: }
433: }
|