最强大的使用Java读取文件或流的方法(防止DoS攻击)

目前我有以下代码用于读取InputStream.我将整个文件存储到StringBuilder变量中,然后处理该字符串.

public static String getContentFromInputStream(InputStream inputStream)
// public static String getContentFromInputStream(InputStream inputStream,
// int maxLineSize, int maxFileSize)
{

    StringBuilder stringBuilder = new StringBuilder();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
    String lineSeparator = System.getProperty("line.separator");
    String fileLine;

    boolean firstLine = true;
    try {
        // Expect some function which checks for line size limit.
        // eg: reading character by character to an char array and checking for
        // linesize in a loop until line feed is encountered.
        // if max line size limit is passed then throw an exception
        // if a line feed is encountered append the char array to a StringBuilder
        // after appending check the size of the StringBuilder
        // if file size exceeds the max file limit then throw an exception

        fileLine = bufferedReader.readLine();

        while (fileLine != null) {
            if (!firstLine) stringBuilder.append(lineSeparator);
            stringBuilder.append(fileLine);
            fileLine = bufferedReader.readLine();
            firstLine = false;
        }
    } catch (IOException e) {
        //TODO : throw or handle the exception
    }
    //TODO : close the stream

    return stringBuilder.toString();

}

该代码与安全团队进行了审核,并收到了以下评论:

> BufferedReader.readLine易受DOS(拒绝服务)攻击(无限长度的行,包含无行换/回车的巨大文件)
> StringBuilder变量的资源耗尽(当包含大于可用内存的数据的文件时)

以下是我能想到的解决方案:

>创建readLine方法的替代实现(readLine(int limit)),它检查no.读取的字节数,如果超过指定的限制,则抛出自定义异常.
>逐行处理文件而不完整加载文件. (纯非Java解决方案:))

请建议是否有任何现有的库实现上述解决方案.
还建议任何替代解决方案,其提供比建议的更稳健或更方便实施.虽然性能也是一项主要要求,但安全性是第一位的.

解决方法:

更新的答案

您希望避免各种DOS攻击(在线路上,文件大小等).但是在函数的最后,你试图将整个文件转换成一个单独的字符串!假设您将行限制为8 KB,但如果某人向您发送包含两个8 KB行的文件会发生什么?行读取部分将通过,但是当最终将所有内容组合成一个字符串时,String将阻塞所有可用内存.

因此,自从最终将所有内容转换为单个字符串后,限制行大小无关紧要,也不安全.您必须限制文件的整个大小.

其次,你基本上要做的是,你正试图以块的形式读取数据.所以你正在使用BufferedReader并逐行读取它.但是你要做的是什么,以及你最终想要的是什么 – 是一种逐段阅读文件的方式.为什么不一次读取2 KB,而不是一次读取一行?

BufferedReader – 按名称 – 里面有一个缓冲区.您可以配置该缓冲区.假设您创建了一个缓冲区大小为2 KB的BufferedReader:

BufferedReader reader = new BufferedReader(..., 2048);

现在,如果传递给BufferedReader的InputStream具有100 KB的数据,BufferedReader将自动将其读取为2 KB.因此它将读取流50次,每次2 KB(50x2KB = 100 KB).同样,如果您创建缓冲区大小为10 KB的BufferedReader,它将读取输入10次(10x10KB = 100 KB).

BufferedReader已经完成了以块为单位读取文件的工作.因此,您不希望在其上方添加额外的逐行图层.只关注最终结果 – 如果你的文件太大(>可用内存) – 你最后如何将它转换为字符串?

一种更好的方法是将事物作为CharSequence传递.这就是Android的功能.在整个Android API中,您将看到它们在任何地方都返回CharSequence.由于StringBuilder也是CharSequence的子类,因此Android将根据输入的大小/性质在内部使用String,StringBuilder或其他一些优化的字符串类.因此,一旦读完所有内容,您就可以直接返回StringBuilder对象,而不是将其转换为String.这对大数据更安全. StringBuilder还在其中维护相同的缓冲区概念,它将在内部为大字符串分配多个缓冲区,而不是一个长字符串.

整体而言:

>限制整体文件大小,因为您将在某个时刻处理整个内容.忘记限制或分裂线
>读入块

使用Apache Commons IO,以下是如何将BoundedInputStream中的数据读入StringBuilder,分割2 KB块而不是行:

// import org.apache.commons.io.output.StringBuilderWriter;
// import org.apache.commons.io.input.BoundedInputStream;
// import org.apache.commons.io.IOUtils;

BoundedInputStream boundedInput = new BoundedInputStream(originalInput, <max-file-size>);
BufferedReader reader = new BufferedReader(new InputStreamReader(boundedInput), 2048);

StringBuilder output = new StringBuilder();
StringBuilderWriter writer = new StringBuilderWriter(output);

IOUtils.copy(reader, writer); // copies data from "reader" => "writer"
return output;

原始答案

Apache Commons IO库使用BoundedInputStream.你的工作变得容易多了.

以下代码将执行您想要的操作:

public static String getContentFromInputStream(InputStream inputStream) {
  inputStream = new BoundedInputStream(inputStream, <number-of-bytes>);
  // Rest code are all same

您只需使用BoundedInputStream包装InputStream,并指定最大大小. BoundedInputStream将负责限制读取到最大大小.

或者,您可以在创建阅读器时执行此操作:

BufferedReader bufferedReader = new BufferedReader(
  new InputStreamReader(
    new BoundedInputStream(inputStream, <no-of-bytes>)
  )
);

基本上我们在这里做的是,我们限制InputStream层本身的读取大小,而不是在读取行时这样做.因此,您最终得到了一个可重用的组件,如BoundedInputStream,它限制了InputStream层的读取,您可以在任何地方使用它.

编辑:添加脚注

编辑2:根据评论添加更新的答案

上一篇:oracle linux 6.8 安装和配置rlwrap,方便oracle的SQL输入历史命令回显


下一篇:java基础篇---网络编程(UDP程序设计)