1. /*
  2. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  3. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  4. */
  5. package javax.mail.internet;
  6. import java.io.*;
  7. import java.util.*;
  8. import javax.mail.*;
  9. import com.sun.mail.util.LineInputStream;
  10. /**
  11. * InternetHeaders is a utility class that manages RFC822 style
  12. * headers. Given a rfc822 format message stream, it reads lines
  13. * till the blank line that indicates end of header. The input stream
  14. * is positioned at the start of the body. The lines are stored
  15. * within the object and can be extracted as either Strings or
  16. * Header objects. <p>
  17. *
  18. * This class is mostly intended for service providers. MimeMessage
  19. * and MimeBody use this class for holding their headers. <p>
  20. *
  21. * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
  22. *
  23. * RFC822 and MIME header fields <strong>must</strong> contain only
  24. * US-ASCII characters. If a header contains non US-ASCII characters,
  25. * it must be encoded as per the rules in RFC 2047. The MimeUtility
  26. * class provided in this package can be used to to achieve this.
  27. * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
  28. * <code>addHeaderLine</code> methods are responsible for enforcing
  29. * the MIME requirements for the specified headers. In addition, these
  30. * header fields must be folded (wrapped) before being sent if they
  31. * exceed the line length limitation for the transport (1000 bytes for
  32. * SMTP). Received headers may have been folded. The application is
  33. * responsible for folding and unfolding headers as appropriate. <p>
  34. *
  35. * @see javax.mail.internet.MimeUtility
  36. * @author John Mani
  37. * @author Bill Shannon
  38. */
  39. public class InternetHeaders {
  40. private Vector headers;
  41. /**
  42. * Create an empty InternetHeaders object.
  43. */
  44. public InternetHeaders() {
  45. headers = new Vector();
  46. headers.addElement(new hdr("Return-Path", null));
  47. headers.addElement(new hdr("Received", null));
  48. headers.addElement(new hdr("Message-Id", null));
  49. headers.addElement(new hdr("Resent-Date", null));
  50. headers.addElement(new hdr("Date", null));
  51. headers.addElement(new hdr("Resent-From", null));
  52. headers.addElement(new hdr("From", null));
  53. headers.addElement(new hdr("Reply-To", null));
  54. headers.addElement(new hdr("To", null));
  55. headers.addElement(new hdr("Subject", null));
  56. headers.addElement(new hdr("Cc", null));
  57. headers.addElement(new hdr("In-Reply-To", null));
  58. headers.addElement(new hdr("Resent-Message-Id", null));
  59. headers.addElement(new hdr("Errors-To", null));
  60. headers.addElement(new hdr("Mime-Version", null));
  61. headers.addElement(new hdr("Content-Type", null));
  62. headers.addElement(new hdr("Content-Transfer-Encoding", null));
  63. headers.addElement(new hdr("Content-MD5", null));
  64. headers.addElement(new hdr(":", null));
  65. headers.addElement(new hdr("Content-Length", null));
  66. headers.addElement(new hdr("Status", null));
  67. }
  68. /**
  69. * Read and parse the given rfc822 message stream till the
  70. * blank line separating the header from the body. The input
  71. * stream is left positioned at the start of the body. The
  72. * header lines are stored internally. <p>
  73. *
  74. * For efficiency, wrap a BufferedInputStream around the actual
  75. * input stream and pass it as the parameter.
  76. *
  77. * @param is rfc822 input stream
  78. */
  79. public InternetHeaders(InputStream is) throws MessagingException {
  80. headers = new Vector();
  81. load(is);
  82. }
  83. /**
  84. * Read and parse the given rfc822 message stream till the
  85. * blank line separating the header from the body. Store the
  86. * header lines inside this InternetHeaders object. <p>
  87. *
  88. * Note that the header lines are added into this InternetHeaders
  89. * object, so any existing headers in this object will not be
  90. * affected.
  91. *
  92. * @param is rfc822 input stream
  93. */
  94. public void load(InputStream is) throws MessagingException {
  95. // Read header lines until a blank line. It is valid
  96. // to have BodyParts with no header lines.
  97. String line;
  98. LineInputStream lis = new LineInputStream(is);
  99. try {
  100. while ((line = lis.readLine()) != null) {
  101. if (line.length() == 0)
  102. // blank line, end of header
  103. break;
  104. addHeaderLine(line);
  105. }
  106. } catch (IOException ioex) {
  107. throw new MessagingException("Error in input stream", ioex);
  108. }
  109. }
  110. /**
  111. * Return all the values for the specified header. The
  112. * values are String objects.
  113. *
  114. * @param name header name
  115. */
  116. public String[] getHeader(String name) {
  117. Enumeration e = headers.elements();
  118. // XXX - should we just step through in index order?
  119. Vector v = new Vector(); // accumulate return values
  120. while (e.hasMoreElements()) {
  121. hdr h = (hdr)e.nextElement();
  122. if (name.equalsIgnoreCase(h.name) && h.line != null) {
  123. v.addElement(h.getValue());
  124. }
  125. }
  126. if (v.size() == 0)
  127. return (null);
  128. // convert Vector to an array for return
  129. String r[] = new String[v.size()];
  130. v.copyInto(r);
  131. return (r);
  132. }
  133. /**
  134. * Get all the headers for this header name, returned as a single
  135. * String, with headers separated by the delimiter. If the
  136. * delimiter is <code>null</code>, only the first header is
  137. * returned.
  138. *
  139. * @param name header name
  140. * @param delimiter delimiter
  141. * @return the value fields for all headers with
  142. * this name
  143. */
  144. public String getHeader(String name, String delimiter) {
  145. String s[] = getHeader(name);
  146. if (s == null)
  147. return null;
  148. if ((s.length == 1) || delimiter == null)
  149. return s[0];
  150. StringBuffer r = new StringBuffer(s[0]);
  151. for (int i = 1; i < s.length; i++) {
  152. r.append(delimiter);
  153. r.append(s[i]);
  154. }
  155. return r.toString();
  156. }
  157. /**
  158. * Change the first header line that matches name
  159. * to have value, adding a new header if no existing header
  160. * matches. Remove all matching headers but the first. <p>
  161. *
  162. * Note that RFC822 headers can only contain US-ASCII characters
  163. *
  164. * @param name header name
  165. * @param value header value
  166. */
  167. public void setHeader(String name, String value) {
  168. boolean found = false;
  169. for (int i = 0; i < headers.size(); i++) {
  170. hdr h = (hdr)headers.elementAt(i);
  171. if (name.equalsIgnoreCase(h.name)) {
  172. if (!found) {
  173. int j;
  174. if (h.line != null && (j = h.line.indexOf(':')) >= 0) {
  175. h.line = h.line.substring(0, j + 1) + " " + value;
  176. } else {
  177. h.line = name + ": " + value;
  178. }
  179. found = true;
  180. } else {
  181. headers.removeElementAt(i);
  182. i--; // have to look at i again
  183. }
  184. }
  185. }
  186. if (!found) {
  187. addHeader(name, value);
  188. }
  189. }
  190. /**
  191. * Add a header with the specified name and value to the header list. <p>
  192. *
  193. * Note that RFC822 headers can only contain US-ASCII characters.
  194. *
  195. * @param name header name
  196. * @param value header value
  197. */
  198. public void addHeader(String name, String value) {
  199. int pos = headers.size();
  200. for (int i = pos - 1; i >= 0; i--) {
  201. hdr h = (hdr)headers.elementAt(i);
  202. if (name.equalsIgnoreCase(h.name)) {
  203. headers.insertElementAt(new hdr(name, value), i + 1);
  204. return;
  205. }
  206. // marker for default place to add new headers
  207. if (h.name.equals(":"))
  208. pos = i;
  209. }
  210. headers.insertElementAt(new hdr(name, value), pos);
  211. }
  212. /**
  213. * Remove all header entries that match the given name
  214. * @param name header name
  215. */
  216. public void removeHeader(String name) {
  217. for (int i = 0; i < headers.size(); i++) {
  218. hdr h = (hdr)headers.elementAt(i);
  219. if (name.equalsIgnoreCase(h.name)) {
  220. h.line = null;
  221. //headers.removeElementAt(i);
  222. //i--; // have to look at i again
  223. }
  224. }
  225. }
  226. /**
  227. * Return all the headers as an Enumeration of Header objects
  228. * @return Header objects
  229. */
  230. public Enumeration getAllHeaders() {
  231. return (new matchEnum(headers, null, false, false));
  232. }
  233. /**
  234. * Return all matching Header objects
  235. * @return matching Header objects
  236. */
  237. public Enumeration getMatchingHeaders(String[] names) {
  238. return (new matchEnum(headers, names, true, false));
  239. }
  240. /**
  241. * Return all non-matching Header objects
  242. * @return non-matching Header objects
  243. */
  244. public Enumeration getNonMatchingHeaders(String[] names) {
  245. return (new matchEnum(headers, names, false, false));
  246. }
  247. /**
  248. * Add an RFC822 header line to the header store.
  249. * If the line starts with a space or tab (a continuation line),
  250. * add it to the last header line in the list. <p>
  251. *
  252. * Note that RFC822 headers can only contain US-ASCII characters
  253. *
  254. * @param line raw rfc822 header line
  255. */
  256. public void addHeaderLine(String line) {
  257. try {
  258. char c = line.charAt(0);
  259. if (c == ' ' || c == '\t') {
  260. hdr h = (hdr)headers.lastElement();
  261. h.line += "\r\n" + line;
  262. } else
  263. headers.addElement(new hdr(line));
  264. } catch (StringIndexOutOfBoundsException e) {
  265. // line is empty, ignore it
  266. return;
  267. } catch (NoSuchElementException e) {
  268. // XXX - vector is empty?
  269. }
  270. }
  271. /**
  272. * Return all the header lines as an Enumeration of Strings.
  273. */
  274. public Enumeration getAllHeaderLines() {
  275. return (getNonMatchingHeaderLines(null));
  276. }
  277. /**
  278. * Return all matching header lines as an Enumeration of Strings.
  279. */
  280. public Enumeration getMatchingHeaderLines(String[] names) {
  281. return (new matchEnum(headers, names, true, true));
  282. }
  283. /**
  284. * Return all non-matching header lines
  285. */
  286. public Enumeration getNonMatchingHeaderLines(String[] names) {
  287. return (new matchEnum(headers, names, false, true));
  288. }
  289. }
  290. /*
  291. * A private utility class to represent an individual header.
  292. * A hdr object with a null line field is used as a "place holder"
  293. * for headers of that name, to preserve order of headers.
  294. */
  295. class hdr {
  296. // XXX - should these be private?
  297. String name; // the canonicalized (trimmed) name of this header
  298. // XXX - should name be stored in lower case?
  299. String line; // the entire RFC822 header "line", or null if place holder
  300. /*
  301. * Constructor that takes a line and splits out
  302. * the header name.
  303. */
  304. hdr(String l) {
  305. int i = l.indexOf(':');
  306. if (i < 0) {
  307. // should never happen
  308. name = l.trim();
  309. } else {
  310. name = l.substring(0, i).trim();
  311. }
  312. line = l;
  313. }
  314. /*
  315. * Constructor that takes a header name and value.
  316. */
  317. hdr(String n, String v) {
  318. name = n;
  319. if (v != null)
  320. line = n + ": " + v;
  321. else
  322. line = null;
  323. }
  324. /*
  325. * Return the "name" part of the header line.
  326. */
  327. String getName() {
  328. return (name);
  329. }
  330. /*
  331. * Return the "value" part of the header line.
  332. */
  333. String getValue() {
  334. int i = line.indexOf(':');
  335. if (i < 0)
  336. return (line);
  337. // skip whitespace after ':'
  338. int j;
  339. for (j = i + 1; j < line.length(); j++) {
  340. char c = line.charAt(j);
  341. if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
  342. break;
  343. }
  344. return (line.substring(j));
  345. }
  346. }
  347. /*
  348. * The enumeration object used to enumerate an
  349. * InternetHeaders object. Can return
  350. * either a String or a Header object.
  351. */
  352. class matchEnum implements Enumeration {
  353. private Enumeration e; // enum object of headers Vector
  354. // XXX - is this overkill? should we step through in index
  355. // order instead?
  356. private String names[]; // names to match, or not
  357. private boolean match; // return matching headers?
  358. private boolean want_line; // return header lines?
  359. private hdr next_header; // the next header to be returned
  360. /*
  361. * Constructor. Initialize the enumeration for the entire
  362. * Vector of headers, the set of headers, whether to return
  363. * matching or non-matching headers, and whether to return
  364. * header lines or Header objects.
  365. */
  366. matchEnum(Vector v, String n[], boolean m, boolean l) {
  367. e = v.elements();
  368. names = n;
  369. match = m;
  370. want_line = l;
  371. next_header = null;
  372. }
  373. /*
  374. * Any more elements in this enumeration?
  375. */
  376. public boolean hasMoreElements() {
  377. // if necessary, prefetch the next matching header,
  378. // and remember it.
  379. if (next_header == null)
  380. next_header = nextMatch();
  381. return (next_header != null);
  382. }
  383. /*
  384. * Return the next element.
  385. */
  386. public Object nextElement() {
  387. if (next_header == null)
  388. next_header = nextMatch();
  389. if (next_header == null)
  390. throw new NoSuchElementException("No more headers");
  391. hdr h = next_header;
  392. next_header = null;
  393. if (want_line)
  394. return (h.line);
  395. else
  396. return (new Header(h.getName(),
  397. h.getValue()));
  398. }
  399. /*
  400. * Return the next hdr object according to the match
  401. * criteria, or null if none left.
  402. */
  403. private hdr nextMatch() {
  404. next:
  405. while (e.hasMoreElements()) {
  406. hdr h = (hdr)e.nextElement();
  407. // skip "place holder" headers
  408. if (h.line == null)
  409. continue;
  410. // if no names to match against, return appropriately
  411. if (names == null)
  412. return (match ? null : h);
  413. // check whether this header matches any of the names
  414. for (int i = 0; i < names.length; i++) {
  415. if (names[i].equalsIgnoreCase(h.name)) {
  416. if (match)
  417. return (h);
  418. else
  419. // found a match, but we're
  420. // looking for non-matches.
  421. // try next header.
  422. continue next;
  423. }
  424. }
  425. // found no matches. if that's what we wanted, return it.
  426. if (!match)
  427. return (h);
  428. }
  429. return (null);
  430. }
  431. }