BufferedReader 的安全实现
Safe implementation of BufferdReader
我想使用 BufferedReader 读取上传到我的服务器的文件。
该文件将被写成 CSV 文件,但我不能假设这一点,所以我编写了一些测试,其中文件是图像或二进制文件(假设客户端向我发送了错误的文件或攻击者试图破坏我的服务),或者更糟的是,该文件是一个有效的 CSV 文件,但有一行 100MB。
我的应用程序可以处理这个问题,但它必须读取文件的第一行:
...
String firstLine = bufferedReader.readLine();
//Perform some validations and reject the file if it's not a CSV file
...
但是,当我编写一些测试代码时,我发现了一个潜在的风险:BufferedReader 在找到 return 行之前不会对其读取的字节数执行任何控制,因此它可以结束抛出 OutOfMemoryError。
这是我的测试:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import org.junit.Test;
public class BufferedReaderTest {
@Test(expected=OutOfMemoryError.class)
public void testReadFileWithoutReturnLineCharacter() throws IOException {
BufferedReader bf = new BufferedReader(getInfiniteReader());
bf.readLine();
bf.close();
}
private Reader getInfiniteReader() {
return new Reader(){
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
return 'A';
}
@Override
public void close() throws IOException {
}
};
}
}
我一直在互联网上查找一些安全的 BufferedReader 实现,但找不到任何东西。我发现的唯一 class 是来自 apache IO 的 BoundedInputStream
,它限制了输入流读取的字节数。
我需要一个 BufferedReader 的实现,它知道如何限制每行 bytes/characters 读取 的数量。
像这样:
- 应用调用'readLine()'
- BufferedReader 读取字节直到找到 return 行字符或达到允许的最大字节数
- 如果找到 return 行字符,则重置读取的字节(以便它可以读取下一行)和 return 内容
- 如果已达到允许的最大字节数,则抛出异常
有人知道具有这种行为的 BufferedReader 实现吗?
这不是您应该继续检测文件是否为二进制文件的方式。
以下是检查文件是否真正为文本的方法;请注意,这需要您事先知道编码:
final Charset cs = StandardCharsets.UTF_8; // or another
final CharsetDecoder decoder = cs.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT); // default is REPLACE!
// Here, "in" is the input stream from the file
try (
final Reader reader = new InputStreamReader(in, decoder);
) {
final char[] buf = new char[4096]; // or other size
while (reader.read(buf) != -1)
; // nothing
} catch (MalformedInputException e) {
// cannot decode; binary, or wrong encoding
}
现在,由于您可以在 Reader
上初始化 BufferedReader
,您可以使用:
try (
final Reader r = new InputStreamReader(in, decoder);
final BufferedReader reader = new BufferedReader(r);
) {
// Read lines normally
} catch (CharacterCodingException e) {
// Not a CSV, it seems
}
// etc
现在,稍微解释一下它是如何工作的……虽然这是阅读 Java 中文本的基本部分,但它也是同样被根本性误解的部分!
当您使用 Reader
以文本形式读取文件时,您必须指定字符编码;在 Java 中,这是 Charset
.
内部发生的是 Java 将从 Charset
创建一个 CharsetDecoder
,读取 byte
流并输出 char
流。并且有3种处理错误的方法:
CodingErrorAction.REPLACE
(默认值):不可映射的字节序列被替换为Unicode replacement character(它确实敲响了警钟,对吧?);
CodingErrorAction.IGNORE
:不可映射的字节序列不会触发 char
; 的发射
CodingErrorAction.REPORT
:不可映射的字节序列触发抛出一个CharacterCodingException
,继承了IOException
;反过来,CharacterCodingException
的两个子类是 MalformedInputException
和 UnmappableCharacterException
.
因此,要检测文件是否为真正的文本,您需要做的是:
- 事先知道编码!
- 使用
CharsetDecoder
配置 CodingErrorAction.REPORT
;
- 在
InputStreamReader
. 中使用它
这是一种方式;还有其他人。然而,他们所有人都会在某个时候使用 CharsetDecoder
。
同样,还有一个CharsetEncoder
用于反向操作(char
流到byte
流),这就是Writer
家族所使用的。
谢谢@fge 的回答。我最终实现了一个安全的 Reader
可以处理行太长(或根本没有行)的文件。
如果有人想看代码,可以在这里找到项目(非常小的项目,但有很多测试):
我想使用 BufferedReader 读取上传到我的服务器的文件。
该文件将被写成 CSV 文件,但我不能假设这一点,所以我编写了一些测试,其中文件是图像或二进制文件(假设客户端向我发送了错误的文件或攻击者试图破坏我的服务),或者更糟的是,该文件是一个有效的 CSV 文件,但有一行 100MB。
我的应用程序可以处理这个问题,但它必须读取文件的第一行:
...
String firstLine = bufferedReader.readLine();
//Perform some validations and reject the file if it's not a CSV file
...
但是,当我编写一些测试代码时,我发现了一个潜在的风险:BufferedReader 在找到 return 行之前不会对其读取的字节数执行任何控制,因此它可以结束抛出 OutOfMemoryError。
这是我的测试:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import org.junit.Test;
public class BufferedReaderTest {
@Test(expected=OutOfMemoryError.class)
public void testReadFileWithoutReturnLineCharacter() throws IOException {
BufferedReader bf = new BufferedReader(getInfiniteReader());
bf.readLine();
bf.close();
}
private Reader getInfiniteReader() {
return new Reader(){
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
return 'A';
}
@Override
public void close() throws IOException {
}
};
}
}
我一直在互联网上查找一些安全的 BufferedReader 实现,但找不到任何东西。我发现的唯一 class 是来自 apache IO 的 BoundedInputStream
,它限制了输入流读取的字节数。
我需要一个 BufferedReader 的实现,它知道如何限制每行 bytes/characters 读取 的数量。
像这样:
- 应用调用'readLine()'
- BufferedReader 读取字节直到找到 return 行字符或达到允许的最大字节数
- 如果找到 return 行字符,则重置读取的字节(以便它可以读取下一行)和 return 内容
- 如果已达到允许的最大字节数,则抛出异常
有人知道具有这种行为的 BufferedReader 实现吗?
这不是您应该继续检测文件是否为二进制文件的方式。
以下是检查文件是否真正为文本的方法;请注意,这需要您事先知道编码:
final Charset cs = StandardCharsets.UTF_8; // or another
final CharsetDecoder decoder = cs.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT); // default is REPLACE!
// Here, "in" is the input stream from the file
try (
final Reader reader = new InputStreamReader(in, decoder);
) {
final char[] buf = new char[4096]; // or other size
while (reader.read(buf) != -1)
; // nothing
} catch (MalformedInputException e) {
// cannot decode; binary, or wrong encoding
}
现在,由于您可以在 Reader
上初始化 BufferedReader
,您可以使用:
try (
final Reader r = new InputStreamReader(in, decoder);
final BufferedReader reader = new BufferedReader(r);
) {
// Read lines normally
} catch (CharacterCodingException e) {
// Not a CSV, it seems
}
// etc
现在,稍微解释一下它是如何工作的……虽然这是阅读 Java 中文本的基本部分,但它也是同样被根本性误解的部分!
当您使用 Reader
以文本形式读取文件时,您必须指定字符编码;在 Java 中,这是 Charset
.
内部发生的是 Java 将从 Charset
创建一个 CharsetDecoder
,读取 byte
流并输出 char
流。并且有3种处理错误的方法:
CodingErrorAction.REPLACE
(默认值):不可映射的字节序列被替换为Unicode replacement character(它确实敲响了警钟,对吧?);CodingErrorAction.IGNORE
:不可映射的字节序列不会触发char
; 的发射
CodingErrorAction.REPORT
:不可映射的字节序列触发抛出一个CharacterCodingException
,继承了IOException
;反过来,CharacterCodingException
的两个子类是MalformedInputException
和UnmappableCharacterException
.
因此,要检测文件是否为真正的文本,您需要做的是:
- 事先知道编码!
- 使用
CharsetDecoder
配置CodingErrorAction.REPORT
; - 在
InputStreamReader
. 中使用它
这是一种方式;还有其他人。然而,他们所有人都会在某个时候使用 CharsetDecoder
。
同样,还有一个CharsetEncoder
用于反向操作(char
流到byte
流),这就是Writer
家族所使用的。
谢谢@fge 的回答。我最终实现了一个安全的 Reader
可以处理行太长(或根本没有行)的文件。
如果有人想看代码,可以在这里找到项目(非常小的项目,但有很多测试):