View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.io;
19  
20  import java.io.IOException;
21  import java.io.Writer;
22  
23  /**
24   * DotTerminatedMessageWriter is a class used to write messages to a server that are terminated by a single dot followed by a <CR><LF> sequence and
25   * with double dots appearing at the beginning of lines which do not signal end of message yet start with a dot. Various Internet protocols such as
26   * NNTP and POP3 produce messages of this type.
27   * <p>
28   * This class handles the doubling of line-starting periods, converts single linefeeds to NETASCII newlines, and on closing will send the final message
29   * terminator dot and NETASCII newline sequence.
30   * </p>
31   */
32  public final class DotTerminatedMessageWriter extends Writer {
33      private static final int NOTHING_SPECIAL_STATE = 0;
34      private static final int LAST_WAS_CR_STATE = 1;
35      private static final int LAST_WAS_NL_STATE = 2;
36  
37      private int state;
38      private Writer output;
39  
40      /**
41       * Creates a DotTerminatedMessageWriter that wraps an existing Writer output destination.
42       *
43       * @param output The Writer output destination to write the message.
44       */
45      public DotTerminatedMessageWriter(final Writer output) {
46          super(output);
47          this.output = output;
48          this.state = NOTHING_SPECIAL_STATE;
49      }
50  
51      /**
52       * Flushes the underlying output, writing all buffered output, but doesn't actually close the underlying stream. The underlying stream may still be used for
53       * communicating with the server and therefore is not closed.
54       *
55       * @throws IOException If an error occurs while writing to the underlying output or closing the Writer.
56       */
57      @Override
58      public void close() throws IOException {
59          synchronized (lock) {
60              if (output == null) {
61                  return;
62              }
63  
64              if (state == LAST_WAS_CR_STATE) {
65                  output.write('\n');
66              } else if (state != LAST_WAS_NL_STATE) {
67                  output.write("\r\n");
68              }
69  
70              output.write(".\r\n");
71  
72              output.flush();
73              output = null;
74          }
75      }
76  
77      /**
78       * Flushes the underlying output, writing all buffered output.
79       *
80       * @throws IOException If an error occurs while writing to the underlying output.
81       */
82      @Override
83      public void flush() throws IOException {
84          synchronized (lock) {
85              output.flush();
86          }
87      }
88  
89      /**
90       * Writes a character array to the output.
91       *
92       * @param buffer The character array to write.
93       * @throws IOException If an error occurs while writing to the underlying output.
94       */
95      @Override
96      public void write(final char[] buffer) throws IOException {
97          write(buffer, 0, buffer.length);
98      }
99  
100     /**
101      * Writes a number of characters from a character array to the output starting from a given offset.
102      *
103      * @param buffer The character array to write.
104      * @param offset The offset into the array at which to start copying data.
105      * @param length The number of characters to write.
106      * @throws IOException If an error occurs while writing to the underlying output.
107      */
108     @Override
109     public void write(final char[] buffer, int offset, int length) throws IOException {
110         synchronized (lock) {
111             while (length-- > 0) {
112                 write(buffer[offset++]);
113             }
114         }
115     }
116 
117     /**
118      * Writes a character to the output. Note that a call to this method may result in multiple writes to the underling Writer in order to convert naked
119      * linefeeds to NETASCII line separators and to double line-leading periods. This is transparent to the programmer and is only mentioned for completeness.
120      *
121      * @param ch The character to write.
122      * @throws IOException If an error occurs while writing to the underlying output.
123      */
124     @Override
125     public void write(final int ch) throws IOException {
126         synchronized (lock) {
127             switch (ch) {
128             case '\r':
129                 state = LAST_WAS_CR_STATE;
130                 output.write('\r');
131                 return;
132             case '\n':
133                 if (state != LAST_WAS_CR_STATE) {
134                     output.write('\r');
135                 }
136                 output.write('\n');
137                 state = LAST_WAS_NL_STATE;
138                 return;
139             case '.':
140                 // Double the dot at the beginning of a line
141                 if (state == LAST_WAS_NL_STATE) {
142                     output.write('.');
143                 }
144                 // falls through$
145             default:
146                 state = NOTHING_SPECIAL_STATE;
147                 output.write(ch);
148             }
149         }
150     }
151 
152     /**
153      * Writes a String to the output.
154      *
155      * @param string The String to write.
156      * @throws IOException If an error occurs while writing to the underlying output.
157      */
158     @Override
159     public void write(final String string) throws IOException {
160         write(string.toCharArray());
161     }
162 
163     /**
164      * Writes part of a String to the output starting from a given offset.
165      *
166      * @param string The String to write.
167      * @param offset The offset into the String at which to start copying data.
168      * @param length The number of characters to write.
169      * @throws IOException If an error occurs while writing to the underlying output.
170      */
171     @Override
172     public void write(final String string, final int offset, final int length) throws IOException {
173         write(string.toCharArray(), offset, length);
174     }
175 
176 }