目前我有以下代码用于阅读 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)
),它检查编号。读取的字节数,如果超过指定的限制,则抛出自定义异常。 请建议是否有任何现有的库可以实现上述解决方案。
还建议任何替代解决方案,这些解决方案比提议的解决方案更健壮或更便于实现。尽管性能也是一项主要要求,但安全性是第一位的。
最佳答案
更新答案
您想避免各种 DOS 攻击(在线、文件大小等)。但是在函数的最后,您试图将整个文件转换为一个 String
!!!假设您将行限制为 8 KB,但是如果有人向您发送一个包含两个 8 KB 行的文件,会发生什么情况?行读取部分将通过,但是当您最终将所有内容组合成一个字符串时,该字符串将阻塞所有可用内存。
因此,由于最终您将所有内容都转换为一个字符串,因此限制行大小并不重要,也不安全。您必须限制文件的整个大小。
其次,您基本上要做的是,您正在尝试分块读取数据。所以你正在使用 BufferedReader
并逐行阅读。但是你想要做的,以及你最终真正想要的 - 是一种逐个阅读文件的方式。与其一次读取一行,不如一次读取 2 KB?BufferedReader
- 顾名思义 - 里面有一个缓冲区。您可以配置该缓冲区。假设您创建了一个 BufferedReader
缓冲区大小为 2 KB:
BufferedReader reader = new BufferedReader(..., 2048);
现在如果
InputStream
您传递给 BufferedReader
有 100 KB 的数据,BufferedReader
一次将自动读取 2 KB。因此它将读取流 50 次,每次 2 KB (50x2KB = 100 KB)。同样,如果您创建 BufferedReader
缓冲区大小为 10 KB 时,它将读取输入 10 次 (10x10KB = 100 KB)。BufferedReader
已经完成了逐块读取文件的工作。所以你不想在它上面逐行添加额外的一层。只关注最终结果 - 如果最后的文件太大(> 可用 RAM) - 你将如何将其转换为 String
在末尾?一种更好的方法是将东西作为
CharSequence
传递。 .这就是安卓所做的。在整个 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;
原答案
使用 BoundedInputStream来自 Apache Commons IO图书馆。您的工作变得更加轻松。
以下代码将执行您想要的操作:
public static String getContentFromInputStream(InputStream inputStream) {
inputStream = new BoundedInputStream(inputStream, <number-of-bytes>);
// Rest code are all same
您只需简单地包裹您的
InputStream
与 BoundedInputStream
并指定最大大小。 BoundedInputStream
将负责将读取限制到该最大大小。或者您可以在创建阅读器时执行此操作:
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(
new BoundedInputStream(inputStream, <no-of-bytes>)
)
);
基本上我们在这里做的是,我们将读取大小限制在
InputStream
层本身,而不是在阅读行时这样做。所以你最终会得到一个可重用的组件,比如 BoundedInputStream
这限制了 InputStream 层的读取,您可以在任何地方使用它。编辑:添加脚注
编辑 2:根据评论添加了更新的答案
关于java - 使用 Java 读取文件或流的最可靠方法(防止 DoS 攻击),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17084657/