Le moyen le plus robuste de lire un fichier ou un stream en utilisant Java (Pour prévenir les attaques par déni de service)

Actuellement, j’ai le code ci-dessous pour lire un inputStream. Je stocke le fichier entier dans une variable SsortingngBuilder et traite cette chaîne par la suite.

public static Ssortingng getContentFromInputStream(InputStream inputStream) // public static Ssortingng getContentFromInputStream(InputStream inputStream, // int maxLineSize, int maxFileSize) { SsortingngBuilder ssortingngBuilder = new SsortingngBuilder(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); Ssortingng lineSeparator = System.getProperty("line.separator"); Ssortingng 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 SsortingngBuilder // after appending check the size of the SsortingngBuilder // if file size exceeds the max file limit then throw an exception fileLine = bufferedReader.readLine(); while (fileLine != null) { if (!firstLine) ssortingngBuilder.append(lineSeparator); ssortingngBuilder.append(fileLine); fileLine = bufferedReader.readLine(); firstLine = false; } } catch (IOException e) { //TODO : throw or handle the exception } //TODO : close the stream return ssortingngBuilder.toSsortingng(); } 

Le code a été examiné par l’équipe de sécurité et les commentaires suivants ont été reçus:

  1. BufferedReader.readLine est susceptible d’attaques de type DOS (déni de service) (ligne de longueur infinie, fichier énorme ne contenant pas de retour à la ligne / retour de la queue).

  2. Épuisement des ressources pour la variable SsortingngBuilder (cas où un fichier contenant des données supérieures à la mémoire disponible)

Voici les solutions que je pourrais imaginer:

  1. Créez une autre implémentation de la méthode readLine ( readLine(int limit) ), qui recherche le no. d’octets lus et s’il dépasse la limite spécifiée, lève une exception personnalisée.

  2. Traitez le fichier ligne par ligne sans charger le fichier en entier. (solution pure non-java :))

Veuillez suggérer s’il existe des bibliothèques existantes qui implémentent les solutions ci-dessus. Proposez également des solutions alternatives plus robustes ou plus pratiques à mettre en œuvre que celles proposées. Bien que les performances constituent également une exigence majeure, la sécurité est une priorité.

Merci d’avance.

Réponse mise à jour

Vous voulez éviter toutes sortes d’attaques DOS (sur les lignes, sur la taille du fichier, etc.). Mais à la fin de la fonction, vous essayez de convertir le fichier entier en une seule Ssortingng !!! Supposons que vous limitiez la ligne à 8 Ko, mais que se passe-t-il si quelqu’un vous envoie un fichier avec deux lignes de 8 Ko? La partie de lecture de ligne passera, mais lorsque vous combinerez finalement tout en une seule chaîne, la chaîne étouffera toute la mémoire disponible.

Donc, comme vous convertissez finalement tout en une seule chaîne, la limitation de la taille de la ligne importe peu, ni sûre. Vous devez limiter la taille totale du fichier.

Deuxièmement, vous essayez essentiellement de lire des données en morceaux. Donc, vous utilisez BufferedReader et lisez-le ligne par ligne. Mais ce que vous essayez de faire et ce que vous voulez vraiment à la fin, c’est une façon de lire le fichier pièce par pièce. Au lieu de lire une ligne à la fois, pourquoi ne pas lire 2 Ko à la fois?

BufferedReader – par son nom – contient un tampon. Vous pouvez configurer ce tampon. Supposons que vous créez un BufferedReader avec une taille de mémoire tampon de 2 Ko:

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

Désormais, si le InputStream que vous transmettez à BufferedReader contient 100 Ko de données, BufferedReader le lira automatiquement à InputStream 2 Ko à la fois. Ainsi, il lira le stream 50 fois, 2 Ko chacun (50 x 2 Ko = 100 Ko). De même, si vous créez BufferedReader avec une taille de tampon de 10 Ko, il lira 10 fois l’entrée (10 x 10 Ko = 100 Ko).

BufferedReader déjà le travail de lecture de votre fichier par morceau. Donc, vous ne voulez pas append une couche supplémentaire de ligne par ligne au-dessus. Concentrez-vous simplement sur le résultat final – si votre fichier à la fin est trop volumineux (> RAM disponible) – comment allez-vous le convertir en Ssortingng à la fin?

Une meilleure façon de faire est de simplement passer les choses sous forme de CharSequence . C’est ce que fait Android. Tout au long des API Android, vous verrez qu’ils renvoient partout du CharSequence . Puisque SsortingngBuilder est également une sous-classe de CharSequence , Android utilisera en interne soit une Ssortingng , soit un SsortingngBuilder soit une autre classe de chaîne optimisée en fonction de la taille / nature de l’entrée. Vous pouvez donc plutôt retourner directement l’object SsortingngBuilder une fois que vous avez tout lu, plutôt que de le convertir en Ssortingng . Ce serait plus sûr contre de grandes données. SsortingngBuilder conserve également le même concept de mémoire tampon à l’intérieur, et il allouera en interne plusieurs mémoires tampons pour les chaînes volumineuses, plutôt qu’une seule chaîne longue.

Donc globalement:

  • Limitez la taille globale du fichier car vous allez devoir gérer l’intégralité du contenu à un moment donné. Oubliez les lignes de limitation ou de division
  • Lire en morceaux

À l’aide d’Apache Commons IO, voici comment lire des données d’un BoundedInputStream dans un SsortingngBuilder , en scindant par blocs de 2 Ko au lieu de lignes:

 // import org.apache.commons.io.output.SsortingngBuilderWriter; // import org.apache.commons.io.input.BoundedInputStream; // import org.apache.commons.io.IOUtils; BoundedInputStream boundedInput = new BoundedInputStream(originalInput, ); BufferedReader reader = new BufferedReader(new InputStreamReader(boundedInput), 2048); SsortingngBuilder output = new SsortingngBuilder(); SsortingngBuilderWriter writer = new SsortingngBuilderWriter(output); IOUtils.copy(reader, writer); // copies data from "reader" => "writer" return output; 

Réponse originale

Utilisez BoundedInputStream à partir de la bibliothèque Apache Commons IO . Votre travail devient beaucoup plus facile.

Le code suivant fera ce que vous voulez:

 public static Ssortingng getContentFromInputStream(InputStream inputStream) { inputStream = new BoundedInputStream(inputStream, ); // Rest code are all same 

Il vous suffit simplement d’emballer votre InputStream avec un BoundedInputStream et de spécifier une taille maximale. BoundedInputStream se chargera de limiter les lectures à cette taille maximale.

Ou vous pouvez le faire lorsque vous créez le lecteur:

 BufferedReader bufferedReader = new BufferedReader( new InputStreamReader( new BoundedInputStream(inputStream, ) ) ); 

En gros, nous InputStream la taille de lecture à la couche InputStream , plutôt que de le faire lors de la lecture de lignes. Vous vous retrouvez donc avec un composant réutilisable tel que BoundedInputStream qui limite la lecture au niveau de la couche InputStream et que vous pouvez utiliser où vous voulez.

Edit: Ajout de note de bas de page

Edit 2: Ajout d’une réponse mise à jour en fonction des commentaires

Il y a fondamentalement 4 manières de faire le traitement de fichier:

  1. Traitement basé sur les stream (modèle java.io.InputStream ): vous pouvez éventuellement placer un tampon tamponné autour du stream, itérer et lire le prochain texte disponible dans le stream (si aucun texte n’est disponible, bloquer jusqu’à ce que certains soient disponibles), traiter chaque élément. texte indépendamment comme il est lu (s’adressant à des tailles de texte très variables)

  2. Traitement non bloquant basé sur les blocs (modèle java.nio.channels.Channel ): Créez un ensemble de tampons de taille fixe (représentant les «morceaux» à traiter), lus dans chacun des tampons sans bloquer (nio L’API délivre des entrées-sorties natives à l’aide de threads rapides de niveau O / S), votre thread de traitement principal sélectionne chaque tampon une fois qu’il est rempli et traite le bloc de taille fixe, les autres tampons continuant à être chargés de manière asynchrone.

  3. Traitement de fichiers de pièce (y compris le traitement ligne par ligne) (peut utiliser (1) ou (2) pour isoler ou créer chaque “partie”): divisez votre format de fichier en sous-parties sémantiquement significatives (si possible! des lignes pourraient être possibles!), itérer à travers des morceaux de stream ou des morceaux et le contenu accumulé en mémoire unitl la partie suivante est complètement construite, traiter chaque partie dès qu’elle est construite.

  4. Traitement du fichier entier (modèle java.nio.file.Files ): lit le fichier entier en mémoire en une seule opération, traite le contenu complet

Lequel devriez-vous utiliser?
Cela dépend du contenu de votre fichier et du type de traitement requirejs.
Du sharepoint vue de l’efficacité d’utilisation des ressources (le meilleur au pire) est: 1,2,3,4.
Du sharepoint vue de la vitesse de traitement et de l’efficacité (du meilleur au pire), on peut citer: 2,1,3,4.
Du sharepoint vue de la facilité de programmation (du meilleur au pire): 4,3,1,2.
Cependant, certains types de traitement peuvent nécessiter plus que le plus petit élément de texte (en excluant 1 et peut-être 2) et certains formats de fichiers peuvent ne pas comporter de parties internes (en excluant 3).

Vous faites 4. Je vous suggère de passer à 3 (ou moins), si vous le pouvez .

Sous 4, il n’y a qu’une seule façon d’éviter le DOS: limitez la taille avant sa lecture en mémoire (ou pour cela, copiez-la sur votre système de fichiers). Il est trop tard une fois lu. Si cela n’est pas possible, essayez 3, 2 ou 1.

Limiter la taille du fichier

Souvent, le fichier est téléchargé via un formulaire HTML.

Si vous téléchargez à l’aide de l’annotation Servlet @MultipartConfig et de request.getPart().getInputStream() , vous contrôlez la quantité de données lues dans le stream. De plus, request.getPart().getSize() renvoie la taille du fichier à l’avance et si elle est suffisamment petite, vous pouvez faire request.getPart().write(path) pour écrire le fichier sur le disque.

Si vous téléchargez avec JSF, JSF 2.2 (tout nouveau) a le composant HTML standard ( javax.faces.component.html.InputFile ), qui a un atsortingbut pour maxLength ; Les implémentations antérieures à JSF 2.2 ont des composants personnalisés similaires (par exemple, Tomahawk a avec l’atsortingbut maxLength ; PrimeFaces a avec l’atsortingbut sizeLimit ).

Alternatives à lire le dossier entier

Votre code, qui utilise InputStream , SsortingngBuilder , etc., est un moyen efficace de lire l’intégralité du fichier, mais ce n’est pas nécessairement le moyen le plus simple (moindres lignes de code).

Les développeurs juniors / moyens peuvent avoir l’impression que vous effectuez un traitement efficace basé sur les stream, lorsque vous traitez l’intégralité du fichier.

Si vous voulez moins de code, vous pouvez essayer l’une des méthodes suivantes:

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

Mais ils nécessitent des soins ou ils pourraient être inefficaces dans l’utilisation des ressources. Si vous utilisez readAllLines , puis que vous concaténez les éléments List en une seule Ssortingng , vous utiliserez alors deux fois plus de mémoire (pour les éléments List et la Ssortingng concaténée). De même, si vous utilisez readAllBytes , suivi par encoding to Ssortingng ( new Ssortingng(byteContents, charset) ), vous utilisez à nouveau “double” la mémoire. Il est donc préférable de traiter directement avec List ou byte[] , sauf si vous limitez vos fichiers à une taille suffisamment petite.

au lieu de readLine, utilisez read qui lit une quantité donnée de caractères.

dans chaque boucle, vérifiez la quantité de données lues, si elles dépassent une certaine quantité, plus que le maximum d’une entrée attendue, arrêtez-la, renvoyez une erreur et enregistrez-la.

Une note supplémentaire, j’ai remarqué que vous n’avez pas fermé votre BufferedInputStream. Vous devez fermer votre bloc BufferedReader finally car cela risque de provoquer des memory leaks.

 ... } catch (IOException e) { // throw or handle the exception } finally{ bufferedReader.close(); } 

Inutile de fermer explicitement le new InputStreamReader(inputStream) car celui-ci sera automatiquement fermé lorsque vous appelez pour fermer la classe wrapping bufferedReader

J’ai rencontré un problème similaire lors de la copie d’un énorme fichier binary (qui ne contient généralement pas de caractère de nouvelle ligne). Faire une readline () conduit à lire le fichier binary entier en une seule chaîne provoquant OutOfMemory sur l’espace Heap.

Voici une alternative simple au JDK:

 public static void main(Ssortingng[] args) throws Exception { byte[] array = new byte[1024]; FileInputStream fis = new FileInputStream(new File("")); FileOutputStream fos = new FileOutputStream(new File("")); int length = 0; while((length = fis.read(array)) != -1) { fos.write(array, 0, length); } fis.close(); fos.close(); } 

Choses à noter:

  • L’exemple ci-dessus copie le fichier en utilisant un tampon de 1 Ko d’octets. Toutefois, si vous effectuez cette copie sur le réseau, vous souhaiterez peut-être modifier la taille de la mémoire tampon.

  • Si vous souhaitez utiliser FileChannel ou des bibliothèques comme Commons IO , assurez-vous simplement que l’implémentation se résume à quelque chose comme ci-dessus

Je ne peux pas penser à une solution autre que Apache Commons IO FileUtils. C’est assez simple avec la classe FileUtils, car l’attaque dite DOS ne proviendra pas directement de la couche supérieure. Lire et écrire un fichier est très simple, vous pouvez le faire avec une seule ligne de code comme

 Ssortingng content =FileUtils.readFileToSsortingng(new File(filePath)); 

Vous pouvez explorer plus à ce sujet.

Il y a la classe EntityUtils sous Apache httpCore. Utilisez la méthode getSsortingng () de cette classe pour obtenir la chaîne à partir du contenu de la réponse.

Cela a fonctionné pour moi sans aucun problème.

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