001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.io; 019 020import java.io.IOException; 021import java.io.Writer; 022 023/** 024 * 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 025 * 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 026 * NNTP and POP3 produce messages of this type. 027 * <p> 028 * This class handles the doubling of line-starting periods, converts single linefeeds to NETASCII newlines, and on closing will send the final message 029 * terminator dot and NETASCII newline sequence. 030 * </p> 031 */ 032public final class DotTerminatedMessageWriter extends Writer { 033 private static final int NOTHING_SPECIAL_STATE = 0; 034 private static final int LAST_WAS_CR_STATE = 1; 035 private static final int LAST_WAS_NL_STATE = 2; 036 037 private int state; 038 private Writer output; 039 040 /** 041 * Creates a DotTerminatedMessageWriter that wraps an existing Writer output destination. 042 * 043 * @param output The Writer output destination to write the message. 044 */ 045 public DotTerminatedMessageWriter(final Writer output) { 046 super(output); 047 this.output = output; 048 this.state = NOTHING_SPECIAL_STATE; 049 } 050 051 /** 052 * Flushes the underlying output, writing all buffered output, but doesn't actually close the underlying stream. The underlying stream may still be used for 053 * communicating with the server and therefore is not closed. 054 * 055 * @throws IOException If an error occurs while writing to the underlying output or closing the Writer. 056 */ 057 @Override 058 public void close() throws IOException { 059 synchronized (lock) { 060 if (output == null) { 061 return; 062 } 063 064 if (state == LAST_WAS_CR_STATE) { 065 output.write('\n'); 066 } else if (state != LAST_WAS_NL_STATE) { 067 output.write("\r\n"); 068 } 069 070 output.write(".\r\n"); 071 072 output.flush(); 073 output = null; 074 } 075 } 076 077 /** 078 * Flushes the underlying output, writing all buffered output. 079 * 080 * @throws IOException If an error occurs while writing to the underlying output. 081 */ 082 @Override 083 public void flush() throws IOException { 084 synchronized (lock) { 085 output.flush(); 086 } 087 } 088 089 /** 090 * Writes a character array to the output. 091 * 092 * @param buffer The character array to write. 093 * @throws IOException If an error occurs while writing to the underlying output. 094 */ 095 @Override 096 public void write(final char[] buffer) throws IOException { 097 write(buffer, 0, buffer.length); 098 } 099 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}