1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20
21
22 package org.apache.james.socket;
23
24 import java.io.InputStream;
25 import java.io.Reader;
26 import java.io.UnsupportedEncodingException;
27 import java.io.IOException;
28
29 /**
30 * A Reader for use with SMTP or other protocols in which lines
31 * must end with CRLF. Extends Reader and overrides its
32 * readLine() method. The Reader readLine() method cannot
33 * serve for SMTP because it ends lines with either CR or LF alone.
34 */
35 public class CRLFTerminatedReader extends Reader {
36
37 public class TerminationException extends IOException {
38 private int where;
39 public TerminationException(int where) {
40 super();
41 this.where = where;
42 }
43
44 public TerminationException(String s, int where) {
45 super(s);
46 this.where = where;
47 }
48
49 public int position() {
50 return where;
51 }
52 }
53
54 public class LineLengthExceededException extends IOException {
55 public LineLengthExceededException(String s) {
56 super(s);
57 }
58 }
59
60 InputStream in;
61
62 public CRLFTerminatedReader(InputStream in) {
63 this.in = in;
64 }
65
66 /**
67 * Constructs this CRLFTerminatedReader.
68 * @param in an InputStream
69 * @param enc the String name of a supported charset.
70 * "ASCII" is common here.
71 * @throws UnsupportedEncodingException if the named charset
72 * is not supported
73 */
74 public CRLFTerminatedReader(InputStream in, String enc) throws UnsupportedEncodingException {
75 this(in);
76 }
77
78 private StringBuffer lineBuffer = new StringBuffer();
79 private final int
80 EOF = -1,
81 CR = 13,
82 LF = 10;
83
84 private int tainted = -1;
85
86 /**
87 * Read a line of text which is terminated by CRLF. The concluding
88 * CRLF characters are not returned with the String, but if either CR
89 * or LF appears in the text in any other sequence it is returned
90 * in the String like any other character. Some characters at the
91 * end of the stream may be lost if they are in a "line" not
92 * terminated by CRLF.
93 *
94 * @return either a String containing the contents of a
95 * line which must end with CRLF, or null if the end of the
96 * stream has been reached, possibly discarding some characters
97 * in a line not terminated with CRLF.
98 * @throws IOException if an I/O error occurs.
99 */
100 public String readLine() throws IOException{
101
102 //start with the StringBuffer empty
103 lineBuffer.delete(0, lineBuffer.length());
104
105 /* This boolean tells which state we are in,
106 * depending upon whether or not we got a CR
107 * in the preceding read().
108 */
109 boolean cr_just_received = false;
110
111 // Until we add support for specifying a maximum line lenth as
112 // a Service Extension, limit lines to 2K, which is twice what
113 // RFC 2821 4.5.3.1 requires.
114 while (lineBuffer.length() <= 2048) {
115 int inChar = read();
116
117 if (!cr_just_received){
118 //the most common case, somewhere before the end of a line
119 switch (inChar){
120 case CR : cr_just_received = true;
121 break;
122 case EOF : return null; // premature EOF -- discards data(?)
123 case LF : //the normal ending of a line
124 if (tainted == -1) tainted = lineBuffer.length();
125 // intentional fall-through
126 default : lineBuffer.append((char)inChar);
127 }
128 }else{
129 // CR has been received, we may be at end of line
130 switch (inChar){
131 case LF : // LF without a preceding CR
132 if (tainted != -1) {
133 int pos = tainted;
134 tainted = -1;
135 throw new TerminationException("\"bare\" CR or LF in data stream", pos);
136 }
137 return lineBuffer.toString();
138 case EOF : return null; // premature EOF -- discards data(?)
139 case CR : //we got two (or more) CRs in a row
140 if (tainted == -1) tainted = lineBuffer.length();
141 lineBuffer.append((char)CR);
142 break;
143 default : //we got some other character following a CR
144 if (tainted == -1) tainted = lineBuffer.length();
145 lineBuffer.append((char)CR);
146 lineBuffer.append((char)inChar);
147 cr_just_received = false;
148 }
149 }
150 }//while
151 throw new LineLengthExceededException("Exceeded maximum line length");
152 }//method readLine()
153
154 /**
155 * @see java.io.Reader#read()
156 */
157 public int read() throws IOException {
158 return in.read();
159 }
160
161 /**
162 * @see java.io.Reader#ready()
163 */
164 public boolean ready() throws IOException {
165 return in.available() > 0;
166 }
167
168 /**
169 * @see java.io.Reader#read(char[], int, int)
170 */
171 public int read(char cbuf[], int off, int len) throws IOException {
172 byte [] temp = new byte[len];
173 int result = in.read(temp, 0, len);
174 for (int i=0;i<result;i++) cbuf[i] = (char) temp[i];
175 return result;
176 }
177
178 /**
179 * @see java.io.Reader#close()
180 */
181 public void close() throws IOException {
182 in.close();
183 }
184 }