最强大的使用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:根据评论添加更新的答案
标签:java,readline,bufferedreader,denial-of-service 来源: https://codeday.me/bug/20190926/1821144.html