1 package org.bouncycastle.mime;
2 
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.Iterator;
9 import java.util.LinkedHashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.TreeMap;
13 
14 import org.bouncycastle.util.Iterable;
15 import org.bouncycastle.util.Strings;
16 
17 public class Headers
18     implements Iterable<String>
19 {
20     private final Map<String, List> headers = new TreeMap<String, List>(String.CASE_INSENSITIVE_ORDER);
21     private final List<String> headersAsPresented;
22     private final String contentTransferEncoding;
23 
24     private String boundary;
25     private boolean multipart;
26     private String contentType;
27     private Map<String, String> contentTypeParameters;
28 
parseHeaders(InputStream src)29     private static List<String> parseHeaders(InputStream src)
30         throws IOException
31     {
32         String s;
33         List<String> headerLines = new ArrayList<String>();
34         LineReader   rd = new LineReader(src);
35 
36         while ((s = rd.readLine()) != null)
37         {
38             if (s.length() == 0)
39             {
40                 break;
41             }
42             headerLines.add(s);
43         }
44 
45         return headerLines;
46     }
47 
Headers(InputStream source, String defaultContentTransferEncoding)48     public Headers(InputStream source, String defaultContentTransferEncoding)
49         throws IOException
50     {
51         this(parseHeaders(source), defaultContentTransferEncoding);
52     }
53 
Headers(List<String> headerLines, String defaultContentTransferEncoding)54     public Headers(List<String> headerLines, String defaultContentTransferEncoding)
55     {
56         this.headersAsPresented = headerLines;
57 
58         String header = "";
59         for (Iterator it = headerLines.iterator(); it.hasNext();)
60         {
61             String line = (String)it.next();
62             if (line.startsWith(" ") || line.startsWith("\t"))
63             {
64                 header = header + line.trim();
65             }
66             else
67             {
68                 if (header.length() != 0)
69                 {
70                     this.put(header.substring(0, header.indexOf(':')).trim(), header.substring(header.indexOf(':') + 1).trim());
71                 }
72                 header = line;
73             }
74         }
75 
76         // pick up last header line
77         if (header.trim().length() != 0)
78         {
79             this.put(header.substring(0, header.indexOf(':')).trim(), header.substring(header.indexOf(':') + 1).trim());
80         }
81 
82         String contentTypeHeader = (this.getValues("Content-Type") == null) ? "text/plain" : this.getValues("Content-Type")[0];
83 
84         int parameterIndex = contentTypeHeader.indexOf(';');
85         if (parameterIndex < 0)
86         {
87             contentType = contentTypeHeader;
88             contentTypeParameters = Collections.EMPTY_MAP;
89         }
90         else
91         {
92             contentType = contentTypeHeader.substring(0, parameterIndex);
93             contentTypeParameters = createContentTypeParameters(contentTypeHeader.substring(parameterIndex + 1).trim());
94         }
95 
96         contentTransferEncoding = this.getValues("Content-Transfer-Encoding") == null ? defaultContentTransferEncoding : this.getValues("Content-Transfer-Encoding")[0];
97 
98         if (contentType.indexOf("multipart") >= 0)
99         {
100             multipart = true;
101             String bound = (String)contentTypeParameters.get("boundary");
102             boundary = bound.substring(1, bound.length() - 1); // quoted-string
103         }
104         else
105         {
106             boundary = null;
107             multipart = false;
108         }
109     }
110 
111     /**
112      * Return the a Map of the ContentType attributes and their values.
113      *
114      * @return a Map of ContentType parameters - empty if none present.
115      */
getContentTypeAttributes()116     public Map<String, String> getContentTypeAttributes()
117     {
118         return contentTypeParameters;
119     }
120 
121     /**
122      * Return the a list of the ContentType parameters.
123      *
124      * @return a list of ContentType parameters - empty if none present.
125      */
createContentTypeParameters(String contentTypeParameters)126     private Map<String, String> createContentTypeParameters(String contentTypeParameters)
127     {
128         String[] parameterSplit = contentTypeParameters.split(";");
129         Map<String, String> rv = new LinkedHashMap<String, String>();
130 
131         for (int i = 0; i != parameterSplit.length; i++)
132         {
133             String parameter = parameterSplit[i];
134 
135             int eqIndex = parameter.indexOf('=');
136             if (eqIndex < 0)
137             {
138                 throw new IllegalArgumentException("malformed Content-Type header");
139             }
140 
141             rv.put(parameter.substring(0, eqIndex).trim(), parameter.substring(eqIndex + 1).trim());
142         }
143 
144         return Collections.unmodifiableMap(rv);
145     }
146 
isMultipart()147     public boolean isMultipart()
148     {
149         return multipart;
150     }
151 
getBoundary()152     public String getBoundary()
153     {
154         return boundary;
155     }
156 
getContentType()157     public String getContentType()
158     {
159         return contentType;
160     }
161 
getContentTransferEncoding()162     public String getContentTransferEncoding()
163     {
164         return contentTransferEncoding;
165     }
166 
put(String field, String value)167     private void put(String field, String value)
168     {
169         synchronized (this)
170         {
171             KV kv = new KV(field, value);
172             List<KV> list = (List<KV>)headers.get(field);
173             if (list == null)
174             {
175                 list = new ArrayList<KV>();
176                 headers.put(field, list);
177             }
178             list.add(kv);
179         }
180     }
181 
getNames()182     public Iterator<String> getNames()
183     {
184         return headers.keySet().iterator();
185     }
186 
getValues(String header)187     public String[] getValues(String header)
188     {
189 
190         synchronized (this)
191         {
192             List<KV> kvList = (List<KV>)headers.get(header);
193             if (kvList == null)
194             {
195                 return null;
196             }
197             String[] out = new String[kvList.size()];
198 
199             for (int t = 0; t < kvList.size(); t++)
200             {
201                 out[t] = ((KV)kvList.get(t)).value;
202             }
203 
204             return out;
205         }
206     }
207 
isEmpty()208     public boolean isEmpty()
209     {
210         synchronized (this)
211         {
212             return headers.isEmpty();
213         }
214     }
215 
containsKey(String s)216     public boolean containsKey(String s)
217     {
218         return headers.containsKey(s);
219     }
220 
iterator()221     public Iterator<String> iterator()
222     {
223         return headers.keySet().iterator();
224     }
225 
dumpHeaders(OutputStream outputStream)226     public void dumpHeaders(OutputStream outputStream)
227         throws IOException
228     {
229         for (Iterator it = headersAsPresented.iterator(); it.hasNext();)
230         {
231             outputStream.write(Strings.toUTF8ByteArray(it.next().toString()));
232             outputStream.write('\r');
233             outputStream.write('\n');
234         }
235     }
236 
237     private class KV
238     {
239         public final String key;
240         public final String value;
241 
KV(String key, String value)242         public KV(String key, String value)
243         {
244             this.key = key;
245             this.value = value;
246         }
247 
KV(KV kv)248         public KV(KV kv)
249         {
250             this.key = kv.key;
251             this.value = kv.value;
252         }
253     }
254 }
255