obtener leer escribir datos archivos java bufferedreader readline denial-of-service
Apache Commons IO FileUtils.

escribir - La forma más robusta de leer un archivo o flujo usando Java(para evitar ataques DoS)



leer y escribir xml en java (7)

Actualmente tengo el siguiente código para leer un InputStream . Estoy almacenando todo el archivo en una variable StringBuilder y luego proceso esta cadena.

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(); }

El código fue revisado por el equipo de seguridad y se recibieron los siguientes comentarios:

  1. BufferedReader.readLine es susceptible a ataques de DOS (Denegación de Servicio) (línea de longitud infinita, archivo enorme que no contiene avance de línea / retorno de carro)

  2. Agotamiento de recursos para la variable StringBuilder (casos en los que un archivo contiene datos mayores que la memoria disponible)

A continuación se presentan las soluciones que podría pensar:

  1. Cree una implementación alternativa del método readLine ( readLine(int limit) ), que verifica el no. de bytes leídos y, si excede el límite especificado, lanza una excepción personalizada.

  2. Procese el archivo línea por línea sin cargar el archivo en su totalidad. (solución no Java pura :))

Por favor sugiera si hay alguna biblioteca existente que implemente las soluciones anteriores. También sugiera soluciones alternativas que ofrezcan más robustez o que sean más convenientes de implementar que las propuestas. Aunque el rendimiento también es un requisito importante, la seguridad es lo primero.


Respuesta actualizada

Desea evitar todo tipo de ataques de DOS (en líneas, en el tamaño del archivo, etc.). Pero al final de la función, ¡estás intentando convertir todo el archivo en una sola String ! Supongamos que limita la línea a 8 KB, pero ¿qué sucede si alguien le envía un archivo con dos líneas de 8 KB? La parte de lectura de la línea pasará, pero cuando finalmente combine todo en una sola cadena, la cadena ahogará toda la memoria disponible.

Entonces, como finalmente estás convirtiendo todo en una sola Cadena, no importa el tamaño de la línea, ni tampoco es seguro. Tienes que limitar todo el tamaño del archivo.

En segundo lugar, lo que básicamente intentas hacer es intentar leer los datos en fragmentos. Así que estás usando BufferedReader y BufferedReader línea por línea. Pero lo que estás tratando de hacer, y lo que realmente quieres al final, es una forma de leer el archivo pieza por pieza. En lugar de leer una línea a la vez, ¿por qué no leer 2 KB a la vez?

BufferedReader , por su nombre, tiene un búfer en su interior. Puede configurar ese búfer. Digamos que creas un BufferedReader con un tamaño de búfer de 2 KB:

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

Ahora, si el InputStream que pasa a BufferedReader tiene 100 KB de datos, BufferedReader lo leerá automáticamente 2 KB a la hora. Entonces leerá la secuencia 50 veces, 2 KB cada una (50x2KB = 100 KB). De manera similar, si crea BufferedReader con un tamaño de búfer de 10 KB, leerá la entrada 10 veces (10x10KB = 100 KB).

BufferedReader ya hace el trabajo de leer tu archivo parte por parte. Por lo tanto, no desea agregar una capa adicional de línea por línea sobre ella. Solo concéntrese en el resultado final: si su archivo al final es demasiado grande (> RAM disponible), ¿cómo lo va a convertir en una String al final?

Una forma mejor es simplemente pasar las cosas como una CharSequence . Eso es lo que hace Android. A lo largo de las API de Android, verás que devuelven CharSequence todas partes. Dado que StringBuilder también es una subclase de CharSequence , Android utilizará internamente una String , un StringBuilder o alguna otra clase de cadena optimizada basada en el tamaño / naturaleza de la entrada. Por lo tanto, podría devolver directamente el objeto StringBuilder una vez que haya leído todo, en lugar de convertirlo en un String . Esto sería más seguro contra grandes datos. StringBuilder también mantiene el mismo concepto de búferes dentro de él, y asignará internamente múltiples búferes para cadenas grandes, en lugar de una cadena larga.

Así que en general:

  • Limite el tamaño del archivo en general, ya que en algún momento tratará con todo el contenido. Olvídate de limitar o dividir líneas.
  • Leer en trozos

Usando Apache Commons IO, aquí es cómo leería los datos de un BoundedInputStream en un StringBuilder , dividiendo por bloques de 2 KB en lugar de líneas:

// 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;

Respuesta original

Utilice BoundedInputStream de la biblioteca IO de Apache Commons . Tu trabajo se vuelve mucho más fácil.

El siguiente código hará lo que quieras:

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

Simplemente envuelva su InputStream con un BoundedInputStream y especifique un tamaño máximo. BoundedInputStream se encargará de limitar las lecturas hasta ese tamaño máximo.

O puedes hacer esto cuando estás creando el lector:

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

Básicamente, lo que estamos haciendo aquí es que estamos limitando el tamaño de lectura en la capa InputStream , en lugar de hacerlo al leer líneas. Así que terminas con un componente reutilizable como BoundedInputStream que limita la lectura en la capa InputStream, y puedes usarlo donde quieras.

Edición: Nota al pie agregada

Edición 2: respuesta actualizada agregada basada en comentarios


Básicamente hay 4 formas de hacer el procesamiento de archivos:

  1. Procesamiento basado en flujo (el modelo java.io.InputStream ): Opcionalmente, coloque un bufferedReader alrededor del flujo, itere y lea el siguiente texto disponible del flujo (si no hay texto disponible, bloquee hasta que esté disponible), procese cada parte de texto de forma independiente a medida que se lee (para tamaños de texto variados)

  2. Procesamiento no java.nio.channels.Channel (el modelo java.nio.channels.Channel ): Cree un conjunto de búferes de tamaño fijo (que representan los "trozos" que se procesarán), lea cada uno de los búferes a su vez sin bloquearlos (nio La API delega en el IO nativo, utilizando subprocesos rápidos de nivel O / S), su hilo de procesamiento principal selecciona cada búfer una vez que se llena y procesa el trozo de tamaño fijo, ya que otros búferes continúan siendo cargados de forma asíncrona.

  3. Procesamiento de archivos de partes (incluido el procesamiento línea por línea) (puede aprovechar (1) o (2) para aislar o construir cada "parte"): divida su formato de archivo en subpartes semánticamente significativas (¡si es posible! las líneas podrían ser posibles!), iterar a través de secuencias o fragmentos y acumular contenido en la memoria hasta que la siguiente parte esté completamente construida, procese cada parte tan pronto como se haya creado.

  4. Procesamiento de archivo completo (el modelo java.nio.file.Files ): lea todo el archivo en la memoria en una sola operación, procese el contenido completo

¿Cual deberías usar?
Depende del contenido de su archivo y del tipo de procesamiento que requiera.
Desde una perspectiva de eficiencia de uso de recursos (mejor a peor) es: 1,2,3,4.
Desde una perspectiva de velocidad de procesamiento y eficiencia (mejor a peor) es: 2,1,3,4.
Desde una perspectiva de facilidad de programación (mejor a peor): 4,3,1,2.
Sin embargo, algunos tipos de procesamiento pueden requerir más que el fragmento de texto más pequeño (descartar 1 y quizás 2) y algunos formatos de archivo pueden no tener partes internas (descartar 3).

Estás haciendo 4. Te sugiero que cambies a 3 (o menos), si puedes .

Debajo de 4, solo hay una forma de evitar DOS: limitar el tamaño antes de que se lea en la memoria (o, para el caso, copiarlo en su sistema de archivos). Es demasiado tarde una vez que se lee. Si esto no es posible, intente con 3, 2 o 1.

Limitar el tamaño del archivo

A menudo, el archivo se carga a través de un formulario HTML.

Si realiza la carga con la anotación Servlet @MultipartConfig y request.getPart().getInputStream() , usted tiene control sobre la cantidad de datos que lee de la transmisión. Además, request.getPart().getSize() devuelve el tamaño del archivo por adelantado y, si es lo suficientemente pequeño, puede hacer request.getPart().write(path) para escribir el archivo en el disco.

Si se está cargando utilizando JSF, entonces JSF 2.2 (muy nuevo) tiene el componente html estándar <h:inputFile> ( javax.faces.component.html.InputFile ), que tiene un atributo para maxLength ; las implementaciones pre-JSF 2.2 tienen componentes personalizados similares (por ejemplo, Tomahawk tiene <t:InputFileUpload> con el atributo maxLength ; PrimeFaces tiene <p:FileUpload> con el atributo sizeLimit ).

Alternativas para leer el archivo completo

Su código que utiliza InputStream , StringBuilder , etc., es una forma eficiente de leer todo el archivo, pero no es necesariamente la forma más simple (menos líneas de código).

Los desarrolladores junior / promedio podrían comprender erróneamente que estás realizando un procesamiento eficiente basado en flujos de datos cuando procesas todo el archivo, así que incluye los comentarios apropiados.

Si quieres menos código, puedes probar uno de los siguientes:

List<String> stringList = java.nio.file.Files.readAllLines(path, charset); or byte[] byteContents = java.nio.file.Files.readAllBytes(path);

Pero requieren atención, o podrían ser ineficientes en el uso de recursos. Si usa readAllLines y luego concatena los elementos de la List en una sola String , entonces consumirá el doble de memoria (para los elementos de la List + la String concatenada). De manera similar, si usa readAllBytes , seguido de la codificación a String ( new String(byteContents, charset) ), entonces nuevamente está usando la memoria "doble". Por lo tanto, es mejor procesar directamente contra List<String> o byte[] , a menos que limite sus archivos a un tamaño lo suficientemente pequeño.


Esto funcionó para mí sin ningún problema.

char charArray[] = new char[ MAX_BUFFER_SIZE ]; int i = 0; int c = 0; while((c = br.read()) != -1 && i < MAX_BUFFER_SIZE) { char character = (char) c; charArray[i++] = character; } return Arrays.copyOfRange(charArray,0,i);


Hay clase EntityUtils bajo Apache httpCore. Utilice el método getString () de esta clase para obtener el contenido de String from Response.


Me enfrenté a un problema similar al copiar un archivo binario enorme (que generalmente no contiene caracteres de nueva línea). Al hacer una línea de lectura (), se lee el archivo binario completo en una sola cadena que causa el espacio de OutOfMemory on Heap.

Aquí hay una alternativa simple a 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(); }

Cosas a tener en cuenta:

  • El ejemplo anterior copia el archivo utilizando un búfer de 1K bytes. Sin embargo, si está haciendo esta copia a través de la red, es posible que desee ajustar el tamaño del búfer.

  • Si desea usar FileChannel o bibliotecas como Commons IO , solo asegúrese de que la implementación se reduzca a algo como lo anterior.


No puedo pensar en otra solución que no sea Apache Commons IO FileUtils. Es bastante simple con la clase FileUtils, ya que el llamado ataque DOS no viene directamente de la capa superior. Leer y escribir un archivo es muy simple, ya que puede hacerlo con solo una línea de código como

String content =FileUtils.readFileToString(new File(filePath));

Puedes explorar más sobre esto.


en lugar de readLine use read, que lee una cantidad determinada de caracteres.

en cada bucle, compruebe la cantidad de datos que se han leído, si es más que una cierta cantidad, más del máximo de una entrada esperada, deténgala, devuelva un error y regístrela.