Clover coverage report - Maven Clover report
Coverage timestamp: Sun Aug 20 2006 04:01:04 PDT
file stats: LOC: 678   Methods: 20
NCLOC: 359   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
QuotedPrintableEncoder.java 44.4% 53.7% 65% 51.7%
coverage coverage
 1    /**
 2    *
 3    * Copyright 2003-2004 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 org.apache.geronimo.mail.util;
 19   
 20    import java.io.EOFException;
 21    import java.io.IOException;
 22    import java.io.InputStream;
 23    import java.io.OutputStream;
 24    import java.io.PrintStream;
 25    import java.io.PushbackInputStream;
 26    import java.io.UnsupportedEncodingException;
 27   
 28    public class QuotedPrintableEncoder implements Encoder {
 29   
 30    static protected final byte[] encodingTable =
 31    {
 32    (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
 33    (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F'
 34    };
 35   
 36    /*
 37    * set up the decoding table.
 38    */
 39    static protected final byte[] decodingTable = new byte[128];
 40   
 41    static {
 42    // initialize the decoding table
 43  1 for (int i = 0; i < encodingTable.length; i++)
 44    {
 45  16 decodingTable[encodingTable[i]] = (byte)i;
 46    }
 47    }
 48   
 49   
 50    // default number of characters we will write per line.
 51    static private final int DEFAULT_CHARS_PER_LINE = 76;
 52   
 53    // the output stream we're wrapped around
 54    protected OutputStream out;
 55    // the number of bytes written;
 56    protected int bytesWritten = 0;
 57    // number of bytes written on the current line
 58    protected int lineCount = 0;
 59    // line length we're dealing with
 60    protected int lineLength;
 61    // number of deferred whitespace characters in decode mode.
 62    protected int deferredWhitespace = 0;
 63   
 64    protected int cachedCharacter = -1;
 65   
 66    // indicates whether the last character was a '\r', potentially part of a CRLF sequence.
 67    protected boolean lastCR = false;
 68    // remember whether last character was a white space.
 69    protected boolean lastWhitespace = false;
 70   
 71  19 public QuotedPrintableEncoder() {
 72  19 this(null, DEFAULT_CHARS_PER_LINE);
 73    }
 74   
 75  0 public QuotedPrintableEncoder(OutputStream out) {
 76  0 this(out, DEFAULT_CHARS_PER_LINE);
 77    }
 78   
 79  22 public QuotedPrintableEncoder(OutputStream out, int lineLength) {
 80  22 this.out = out;
 81  22 this.lineLength = lineLength;
 82    }
 83   
 84  765 private void checkDeferred(int ch) throws IOException {
 85    // was the last character we looked at a whitespace? Try to decide what to do with it now.
 86  765 if (lastWhitespace) {
 87    // if this whitespace is at the end of the line, write it out encoded
 88  0 if (ch == '\r' || ch == '\n') {
 89  0 writeEncodedCharacter(' ');
 90    }
 91    else {
 92    // we can write this out without encoding.
 93  0 writeCharacter(' ');
 94    }
 95    // we always turn this off.
 96  0 lastWhitespace = false;
 97    }
 98    // deferred carriage return?
 99  765 else if (lastCR) {
 100    // if the char following the CR was not a new line, write an EOL now.
 101  0 if (ch != '\n') {
 102  0 writeEOL();
 103    }
 104    // we always turn this off too
 105  0 lastCR = false;
 106    }
 107    }
 108   
 109   
 110    /**
 111    * encode the input data producing a UUEncoded output stream.
 112    *
 113    * @param data The array of byte data.
 114    * @param off The starting offset within the data.
 115    * @param length Length of the data to encode.
 116    *
 117    * @return the number of bytes produced.
 118    */
 119  3 public int encode(byte[] data, int off, int length) throws IOException {
 120  3 int endOffset = off + length;
 121   
 122  3 while (off < endOffset) {
 123    // get the character
 124  765 byte ch = data[off++];
 125   
 126    // handle the encoding of this character.
 127  765 encode(ch);
 128    }
 129   
 130  3 return bytesWritten;
 131    }
 132   
 133   
 134  765 public void encode(int ch) throws IOException {
 135    // make sure this is just a single byte value.
 136  765 ch = ch &0xFF;
 137   
 138    // see if we had to defer handling of a whitespace or '\r' character, and handle it if necessary.
 139  765 checkDeferred(ch);
 140    // different characters require special handling.
 141  765 switch (ch) {
 142    // spaces require special handling. If the next character is a line terminator, then
 143    // the space needs to be encoded.
 144  0 case ' ':
 145    {
 146    // at this point, we don't know whether this needs encoding or not. If the next
 147    // character is a linend, it gets encoded. If anything else, we just write it as is.
 148  0 lastWhitespace = true;
 149    // turn off any CR flags.
 150  0 lastCR = false;
 151  0 break;
 152    }
 153   
 154    // carriage return, which may be part of a CRLF sequence.
 155  0 case '\r':
 156    {
 157    // just flag this until we see the next character.
 158  0 lastCR = true;
 159  0 break;
 160    }
 161   
 162    // a new line character...we need to check to see if it was paired up with a '\r' char.
 163  0 case '\n':
 164    {
 165    // we always write this out for a newline. We defer CRs until we see if the LF follows.
 166  0 writeEOL();
 167  0 break;
 168    }
 169   
 170    // an '=' is the escape character for an encoded character, so it must also
 171    // be written encoded.
 172  3 case '=':
 173    {
 174  3 writeEncodedCharacter(ch);
 175  3 break;
 176    }
 177   
 178    // all other characters. If outside the printable character range, write it encoded.
 179  762 default:
 180    {
 181  762 if (ch < 32 || ch >= 127) {
 182  519 writeEncodedCharacter(ch);
 183    }
 184    else {
 185  243 writeCharacter(ch);
 186    }
 187  762 break;
 188    }
 189    }
 190    }
 191   
 192   
 193    /**
 194    * encode the input data producing a UUEncoded output stream.
 195    *
 196    * @param data The array of byte data.
 197    * @param off The starting offset within the data.
 198    * @param length Length of the data to encode.
 199    *
 200    * @return the number of bytes produced.
 201    */
 202  0 public int encode(byte[] data, int off, int length, String specials) throws IOException {
 203  0 int endOffset = off + length;
 204   
 205  0 while (off < endOffset) {
 206    // get the character
 207  0 byte ch = data[off++];
 208   
 209    // handle the encoding of this character.
 210  0 encode(ch, specials);
 211    }
 212   
 213  0 return bytesWritten;
 214    }
 215   
 216   
 217    /**
 218    * encode the input data producing a UUEncoded output stream.
 219    *
 220    * @param data The array of byte data.
 221    * @param off The starting offset within the data.
 222    * @param length Length of the data to encode.
 223    *
 224    * @return the number of bytes produced.
 225    */
 226  18 public int encode(PushbackInputStream in, StringBuffer out, String specials, int limit) throws IOException {
 227  18 int count = 0;
 228   
 229  138 while (count < limit) {
 230  138 int ch = in.read();
 231   
 232  138 if (ch == -1) {
 233  18 return count;
 234    }
 235    // make sure this is just a single byte value.
 236  120 ch = ch &0xFF;
 237   
 238    // spaces require special handling. If the next character is a line terminator, then
 239    // the space needs to be encoded.
 240  120 if (ch == ' ') {
 241    // blanks get translated into underscores, because the encoded tokens can't have embedded blanks.
 242  17 out.append('_');
 243  17 count++;
 244    }
 245    // non-ascii chars and the designated specials all get encoded.
 246  103 else if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
 247    // we need at least 3 characters to write this out, so we need to
 248    // forget we saw this one and try in the next segment.
 249  34 if (count + 3 > limit) {
 250  0 in.unread(ch);
 251  0 return count;
 252    }
 253  34 out.append('=');
 254  34 out.append((char)encodingTable[ch >> 4]);
 255  34 out.append((char)encodingTable[ch & 0x0F]);
 256  34 count += 3;
 257    }
 258    else {
 259    // good character, just use unchanged.
 260  69 out.append((char)ch);
 261  69 count++;
 262    }
 263    }
 264  0 return count;
 265    }
 266   
 267   
 268    /**
 269    * Specialized version of the decoder that handles encoding of
 270    * RFC 2047 encoded word values. This has special handling for
 271    * certain characters, but less special handling for blanks and
 272    * linebreaks.
 273    *
 274    * @param ch
 275    * @param specials
 276    *
 277    * @exception IOException
 278    */
 279  0 public void encode(int ch, String specials) throws IOException {
 280    // make sure this is just a single byte value.
 281  0 ch = ch &0xFF;
 282   
 283    // spaces require special handling. If the next character is a line terminator, then
 284    // the space needs to be encoded.
 285  0 if (ch == ' ') {
 286    // blanks get translated into underscores, because the encoded tokens can't have embedded blanks.
 287  0 writeCharacter('_');
 288    }
 289    // non-ascii chars and the designated specials all get encoded.
 290  0 else if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
 291  0 writeEncodedCharacter(ch);
 292    }
 293    else {
 294    // good character, just use unchanged.
 295  0 writeCharacter(ch);
 296    }
 297    }
 298   
 299   
 300    /**
 301    * encode the input data producing a UUEncoded output stream.
 302    *
 303    * @param data The array of byte data.
 304    * @param off The starting offset within the data.
 305    * @param length Length of the data to encode.
 306    * @param out The output stream the encoded data is written to.
 307    *
 308    * @return the number of bytes produced.
 309    */
 310  0 public int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
 311    // make sure we're writing to the correct stream
 312  0 this.out = out;
 313  0 bytesWritten = 0;
 314   
 315    // do the actual encoding
 316  0 return encode(data, off, length);
 317    }
 318   
 319   
 320    /**
 321    * decode the uuencoded byte data writing it to the given output stream
 322    *
 323    * @param data The array of byte data to decode.
 324    * @param off Starting offset within the array.
 325    * @param length The length of data to encode.
 326    * @param out The output stream used to return the decoded data.
 327    *
 328    * @return the number of bytes produced.
 329    * @exception IOException
 330    */
 331  0 public int decode(byte[] data, int off, int length, OutputStream out) throws IOException {
 332    // make sure we're writing to the correct stream
 333  0 this.out = out;
 334   
 335  0 int endOffset = off + length;
 336  0 int bytesWritten = 0;
 337   
 338  0 while (off < endOffset) {
 339  0 byte ch = data[off++];
 340   
 341    // space characters are a pain. We need to scan ahead until we find a non-space character.
 342    // if the character is a line terminator, we need to discard the blanks.
 343  0 if (ch == ' ') {
 344  0 int trailingSpaces = 1;
 345    // scan forward, counting the characters.
 346  0 while (off < endOffset && data[off] == ' ') {
 347    // step forward and count this.
 348  0 off++;
 349  0 trailingSpaces++;
 350    }
 351    // is this a lineend at the current location?
 352  0 if (off >= endOffset || data[off] == '\r' || data[off] == '\n') {
 353    // go to the next one
 354  0 continue;
 355    }
 356    else {
 357    // make sure we account for the spaces in the output count.
 358  0 bytesWritten += trailingSpaces;
 359    // write out the blank characters we counted and continue with the non-blank.
 360  0 while (trailingSpaces-- > 0) {
 361  0 out.write(' ');
 362    }
 363    }
 364    }
 365  0 else if (ch == '=') {
 366    // we found an encoded character. Reduce the 3 char sequence to one.
 367    // but first, make sure we have two characters to work with.
 368  0 if (off + 1 >= endOffset) {
 369  0 throw new IOException("Invalid quoted printable encoding");
 370    }
 371    // convert the two bytes back from hex.
 372  0 byte b1 = data[off++];
 373  0 byte b2 = data[off++];
 374   
 375    // we've found an encoded carriage return. The next char needs to be a newline
 376  0 if (b1 == '\r') {
 377  0 if (b2 != '\n') {
 378  0 throw new IOException("Invalid quoted printable encoding");
 379    }
 380    // this was a soft linebreak inserted by the encoding. We just toss this away
 381    // on decode.
 382    }
 383    else {
 384    // this is a hex pair we need to convert back to a single byte.
 385  0 b1 = decodingTable[b1];
 386  0 b2 = decodingTable[b2];
 387  0 out.write((b1 << 4) | b2);
 388    // 3 bytes in, one byte out
 389  0 bytesWritten++;
 390    }
 391    }
 392    else {
 393    // simple character, just write it out.
 394  0 out.write(ch);
 395  0 bytesWritten++;
 396    }
 397    }
 398   
 399  0 return bytesWritten;
 400    }
 401   
 402    /**
 403    * Decode a byte array of data.
 404    *
 405    * @param data The data array.
 406    * @param out The output stream target for the decoded data.
 407    *
 408    * @return The number of bytes written to the stream.
 409    * @exception IOException
 410    */
 411  7 public int decodeWord(byte[] data, OutputStream out) throws IOException {
 412  7 return decodeWord(data, 0, data.length, out);
 413    }
 414   
 415   
 416    /**
 417    * decode the uuencoded byte data writing it to the given output stream
 418    *
 419    * @param data The array of byte data to decode.
 420    * @param off Starting offset within the array.
 421    * @param length The length of data to encode.
 422    * @param out The output stream used to return the decoded data.
 423    *
 424    * @return the number of bytes produced.
 425    * @exception IOException
 426    */
 427  7 public int decodeWord(byte[] data, int off, int length, OutputStream out) throws IOException {
 428    // make sure we're writing to the correct stream
 429  7 this.out = out;
 430   
 431  7 int endOffset = off + length;
 432  7 int bytesWritten = 0;
 433   
 434  7 while (off < endOffset) {
 435  96 byte ch = data[off++];
 436   
 437    // space characters were translated to '_' on encode, so we need to translate them back.
 438  96 if (ch == '_') {
 439  15 out.write(' ');
 440    }
 441  81 else if (ch == '=') {
 442    // we found an encoded character. Reduce the 3 char sequence to one.
 443    // but first, make sure we have two characters to work with.
 444  30 if (off + 1 >= endOffset) {
 445  0 throw new IOException("Invalid quoted printable encoding");
 446    }
 447    // convert the two bytes back from hex.
 448  30 byte b1 = data[off++];
 449  30 byte b2 = data[off++];
 450   
 451    // we've found an encoded carriage return. The next char needs to be a newline
 452  30 if (b1 == '\r') {
 453  0 if (b2 != '\n') {
 454  0 throw new IOException("Invalid quoted printable encoding");
 455    }
 456    // this was a soft linebreak inserted by the encoding. We just toss this away
 457    // on decode.
 458    }
 459    else {
 460    // this is a hex pair we need to convert back to a single byte.
 461  30 byte c1 = decodingTable[b1];
 462  30 byte c2 = decodingTable[b2];
 463  30 out.write((c1 << 4) | c2);
 464    // 3 bytes in, one byte out
 465  30 bytesWritten++;
 466    }
 467    }
 468    else {
 469    // simple character, just write it out.
 470  51 out.write(ch);
 471  51 bytesWritten++;
 472    }
 473    }
 474   
 475  7 return bytesWritten;
 476    }
 477   
 478   
 479    /**
 480    * decode the UUEncoded String data writing it to the given output stream.
 481    *
 482    * @param data The String data to decode.
 483    * @param out The output stream to write the decoded data to.
 484    *
 485    * @return the number of bytes produced.
 486    * @exception IOException
 487    */
 488  0 public int decode(String data, OutputStream out) throws IOException {
 489  0 try {
 490    // just get the byte data and decode.
 491  0 byte[] bytes = data.getBytes("US-ASCII");
 492  0 return decode(bytes, 0, bytes.length, out);
 493    } catch (UnsupportedEncodingException e) {
 494  0 throw new IOException("Invalid UUEncoding");
 495    }
 496    }
 497   
 498  765 private void checkLineLength(int required) throws IOException {
 499    // if we're at our line length limit, write out a soft line break and reset.
 500  765 if ((lineCount + required) > lineLength ) {
 501  23 out.write('=');
 502  23 out.write('\r');
 503  23 out.write('\n');
 504  23 bytesWritten += 3;
 505  23 lineCount = 0;
 506    }
 507    }
 508   
 509   
 510  522 public void writeEncodedCharacter(int ch) throws IOException {
 511    // we need 3 characters for an encoded value
 512  522 checkLineLength(3);
 513  522 out.write('=');
 514  522 out.write(encodingTable[ch >> 4]);
 515  522 out.write(encodingTable[ch & 0x0F]);
 516  522 lineCount += 3;
 517  522 bytesWritten += 3;
 518    }
 519   
 520   
 521  243 public void writeCharacter(int ch) throws IOException {
 522    // we need 3 characters for an encoded value
 523  243 checkLineLength(1);
 524  243 out.write(ch);
 525  243 lineCount++;
 526  243 bytesWritten++;
 527    }
 528   
 529   
 530  0 public void writeEOL() throws IOException {
 531  0 out.write('\r');
 532  0 out.write('\n');
 533  0 lineCount = 0;
 534  0 bytesWritten += 3;
 535    }
 536   
 537   
 538  788 public int decode(InputStream in) throws IOException {
 539   
 540    // we potentially need to scan over spans of whitespace characters to determine if they're real
 541    // we just return blanks until the count goes to zero.
 542  788 if (deferredWhitespace > 0) {
 543  0 deferredWhitespace--;
 544  0 return ' ';
 545    }
 546   
 547    // we may have needed to scan ahead to find the first non-blank character, which we would store here.
 548    // hand that back once we're done with the blanks.
 549  788 if (cachedCharacter != -1) {
 550  0 int result = cachedCharacter;
 551  0 cachedCharacter = -1;
 552  0 return result;
 553    }
 554   
 555  788 int ch = in.read();
 556   
 557    // reflect back an EOF condition.
 558  788 if (ch == -1) {
 559  0 return -1;
 560    }
 561   
 562    // space characters are a pain. We need to scan ahead until we find a non-space character.
 563    // if the character is a line terminator, we need to discard the blanks.
 564  788 if (ch == ' ') {
 565    // scan forward, counting the characters.
 566  0 while ((ch = in.read()) == ' ') {
 567  0 deferredWhitespace++;
 568    }
 569   
 570    // is this a lineend at the current location?
 571  0 if (ch == -1 || ch == '\r' || ch == '\n') {
 572    // those blanks we so zealously counted up don't really exist. Clear out the counter.
 573  0 deferredWhitespace = 0;
 574    // return the real significant character now.
 575  0 return ch;
 576    }
 577    else {
 578    // remember this character for later, after we've used up the deferred blanks.
 579  0 cachedCharacter = ch;
 580    // return this space. We did not include this one in the deferred count, so we're right in sync.
 581  0 return ' ';
 582    }
 583    }
 584  788 else if (ch == '=') {
 585  545 int b1 = in.read();
 586    // we need to get two characters after the quotation marker
 587  545 if (b1 == -1) {
 588  0 throw new IOException("Truncated quoted printable data");
 589    }
 590  545 int b2 = in.read();
 591    // we need to get two characters after the quotation marker
 592  545 if (b2 == -1) {
 593  0 throw new IOException("Truncated quoted printable data");
 594    }
 595   
 596    // we've found an encoded carriage return. The next char needs to be a newline
 597  545 if (b1 == '\r') {
 598  23 if (b2 != '\n') {
 599  0 throw new IOException("Invalid quoted printable encoding");
 600    }
 601    // this was a soft linebreak inserted by the encoding. We just toss this away
 602    // on decode. We need to return something, so recurse and decode the next.
 603  23 return decode(in);
 604    }
 605    else {
 606    // this is a hex pair we need to convert back to a single byte.
 607  522 b1 = decodingTable[b1];
 608  522 b2 = decodingTable[b2];
 609  522 return (b1 << 4) | b2;
 610    }
 611    }
 612    else {
 613  243 return ch;
 614    }
 615    }
 616   
 617   
 618    /**
 619    * Perform RFC-2047 word encoding using Q-P data encoding.
 620    *
 621    * @param in The source for the encoded data.
 622    * @param charset The charset tag to be added to each encoded data section.
 623    * @param specials The set of special characters that we require to encoded.
 624    * @param out The output stream where the encoded data is to be written.
 625    * @param fold Controls whether separate sections of encoded data are separated by
 626    * linebreaks or whitespace.
 627    *
 628    * @exception IOException
 629    */
 630  9 public void encodeWord(InputStream in, String charset, String specials, OutputStream out, boolean fold) throws IOException
 631    {
 632    // we need to scan ahead in a few places, which may require pushing characters back on to the stream.
 633    // make sure we have a stream where this is possible.
 634  9 PushbackInputStream inStream = new PushbackInputStream(in);
 635  9 PrintStream writer = new PrintStream(out);
 636   
 637    // segments of encoded data are limited to 76 byes, including the control sections.
 638  9 int limit = 76 - 7 - charset.length();
 639  9 boolean firstLine = true;
 640  9 StringBuffer encodedString = new StringBuffer(76);
 641   
 642  9 while (true) {
 643   
 644    // encode another segment of data.
 645  18 encode(inStream, encodedString, specials, limit);
 646    // nothing encoded means we've hit the end of the data.
 647  18 if (encodedString.length() == 0) {
 648  9 break;
 649    }
 650    // if we have more than one segment, we need to insert separators. Depending on whether folding
 651    // was requested, this is either a blank or a linebreak.
 652  9 if (!firstLine) {
 653  0 if (fold) {
 654  0 writer.print("\r\n");
 655    }
 656    else {
 657  0 writer.print(" ");
 658    }
 659    }
 660   
 661    // add the encoded word header
 662  9 writer.print("=?");
 663  9 writer.print(charset);
 664  9 writer.print("?Q?");
 665    // the data
 666  9 writer.print(encodedString.toString());
 667    // and the terminator mark
 668  9 writer.print("?=");
 669  9 writer.flush();
 670   
 671    // we reset the string buffer and reuse it.
 672  9 encodedString.setLength(0);
 673    }
 674    }
 675    }
 676   
 677   
 678