Skip to content

Package: InternetHeaders$MatchStringEnum

InternetHeaders$MatchStringEnum

nameinstructionbranchcomplexitylinemethod
InternetHeaders.MatchStringEnum(List, String[], boolean)
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
nextElement()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package jakarta.mail.internet;
18:
19: import jakarta.mail.Header;
20: import jakarta.mail.MessagingException;
21: import jakarta.mail.util.LineInputStream;
22: import jakarta.mail.util.StreamProvider;
23:
24: import java.io.IOException;
25: import java.io.InputStream;
26: import java.util.ArrayList;
27: import java.util.Enumeration;
28: import java.util.Iterator;
29: import java.util.List;
30: import java.util.NoSuchElementException;
31:
32:
33: /**
34: * InternetHeaders is a utility class that manages RFC822 style
35: * headers. Given an RFC822 format message stream, it reads lines
36: * until the blank line that indicates end of header. The input stream
37: * is positioned at the start of the body. The lines are stored
38: * within the object and can be extracted as either Strings or
39: * {@link jakarta.mail.Header} objects. <p>
40: *
41: * This class is mostly intended for service providers. MimeMessage
42: * and MimeBody use this class for holding their headers.
43: *
44: * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
45: *
46: * RFC822 and MIME header fields <strong>must</strong> contain only
47: * US-ASCII characters. If a header contains non US-ASCII characters,
48: * it must be encoded as per the rules in RFC 2047. The MimeUtility
49: * class provided in this package can be used to to achieve this.
50: * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
51: * <code>addHeaderLine</code> methods are responsible for enforcing
52: * the MIME requirements for the specified headers. In addition, these
53: * header fields must be folded (wrapped) before being sent if they
54: * exceed the line length limitation for the transport (1000 bytes for
55: * SMTP). Received headers may have been folded. The application is
56: * responsible for folding and unfolding headers as appropriate. <p>
57: *
58: * The current implementation supports the System property
59: * <code>mail.mime.ignorewhitespacelines</code>, which if set to true
60: * will cause a line containing only whitespace to be considered
61: * a blank line terminating the header.
62: *
63: * @author John Mani
64: * @author Bill Shannon
65: * @see jakarta.mail.internet.MimeUtility
66: */
67:
68: public class InternetHeaders {
69: private static final boolean ignoreWhitespaceLines =
70: MimeUtility.getBooleanSystemProperty("mail.mime.ignorewhitespacelines",
71: false);
72:
73: /**
74: * An individual internet header. This class is only used by
75: * subclasses of InternetHeaders. <p>
76: *
77: * An InternetHeader object with a null value is used as a placeholder
78: * for headers of that name, to preserve the order of headers.
79: * A placeholder InternetHeader object with a name of ":" marks
80: * the location in the list of headers where new headers are
81: * added by default.
82: *
83: * @since JavaMail 1.4
84: */
85: protected static final class InternetHeader extends Header {
86: /*
87: * Note that the value field from the superclass
88: * isn't used in this class. We extract the value
89: * from the line field as needed. We store the line
90: * rather than just the value to ensure that we can
91: * get back the exact original line, with the original
92: * whitespace, etc.
93: */
94: String line; // the entire RFC822 header "line",
95: // or null if placeholder
96:
97: /**
98: * Constructor that takes a line and splits out
99: * the header name.
100: *
101: * @param l the header line
102: */
103: public InternetHeader(String l) {
104: super("", ""); // XXX - we'll change it later
105: int i = l.indexOf(':');
106: if (i < 0) {
107: // should never happen
108: name = l.trim();
109: } else {
110: name = l.substring(0, i).trim();
111: }
112: line = l;
113: }
114:
115: /**
116: * Constructor that takes a header name and value.
117: *
118: * @param n the name of the header
119: * @param v the value of the header
120: */
121: public InternetHeader(String n, String v) {
122: super(n, "");
123: if (v != null)
124: line = n + ": " + v;
125: else
126: line = null;
127: }
128:
129: /**
130: * Return the "value" part of the header line.
131: */
132: @Override
133: public String getValue() {
134: int i = line.indexOf(':');
135: if (i < 0)
136: return line;
137: // skip whitespace after ':'
138: int j;
139: for (j = i + 1; j < line.length(); j++) {
140: char c = line.charAt(j);
141: if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
142: break;
143: }
144: return line.substring(j);
145: }
146: }
147:
148: /*
149: * The enumeration object used to enumerate an
150: * InternetHeaders object. Can return
151: * either a String or a Header object.
152: */
153: static class MatchEnum {
154: private Iterator<InternetHeader> e; // enum object of headers List
155: // XXX - is this overkill? should we step through in index
156: // order instead?
157: private String[] names; // names to match, or not
158: private boolean match; // return matching headers?
159: private boolean want_line; // return header lines?
160: private InternetHeader next_header; // the next header to be returned
161:
162: /*
163: * Constructor. Initialize the enumeration for the entire
164: * List of headers, the set of headers, whether to return
165: * matching or non-matching headers, and whether to return
166: * header lines or Header objects.
167: */
168: MatchEnum(List<InternetHeader> v, String[] n, boolean m, boolean l) {
169: e = v.iterator();
170: names = n;
171: match = m;
172: want_line = l;
173: next_header = null;
174: }
175:
176: /*
177: * Any more elements in this enumeration?
178: */
179: public boolean hasMoreElements() {
180: // if necessary, prefetch the next matching header,
181: // and remember it.
182: if (next_header == null)
183: next_header = nextMatch();
184: return next_header != null;
185: }
186:
187: /*
188: * Return the next element.
189: */
190: public Object nextElement() {
191: if (next_header == null)
192: next_header = nextMatch();
193:
194: if (next_header == null)
195: throw new NoSuchElementException("No more headers");
196:
197: InternetHeader h = next_header;
198: next_header = null;
199: if (want_line)
200: return h.line;
201: else
202: return new Header(h.getName(), h.getValue());
203: }
204:
205: /*
206: * Return the next Header object according to the match
207: * criteria, or null if none left.
208: */
209: private InternetHeader nextMatch() {
210: next:
211: while (e.hasNext()) {
212: InternetHeader h = e.next();
213:
214: // skip "place holder" headers
215: if (h.line == null)
216: continue;
217:
218: // if no names to match against, return appropriately
219: if (names == null)
220: return match ? null : h;
221:
222: // check whether this header matches any of the names
223: for (int i = 0; i < names.length; i++) {
224: if (names[i].equalsIgnoreCase(h.getName())) {
225: if (match)
226: return h;
227: else
228: // found a match, but we're
229: // looking for non-matches.
230: // try next header.
231: continue next;
232: }
233: }
234: // found no matches. if that's what we wanted, return it.
235: if (!match)
236: return h;
237: }
238: return null;
239: }
240: }
241:
242: static class MatchStringEnum extends MatchEnum
243: implements Enumeration<String> {
244:
245: MatchStringEnum(List<InternetHeader> v, String[] n, boolean m) {
246: super(v, n, m, true);
247: }
248:
249: @Override
250: public String nextElement() {
251: return (String) super.nextElement();
252: }
253:
254: }
255:
256: static class MatchHeaderEnum extends MatchEnum
257: implements Enumeration<Header> {
258:
259: MatchHeaderEnum(List<InternetHeader> v, String[] n, boolean m) {
260: super(v, n, m, false);
261: }
262:
263: @Override
264: public Header nextElement() {
265: return (Header) super.nextElement();
266: }
267:
268: }
269:
270: /**
271: * The actual list of Headers, including placeholder entries.
272: * Placeholder entries are Headers with a null value and
273: * are never seen by clients of the InternetHeaders class.
274: * Placeholder entries are used to keep track of the preferred
275: * order of headers. Headers are never actually removed from
276: * the list, they're converted into placeholder entries.
277: * New headers are added after existing headers of the same name
278: * (or before in the case of <code>Received</code> and
279: * <code>Return-Path</code> headers). If no existing header
280: * or placeholder for the header is found, new headers are
281: * added after the special placeholder with the name ":".
282: *
283: * @since JavaMail 1.4
284: */
285: protected List<InternetHeader> headers;
286:
287: /**
288: * Create an empty InternetHeaders object. Placeholder entries
289: * are inserted to indicate the preferred order of headers.
290: */
291: public InternetHeaders() {
292: headers = new ArrayList<>(40);
293: headers.add(new InternetHeader("Return-Path", null));
294: headers.add(new InternetHeader("Received", null));
295: headers.add(new InternetHeader("Resent-Date", null));
296: headers.add(new InternetHeader("Resent-From", null));
297: headers.add(new InternetHeader("Resent-Sender", null));
298: headers.add(new InternetHeader("Resent-To", null));
299: headers.add(new InternetHeader("Resent-Cc", null));
300: headers.add(new InternetHeader("Resent-Bcc", null));
301: headers.add(new InternetHeader("Resent-Message-Id", null));
302: headers.add(new InternetHeader("Date", null));
303: headers.add(new InternetHeader("From", null));
304: headers.add(new InternetHeader("Sender", null));
305: headers.add(new InternetHeader("Reply-To", null));
306: headers.add(new InternetHeader("To", null));
307: headers.add(new InternetHeader("Cc", null));
308: headers.add(new InternetHeader("Bcc", null));
309: headers.add(new InternetHeader("Message-Id", null));
310: headers.add(new InternetHeader("In-Reply-To", null));
311: headers.add(new InternetHeader("References", null));
312: headers.add(new InternetHeader("Subject", null));
313: headers.add(new InternetHeader("Comments", null));
314: headers.add(new InternetHeader("Keywords", null));
315: headers.add(new InternetHeader("Errors-To", null));
316: headers.add(new InternetHeader("MIME-Version", null));
317: headers.add(new InternetHeader("Content-Type", null));
318: headers.add(new InternetHeader("Content-Transfer-Encoding", null));
319: headers.add(new InternetHeader("Content-MD5", null));
320: headers.add(new InternetHeader(":", null));
321: headers.add(new InternetHeader("Content-Length", null));
322: headers.add(new InternetHeader("Status", null));
323: }
324:
325: /**
326: * Read and parse the given RFC822 message stream till the
327: * blank line separating the header from the body. The input
328: * stream is left positioned at the start of the body. The
329: * header lines are stored internally. <p>
330: *
331: * For efficiency, wrap a BufferedInputStream around the actual
332: * input stream and pass it as the parameter. <p>
333: *
334: * No placeholder entries are inserted; the original order of
335: * the headers is preserved.
336: *
337: * @param is RFC822 input stream
338: * @throws MessagingException for any I/O error reading the stream
339: */
340: public InternetHeaders(InputStream is) throws MessagingException {
341: this(is, false);
342: }
343:
344: /**
345: * Read and parse the given RFC822 message stream till the
346: * blank line separating the header from the body. The input
347: * stream is left positioned at the start of the body. The
348: * header lines are stored internally. <p>
349: *
350: * For efficiency, wrap a BufferedInputStream around the actual
351: * input stream and pass it as the parameter. <p>
352: *
353: * No placeholder entries are inserted; the original order of
354: * the headers is preserved.
355: *
356: * @param is RFC822 input stream
357: * @param allowutf8 if UTF-8 encoded headers are allowed
358: * @throws MessagingException for any I/O error reading the stream
359: * @since JavaMail 1.6
360: */
361: public InternetHeaders(InputStream is, boolean allowutf8)
362: throws MessagingException {
363: headers = new ArrayList<>(40);
364: load(is, allowutf8);
365: }
366:
367: /**
368: * Read and parse the given RFC822 message stream till the
369: * blank line separating the header from the body. Store the
370: * header lines inside this InternetHeaders object. The order
371: * of header lines is preserved. <p>
372: *
373: * Note that the header lines are added into this InternetHeaders
374: * object, so any existing headers in this object will not be
375: * affected. Headers are added to the end of the existing list
376: * of headers, in order.
377: *
378: * @param is RFC822 input stream
379: * @throws MessagingException for any I/O error reading the stream
380: */
381: public void load(InputStream is) throws MessagingException {
382: load(is, false);
383: }
384:
385: /**
386: * Read and parse the given RFC822 message stream till the
387: * blank line separating the header from the body. Store the
388: * header lines inside this InternetHeaders object. The order
389: * of header lines is preserved. <p>
390: *
391: * Note that the header lines are added into this InternetHeaders
392: * object, so any existing headers in this object will not be
393: * affected. Headers are added to the end of the existing list
394: * of headers, in order.
395: *
396: * @param is RFC822 input stream
397: * @param allowutf8 if UTF-8 encoded headers are allowed
398: * @throws MessagingException for any I/O error reading the stream
399: * @since JavaMail 1.6
400: */
401: public void load(InputStream is, boolean allowutf8)
402: throws MessagingException {
403: // Read header lines until a blank line. It is valid
404: // to have BodyParts with no header lines.
405: String line;
406: LineInputStream lis = StreamProvider.provider().inputLineStream(is, allowutf8);
407: String prevline = null; // the previous header line, as a string
408: // a buffer to accumulate the header in, when we know it's needed
409: StringBuilder lineBuffer = new StringBuilder();
410:
411: try {
412: // if the first line being read is a continuation line,
413: // we ignore it if it's otherwise empty or we treat it as
414: // a non-continuation line if it has non-whitespace content
415: boolean first = true;
416: do {
417: line = lis.readLine();
418: if (line != null &&
419: (line.startsWith(" ") || line.startsWith("\t"))) {
420: // continuation of header
421: if (prevline != null) {
422: lineBuffer.append(prevline);
423: prevline = null;
424: }
425: if (first) {
426: String lt = line.trim();
427: if (lt.length() > 0)
428: lineBuffer.append(lt);
429: } else {
430: if (lineBuffer.length() > 0)
431: lineBuffer.append("\r\n");
432: lineBuffer.append(line);
433: }
434: } else {
435: // new header
436: if (prevline != null)
437: addHeaderLine(prevline);
438: else if (lineBuffer.length() > 0) {
439: // store previous header first
440: addHeaderLine(lineBuffer.toString());
441: lineBuffer.setLength(0);
442: }
443: prevline = line;
444: }
445: first = false;
446: } while (line != null && !isEmpty(line));
447: } catch (IOException ioex) {
448: throw new MessagingException("Error in input stream", ioex);
449: }
450: }
451:
452: /**
453: * Is this line an empty (blank) line?
454: */
455: private static final boolean isEmpty(String line) {
456: return line.length() == 0 ||
457: (ignoreWhitespaceLines && line.trim().length() == 0);
458: }
459:
460: /**
461: * Return all the values for the specified header. The
462: * values are String objects. Returns <code>null</code>
463: * if no headers with the specified name exist.
464: *
465: * @param name header name
466: * @return array of header values, or null if none
467: */
468: public String[] getHeader(String name) {
469: Iterator<InternetHeader> e = headers.iterator();
470: // XXX - should we just step through in index order?
471: List<String> v = new ArrayList<>(); // accumulate return values
472:
473: while (e.hasNext()) {
474: InternetHeader h = e.next();
475: if (name.equalsIgnoreCase(h.getName()) && h.line != null) {
476: v.add(h.getValue());
477: }
478: }
479: if (v.size() == 0)
480: return (null);
481: // convert List to an array for return
482: String[] r = new String[v.size()];
483: r = v.toArray(r);
484: return (r);
485: }
486:
487: /**
488: * Get all the headers for this header name, returned as a single
489: * String, with headers separated by the delimiter. If the
490: * delimiter is <code>null</code>, only the first header is
491: * returned. Returns <code>null</code>
492: * if no headers with the specified name exist.
493: *
494: * @param delimiter delimiter
495: * @param name header name
496: * @return the value fields for all headers with
497: * this name, or null if none
498: */
499: public String getHeader(String name, String delimiter) {
500: String[] s = getHeader(name);
501:
502: if (s == null)
503: return null;
504:
505: if ((s.length == 1) || delimiter == null)
506: return s[0];
507:
508: StringBuilder r = new StringBuilder(s[0]);
509: for (int i = 1; i < s.length; i++) {
510: r.append(delimiter);
511: r.append(s[i]);
512: }
513: return r.toString();
514: }
515:
516: /**
517: * Change the first header line that matches name
518: * to have value, adding a new header if no existing header
519: * matches. Remove all matching headers but the first. <p>
520: *
521: * Note that RFC822 headers can only contain US-ASCII characters
522: *
523: * @param name header name
524: * @param value header value
525: */
526: public void setHeader(String name, String value) {
527: boolean found = false;
528:
529: for (int i = 0; i < headers.size(); i++) {
530: InternetHeader h = headers.get(i);
531: if (name.equalsIgnoreCase(h.getName())) {
532: if (!found) {
533: int j;
534: if (h.line != null && (j = h.line.indexOf(':')) >= 0) {
535: h.line = h.line.substring(0, j + 1) + " " + value;
536: // preserves capitalization, spacing
537: } else {
538: h.line = name + ": " + value;
539: }
540: found = true;
541: } else {
542: headers.remove(i);
543: i--; // have to look at i again
544: }
545: }
546: }
547:
548: if (!found) {
549: addHeader(name, value);
550: }
551: }
552:
553: /**
554: * Add a header with the specified name and value to the header list. <p>
555: *
556: * The current implementation knows about the preferred order of most
557: * well-known headers and will insert headers in that order. In
558: * addition, it knows that <code>Received</code> headers should be
559: * inserted in reverse order (newest before oldest), and that they
560: * should appear at the beginning of the headers, preceeded only by
561: * a possible <code>Return-Path</code> header. <p>
562: *
563: * Note that RFC822 headers can only contain US-ASCII characters.
564: *
565: * @param name header name
566: * @param value header value
567: */
568: public void addHeader(String name, String value) {
569: int pos = headers.size();
570: boolean addReverse =
571: name.equalsIgnoreCase("Received") ||
572: name.equalsIgnoreCase("Return-Path");
573: if (addReverse)
574: pos = 0;
575: for (int i = headers.size() - 1; i >= 0; i--) {
576: InternetHeader h = headers.get(i);
577: if (name.equalsIgnoreCase(h.getName())) {
578: if (addReverse) {
579: pos = i;
580: } else {
581: headers.add(i + 1, new InternetHeader(name, value));
582: return;
583: }
584: }
585: // marker for default place to add new headers
586: if (!addReverse && h.getName().equals(":"))
587: pos = i;
588: }
589: headers.add(pos, new InternetHeader(name, value));
590: }
591:
592: /**
593: * Remove all header entries that match the given name
594: *
595: * @param name header name
596: */
597: public void removeHeader(String name) {
598: for (int i = 0; i < headers.size(); i++) {
599: InternetHeader h = headers.get(i);
600: if (name.equalsIgnoreCase(h.getName())) {
601: h.line = null;
602: //headers.remove(i);
603: //i--; // have to look at i again
604: }
605: }
606: }
607:
608: /**
609: * Return all the headers as an Enumeration of
610: * {@link jakarta.mail.Header} objects.
611: *
612: * @return Enumeration of Header objects
613: */
614: public Enumeration<Header> getAllHeaders() {
615: return (new MatchHeaderEnum(headers, null, false));
616: }
617:
618: /**
619: * Return all matching {@link jakarta.mail.Header} objects.
620: *
621: * @param names the headers to return
622: * @return Enumeration of matching Header objects
623: */
624: public Enumeration<Header> getMatchingHeaders(String[] names) {
625: return (new MatchHeaderEnum(headers, names, true));
626: }
627:
628: /**
629: * Return all non-matching {@link jakarta.mail.Header} objects.
630: *
631: * @param names the headers to not return
632: * @return Enumeration of non-matching Header objects
633: */
634: public Enumeration<Header> getNonMatchingHeaders(String[] names) {
635: return (new MatchHeaderEnum(headers, names, false));
636: }
637:
638: /**
639: * Add an RFC822 header line to the header store.
640: * If the line starts with a space or tab (a continuation line),
641: * add it to the last header line in the list. Otherwise,
642: * append the new header line to the list. <p>
643: *
644: * Note that RFC822 headers can only contain US-ASCII characters
645: *
646: * @param line raw RFC822 header line
647: */
648: public void addHeaderLine(String line) {
649: try {
650: char c = line.charAt(0);
651: if (c == ' ' || c == '\t') {
652: InternetHeader h = headers.get(headers.size() - 1);
653: h.line += "\r\n" + line;
654: } else
655: headers.add(new InternetHeader(line));
656: } catch (StringIndexOutOfBoundsException e) {
657: // line is empty, ignore it
658: return;
659: } catch (NoSuchElementException e) {
660: // XXX - list is empty?
661: }
662: }
663:
664: /**
665: * Return all the header lines as an Enumeration of Strings.
666: *
667: * @return Enumeration of Strings of all header lines
668: */
669: public Enumeration<String> getAllHeaderLines() {
670: return (getNonMatchingHeaderLines(null));
671: }
672:
673: /**
674: * Return all matching header lines as an Enumeration of Strings.
675: *
676: * @param names the headers to return
677: * @return Enumeration of Strings of all matching header lines
678: */
679: public Enumeration<String> getMatchingHeaderLines(String[] names) {
680: return (new MatchStringEnum(headers, names, true));
681: }
682:
683: /**
684: * Return all non-matching header lines
685: *
686: * @param names the headers to not return
687: * @return Enumeration of Strings of all non-matching header lines
688: */
689: public Enumeration<String> getNonMatchingHeaderLines(String[] names) {
690: return (new MatchStringEnum(headers, names, false));
691: }
692: }