Façon la plus robuste de lire un fichier ou un flux en utilisant Java (Pour éviter les attaques DoS)
Actuellement, j'ai le code ci-dessous pour la lecture d'un inputStream. Je suis le stockage de l'intégralité du fichier en un StringBuilder variable et le traitement de cette chaîne par la suite.
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();
}
Le code est allé pour un examen avec l'équipe de Sécurité et les commentaires suivants ont été reçus:
-
BufferedReader.readLine
est sensible à DOS (Déni de Service) attaques (ligne de longueur infinie, énorme fichier contenant pas de saut de ligne/carriage retrun) -
Épuisement de ressources pour la classe StringBuilder variable (cas lorsqu'un fichier contenant des données plus grande que la mémoire disponible)
Ci-dessous sont les solutions que je pouvais penser:
-
Créer une variante de mise en œuvre de la méthode readLine (
readLine(int limit)
), qui vérifie le pas. d'octets lus et si elle dépasse la limite spécifiée, lever une exception personnalisée. -
Traiter le fichier ligne par ligne, sans charger le fichier en entier. (pur non-java solution 🙂 )
Veuillez indiquer s'il existe des bibliothèques qui mettent en œuvre ci-dessus solutiuons.
Aussi suggérer des solutions de rechange qui offrent plus de robustesse et sont plus faciles à mettre en œuvre que le projet de ceux de. Si la performance est également un impératif majeur, la sécurité vient en premier.
Merci d'Avance.
source d'informationauteur Unni Kris
Vous devez vous connecter pour publier un commentaire.
Mise À Jour De Réponse
Vous voulez éviter toutes sortes d'attaques DOS (sur les lignes, sur la taille du fichier, etc). Mais à la fin de la fonction, vous êtes à essayer de convertir le fichier en entier dans un seul
String
!!! Supposons que vous limitez la ligne à 8 KO, mais ce qui se passe si quelqu'un vous envoie un fichier avec deux 8 KO lignes? La ligne de lecture de la première partie va passer, mais quand, finalement, vous mélangez le tout dans une seule chaîne, la Chaîne va s'étouffer toute la mémoire disponible.Donc, puisque enfin vous convertir le tout en une seule Chaîne, la limitation de taille de ligne n'a pas d'importance, ni est sûr. Vous avez pour limiter la taille totale du fichier.
Deuxièmement, ce que vous tentons de faire, c'est que vous êtes en train de lire les données en morceaux. Si vous utilisez
BufferedReader
et de lire ligne par ligne. Mais ce que vous êtes en train de faire, et ce que vous voulez vraiment à la fin est d'une certaine façon de lire le fichier en pièce par pièce. Au lieu de lire une ligne à la fois, pourquoi ne pas plutôt de lire 2 KO à la fois?BufferedReader
par son nom - a un tampon à l'intérieur. Vous pouvez configurer la mémoire tampon. Imaginons que vous créez unBufferedReader
avec la taille de la mémoire tampon de 2 KO:Maintenant, si la
InputStream
que vous passez àBufferedReader
a 100 KO de données,BufferedReader
sera automatiquement lu de 2 KO à la fois. Donc, il va lire le flux 50 fois, 2 KO (50x2KB = 100 KO). De même, si vous créezBufferedReader
avec un 10 KO taille de la mémoire tampon, il va lire l'entrée 10 fois (10x10KB = 100 KO).BufferedReader
déjà fait le travail de la lecture de votre fichier de morceau par morceau. Si vous ne voulez pas d'ajouter une couche supplémentaire de ligne-par-ligne au-dessus d'elle. Juste se concentrer sur le résultat final, si votre fichier à la fin est trop gros (> mémoire vive disponible) - comment allez-vous pour le convertir en unString
à la fin?Une meilleure façon, c'est juste passer les choses autour de lui comme un
CharSequence
. C'est ce que Android ne. Tout au long de l'Api Android, vous verrez qu'ils sont de retourCharSequence
partout. DepuisStringBuilder
est également une sous-classe deCharSequence
Android va utiliser à l'interne, soit unString
ou unStringBuilder
ou certains autres optimisée de la chaîne de classe en fonction de la taille et de la nature de l'apport. Donc, vous pourriez plutôt retourner directement laStringBuilder
objet lui-même une fois que vous avez tout lu, plutôt que de le convertir en unString
. Ce serait plus sûr contre les gros volumes de données.StringBuilder
maintient également le même concept de tampons à l'intérieur, et il en interne allouer plusieurs tampons pour les grandes chaînes, plutôt que d'une longue chaîne.De manière globale:
À l'aide d'Apache Commons IO, voici comment vous pourriez lire des données à partir d'un
BoundedInputStream
dans unStringBuilder
en divisant par 2 KO blocs au lieu de lignes:Réponse Originale À Cette Question
Utilisation BoundedInputStream de Apache Commons IO de la bibliothèque. Votre travail devient beaucoup plus facile.
Le code suivant à faire ce que vous voulez:
Vous suffit simplement de l'enveloppe de votre
InputStream
avec unBoundedInputStream
et que vous spécifiez une taille maximale.BoundedInputStream
prendra soin de limiter le lit jusqu'à la taille maximale.Ou vous pouvez le faire lorsque vous êtes en train de créer le lecteur:
Fondamentalement, ce que nous faisons ici, c'est que nous sommes en limitant la taille de lecture à l'
InputStream
couche elle-même, plutôt que de le faire que lors de la lecture des lignes. Si vous vous retrouvez avec un composant réutilisable commeBoundedInputStream
qui limite la lecture à l'InputStream couche, et vous pouvez l'utiliser partout où vous voulez.Edit: Ajout de la note de bas de page
Edit 2: Ajout de la mise à jour de la réponse basée sur les commentaires
Fondamentalement, il y a 4 façons de faire de traitement du fichier:
Basée sur les flux de Traitement (le
java.io.InputStream
modèle): Éventuellement mettre un bufferedReader dans le flux, itérer & lire le prochain texte disponible à partir du flux (si aucun texte n'est disponible, bloc jusqu'à ce que certains deviennent disponibles), les processus de chaque morceau de texte de façon indépendante, comme il est lu (restauration très-différentes tailles de texte pièces)Morceau à Base de Non-Blocage de Traitement (le
java.nio.channels.Channel
modèle): Créer un ensemble de taille fixe tampons (représentant les "morceaux" à traiter), lire dans chacun des tampons à son tour, sans blocage (nio API délégués natif IO, à l'aide de fast-O/S-niveau threads), votre principal thread de traitement récupère chaque tampon à son tour, une fois qu'il est rempli et les processus de la taille fixe morceau, comme d'autres tampons de continuer à être chargé de manière asynchrone.Partie de Traitement de Fichiers (y compris ligne par ligne de traitement) (effet de levier (1) ou (2) d'isoler ou de renforcer chaque "partie"): casser votre format de fichier vers le bas dans sémantiquement significatifs sous-parties (si possible! la rupture dans les lignes pourrait être possible!), itérer sur les flux de morceaux, de morceaux et l'accumulation de contenu dans la mémoire jusqu'à la prochaine partie est complètement construite, processus de chaque partie dès qu'il est construit.
Ensemble du Traitement du Fichier de (le
java.nio.file.Files
modèle): Lire l'intégralité du fichier en mémoire en une seule opération, les processus, le contenu completLequel devriez-vous utiliser?
Il dépend de votre contenu du fichier et le type de traitement dont vous avez besoin.
À partir d'une ressource-l'efficacité de l'utilisation de la perspective (du meilleur au pire) est: 1,2,3,4.
À partir d'une vitesse de traitement & efficacité de la perspective (du meilleur au pire) est: 2,1,3,4.
À partir d'une facilité de programmation (du meilleur au pire): 4,3,1,2.
Toutefois, certains types de traitement peut exiger plus que le plus petit morceau de texte (décision 1, et peut-être 2) et certains formats de fichier ne peut pas avoir les pièces internes (décision 3).
Vous êtes en train de faire 4. Je vous suggère de passage à 3 (ou moins), si vous pouvez.
De moins de 4, il n'y a qu'une façon d'éviter de DOS - limiter la taille avant de la lire dans la mémoire (ou copiés sur votre système de fichiers). Il est trop tard une fois qu'il est lu. Si ce n'est pas possible, essayez alors de 3, 2 ou 1.
La Limitation De Taille De Fichier
Souvent, le fichier est téléchargé via un formulaire HTML.
Si le téléchargement en utilisant Servlet
@MultipartConfig
d'annotation et derequest.getPart().getInputStream()
vous avez le contrôle sur la quantité de données lues à partir du flux. Aussi,request.getPart().getSize()
retourne la taille du fichier à l'avance et si il est assez petit, vous pouvez le fairerequest.getPart().write(path)
pour écrire le fichier sur le disque.Si le téléchargement en utilisant JSF, alors JSF 2.2 (très nouveau) a la norme html composant
<h:inputFile>
(javax.faces.component.html.InputFile
), qui a un attribut pourmaxLength
; pré-JSF 2.2 implémentations ont les mêmes composants personnalisés (par exemple, Tomahawk a<t:InputFileUpload>
avecmaxLength
attribut; PrimeFaces a<p:FileUpload>
avecsizeLimit
attribut).Alternatives pour Lire l'Intégralité du Fichier
Votre code qui utilise
InputStream
StringBuilder
etc, est un efficace façon de lire le fichier en entier, mais n'est pas nécessairement le plus simple moyen (moins de lignes de code).Junior/nombre de développeurs pourraient obtenir de la méconnaissance que vous êtes en train de faire efficace basée sur les flux de traitement, lorsque vous êtes de traitement de l'ensemble de fichiers afin d'inclure des commentaires.
Si vous voulez moins de code, vous pouvez essayer l'une des opérations suivantes:
Mais ils ont besoin de soins, ou ils pourraient être inefficaces dans l'utilisation des ressources. Si vous utilisez
readAllLines
et puis concaténer lesList
éléments en un seulString
vous n'en consomment le double de la mémoire (pour leList
éléments + la concaténation deString
). De même, si vous utilisezreadAllBytes
suivie par l'encodageString
(new String(byteContents, charset)
), puis de nouveau, vous êtes à l'aide de "double" de la mémoire. Donc il vaut mieux le processus directement à l'encontre deList<String>
oubyte[]
sauf si vous limitez vos fichiers sur une assez petite taille.au lieu de readLine utiliser la lecture qui se lit d'une quantité donnée de caractères.
dans chaque boucle de vérifier la quantité de données a été lu, si c'est plus d'une certaine quantité, plus que le maximum de l'apport attendu de l'arrêter et de retourner une erreur et de l'enregistrer.
Une note supplémentaire, j'ai remarqué que vous n'avez pas à proximité de votre BufferedInputStream. Vous devez fermer votre BufferedReader la
finally
bloc comme cela est sensible à des fuites de mémoire.Pas besoin de fermer explicitement
new InputStreamReader(inputStream)
que ce sera automatiquement fermé lorsque vous appelez pour fermer l'emballage de la classebufferedReader
J'ai fait face à un problème similaire lors de la copie d'un énorme fichier binaire (qui ne comporte généralement pas de caractère de saut de ligne). faire un readline() conduit à la lecture de tout fichier binaire en une seule chaîne provoquant
OutOfMemory
sur le Tas de l'espace.Ici est une simple JDK alternative:
Choses à noter:
L'exemple ci-dessus copie le fichier à l'aide d'un tampon de 1K octets. Cependant, si vous faites cette copie sur le réseau, vous pouvez ajuster la taille de la mémoire tampon.
Si vous souhaitez utiliser FileChannel ou des bibliothèques comme Commons IOassurez-vous que la mise en œuvre se résume à quelque chose comme ci-dessus
Je n'en pense pas un soloution autres que Apache Commons IO FileUtils.
Son assez simple avec FileUtils classe, comme le soi-disant attaque de DOS l'habitude de venir directement à partir de la couche supérieure.
De la lecture et de l'écriture d'un fichier est très simple que vous pouvez faire avec juste une ligne de code comme
Vous pouvez explorer plus à ce sujet.
Il est classe EntityUtils sous Apache httpCore. Utiliser la méthode getString() de cette classe pour obtenir la Chaîne de caractères du contenu de la Réponse.
Cela a fonctionné pour moi sans aucun problème.