Clover coverage report - Maven Clover report
Coverage timestamp: Sun Aug 20 2006 04:01:44 PDT
file stats: LOC: 670   Methods: 29
NCLOC: 368   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
InternetHeaders.java 50.9% 63.3% 55.2% 58.6%
coverage coverage
 1    /**
 2    *
 3    * Copyright 2003-2006 The Apache Software Foundation
 4    *
 5    * Licensed under the Apache License, Version 2.0 (the "License");
 6    * you may not use this file except in compliance with the License.
 7    * You may obtain a copy of the License at
 8    *
 9    * http://www.apache.org/licenses/LICENSE-2.0
 10    *
 11    * Unless required by applicable law or agreed to in writing, software
 12    * distributed under the License is distributed on an "AS IS" BASIS,
 13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14    * See the License for the specific language governing permissions and
 15    * limitations under the License.
 16    */
 17   
 18    package javax.mail.internet;
 19   
 20    import java.io.IOException;
 21    import java.io.InputStream;
 22    import java.io.OutputStream;
 23    import java.util.ArrayList;
 24    import java.util.Arrays;
 25    import java.util.Collections;
 26    import java.util.Enumeration;
 27    import java.util.HashSet;
 28    import java.util.Iterator;
 29    import java.util.LinkedHashMap;
 30    import java.util.List;
 31    import java.util.Map;
 32    import java.util.Set;
 33   
 34    import javax.mail.Address;
 35    import javax.mail.Header;
 36    import javax.mail.MessagingException;
 37   
 38    /**
 39    * Class that represents the RFC822 headers associated with a message.
 40    *
 41    * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
 42    */
 43    public class InternetHeaders {
 44    // the list of headers (to preserve order);
 45    protected List headers = new ArrayList();
 46   
 47    private transient String lastHeaderName;
 48   
 49    /**
 50    * Create an empty InternetHeaders
 51    */
 52  47 public InternetHeaders() {
 53    // these are created in the preferred order of the headers.
 54  47 addHeader("Return-path", null);
 55  47 addHeader("Received", null);
 56  47 addHeader("Message-ID", null);
 57  47 addHeader("Resent-Date", null);
 58  47 addHeader("Date", null);
 59  47 addHeader("Resent-From", null);
 60  47 addHeader("From", null);
 61  47 addHeader("Reply-To", null);
 62  47 addHeader("Sender", null);
 63  47 addHeader("To", null);
 64  47 addHeader("Subject", null);
 65  47 addHeader("Cc", null);
 66  47 addHeader("In-Reply-To", null);
 67  47 addHeader("Resent-Message-Id", null);
 68  47 addHeader("Errors-To", null);
 69  47 addHeader("MIME-Version", null);
 70  47 addHeader("Content-Type", null);
 71  47 addHeader("Content-Transfer-Encoding", null);
 72  47 addHeader("Content-MD5", null);
 73    // the following is a special marker used to identify new header insertion points.
 74  47 addHeader(":", null);
 75  47 addHeader("Content-Length", null);
 76  47 addHeader("Status", null);
 77    }
 78   
 79    /**
 80    * Create a new InternetHeaders initialized by reading headers from the
 81    * stream.
 82    *
 83    * @param in
 84    * the RFC822 input stream to load from
 85    * @throws MessagingException
 86    * if there is a problem pasring the stream
 87    */
 88  9 public InternetHeaders(InputStream in) throws MessagingException {
 89  9 load(in);
 90    }
 91   
 92    /**
 93    * Read and parse the supplied stream and add all headers to the current
 94    * set.
 95    *
 96    * @param in
 97    * the RFC822 input stream to load from
 98    * @throws MessagingException
 99    * if there is a problem pasring the stream
 100    */
 101  10 public void load(InputStream in) throws MessagingException {
 102  10 try {
 103  10 StringBuffer name = new StringBuffer(32);
 104  10 StringBuffer value = new StringBuffer(128);
 105  10 done: while (true) {
 106  44 int c = in.read();
 107  44 char ch = (char) c;
 108  44 if (c == -1) {
 109  0 break;
 110  44 } else if (c == 13) {
 111    // empty line terminates header
 112  10 in.read(); // skip LF
 113  10 break;
 114  34 } else if (Character.isWhitespace(ch)) {
 115    // handle continuation
 116  0 do {
 117  0 c = in.read();
 118  0 if (c == -1) {
 119  0 break done;
 120    }
 121  0 ch = (char) c;
 122  0 } while (Character.isWhitespace(ch));
 123    } else {
 124    // new header
 125  34 if (name.length() > 0) {
 126  24 addHeader(name.toString().trim(), value.toString().trim());
 127    }
 128  34 name.setLength(0);
 129  34 value.setLength(0);
 130  34 while (true) {
 131  422 name.append((char) c);
 132  422 c = in.read();
 133  422 if (c == -1) {
 134  0 break done;
 135  422 } else if (c == ':') {
 136  34 break;
 137    }
 138    }
 139  34 c = in.read();
 140  34 if (c == -1) {
 141  0 break done;
 142    }
 143    }
 144   
 145  34 while (c != 13) {
 146  806 ch = (char) c;
 147  806 value.append(ch);
 148  806 c = in.read();
 149  806 if (c == -1) {
 150  0 break done;
 151    }
 152    }
 153    // skip LF
 154  34 c = in.read();
 155  34 if (c == -1) {
 156  0 break;
 157    }
 158    }
 159  10 if (name.length() > 0) {
 160  10 addHeader(name.toString().trim(), value.toString().trim());
 161    }
 162    } catch (IOException e) {
 163  0 throw new MessagingException("Error loading headers", e);
 164    }
 165    }
 166   
 167   
 168    /**
 169    * Return all the values for the specified header.
 170    *
 171    * @param name
 172    * the header to return
 173    * @return the values for that header, or null if the header is not present
 174    */
 175  210 public String[] getHeader(String name) {
 176  210 List accumulator = new ArrayList();
 177   
 178  210 for (int i = 0; i < headers.size(); i++) {
 179  4286 InternetHeader header = (InternetHeader)headers.get(i);
 180  4286 if (header.getName().equalsIgnoreCase(name) && header.getValue() != null) {
 181  122 accumulator.add(header.getValue());
 182    }
 183    }
 184   
 185    // this is defined as returning null of nothing is found.
 186  210 if (accumulator.isEmpty()) {
 187  102 return null;
 188    }
 189   
 190    // convert this to an array.
 191  108 return (String[])accumulator.toArray(new String[accumulator.size()]);
 192    }
 193   
 194    /**
 195    * Return the values for the specified header as a single String. If the
 196    * header has more than one value then all values are concatenated together
 197    * separated by the supplied delimiter.
 198    *
 199    * @param name
 200    * the header to return
 201    * @param delimiter
 202    * the delimiter used in concatenation
 203    * @return the header as a single String
 204    */
 205  86 public String getHeader(String name, String delimiter) {
 206    // get all of the headers with this name
 207  86 String[] matches = getHeader(name);
 208   
 209    // no match? return a null.
 210  86 if (matches == null) {
 211  38 return null;
 212    }
 213   
 214    // a null delimiter means just return the first one. If there's only one item, this is easy too.
 215  48 if (matches.length == 1 || delimiter == null) {
 216  34 return matches[0];
 217    }
 218   
 219    // perform the concatenation
 220  14 StringBuffer result = new StringBuffer(matches[0]);
 221   
 222  14 for (int i = 1; i < matches.length; i++) {
 223  14 result.append(delimiter);
 224  14 result.append(matches[i]);
 225    }
 226   
 227  14 return result.toString();
 228    }
 229   
 230   
 231    /**
 232    * Set the value of the header to the supplied value; any existing headers
 233    * are removed.
 234    *
 235    * @param name
 236    * the name of the header
 237    * @param value
 238    * the new value
 239    */
 240  85 public void setHeader(String name, String value) {
 241    // look for a header match
 242  85 for (int i = 0; i < headers.size(); i++) {
 243  1328 InternetHeader header = (InternetHeader)headers.get(i);
 244    // found a matching header
 245  1328 if (name.equalsIgnoreCase(header.getName())) {
 246    // just update the header value
 247  71 header.setValue(value);
 248    // remove all of the headers from this point
 249  71 removeHeaders(name, i + 1);
 250  71 return;
 251    }
 252    }
 253   
 254    // doesn't exist, so process as an add.
 255  14 addHeader(name, value);
 256    }
 257   
 258   
 259    /**
 260    * Remove all headers with the given name, starting with the
 261    * specified start position.
 262    *
 263    * @param name The target header name.
 264    * @param pos The position of the first header to examine.
 265    */
 266  121 private void removeHeaders(String name, int pos) {
 267    // now go remove all other instances of this header
 268  121 for (int i = pos; i < headers.size(); i++) {
 269  863 InternetHeader header = (InternetHeader)headers.get(i);
 270    // found a matching header
 271  863 if (name.equalsIgnoreCase(header.getName())) {
 272    // remove this item, and back up
 273  10 headers.remove(i);
 274  10 i--;
 275    }
 276    }
 277    }
 278   
 279   
 280    /**
 281    * Find a header in the current list by name, returning the index.
 282    *
 283    * @param name The target name.
 284    *
 285    * @return The index of the header in the list. Returns -1 for a not found
 286    * condition.
 287    */
 288  2138 private int findHeader(String name) {
 289  2138 return findHeader(name, 0);
 290    }
 291   
 292   
 293    /**
 294    * Find a header in the current list, beginning with the specified
 295    * start index.
 296    *
 297    * @param name The target header name.
 298    * @param start The search start index.
 299    *
 300    * @return The index of the first matching header. Returns -1 if the
 301    * header is not located.
 302    */
 303  2152 private int findHeader(String name, int start) {
 304  2152 for (int i = start; i < headers.size(); i++) {
 305  23645 InternetHeader header = (InternetHeader)headers.get(i);
 306    // found a matching header
 307  23645 if (name.equalsIgnoreCase(header.getName())) {
 308  176 return i;
 309    }
 310    }
 311  1976 return -1;
 312    }
 313   
 314    /**
 315    * Add a new value to the header with the supplied name.
 316    *
 317    * @param name
 318    * the name of the header to add a new value for
 319    * @param value
 320    * another value
 321    */
 322  1099 public void addHeader(String name, String value) {
 323  1099 InternetHeader newHeader = new InternetHeader(name, value);
 324   
 325    // The javamail spec states that "Recieved" headers need to be added in reverse order.
 326    // Return-Path is permitted before Received, so handle it the same way.
 327  1099 if (name.equalsIgnoreCase("Received") || name.equalsIgnoreCase("Return-Path")) {
 328    // see if we have one of these already
 329  94 int pos = findHeader(name);
 330   
 331    // either insert before an existing header, or insert at the very beginning
 332  94 if (pos != -1) {
 333    // this could be a placeholder header with a null value. If it is, just update
 334    // the value. Otherwise, insert in front of the existing header.
 335  0 InternetHeader oldHeader = (InternetHeader)headers.get(pos);
 336  0 if (oldHeader.getValue() == null) {
 337  0 oldHeader.setValue(value);
 338    }
 339    else {
 340  0 headers.add(pos, newHeader);
 341    }
 342    }
 343    else {
 344    // doesn't exist, so insert at the beginning
 345  94 headers.add(0, newHeader);
 346    }
 347    }
 348    // normal insertion
 349    else {
 350    // see if we have one of these already
 351  1005 int pos = findHeader(name);
 352   
 353    // either insert before an existing header, or insert at the very beginning
 354  1005 if (pos != -1) {
 355  16 InternetHeader oldHeader = (InternetHeader)headers.get(pos);
 356    // if the existing header is a place holder, we can just update the value
 357  16 if (oldHeader.getValue() == null) {
 358  2 oldHeader.setValue(value);
 359    }
 360    else {
 361    // we have at least one existing header with this name. We need to find the last occurrance,
 362    // and insert after that spot.
 363   
 364  14 int lastPos = findHeader(name, pos + 1);
 365   
 366  14 while (lastPos != -1) {
 367  0 pos = lastPos;
 368  0 lastPos = findHeader(name, pos + 1);
 369    }
 370   
 371    // ok, we have the insertion position
 372  14 headers.add(pos + 1, newHeader);
 373    }
 374    }
 375    else {
 376    // find the insertion marker. If that is missing somehow, insert at the end.
 377  989 pos = findHeader(":");
 378  989 if (pos == -1) {
 379  879 pos = headers.size();
 380    }
 381  989 headers.add(pos, newHeader);
 382    }
 383    }
 384    }
 385   
 386   
 387    /**
 388    * Remove all header entries with the supplied name
 389    *
 390    * @param name
 391    * the header to remove
 392    */
 393  50 public void removeHeader(String name) {
 394    // the first occurrance of a header is just zeroed out.
 395  50 int pos = findHeader(name);
 396   
 397  50 if (pos != -1) {
 398  50 InternetHeader oldHeader = (InternetHeader)headers.get(pos);
 399    // keep the header in the list, but with a null value
 400  50 oldHeader.setValue(null);
 401    // now remove all other headers with this name
 402  50 removeHeaders(name, pos + 1);
 403    }
 404    }
 405   
 406   
 407    /**
 408    * Return all headers.
 409    *
 410    * @return an Enumeration<Header> containing all headers
 411    */
 412  0 public Enumeration getAllHeaders() {
 413  0 List result = new ArrayList();
 414   
 415  0 for (int i = 0; i < headers.size(); i++) {
 416  0 InternetHeader header = (InternetHeader)headers.get(i);
 417    // we only include headers with real values, no placeholders
 418  0 if (header.getValue() != null) {
 419  0 result.add(header);
 420    }
 421    }
 422    // just return a list enumerator for the header list.
 423  0 return Collections.enumeration(result);
 424    }
 425   
 426   
 427    /**
 428    * Test if a given header name is a match for any header in the
 429    * given list.
 430    *
 431    * @param name The name of the current tested header.
 432    * @param names The list of names to match against.
 433    *
 434    * @return True if this is a match for any name in the list, false
 435    * for a complete mismatch.
 436    */
 437  0 private boolean matchHeader(String name, String[] names) {
 438  0 for (int i = 0; i < names.length; i++) {
 439  0 if (name.equalsIgnoreCase(names[i])) {
 440  0 return true;
 441    }
 442    }
 443  0 return false;
 444    }
 445   
 446   
 447    /**
 448    * Return all matching Header objects.
 449    */
 450  0 public Enumeration getMatchingHeaders(String[] names) {
 451  0 List result = new ArrayList();
 452   
 453  0 for (int i = 0; i < headers.size(); i++) {
 454  0 InternetHeader header = (InternetHeader)headers.get(i);
 455    // we only include headers with real values, no placeholders
 456  0 if (header.getValue() != null) {
 457    // only add the matching ones
 458  0 if (matchHeader(header.getName(), names)) {
 459  0 result.add(header);
 460    }
 461    }
 462    }
 463  0 return Collections.enumeration(result);
 464    }
 465   
 466   
 467    /**
 468    * Return all non matching Header objects.
 469    */
 470  0 public Enumeration getNonMatchingHeaders(String[] names) {
 471  0 List result = new ArrayList();
 472   
 473  0 for (int i = 0; i < headers.size(); i++) {
 474  0 InternetHeader header = (InternetHeader)headers.get(i);
 475    // we only include headers with real values, no placeholders
 476  0 if (header.getValue() != null) {
 477    // only add the non-matching ones
 478  0 if (!matchHeader(header.getName(), names)) {
 479  0 result.add(header);
 480    }
 481    }
 482    }
 483  0 return Collections.enumeration(result);
 484    }
 485   
 486   
 487    /**
 488    * Add an RFC822 header line to the header store. If the line starts with a
 489    * space or tab (a continuation line), add it to the last header line in the
 490    * list. Otherwise, append the new header line to the list.
 491    *
 492    * Note that RFC822 headers can only contain US-ASCII characters
 493    *
 494    * @param line
 495    * raw RFC822 header line
 496    */
 497  0 public void addHeaderLine(String line) {
 498    // null lines are a nop
 499  0 if (line.length() == 0) {
 500  0 return;
 501    }
 502   
 503    // we need to test the first character to see if this is a continuation whitespace
 504  0 char ch = line.charAt(0);
 505   
 506    // tabs and spaces are special. This is a continuation of the last header in the list.
 507  0 if (ch == ' ' || ch == '\t') {
 508  0 InternetHeader header = (InternetHeader)headers.get(headers.size() - 1);
 509  0 header.appendValue(line);
 510    }
 511    else {
 512    // this just gets appended to the end, preserving the addition order.
 513  0 headers.add(new InternetHeader(line));
 514    }
 515    }
 516   
 517   
 518    /**
 519    * Return all the header lines as an Enumeration of Strings.
 520    */
 521  0 public Enumeration getAllHeaderLines() {
 522  0 return new HeaderLineEnumeration(getAllHeaders());
 523    }
 524   
 525    /**
 526    * Return all matching header lines as an Enumeration of Strings.
 527    */
 528  0 public Enumeration getMatchingHeaderLines(String[] names) {
 529  0 return new HeaderLineEnumeration(getMatchingHeaders(names));
 530    }
 531   
 532    /**
 533    * Return all non-matching header lines.
 534    */
 535  0 public Enumeration getNonMatchingHeaderLines(String[] names) {
 536  0 return new HeaderLineEnumeration(getNonMatchingHeaders(names));
 537    }
 538   
 539   
 540    /**
 541    * Set an internet header from a list of addresses. The
 542    * first address item is set, followed by a series of addHeaders().
 543    *
 544    * @param name The name to set.
 545    * @param addresses The list of addresses to set.
 546    */
 547  12 void setHeader(String name, Address[] addresses) {
 548    // if this is empty, then ew need to replace this
 549  12 if (addresses.length == 0) {
 550  0 removeHeader(name);
 551    }
 552   
 553    // replace the first header
 554  12 setHeader(name, addresses[0].toString());
 555   
 556    // now add the rest as extra headers.
 557  12 for (int i = 1; i < addresses.length; i++) {
 558  5 Address address = addresses[i];
 559  5 addHeader(name, address.toString());
 560    }
 561    }
 562   
 563   
 564  16 void writeTo(OutputStream out, String[] ignore) throws IOException {
 565  16 if (ignore == null) {
 566    // write out all header lines with non-null values
 567  16 for (int i = 0; i < headers.size(); i++) {
 568  361 InternetHeader header = (InternetHeader)headers.get(i);
 569    // we only include headers with real values, no placeholders
 570  361 if (header.getValue() != null) {
 571  44 header.writeTo(out);
 572    }
 573    }
 574    }
 575    else {
 576    // write out all matching header lines with non-null values
 577  0 for (int i = 0; i < headers.size(); i++) {
 578  0 InternetHeader header = (InternetHeader)headers.get(i);
 579    // we only include headers with real values, no placeholders
 580  0 if (header.getValue() != null) {
 581  0 if (matchHeader(header.getName(), ignore)) {
 582  0 header.writeTo(out);
 583    }
 584    }
 585    }
 586    }
 587    }
 588   
 589    protected static final class InternetHeader extends Header {
 590   
 591  0 public InternetHeader(String h) {
 592    // initialize with null values, which we'll update once we parse the string
 593  0 super("", "");
 594  0 int separator = h.indexOf(':');
 595    // no separator, then we take this as a name with a null string value.
 596  0 if (separator == -1) {
 597  0 name = h.trim();
 598    }
 599    else {
 600  0 name = h.substring(0, separator);
 601    // step past the separator. Now we need to remove any leading white space characters.
 602  0 separator++;
 603   
 604  0 while (separator < h.length()) {
 605  0 char ch = h.charAt(separator);
 606  0 if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
 607  0 break;
 608    }
 609  0 separator++;
 610    }
 611   
 612  0 value = h.substring(separator);
 613    }
 614    }
 615   
 616  1099 public InternetHeader(String name, String value) {
 617  1099 super(name, value);
 618    }
 619   
 620   
 621    /**
 622    * Package scope method for setting the header value.
 623    *
 624    * @param value The new header value.
 625    */
 626  123 void setValue(String value) {
 627  123 this.value = value;
 628    }
 629   
 630    /**
 631    * Package scope method for extending a header value.
 632    *
 633    * @param value The appended header value.
 634    */
 635  0 void appendValue(String value) {
 636  0 if (this.value == null) {
 637  0 this.value = value;
 638    }
 639    else {
 640  0 this.value = this.value + "\r\n" + value;
 641    }
 642    }
 643   
 644  44 void writeTo(OutputStream out) throws IOException {
 645  44 out.write(name.getBytes());
 646  44 out.write(':');
 647  44 out.write(' ');
 648  44 out.write(value.getBytes());
 649  44 out.write('\r');
 650  44 out.write('\n');
 651    }
 652    }
 653   
 654    private static class HeaderLineEnumeration implements Enumeration {
 655    private Enumeration headers;
 656   
 657  0 public HeaderLineEnumeration(Enumeration headers) {
 658  0 this.headers = headers;
 659    }
 660   
 661  0 public boolean hasMoreElements() {
 662  0 return headers.hasMoreElements();
 663    }
 664   
 665  0 public Object nextElement() {
 666  0 Header h = (Header) headers.nextElement();
 667  0 return h.getName() + ": " + h.getValue();
 668    }
 669    }
 670    }