001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020 package org.apache.geronimo.javamail.util;
021
022 import java.io.IOException;
023 import java.io.Reader;
024
025 /**
026 * An implementation of an OutputStream that performs MIME linebreak
027 * canonicalization and "byte-stuff" so that data content does not get mistaken
028 * for a message data-end marker (CRLF.CRLF)l
029 *
030 * @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
031 */
032 public class MIMEInputReader extends Reader {
033
034 // the wrappered output stream.
035 protected Reader source;
036
037 // a flag to indicate we've just processed a line break. This is used for
038 // byte stuffing purposes. This
039 // is initially true, because if the first character of the content is a
040 // period, we need to byte-stuff
041 // immediately.
042 protected boolean atLineBreak = true;
043 // we've hit the terminating marker on the data
044 protected boolean endOfData = false;
045
046
047 /**
048 * Create an input reader that reads from the source input reader
049 *
050 * @param out
051 * The wrapped Reader
052 */
053 public MIMEInputReader(Reader source) {
054 this.source = source;
055 }
056
057 /**
058 * Concrete implementation of the Reader read()
059 * abstract method. This appears to be the only
060 * abstract method, so all of the other reads must
061 * funnel through this method.
062 *
063 * @param buffer The buffer to fill.
064 * @param off The offset to start adding characters.
065 * @param len The number of requested characters.
066 *
067 * @return The actual count of characters read. Returns -1
068 * if we hit an EOF without reading any characters.
069 * @exception IOException
070 */
071 public int read(char buffer[], int off, int len) throws IOException {
072 // we've been asked for nothing, we'll return nothing.
073 if (len == 0) {
074 return 0;
075 }
076
077 // have we hit the end of data? Return a -1 indicator
078 if (endOfData) {
079 return -1;
080 }
081
082 // number of bytes read
083 int bytesRead = 0;
084
085 int lastRead;
086
087 while (bytesRead < len && (lastRead = source.read()) >= 0) {
088 // We are checking for the end of a multiline response
089 // the format is .CRLF
090
091 // we also have to check for byte-stuffing situation
092 // where we remove a leading period.
093 if (atLineBreak && lastRead == '.') {
094 // step to the next character
095 lastRead = source.read();
096 // we have ".CR"...this is our end of stream
097 // marker. Consume the LF from the reader and return
098 if (lastRead == '\r') {
099 source.read();
100 // no more reads from this point.
101 endOfData = true;
102 break;
103 }
104 // the next character SHOULD be a ".". We swallow the first
105 // dot and just write the next character to the buffer
106 atLineBreak = false;
107 }
108 else if (lastRead == '\n') {
109 // hit an end-of-line marker?
110 // remember we just had a line break
111 atLineBreak = true;
112 }
113 else
114 {
115 // something other than a line break character
116 atLineBreak = false;
117 }
118 // add the character to the buffer
119 buffer[off++] = (char)lastRead;
120 bytesRead++;
121 }
122
123 // we must have had an EOF condition of some sort
124 if (bytesRead == 0) {
125 return -1;
126 }
127 // return the actual length read in
128 return bytesRead;
129 }
130
131 /**
132 * Close the stream. This is a NOP for this stream.
133 *
134 * @exception IOException
135 */
136 public void close() throws IOException {
137 // does nothing
138 }
139 }
140