org.apache.sshd.server.scp.InputStreamReader Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright (c) 2002-2016, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package org.apache.sshd.server.scp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
/**
*
* NOTE for SSHD: the default InputStreamReader that comes from the JRE
* usually read more bytes than needed from the input stream, which
* is not usable in a character per character model used in the terminal.
* We thus use the harmony code which only reads the minimal number of bytes.
*/
/**
* A class for turning a byte stream into a character stream. Data read from the source input stream is converted into
* characters by either a default or a provided character converter. The default encoding is taken from the
* "file.encoding" system property. {@code InputStreamReader} contains a buffer of bytes read from the source stream and
* converts these into characters as needed. The buffer size is 8K.
*
* @see OutputStreamWriter
*/
public class InputStreamReader extends Reader {
private static final int BUFFER_SIZE = 4;
CharsetDecoder decoder;
ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE);
char pending = (char) -1;
private InputStream in;
private boolean endOfInput;
/**
* Constructs a new {@code InputStreamReader} on the {@link InputStream} {@code in}. This constructor sets the
* character converter to the encoding specified in the "file.encoding" property and falls back to ISO 8859_1
* (ISO-Latin-1) if the property doesn't exist.
*
* @param in the input stream from which to read characters.
*/
public InputStreamReader(InputStream in) {
super(in);
this.in = in;
decoder = Charset.defaultCharset().newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in}. The character converter that is used to decode
* bytes into characters is identified by name by {@code enc}. If the encoding cannot be found, an
* UnsupportedEncodingException error is thrown.
*
* @param in the InputStream from which to read characters.
* @param enc identifies the character converter to use.
* @throws NullPointerException if {@code enc} is {@code null}.
* @throws UnsupportedEncodingException if the encoding specified by {@code enc} cannot be found.
*/
public InputStreamReader(InputStream in, final String enc)
throws UnsupportedEncodingException {
super(in);
if (enc == null) {
throw new NullPointerException();
}
this.in = in;
try {
decoder = Charset.forName(enc).newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
} catch (IllegalArgumentException e) {
throw (UnsupportedEncodingException) new UnsupportedEncodingException(enc).initCause(e);
}
bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in} and CharsetDecoder {@code dec}.
*
* @param in the source InputStream from which to read characters.
* @param dec the CharsetDecoder used by the character conversion.
*/
public InputStreamReader(InputStream in, CharsetDecoder dec) {
super(in);
dec.averageCharsPerByte();
this.in = in;
decoder = dec;
bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in} and Charset {@code charset}.
*
* @param in the source InputStream from which to read characters.
* @param charset the Charset that defines the character converter
*/
public InputStreamReader(InputStream in, Charset charset) {
super(in);
this.in = in;
decoder = charset.newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
bytes.limit(0);
}
/**
* Closes this reader. This implementation closes the source InputStream and releases all local storage.
*
* @throws IOException if an error occurs attempting to close this reader.
*/
@Override
public void close() throws IOException {
synchronized (lock) {
decoder = null;
if (in != null) {
in.close();
in = null;
}
}
}
/**
* Returns the name of the encoding used to convert bytes into characters. The value {@code null} is returned if
* this reader has been closed.
*
* @return the name of the character converter or {@code null} if this reader is closed.
*/
public String getEncoding() {
if (!isOpen()) {
return null;
}
return decoder.charset().name();
}
/**
* Reads a single character from this reader and returns it as an integer with the two higher-order bytes set to 0.
* Returns -1 if the end of the reader has been reached. The byte value is either obtained from converting bytes in
* this reader's buffer or by first filling the buffer from the source InputStream and then reading from the buffer.
*
* @return the character read or -1 if the end of the reader has been reached.
* @throws IOException if this reader is closed or some other I/O error occurs.
*/
@Override
public int read() throws IOException {
synchronized (lock) {
if (!isOpen()) {
throw new IOException("InputStreamReader is closed.");
}
if (pending != (char) -1) {
char c = pending;
pending = (char) -1;
return c;
}
char buf[] = new char[2];
int nb = read(buf, 0, 2);
if (nb == 2) {
pending = buf[1];
}
if (nb > 0) {
return buf[0];
} else {
return -1;
}
}
}
/**
* Reads at most {@code length} characters from this reader and stores them at position {@code offset} in the
* character array {@code buf}. Returns the number of characters actually read or -1 if the end of the reader has
* been reached. The bytes are either obtained from converting bytes in this reader's buffer or by first filling the
* buffer from the source InputStream and then reading from the buffer.
*
* @param buf the array to store the characters read.
* @param offset the initial position in {@code buf} to store the characters read from this
* reader.
* @param length the maximum number of characters to read.
* @return the number of characters read or -1 if the end of the reader has been reached.
* @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code offset + length} is
* greater than the length of {@code buf}.
* @throws IOException if this reader is closed or some other I/O error occurs.
*/
@Override
public int read(char[] buf, int offset, int length) throws IOException {
synchronized (lock) {
if (!isOpen()) {
throw new IOException("InputStreamReader is closed.");
}
if (offset < 0 || offset > buf.length - length || length < 0) {
throw new IndexOutOfBoundsException();
}
if (length == 0) {
return 0;
}
CharBuffer out = CharBuffer.wrap(buf, offset, length);
CoderResult result = CoderResult.UNDERFLOW;
// bytes.remaining() indicates number of bytes in buffer
// when 1-st time entered, it'll be equal to zero
boolean needInput = !bytes.hasRemaining();
while (out.position() == offset) {
// fill the buffer if needed
if (needInput) {
try {
if ((in.available() == 0)
&& (out.position() > offset)) {
// we could return the result without blocking read
break;
}
} catch (IOException e) {
// available didn't work so just try the read
}
int off = bytes.arrayOffset() + bytes.limit();
int was_red = in.read(bytes.array(), off, 1);
if (was_red == -1) {
endOfInput = true;
break;
} else if (was_red == 0) {
break;
}
bytes.limit(bytes.limit() + was_red);
}
// decode bytes
result = decoder.decode(bytes, out, false);
if (result.isUnderflow()) {
// compact the buffer if no space left
if (bytes.limit() == bytes.capacity()) {
bytes.compact();
bytes.limit(bytes.position());
bytes.position(0);
}
needInput = true;
} else {
break;
}
}
if (result == CoderResult.UNDERFLOW && endOfInput) {
result = decoder.decode(bytes, out, true);
decoder.flush(out);
decoder.reset();
}
if (result.isMalformed()) {
throw new MalformedInputException(result.length());
} else if (result.isUnmappable()) {
throw new UnmappableCharacterException(result.length());
}
return out.position() - offset == 0 ? -1 : out.position() - offset;
}
}
/*
* Answer a boolean indicating whether or not this InputStreamReader is
* open.
*/
private boolean isOpen() {
return in != null;
}
/**
* Indicates whether this reader is ready to be read without blocking. If the result is {@code true}, the next
* {@code read()} will not block. If the result is {@code false} then this reader may or may not block when
* {@code read()} is called. This implementation returns {@code true} if there are bytes available in the buffer or
* the source stream has bytes available.
*
* @return {@code true} if the receiver will not block when {@code read()} is called, {@code false} if
* unknown or blocking will occur.
* @throws IOException if this reader is closed or some other I/O error occurs.
*/
@Override
public boolean ready() throws IOException {
synchronized (lock) {
if (in == null) {
throw new IOException("InputStreamReader is closed.");
}
try {
return bytes.hasRemaining() || in.available() > 0;
} catch (IOException e) {
return false;
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy