View Javadoc

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  package org.apache.james.util.stream;
21  
22  import java.io.FilterOutputStream;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  
26  /**
27   * A Filter for use with SMTP or other protocols in which lines must end with
28   * CRLF. Converts every "isolated" occourency of \r or \n with \r\n
29   * 
30   * RFC 2821 #2.3.7 mandates that line termination is CRLF, and that CR and LF
31   * must not be transmitted except in that pairing. If we get a naked LF, convert
32   * to CRLF.
33   * 
34   */
35  public class CRLFOutputStream extends FilterOutputStream {
36  
37      /**
38       * Counter for number of last (0A or 0D).
39       */
40      protected int statusLast;
41  
42      protected final static int LAST_WAS_OTHER = 0;
43  
44      protected final static int LAST_WAS_CR = 1;
45  
46      protected final static int LAST_WAS_LF = 2;
47      
48      protected boolean startOfLine = true;
49  
50      /**
51       * Constructor that wraps an OutputStream.
52       * 
53       * @param out
54       *                the OutputStream to be wrapped
55       */
56      public CRLFOutputStream(OutputStream out) {
57          super(out);
58          statusLast = LAST_WAS_LF; // we already assume a CRLF at beginning
59                                      // (otherwise TOP would not work correctly
60                                      // !)
61      }
62  
63      /**
64       * Writes a byte to the stream Fixes any naked CR or LF to the RFC 2821
65       * mandated CFLF pairing.
66       * 
67       * @param b
68       *                the byte to write
69       * 
70       * @throws IOException
71       *                 if an error occurs writing the byte
72       */
73      public void write(int b) throws IOException {
74          switch (b) {
75              case '\r':
76                  out.write('\r');
77                  out.write('\n');
78                  startOfLine = true;
79                  statusLast = LAST_WAS_CR;
80                  break;
81              case '\n':
82                  if (statusLast != LAST_WAS_CR) {
83                      out.write('\r');
84                      out.write('\n');
85                      startOfLine = true;
86                  }
87                  statusLast = LAST_WAS_LF;
88                  break;
89              default:
90                  // we're no longer at the start of a line
91                  out.write(b);
92                  startOfLine = false;
93                  statusLast = LAST_WAS_OTHER;
94                  break;
95          }
96      }
97      
98      /**
99       * Provides an extension point for ExtraDotOutputStream to be able to add dots
100      * at the beginning of new lines.
101      * 
102      * @see java.io.FilterOutputStream#write(byte[], int, int)
103      */
104     protected void writeChunk(byte buffer[], int offset, int length) throws IOException {
105         out.write(buffer, offset, length);
106     }
107 
108     /**
109      * @see java.io.FilterOutputStream#write(byte[], int, int)
110      */
111     public synchronized void write(byte buffer[], int offset, int length)
112             throws IOException {
113         /* optimized */
114         int lineStart = offset;
115         for (int i = offset; i < length + offset; i++) {
116             switch(buffer[i]) {
117             case '\r':
118                 // CR case. Write down the last line
119                 // and position the new lineStart at the next char
120                 writeChunk(buffer, lineStart, i - lineStart);
121                 out.write('\r');
122                 out.write('\n');
123                 startOfLine = true;
124                 lineStart = i + 1;
125                 statusLast = LAST_WAS_CR;
126                 break;
127             case '\n':
128                 if (statusLast != LAST_WAS_CR) {
129                     writeChunk(buffer, lineStart, i - lineStart);
130                     out.write('\r');
131                     out.write('\n');
132                     startOfLine = true;
133                 }
134                 lineStart = i + 1;
135                 statusLast = LAST_WAS_LF;
136                 break;
137             default:
138                 statusLast = LAST_WAS_OTHER;
139             }
140         }
141         if (length + offset > lineStart) {
142             writeChunk(buffer, lineStart, length + offset - lineStart);
143             startOfLine = false;
144         }
145     }
146 
147 
148     /**
149      * Ensure that the stream is CRLF terminated.
150      * 
151      * @throws IOException
152      *                 if an error occurs writing the byte
153      */
154     public void checkCRLFTerminator() throws IOException {
155         if (statusLast == LAST_WAS_OTHER) {
156             out.write('\r');
157             out.write('\n');
158             statusLast = LAST_WAS_CR;
159         }
160     }
161 }