Skip to content

Package: InternetHeaders$MatchHeaderEnum

InternetHeaders$MatchHeaderEnum

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