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: ****************************************************************/package org.apache.james.util;
019:
020: import java.io.InputStream;
021: import java.io.Reader;
022: import java.io.UnsupportedEncodingException;
023: import java.io.IOException;
024:
025: /**
026: * A Reader for use with SMTP or other protocols in which lines
027: * must end with CRLF. Extends Reader and overrides its
028: * readLine() method. The Reader readLine() method cannot
029: * serve for SMTP because it ends lines with either CR or LF alone.
030: */
031: public class CRLFTerminatedReader extends Reader {
032:
033: public class TerminationException extends IOException {
034: private int where;
035:
036: public TerminationException(int where) {
037: super ();
038: this .where = where;
039: }
040:
041: public TerminationException(String s, int where) {
042: super (s);
043: this .where = where;
044: }
045:
046: public int position() {
047: return where;
048: }
049: }
050:
051: public class LineLengthExceededException extends IOException {
052: public LineLengthExceededException(String s) {
053: super (s);
054: }
055: }
056:
057: /**
058: * Constructs this CRLFTerminatedReader.
059: * @param in an InputStream
060: * @param charsetName the String name of a supported charset.
061: * "ASCII" is common here.
062: * @throws UnsupportedEncodingException if the named charset
063: * is not supported
064: */
065: InputStream in;
066:
067: public CRLFTerminatedReader(InputStream in) {
068: this .in = in;
069: }
070:
071: public CRLFTerminatedReader(InputStream in, String enc)
072: throws UnsupportedEncodingException {
073: this (in);
074: }
075:
076: private StringBuffer lineBuffer = new StringBuffer();
077: private final int EOF = -1, CR = 13, LF = 10;
078:
079: private int tainted = -1;
080:
081: /**
082: * Read a line of text which is terminated by CRLF. The concluding
083: * CRLF characters are not returned with the String, but if either CR
084: * or LF appears in the text in any other sequence it is returned
085: * in the String like any other character. Some characters at the
086: * end of the stream may be lost if they are in a "line" not
087: * terminated by CRLF.
088: *
089: * @return either a String containing the contents of a
090: * line which must end with CRLF, or null if the end of the
091: * stream has been reached, possibly discarding some characters
092: * in a line not terminated with CRLF.
093: * @throws IOException if an I/O error occurs.
094: */
095: public String readLine() throws IOException {
096:
097: //start with the StringBuffer empty
098: lineBuffer.delete(0, lineBuffer.length());
099:
100: /* This boolean tells which state we are in,
101: * depending upon whether or not we got a CR
102: * in the preceding read().
103: */
104: boolean cr_just_received = false;
105:
106: // Until we add support for specifying a maximum line lenth as
107: // a Service Extension, limit lines to 2K, which is twice what
108: // RFC 2821 4.5.3.1 requires.
109: while (lineBuffer.length() <= 2048) {
110: int inChar = read();
111:
112: if (!cr_just_received) {
113: //the most common case, somewhere before the end of a line
114: switch (inChar) {
115: case CR:
116: cr_just_received = true;
117: break;
118: case EOF:
119: return null; // premature EOF -- discards data(?)
120: case LF: //the normal ending of a line
121: if (tainted == -1)
122: tainted = lineBuffer.length();
123: // intentional fall-through
124: default:
125: lineBuffer.append((char) inChar);
126: }
127: } else {
128: // CR has been received, we may be at end of line
129: switch (inChar) {
130: case LF: // LF without a preceding CR
131: if (tainted != -1) {
132: int pos = tainted;
133: tainted = -1;
134: throw new TerminationException(
135: "\"bare\" CR or LF in data stream", pos);
136: }
137: return lineBuffer.toString();
138: case EOF:
139: return null; // premature EOF -- discards data(?)
140: case CR: //we got two (or more) CRs in a row
141: if (tainted == -1)
142: tainted = lineBuffer.length();
143: lineBuffer.append((char) CR);
144: break;
145: default: //we got some other character following a CR
146: if (tainted == -1)
147: tainted = lineBuffer.length();
148: lineBuffer.append((char) CR);
149: lineBuffer.append((char) inChar);
150: cr_just_received = false;
151: }
152: }
153: }//while
154: throw new LineLengthExceededException(
155: "Exceeded maximum line length");
156: }//method readLine()
157:
158: public int read() throws IOException {
159: return in.read();
160: }
161:
162: public boolean ready() throws IOException {
163: return in.available() > 0;
164: }
165:
166: public int read(char cbuf[], int off, int len) throws IOException {
167: byte[] temp = new byte[len];
168: int result = in.read(temp, 0, len);
169: for (int i = 0; i < result; i++)
170: cbuf[i] = (char) temp[i];
171: return result;
172: }
173:
174: public void close() throws IOException {
175: in.close();
176: }
177: }
|