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.InputStream; 22 import java.io.PushbackInputStream; 23 import java.nio.charset.StandardCharsets; 24 25 import org.apache.commons.net.util.NetConstants; 26 27 /** 28 * This class wraps an input stream, replacing all occurrences of <CR><LF> (carriage return followed by a linefeed), which is the NETASCII standard 29 * for representing a newline, with the local line separator representation. You would use this class to implement ASCII file transfers requiring conversion 30 * from NETASCII. 31 */ 32 public final class FromNetASCIIInputStream extends PushbackInputStream { 33 34 static final boolean NO_CONVERSION_REQUIRED = System.lineSeparator().equals("\r\n"); 35 static final byte[] LINE_SEPARATOR_BYTES = System.lineSeparator().getBytes(StandardCharsets.US_ASCII); 36 37 /** 38 * Returns true if the NetASCII line separator differs from the system line separator, false if they are the same. This method is useful to determine 39 * whether or not you need to instantiate a FromNetASCIIInputStream object. 40 * 41 * @return True if the NETASCII line separator differs from the local system line separator, false if they are the same. 42 */ 43 public static boolean isConversionRequired() { 44 return !NO_CONVERSION_REQUIRED; 45 } 46 47 private int length; 48 49 /** 50 * Creates a FromNetASCIIInputStream instance that wraps an existing InputStream. 51 * 52 * @param input the stream to wrap 53 */ 54 public FromNetASCIIInputStream(final InputStream input) { 55 super(input, LINE_SEPARATOR_BYTES.length + 1); 56 } 57 58 // PushbackInputStream in JDK 1.1.3 returns the wrong thing 59 // TODO - can we delete this override now? 60 /** 61 * Returns the number of bytes that can be read without blocking EXCEPT when newline conversions have to be made somewhere within the available block of 62 * bytes. In other words, you really should not rely on the value returned by this method if you are trying to avoid blocking. 63 */ 64 @Override 65 public int available() throws IOException { 66 if (in == null) { 67 throw new IOException("Stream closed"); 68 } 69 return buf.length - pos + in.available(); 70 } 71 72 /** 73 * Reads and returns the next byte in the stream. If the end of the message has been reached, returns -1. Note that a call to this method may result in 74 * multiple reads from the underlying input stream in order to convert NETASCII line separators to the local line separator format. This is transparent to 75 * the programmer and is only mentioned for completeness. 76 * 77 * @return The next character in the stream. Returns -1 if the end of the stream has been reached. 78 * @throws IOException If an error occurs while reading the underlying stream. 79 */ 80 @Override 81 public int read() throws IOException { 82 if (NO_CONVERSION_REQUIRED) { 83 return super.read(); 84 } 85 return readInt(); 86 } 87 88 /** 89 * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the stream has been reached. 90 * 91 * @param buffer The byte array in which to store the data. 92 * @return The number of bytes read. Returns -1 if the end of the message has been reached. 93 * @throws IOException If an error occurs in reading the underlying stream. 94 */ 95 @Override 96 public int read(final byte buffer[]) throws IOException { 97 return read(buffer, 0, buffer.length); 98 } 99 100 /** 101 * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the message has been reached. 102 * The characters are stored in the array starting from the given offset and up to the length specified. 103 * 104 * @param buffer The byte array in which to store the data. 105 * @param offset The offset into the array at which to start storing data. 106 * @param length The number of bytes to read. 107 * @return The number of bytes read. Returns -1 if the end of the stream has been reached. 108 * @throws IOException If an error occurs while reading the underlying stream. 109 */ 110 @Override 111 public int read(final byte buffer[], int offset, final int length) throws IOException { 112 if (NO_CONVERSION_REQUIRED) { 113 return super.read(buffer, offset, length); 114 } 115 if (length < 1) { 116 return 0; 117 } 118 int ch; 119 final int off; 120 ch = available(); 121 this.length = Math.min(length, ch); 122 // If nothing is available, block to read only one character 123 if (this.length < 1) { 124 this.length = 1; 125 } 126 if ((ch = readInt()) == -1) { 127 return NetConstants.EOS; 128 } 129 off = offset; 130 do { 131 buffer[offset++] = (byte) ch; 132 } while (--this.length > 0 && (ch = readInt()) != -1); 133 return offset - off; 134 } 135 136 private int readInt() throws IOException { 137 int ch; 138 ch = super.read(); 139 if (ch == '\r') { 140 ch = super.read(); 141 if (ch != '\n') { 142 if (ch != -1) { 143 unread(ch); 144 } 145 return '\r'; 146 } 147 unread(LINE_SEPARATOR_BYTES); 148 ch = super.read(); 149 // This is a kluge for read(byte[], ...) to read the right amount 150 --length; 151 } 152 return ch; 153 } 154 155 }