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