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 }