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