Подтвердить что ты не робот

Самый надежный способ чтения файла или потока с использованием Java (для предотвращения DoS-атак)

В настоящее время у меня есть код для чтения 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();

}

Код отправил обзор в группу безопасности, и были получены следующие комментарии:

  1. BufferedReader.readLine восприимчив к атакам DOS (отказ в обслуживании) (линия бесконечной длины, огромный файл, не содержащий строки перевода/возврата каретки)

  2. Исчерпывание ресурсов для переменной StringBuilder (случаи, когда файл, содержащий данные, превышающие доступную память)

Ниже приведены решения, о которых я мог подумать:

  1. Создайте альтернативную реализацию метода readLine (readLine(int limit)), который проверяет отсутствие. прочитанных байтов, и если он превышает указанный предел, создайте настраиваемое исключение.

  2. Обработайте файл по строкам, не загружая файл целиком. (чистое не-Java-решение :))

Пожалуйста, предложите, есть ли существующие библиотеки, которые реализуют вышеупомянутые решения. Также предлагайте альтернативные решения, которые предлагают более надежную или более удобную реализацию, чем предлагаемые. Хотя производительность также является основным требованием, безопасность на первом месте.

4b9b3361

Ответ 1

Обновленный ответ

Вы хотите избежать всех видов атак DOS (по строкам, размеру файла и т.д.). Но в конце функции вы пытаетесь преобразовать весь файл в один единственный String!!! Предположим, что вы ограничиваете строку до 8 КБ, но что произойдет, если кто-то отправит вам файл с двумя строками 8 КБ? Часть считывания строк пройдет, но когда вы, наконец, объедините все в одну строку, String захлопнет всю доступную память.

Итак, поскольку, наконец, вы конвертируете все в одну строку, ограничение размера строки не имеет значения и не безопасно. Вы должны ограничить весь размер файла.

Во-вторых, что вы в основном пытаетесь сделать, вы пытаетесь прочитать данные в кусках. Таким образом, вы используете BufferedReader и читаете его по очереди. Но то, что вы пытаетесь сделать, и что вы действительно хотите в конце, - это способ чтения файла по частям. Вместо того, чтобы читать по одной строке за раз, почему бы не вместо этого читать 2 КБ одновременно?

BufferedReader - по его имени - имеет внутри него буфер. Вы можете настроить этот буфер. Скажем, вы создаете BufferedReader с размером буфера 2 КБ:

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

Теперь, если InputStream, который вы передаете в BufferedReader, имеет 100 КБ данных, BufferedReader будет автоматически читать его 2 КБ в момент времени. Таким образом, он будет читать поток 50 раз, по 2 КБ каждый (50x2KB = 100 КБ). Аналогично, если вы создаете BufferedReader с размером буфера 10 КБ, он будет считывать вход 10 раз (10x10KB = 100 КБ).

BufferedReader уже выполняет работу по чтению вашего файла по блоку. Таким образом, вы не хотите добавлять дополнительный слой строки за строкой над ним. Просто сосредоточьтесь на конечном результате - если ваш файл в конце слишком большой ( > доступная оперативная память) - как вы собираетесь преобразовать его в String в конце?

Один из лучших способов - просто передать вещи как CharSequence. Что делает Android. Во всех API-интерфейсах Android вы увидите, что они возвращают CharSequence всюду. Поскольку StringBuilder также является подклассом CharSequence, Android будет внутренне использовать либо String, либо StringBuilder, либо какой-либо другой оптимизированный строковый класс, основанный на размере/характере ввода. Поэтому вы можете скорее сразу вернуть объект StringBuilder, как только вы прочтете все, а не преобразуете его в String. Это было бы безопаснее против больших данных. StringBuilder также поддерживает ту же концепцию буферов внутри нее и внутренне выделяет несколько буферов для больших строк, а не одну длинную строку.

Итак, в целом:

  • Ограничьте общий размер файла, так как в какой-то момент вы будете иметь дело со всем контентом. Забудьте о лимитирующих или разделяющих линиях.
  • Читайте в кусках

Используя Apache Commons IO, вот как вы будете читать данные из BoundedInputStream в StringBuilder, разделяя на 2 КБ блоков вместо строк:

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

Оригинальный ответ

Используйте BoundedInputStream из библиотеки Apache Commons IO. Ваша работа становится намного проще.

Следующий код сделает то, что вы хотите:

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, а не делаем это при чтении строк. Таким образом, вы получаете многоразовый компонент, например BoundedInputStream, который ограничивает чтение на уровне InputStream, и вы можете использовать его там, где хотите.

Изменить: Добавлена ​​сноска

Изменить 2: Добавлен обновленный ответ на основе комментариев

Ответ 2

Существует 4 способа обработки файлов:

  1. Обработка по потоку (модель java.io.InputStream): необязательно помещать буферизованный считыватель вокруг потока, перебирать и читать следующий доступный текст из потока (если текст не доступен, блокировать до тех пор, пока некоторые не станут доступными), обработать каждый фрагмент текст независимо от того, как он читается (питание для широко варьируемых размеров текстовых фрагментов)

  2. Блокирующая обработка на основе java.nio.channels.Channel модель java.nio.channels.Channel): создайте набор буферов фиксированного размера (представляющих обработанные "куски"), считывая каждый буфер по очереди без блокировки (nio API-интерфейсы для собственного ввода-вывода, используя быстрые потоки потоков O/S), ваш основной поток обработки выбирает каждый буфер поочередно после его заполнения и обрабатывает блок фиксированного размера, так как другие буферы продолжают асинхронно загружаться.

  3. Обработка файлов деталей (в том числе поэтапная обработка) (может использовать (1) или (2) для изоляции или создания каждой "части"): разбить свой формат файла на семантически значимые части (если это возможно! линии могут быть возможны!), итерации через куски потока или куски и наращивание содержимого в памяти до тех пор, пока следующая часть не будет полностью построена, обработать каждую часть, как только она будет построена.

  4. Полная обработка файлов (модель java.nio.file.Files). Прочитайте весь файл в памяти за одну операцию, обработайте полное содержимое

Какой из них вы должны использовать?
Это зависит от вашего содержимого файла и типа требуемой обработки.
С точки зрения эффективности использования ресурсов (от наилучшего к худшему): 1,2,3,4.
С точки зрения скорости обработки и эффективности (от наилучшего к худшему): 2,1,3,4.
С точки зрения простоты программирования (от лучшего к худшему): 4,3,1,2.
Однако для некоторых видов обработки может потребоваться больше, чем наименьшая часть текста (исключая 1 и, возможно, 2), а некоторые форматы файлов могут не иметь внутренних частей (исключая 3).

Вы делаете 4. Я предлагаю вам перейти на 3 (или ниже), если сможете.

В возрасте до 4 лет только один способ избежать DOS - ограничить размер до того, как он будет считаться в памяти (или, в этом случае, скопирован в вашу файловую систему). Слишком поздно, когда он зачитается. Если это невозможно, попробуйте 3, 2 или 1.

Ограничение размера файла

Часто файл загружается через HTML-форму.

Если вы загружаете с @MultipartConfig аннотации 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 + конкатенированная String). Аналогично, если вы используете readAllBytes, а затем кодировку в String (new String(byteContents, charset)), то опять же вы используете "двойную" память. Лучше всего обработать непосредственно против List<String> или byte[], если вы не ограничите свои файлы до небольшого размера.

Ответ 3

вместо readLine используйте чтение, которое читает заданное количество символов.

в каждом цикле проверяет, сколько данных было прочитано, если оно превышает некоторую величину, больше максимального ожидаемого ввода, остановите его и верните ошибку и запишите его.

Ответ 4

Еще одно замечание, я заметил, что вы не закрыли BufferedInputStream. Вы должны закрыть BufferedReader блоком finally, поскольку это восприимчиво к утечкам памяти.

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

Не нужно явно закрывать new InputStreamReader(inputStream), поскольку это будет автоматически закрыто при вызове, чтобы закрыть класс упаковки bufferedReader

Ответ 5

У меня возникла аналогичная проблема при копировании огромного двоичного файла (который обычно не содержит символа новой строки). выполнение 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();
}

Примечания:

  • В приведенном выше примере копируется файл с использованием буфера в 1 Кбайт. Однако, если вы делаете эту копию по сети, вы можете настроить размер буфера.

  • Если вы хотите использовать FileChannel или библиотеки, такие как Commons IO, просто убедитесь, что реализация сводится к чему-то вроде выше

Ответ 6

Я не могу думать о другом, кроме Apache Commons IO FileUtils. Его довольно простой класс FileUtils, так как так называемая атака DOS не будет поступать непосредственно из верхнего уровня. Чтение и запись файла очень просто, поскольку вы можете сделать это только с одной строкой кода, например

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

Подробнее об этом можно узнать.

Ответ 7

В Apache httpCore есть класс EntityUtils. Используйте метод getString() этого класса, чтобы получить содержимое String from Response.

Ответ 8

Это сработало для меня без проблем.

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