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 }