1 /*
   2  * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.org.jvnet.mimepull;
  27 
  28 import java.io.IOException;
  29 import java.util.NoSuchElementException;
  30 import java.util.List;
  31 
  32 /**
  33  * InternetHeaders is a utility class that manages RFC822 style
  34  * headers. Given an RFC822 format message stream, it reads lines
  35  * until the blank line that indicates end of header. The input stream
  36  * is positioned at the start of the body. The lines are stored
  37  * within the object and can be extracted as either Strings or
  38  * {@link Header} objects. <p>
  39  * <p/>
  40  * This class is mostly intended for service providers. MimeMessage
  41  * and MimeBody use this class for holding their headers. <p>
  42  * <p/>
  43  * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
  44  * <p/>
  45  * RFC822 and MIME header fields <strong>must</strong> contain only
  46  * US-ASCII characters. If a header contains non US-ASCII characters,
  47  * it must be encoded as per the rules in RFC 2047. The MimeUtility
  48  * class provided in this package can be used to to achieve this.
  49  * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
  50  * <code>addHeaderLine</code> methods are responsible for enforcing
  51  * the MIME requirements for the specified headers.  In addition, these
  52  * header fields must be folded (wrapped) before being sent if they
  53  * exceed the line length limitation for the transport (1000 bytes for
  54  * SMTP).  Received headers may have been folded.  The application is
  55  * responsible for folding and unfolding headers as appropriate. <p>
  56  *
  57  * @author John Mani
  58  * @author Bill Shannon
  59  */
  60 final class InternetHeaders {
  61 
  62     private final FinalArrayList<hdr> headers = new FinalArrayList<hdr>();
  63 
  64     /**
  65      * Read and parse the given RFC822 message stream till the
  66      * blank line separating the header from the body. Store the
  67      * header lines inside this InternetHeaders object. <p>
  68      * <p/>
  69      * Note that the header lines are added into this InternetHeaders
  70      * object, so any existing headers in this object will not be
  71      * affected.
  72      *
  73      * @param   lis RFC822 input stream
  74      */
  75     InternetHeaders(MIMEParser.LineInputStream lis) {
  76         // Read header lines until a blank line. It is valid
  77         // to have BodyParts with no header lines.
  78         String line;
  79         String prevline = null; // the previous header line, as a string
  80         // a buffer to accumulate the header in, when we know it's needed
  81         StringBuffer lineBuffer = new StringBuffer();
  82 
  83         try {
  84             //while ((line = lis.readLine()) != null) {
  85             do {
  86                 line = lis.readLine();
  87                 if (line != null &&
  88                         (line.startsWith(" ") || line.startsWith("\t"))) {
  89                     // continuation of header
  90                     if (prevline != null) {
  91                         lineBuffer.append(prevline);
  92                         prevline = null;
  93                     }
  94                     lineBuffer.append("\r\n");
  95                     lineBuffer.append(line);
  96                 } else {
  97                     // new header
  98                     if (prevline != null)
  99                         addHeaderLine(prevline);
 100                     else if (lineBuffer.length() > 0) {
 101                         // store previous header first
 102                         addHeaderLine(lineBuffer.toString());
 103                         lineBuffer.setLength(0);
 104                     }
 105                     prevline = line;
 106                 }
 107             } while (line != null && line.length() > 0);
 108         } catch (IOException ioex) {
 109             throw new MIMEParsingException("Error in input stream", ioex);
 110         }
 111     }
 112 
 113     /**
 114      * Return all the values for the specified header. The
 115      * values are String objects.  Returns <code>null</code>
 116      * if no headers with the specified name exist.
 117      *
 118      * @param   name header name
 119      * @return          array of header values, or null if none
 120      */
 121     List<String> getHeader(String name) {
 122         // XXX - should we just step through in index order?
 123         FinalArrayList<String> v = new FinalArrayList<String>(); // accumulate return values
 124 
 125         int len = headers.size();
 126         for( int i=0; i<len; i++ ) {
 127             hdr h = (hdr) headers.get(i);
 128             if (name.equalsIgnoreCase(h.name)) {
 129                 v.add(h.getValue());
 130             }
 131         }
 132         return (v.size() == 0) ? null : v;
 133     }
 134 
 135     /**
 136      * Return all the headers as an Enumeration of
 137      * {@link Header} objects.
 138      *
 139      * @return  Header objects
 140      */
 141     FinalArrayList<? extends Header> getAllHeaders() {
 142         return headers; // conceptually it should be read-only, but for performance reason I'm not wrapping it here
 143     }
 144 
 145     /**
 146      * Add an RFC822 header line to the header store.
 147      * If the line starts with a space or tab (a continuation line),
 148      * add it to the last header line in the list. <p>
 149      * <p/>
 150      * Note that RFC822 headers can only contain US-ASCII characters
 151      *
 152      * @param   line    raw RFC822 header line
 153      */
 154     void addHeaderLine(String line) {
 155         try {
 156             char c = line.charAt(0);
 157             if (c == ' ' || c == '\t') {
 158                 hdr h = (hdr) headers.get(headers.size() - 1);
 159                 h.line += "\r\n" + line;
 160             } else
 161                 headers.add(new hdr(line));
 162         } catch (StringIndexOutOfBoundsException e) {
 163             // line is empty, ignore it
 164             return;
 165         } catch (NoSuchElementException e) {
 166             // XXX - vector is empty?
 167         }
 168     }
 169 
 170 }
 171 
 172 /*
 173  * A private utility class to represent an individual header.
 174  */
 175 
 176 class hdr implements Header {
 177 
 178     String name;    // the canonicalized (trimmed) name of this header
 179     // XXX - should name be stored in lower case?
 180     String line;    // the entire RFC822 header "line"
 181 
 182     /*
 183      * Constructor that takes a line and splits out
 184      * the header name.
 185      */
 186     hdr(String l) {
 187         int i = l.indexOf(':');
 188         if (i < 0) {
 189             // should never happen
 190             name = l.trim();
 191         } else {
 192             name = l.substring(0, i).trim();
 193         }
 194         line = l;
 195     }
 196 
 197     /*
 198      * Constructor that takes a header name and value.
 199      */
 200     hdr(String n, String v) {
 201         name = n;
 202         line = n + ": " + v;
 203     }
 204 
 205     /*
 206      * Return the "name" part of the header line.
 207      */
 208     public String getName() {
 209         return name;
 210     }
 211 
 212     /*
 213      * Return the "value" part of the header line.
 214      */
 215     public String getValue() {
 216         int i = line.indexOf(':');
 217         if (i < 0)
 218             return line;
 219 
 220         int j;
 221         if (name.equalsIgnoreCase("Content-Description")) {
 222             // Content-Description should retain the folded whitespace after header unfolding -
 223             // rf. RFC2822 section 2.2.3, rf. RFC2822 section 3.2.3
 224             for (j = i + 1; j < line.length(); j++) {
 225                 char c = line.charAt(j);
 226                 if (!(/*c == ' ' ||*/c == '\t' || c == '\r' || c == '\n'))
 227                     break;
 228             }
 229         } else {
 230             // skip whitespace after ':'
 231             for (j = i + 1; j < line.length(); j++) {
 232                 char c = line.charAt(j);
 233                 if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
 234                     break;
 235             }
 236         }
 237         return line.substring(j);
 238     }
 239 }