目前,我有以下读取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
容易受到拒绝服务攻击(无限长的行、不包含换行符/回车符的大型文件) StringBuilder
变量的return)下面是我能想到的解决方案:
readLine
方法(readLine(int limit)
)的替代实现,该方法检查no。读取的字节数,如果超过指定的限制,则在不加载整个文件的情况下逐行抛出自定义exception.请建议是否有任何现有的库实现了上述解决方案。还建议任何替代解决方案,这些解决方案提供比所提出的解决方案更健壮或更方便实现的解决方案。虽然性能也是一个主要要求,但安全性是第一位的。
发布于 2013-06-17 15:25:37
更新的答案
你想要避免所有类型的DOS攻击(行攻击,文件大小攻击等)。但在函数的末尾,您试图将整个文件转换为一个String
!假设您将行限制为8KB,但如果有人向您发送包含两个8KB行的文件,会发生什么情况?行读取部分将通过,但当您最终将所有内容组合到一个字符串中时,该字符串将阻塞所有可用内存。
因此,由于最终将所有内容转换为一个单独的字符串,因此限制行的大小并不重要,也不安全。您必须限制文件的整个大小。
其次,你基本上想要做的是,你试图以块的形式读取数据。因此,您正在使用BufferedReader
并逐行阅读它。但你想要做的,也是你最终真正想要的--是一种逐段读取文件的方法。与其一次读取一行,为什么不一次读取2KB呢?
BufferedReader
--顾名思义--在它里面有一个缓冲区。您可以配置该缓冲区。假设您创建了一个缓冲区大小为2 KB的BufferedReader
:
BufferedReader reader = new BufferedReader(..., 2048);
现在,如果您传递给BufferedReader
的InputStream
包含100KB的数据,BufferedReader
将一次自动读取2KB的数据。因此,它将读取流50次,每次2 KB (50x2KB = 100 KB)。类似地,如果使用10KB缓冲区大小创建BufferedReader
,它将读取输入10次(10x10KB =100KB)。
BufferedReader
已经完成了逐块读取文件的工作。所以你不会想要在它上面逐行添加额外的一层。只要关注最终的结果--如果你最后的文件太大了(>可用内存)--你怎么把它转换成String
呢?
一种更好的方法是将事物作为CharSequence
传递。这就是Android所做的。在整个Android中,您会看到它们到处都返回CharSequence
。由于StringBuilder
也是CharSequence
的子类,安卓将根据输入的大小/性质在内部使用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:添加了基于评论的更新答案
发布于 2013-06-18 17:31:26
基本上有4种方式进行文件处理:
块处理模型(
java.io.InputStream
模型):可选地在流周围放置一个块,迭代并读取流中的下一个可用文本(如果没有可用文本,则对块进行块处理,直到其中一些可用为止),在读取文本时独立处理每段文本(迎合大小变化很大的文本的非阻塞处理( java.nio.channels.Channel
模型):创建一组固定大小的bufferedReader (表示要处理的“块”),),在没有阻塞的情况下依次读入每个缓冲区(nio委托给本机IO,使用快速O/S级线程),一旦缓冲区被填满,您的主处理线程将依次挑选每个缓冲区并处理固定大小的块,因为其他缓冲区继续异步loaded.java.nio.file.Files
模型)中尽快处理每个部分:在一个操作中将整个文件读取到内存中,处理完整的内容您应该使用哪一个?
这取决于您的文件内容和所需的处理类型。
从资源使用效率的角度来看(从最好到最差)是:1、2、3、4。
从处理速度和效率的角度来看(从最好到最差)是: 2,1,3,4。
从容易编程的角度来看(从最好到最差):4,3,1,2。
但是,某些类型的处理可能需要超过最小的文本部分(排除1,也许2),而某些文件格式可能没有内部部分(排除3)。
如果可以的话,我建议你切换到3(或更低)。
在4下,只有一种方法可以避免DOS --在读入内存之前限制大小(或者复制到你的文件系统)。一旦它被读入就太晚了。如果这不可能,请尝试3、2或1。
限制文件大小
通常,文件是通过HTML表单上传的。
如果使用Servlet @MultipartConfig
注释和request.getPart().getInputStream()
进行上传,则可以控制从流中读取的数据量。此外,request.getPart().getSize()
提前返回文件大小,如果它足够小,您可以执行request.getPart().write(path)
将文件写入磁盘。
如果使用JSF上传,那么JSF2.2(非常新)具有标准的html组件<h:inputFile>
(javax.faces.component.html.InputFile
),该组件具有maxLength
属性;JSF2.2之前的实现具有类似的自定义组件(例如,Tomahawk具有带有maxLength
属性的<t:InputFileUpload>
;PrimeFaces具有带有sizeLimit
属性的<p:FileUpload>
)。
读取整个文件的替代方案
使用InputStream
、StringBuilder
等的代码是读取整个文件的高效方式,但不一定是最简单的方式(最少的代码行)。
在处理整个文件时,初级/普通开发人员可能会产生误解,认为您正在进行有效的基于流的处理-因此请包含适当的注释。
如果您想要更少的代码,可以尝试以下方法之一:
List<String> stringList = java.nio.file.Files.readAllLines(path, charset);
or
byte[] byteContents = java.nio.file.Files.readAllBytes(path);
但它们需要小心,否则可能在资源使用方面效率低下。如果使用readAllLines
,然后将List
元素连接到单个String
中,那么将消耗两倍的内存( List
元素+连接的String
)。类似地,如果使用readAllBytes
,然后编码为String
(new String(byteContents, charset)
),那么再次使用“双倍”内存。因此,除非您将文件限制为足够小,否则最好直接针对List<String>
或byte[]
进行处理。
发布于 2013-06-13 19:10:06
不使用readLine,而使用read,它读取给定数量的字符。
在每个循环中,检查读取了多少数据,如果读取的数据量超过一定数量,超过预期输入的最大值,则停止该循环并返回错误并记录该错误。
https://stackoverflow.com/questions/17084657
复制相似问题