安全实现BufferedReader

时间:2015-04-21 10:19:54

标签: java security io bufferedreader

我想使用BufferedReader来读取上传到我服务器的文件。

该文件将被写为CSV文件,但我不能假设这一点,所以我编写了一些测试,其中文件是图像或二进制文件(假设客户端向我发送了错误的文件或攻击者是试图破坏我的服务),或者更糟糕的是,该文件是一个有效的CSV文件,但有100MB的行。

我的应用程序可以处理这个问题,但它必须读取文件的第一行:

...
String firstLine = bufferedReader.readLine();
//Perform some validations and reject the file if it's not a CSV file
...

但是,当我编写一些测试代码时,我发现了一个潜在的风险:BufferedReader在找到返回行之前不会对它读取的字节数执行任何控制,因此它最终会抛出OutOfMemoryError。 / p>

这是我的测试:

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实现,但我找不到任何东西。我发现的唯一一个类是来自apache IO的BoundedInputStream,它限制了输入流读取的字节数。

我需要BufferedReader的实现,它知道如何限制每行中读取的字节/字符数

这样的事情:

  • 该应用调用'readLine()'
  • BufferedReader读取字节,直到找到返回行字符或达到允许的最大字节数
  • 如果找到了返回行字符,则重置读取的字节数(因此可以读取下一行)并返回内容
  • 如果它已达到允许的最大字节数,则会抛出异常

有人知道有这种行为的BufferedReader的实现吗?

2 个答案:

答案 0 :(得分:2)

这不是你应该如何检测文件是否是二进制文件。

以下是检查文件是否为真文本的方法;请注意,这要求您事先知道编码:

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
}

现在,由于您可以在BufferedReader上初始化Reader,因此您可以使用:

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将从CharsetDecoder创建Charset,阅读byte流并输出char流。有三种方法可以处理错误:

  • CodingErrorAction.REPLACE默认):不可映射的字节序列被Unicode replacement character替换(它响铃,对吧?);
  • CodingErrorAction.IGNORE:不可映射的字节序列不会触发char的发射;
  • CodingErrorAction.REPORT:不可映射的字节序列触发抛出CharacterCodingException,继承IOException;反过来,CharacterCodingException的两个子类是MalformedInputExceptionUnmappableCharacterException

因此,为了检测文件是否是真正的文本,您需要做的是:

  • 事先知道编码!
  • 使用CharsetDecoder配置的CodingErrorAction.REPORT;
  • InputStreamReader
  • 中使用它

这是一种方式;还有其他人。但是,所有这些都会在某个时刻使用CharsetDecoder

同样,反向操作有CharsetEncoderchar流到byte流),这是Writer系列使用的内容。

答案 1 :(得分:0)

谢谢@fge的答案。我最终实现了一个安全的Reader,它可以处理行太长(或根本没有行)的文件。

如果有人想查看代码,可以在这里找到项目(非常小的项目,即使是很多测试):

https://github.com/jfcorugedo/security-io