目前,我有以下代码来阅读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)
)的备用实现,检查否。读取的字节数,如果超过指定的限制,则抛出自定义异常。
逐行处理文件而不完整加载文件。 (纯非Java解决方案:))
请建议是否有任何现有的库实施上述解决方案。 还建议任何替代解决方案,其提供比建议的更稳健或更方便实施。虽然性能也是一项主要要求,但安全性是第一位的。
答案 0 :(得分:35)
您希望避免各种DOS攻击(在线路上,文件大小等)。但是在函数的最后,您尝试将整个文件转换为单个String
!假设您将行限制为8 KB,但如果某人向您发送包含两个8 KB行的文件会发生什么?行读取部分将通过,但是当最终将所有内容组合成一个字符串时,String将阻塞所有可用内存。
因此,自从最终将所有内容转换为单个字符串后,限制行大小并不重要,也不安全。您必须限制文件的整个大小。
其次,你基本上要做的是,你试图以块的形式读取数据。所以你正在使用BufferedReader
并逐行阅读。但是你要做的是什么,以及你最终想要的是什么 - 是一种逐段阅读文件的方式。而不是一次只读一行,为什么不一次读取2 KB?
BufferedReader
- 按名称 - 里面有一个缓冲区。您可以配置该缓冲区。假设您创建了一个缓冲区大小为2 KB的BufferedReader
:
BufferedReader reader = new BufferedReader(..., 2048);
现在,如果传递给InputStream
的{{1}}有100 KB的数据,BufferedReader
会自动将其读取为2 KB。因此它将读取流50次,每次2 KB(50x2KB = 100 KB)。同样,如果您创建具有10 KB缓冲区大小的BufferedReader
,它将读取输入10次(10x10KB = 100 KB)。
BufferedReader
已经完成了以块为单位读取文件的工作。因此,您不希望在其上方添加额外的逐行图层。只关注最终结果 - 如果你的文件太大(>可用内存) - 你最后如何将它转换为BufferedReader
?
一种更好的方法是将事物作为String
传递。这就是Android的功能。在整个Android API中,您会看到他们在任何地方都返回CharSequence
。由于CharSequence
也是StringBuilder
的子类,因此Android会根据输入的大小/性质在内部使用CharSequence
或String
或其他一些优化的字符串类。因此,一旦您阅读了所有内容,您就可以直接返回StringBuilder
对象,而不是将其转换为StringBuilder
。这对大数据更安全。 String
在其中也保持了相同的缓冲区概念,它将在内部为大字符串分配多个缓冲区,而不是一个长字符串。
总的来说:
使用Apache Commons IO,以下是将StringBuilder
中的数据读入BoundedInputStream
的方法,将2 KB块除以行分割:
StringBuilder
使用BoundedInputStream库中的Apache Commons IO。你的工作变得更加容易。
以下代码将执行您想要的操作:
// 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;
您只需将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
,它限制了InputStream层的读取,你可以在任何你想要的地方使用它。
编辑:添加了脚注
编辑2:根据评论添加了更新的答案
答案 1 :(得分:14)
基本上有4种方法可以进行文件处理:
基于流的处理(java.io.InputStream
模型):可选择在流周围放置一个bufferedReader,迭代&amp;从流中读取下一个可用文本(如果没有可用文本,阻止,直到某些文本可用),在阅读时独立处理每段文本(适应各种文本大小的文本)< / p>
基于块的非阻塞处理(java.nio.channels.Channel
模型):创建一组固定大小的缓冲区(表示要处理的“块”),读取依次进入每个缓冲区而不阻塞(nio API委托给本机IO,使用快速O / S级线程),主处理线程一旦填充就依次选择每个缓冲区并处理固定大小的块,如同其他缓冲区继续异步加载。
部分文件处理(包括逐行处理)(可以利用(1)或(2)隔离或构建每个“部分”):打破您的文件格式下到语义上有意义的子部分(如果可能的话!可以分成几行!),遍历流片段或块并在内存中构建内容直到下一部分完全构建,一旦构建完成就处理每个部分。
整个文件处理(java.nio.file.Files
模型):在一次操作中将整个文件读入内存,处理完整内容
你应该使用哪一个?
这取决于您的文件内容和您需要的处理类型
从资源利用效率的角度(从最好到最差)是:1,2,3,4
从处理速度&amp;效率观点(从最好到最差)是:2,1,3,4。
从简单的编程角度(从最好到最差):4,3,1,2。
但是,某些类型的处理可能需要的不仅仅是最小的文本(排除1,可能是2),而某些文件格式可能没有内部部分(排除3)。
你正在做4.我建议你转到3(或更低),如果可以。
在4下,只有一种方法可以避免DOS - 在读入内存之前限制大小(或者复制到文件系统)。一旦它被读入就太晚了。如果这是不可能的,那么尝试3,2或1.
限制文件大小
通常,文件是通过HTML表单上传的。
如果使用Servlet @MultipartConfig
注释和request.getPart().getInputStream()
进行上传,则可以控制从流中读取的数据量。此外,request.getPart().getSize()
会提前返回文件大小,如果它足够小,您可以request.getPart().write(path)
将文件写入磁盘。
如果使用JSF上传,则JSF 2.2(非常新)具有标准html组件<h:inputFile>
(javax.faces.component.html.InputFile
),其具有maxLength
的属性; JSF 2.2之前的实现具有类似的自定义组件(例如,Tomahawk具有<t:InputFileUpload>
maxLength
属性; PrimeFaces具有<p:FileUpload>
属性sizeLimit
。
阅读整个文件的替代方法
使用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
元素+连接的{{1} })。同样,如果您使用String
,然后编码为readAllBytes
(String
),那么您再次使用“双倍”内存。因此,最好直接针对new String(byteContents, charset)
或List<String>
进行处理,除非您将文件限制为足够小的尺寸。
答案 2 :(得分:3)
而不是readLine使用读取读取给定量的字符。
在每个循环中检查已读取了多少数据,如果它超过一定量,则超过预期输入的最大值,停止并返回错误并记录它。
答案 3 :(得分:1)
在复制巨大的二进制文件(通常不包含换行符)时遇到了类似的问题。执行readline()会导致将整个二进制文件读取到一个单独的字符串中,导致堆空间上出现OutOfMemory
。
这是一个简单的JDK替代方案:
public static void main(String[] args) throws Exception
{
byte[] array = new byte[1024];
FileInputStream fis = new FileInputStream(new File("<Path-to-input-file>"));
FileOutputStream fos = new FileOutputStream(new File("<Path-to-output-file>"));
int length = 0;
while((length = fis.read(array)) != -1)
{
fos.write(array, 0, length);
}
fis.close();
fos.close();
}
注意事项:
上面的示例使用1K字节的缓冲区复制文件。但是,如果您通过网络进行此复制,则可能需要调整缓冲区大小。
如果您想使用FileChannel或Commons IO之类的图书馆,请确保实施归结为上述内容
答案 4 :(得分:0)
除了Apache Commons IO FileUtils.之外,我无法想到一个解决方案 它与FileUtils类非常简单,因为所谓的DOS攻击不会直接来自顶层。 读取和写入文件非常简单,因为只需使用一行代码(如
)即可String content =FileUtils.readFileToString(new File(filePath));
您可以详细了解这一点。
答案 5 :(得分:0)
Apache httpCore下有类EntityUtils。使用此类的getString()方法从Response内容中获取String。
答案 6 :(得分:0)
这对我没有任何问题。
var retval={};
data_model.select_company(index,retval);
console.log(retval.company_name);
console.log(retval.identity_id);
答案 7 :(得分:0)
来自 Fortify Scan 的建议。您可以使 InputStream
适应其他资源,例如 HTTP request InputStream
。
InputStream zipInput = zipFile.getInputStream(zipEntry);
Reader zipReader = new InputStreamReader(zipInput);
BufferedReader br = new BufferedReader(zipReader);
StringBuffer sb = new StringBuffer();
int intC;
while ((intC = br.read()) != -1){
char c = (char)intC;
if (c == "\n"){
break;
}
if (sb.length >= MAX_STR_LEN){
throw new Exception("Input too long");
}
sb.append(c);
}
String line = sb.toString();