001 /** 002 * 003 * Copyright 2003-2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package javax.mail.internet; 019 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.io.OutputStream; 023 import java.util.ArrayList; 024 import java.util.Arrays; 025 import java.util.Collections; 026 import java.util.Enumeration; 027 import java.util.HashSet; 028 import java.util.Iterator; 029 import java.util.LinkedHashMap; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.Set; 033 034 import javax.mail.Address; 035 import javax.mail.Header; 036 import javax.mail.MessagingException; 037 038 /** 039 * Class that represents the RFC822 headers associated with a message. 040 * 041 * @version $Rev: 398618 $ $Date: 2006-05-01 08:18:57 -0700 (Mon, 01 May 2006) $ 042 */ 043 public class InternetHeaders { 044 // RFC822 imposes an ordering on its headers so we use a LinkedHashedMap 045 private final LinkedHashMap headers = new LinkedHashMap(); 046 047 private transient String lastHeaderName; 048 049 /** 050 * Create an empty InternetHeaders 051 */ 052 public InternetHeaders() { 053 // we need to initialize the headers in the correct order 054 // fields: dates source destination optional-field 055 // dates: 056 setHeaderList("Date", null); 057 setHeaderList("Resent-Date", null); 058 // source: trace originator resent 059 // trace: return received 060 setHeaderList("Return-path", null); 061 setHeaderList("Received", null); 062 // originator: authentic reply-to 063 setHeaderList("Sender", null); 064 setHeaderList("From", null); 065 setHeaderList("Reply-To", null); 066 // resent: resent-authentic resent-reply-to 067 setHeaderList("Resent-Sender", null); 068 setHeaderList("Resent-From", null); 069 setHeaderList("Resent-Reply-To", null); 070 // destination: 071 setHeaderList("To", null); 072 setHeaderList("Resent-To", null); 073 setHeaderList("cc", null); 074 setHeaderList("Resent-cc", null); 075 setHeaderList("bcc", null); 076 setHeaderList("Resent-bcc", null); 077 // optional-field: 078 setHeaderList("Message-ID", null); 079 setHeaderList("Resent-Message-ID", null); 080 setHeaderList("In-Reply-To", null); 081 setHeaderList("References", null); 082 setHeaderList("Keywords", null); 083 setHeaderList("Subject", null); 084 setHeaderList("Comments", null); 085 setHeaderList("Encrypted", null); 086 } 087 088 /** 089 * Create a new InternetHeaders initialized by reading headers from the 090 * stream. 091 * 092 * @param in 093 * the RFC822 input stream to load from 094 * @throws MessagingException 095 * if there is a problem pasring the stream 096 */ 097 public InternetHeaders(InputStream in) throws MessagingException { 098 load(in); 099 } 100 101 /** 102 * Read and parse the supplied stream and add all headers to the current 103 * set. 104 * 105 * @param in 106 * the RFC822 input stream to load from 107 * @throws MessagingException 108 * if there is a problem pasring the stream 109 */ 110 public void load(InputStream in) throws MessagingException { 111 try { 112 StringBuffer name = new StringBuffer(32); 113 StringBuffer value = new StringBuffer(128); 114 done: while (true) { 115 int c = in.read(); 116 char ch = (char) c; 117 if (c == -1) { 118 break; 119 } else if (c == 13) { 120 // empty line terminates header 121 in.read(); // skip LF 122 break; 123 } else if (Character.isWhitespace(ch)) { 124 // handle continuation 125 do { 126 c = in.read(); 127 if (c == -1) { 128 break done; 129 } 130 ch = (char) c; 131 } while (Character.isWhitespace(ch)); 132 } else { 133 // new header 134 if (name.length() > 0) { 135 addHeader(name.toString().trim(), value.toString().trim()); 136 } 137 name.setLength(0); 138 value.setLength(0); 139 while (true) { 140 name.append((char) c); 141 c = in.read(); 142 if (c == -1) { 143 break done; 144 } else if (c == ':') { 145 break; 146 } 147 } 148 c = in.read(); 149 if (c == -1) { 150 break done; 151 } 152 } 153 154 while (c != 13) { 155 ch = (char) c; 156 value.append(ch); 157 c = in.read(); 158 if (c == -1) { 159 break done; 160 } 161 } 162 // skip LF 163 c = in.read(); 164 if (c == -1) { 165 break; 166 } 167 } 168 if (name.length() > 0) { 169 addHeader(name.toString().trim(), value.toString().trim()); 170 } 171 } catch (IOException e) { 172 throw new MessagingException("Error loading headers", e); 173 } 174 } 175 176 /** 177 * Return all the values for the specified header. 178 * 179 * @param name 180 * the header to return 181 * @return the values for that header, or null if the header is not present 182 */ 183 public String[] getHeader(String name) { 184 List headers = getHeaderList(name); 185 if (headers == null) { 186 return null; 187 } else { 188 String[] result = new String[headers.size()]; 189 for (int i = 0; i < headers.size(); i++) { 190 InternetHeader header = (InternetHeader) headers.get(i); 191 result[i] = header.getValue(); 192 } 193 return result; 194 } 195 } 196 197 /** 198 * Return the values for the specified header as a single String. If the 199 * header has more than one value then all values are concatenated together 200 * separated by the supplied delimiter. 201 * 202 * @param name 203 * the header to return 204 * @param delimiter 205 * the delimiter used in concatenation 206 * @return the header as a single String 207 */ 208 public String getHeader(String name, String delimiter) { 209 List list = getHeaderList(name); 210 if (list == null) { 211 return null; 212 } else if (list.isEmpty()) { 213 return ""; 214 } else if (list.size() == 1 || delimiter == null) { 215 return ((InternetHeader) list.get(0)).getValue(); 216 } else { 217 StringBuffer buf = new StringBuffer(20 * list.size()); 218 buf.append(((InternetHeader) list.get(0)).getValue()); 219 for (int i = 1; i < list.size(); i++) { 220 buf.append(delimiter); 221 buf.append(((InternetHeader) list.get(i)).getValue()); 222 } 223 return buf.toString(); 224 } 225 } 226 227 /** 228 * Set the value of the header to the supplied value; any existing headers 229 * are removed. 230 * 231 * @param name 232 * the name of the header 233 * @param value 234 * the new value 235 */ 236 public void setHeader(String name, String value) { 237 List list = new ArrayList(); 238 list.add(new InternetHeader(name, value)); 239 setHeaderList(name, list); 240 } 241 242 /** 243 * Add a new value to the header with the supplied name. 244 * 245 * @param name 246 * the name of the header to add a new value for 247 * @param value 248 * another value 249 */ 250 public void addHeader(String name, String value) { 251 List list = getHeaderList(name); 252 if (list == null) { 253 list = new ArrayList(); 254 headers.put(name.toLowerCase(), list); 255 } 256 list.add(new InternetHeader(name, value)); 257 } 258 259 /** 260 * Remove all header entries with the supplied name 261 * 262 * @param name 263 * the header to remove 264 */ 265 public void removeHeader(String name) { 266 List list = getHeaderList(name); 267 // it's possible we might not have the named header. This is a nop if the header doesn't exist. 268 if (list != null) { 269 list.clear(); 270 } 271 } 272 273 /** 274 * Return all headers. 275 * 276 * @return an Enumeration<Header> containing all headers 277 */ 278 public Enumeration getAllHeaders() { 279 List result = new ArrayList(headers.size() * 2); 280 Iterator it = headers.values().iterator(); 281 while (it.hasNext()) { 282 List list = (List) it.next(); 283 if (list != null) { 284 result.addAll(list); 285 } 286 } 287 return Collections.enumeration(result); 288 } 289 290 /** 291 * Return all matching Header objects. 292 */ 293 public Enumeration getMatchingHeaders(String[] names) { 294 Set include = new HashSet(names.length); 295 for (int i = 0; i < names.length; i++) { 296 String name = names[i]; 297 include.add(name.toLowerCase()); 298 } 299 List result = new ArrayList(headers.size()); 300 for (Iterator i = headers.entrySet().iterator(); i.hasNext();) { 301 Map.Entry entry = (Map.Entry) i.next(); 302 if (entry.getValue() != null && include.contains(((String) entry.getKey()).toLowerCase())) { 303 result.addAll((List) entry.getValue()); 304 } 305 } 306 return Collections.enumeration(result); 307 } 308 309 /** 310 * Return all non matching Header objects. 311 */ 312 public Enumeration getNonMatchingHeaders(String[] names) { 313 Set exclude = new HashSet(names.length); 314 for (int i = 0; i < names.length; i++) { 315 String name = names[i]; 316 exclude.add(name.toLowerCase()); 317 } 318 List result = new ArrayList(headers.size()); 319 for (Iterator i = headers.entrySet().iterator(); i.hasNext();) { 320 Map.Entry entry = (Map.Entry) i.next(); 321 if (entry.getValue() != null && !exclude.contains(((String) entry.getKey()).toLowerCase())) { 322 result.addAll((List) entry.getValue()); 323 } 324 } 325 return Collections.enumeration(result); 326 } 327 328 /** 329 * Add an RFC822 header line to the header store. If the line starts with a 330 * space or tab (a continuation line), add it to the last header line in the 331 * list. Otherwise, append the new header line to the list. 332 * 333 * Note that RFC822 headers can only contain US-ASCII characters 334 * 335 * @param line 336 * raw RFC822 header line 337 */ 338 public void addHeaderLine(String line) { 339 StringBuffer name = new StringBuffer(32); 340 StringBuffer value = new StringBuffer(128); 341 boolean inName = true; 342 boolean continuation = false; 343 if (Character.isWhitespace(line.charAt(0))) { 344 continuation = true; 345 inName = false; 346 } 347 for (int i = 0; i < line.length(); i++) { 348 char c = line.charAt(i); 349 if (inName && c == ':') { 350 inName = false; 351 } else if (inName) { 352 name.append(c); 353 } else { 354 value.append(c); 355 } 356 } 357 if (continuation) { 358 List list = getHeaderList(lastHeaderName); 359 Header h = (Header) list.remove(list.size() - 1); 360 list.add(new InternetHeader(lastHeaderName, (h.getValue() + value.toString()).trim())); 361 } else { 362 lastHeaderName = name.toString().trim(); 363 addHeader(lastHeaderName, value.toString().trim()); 364 } 365 } 366 367 /** 368 * Return all the header lines as an Enumeration of Strings. 369 */ 370 public Enumeration getAllHeaderLines() { 371 return new HeaderLineEnumeration(getAllHeaders()); 372 } 373 374 /** 375 * Return all matching header lines as an Enumeration of Strings. 376 */ 377 public Enumeration getMatchingHeaderLines(String[] names) { 378 return new HeaderLineEnumeration(getMatchingHeaders(names)); 379 } 380 381 /** 382 * Return all non-matching header lines. 383 */ 384 public Enumeration getNonMatchingHeaderLines(String[] names) { 385 return new HeaderLineEnumeration(getNonMatchingHeaders(names)); 386 } 387 388 void setHeader(String name, Address[] addresses) { 389 List list = new ArrayList(addresses.length); 390 for (int i = 0; i < addresses.length; i++) { 391 Address address = addresses[i]; 392 list.add(new InternetHeader(name, address.toString())); 393 } 394 headers.put(name.toLowerCase(), list); 395 } 396 397 private List getHeaderList(String name) { 398 return (List) headers.get(name.toLowerCase()); 399 } 400 401 private void setHeaderList(String name, List list) { 402 headers.put(name.toLowerCase(), list); 403 } 404 405 void writeTo(OutputStream out, String[] ignore) throws IOException { 406 Map map = new LinkedHashMap(headers); 407 if (ignore != null) { 408 // remove each of these from the header list (note, they are stored as lower case keys). 409 for (int i = 0; i < ignore.length; i++) { 410 String key = ignore[i].toLowerCase(); 411 map.remove(key); 412 } 413 } 414 for (Iterator i = map.entrySet().iterator(); i.hasNext();) { 415 Map.Entry entry = (Map.Entry) i.next(); 416 String name = (String) entry.getKey(); 417 List headers = (List) entry.getValue(); 418 if (headers != null) { 419 for (int j = 0; j < headers.size(); j++) { 420 InternetHeader header = (InternetHeader) headers.get(j); 421 out.write(header.getName().getBytes()); 422 out.write(':'); 423 out.write(' '); 424 out.write(header.getValue().getBytes()); 425 out.write(13); 426 out.write(10); 427 } 428 } 429 } 430 } 431 432 private static class InternetHeader extends Header { 433 public InternetHeader(String name, String value) { 434 super(name, value); 435 } 436 437 public boolean equals(Object obj) { 438 if (this == obj) 439 return true; 440 if (obj instanceof InternetHeader == false) 441 return false; 442 final InternetHeader other = (InternetHeader) obj; 443 return getName().equalsIgnoreCase(other.getName()); 444 } 445 446 public int hashCode() { 447 return getName().toLowerCase().hashCode(); 448 } 449 } 450 451 private static class HeaderLineEnumeration implements Enumeration { 452 private Enumeration headers; 453 454 public HeaderLineEnumeration(Enumeration headers) { 455 this.headers = headers; 456 } 457 458 public boolean hasMoreElements() { 459 return headers.hasMoreElements(); 460 } 461 462 public Object nextElement() { 463 Header h = (Header) headers.nextElement(); 464 return h.getName() + ": " + h.getValue(); 465 } 466 } 467 }