InputStreamUtils.java

  1. package com.renomad.minum.web;

  2. import com.renomad.minum.security.ForbiddenUseException;
  3. import com.renomad.minum.utils.UtilsException;

  4. import java.io.ByteArrayOutputStream;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.nio.charset.StandardCharsets;
  8. import java.util.Objects;

  9. /**
  10.  * Handy helpful utilities for working with input streams.
  11.  */
  12. final class InputStreamUtils implements IInputStreamUtils {

  13.     private final int maxReadLineSizeBytes;

  14.     public InputStreamUtils(int maxReadLineSizeBytes) {
  15.         this.maxReadLineSizeBytes = maxReadLineSizeBytes;
  16.     }

  17.     @Override
  18.     public String readLine(InputStream inputStream) throws IOException  {
  19.         final int NEWLINE_DECIMAL = 10;
  20.         final int CARRIAGE_RETURN_DECIMAL = 13;

  21.         final var result = new ByteArrayOutputStream(maxReadLineSizeBytes / 3);
  22.         for (int i = 0;; i++) {
  23.             if (i >= maxReadLineSizeBytes) {
  24.                 inputStream.close();
  25.                 throw new ForbiddenUseException("client sent more bytes than allowed for a single line.  max: " + maxReadLineSizeBytes);
  26.             }
  27.             int a = inputStream.read();
  28.             if (a == -1) return result.toString(StandardCharsets.UTF_8);
  29.             if (a == CARRIAGE_RETURN_DECIMAL) continue;
  30.             if (a == NEWLINE_DECIMAL) break;
  31.             result.write(a);
  32.         }
  33.         return result.toString(StandardCharsets.UTF_8);
  34.     }

  35.     @Override
  36.     public byte[] read(int lengthToRead, InputStream inputStream) {
  37.         final int typicalBufferSize = 1024 * 8;
  38.         byte[] buf = new byte[Math.min(lengthToRead, typicalBufferSize)]; // 8k buffer is my understanding of a decent size.  Fast, doesn't waste too much space.
  39.         byte[] data;
  40.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  41.         int read;
  42.         int totalRead = 0;
  43.         try {
  44.             while ((read = inputStream.read(buf)) >= 0) {
  45.                 totalRead += read;
  46.                 if (totalRead < lengthToRead) {
  47.                     // if we haven't gotten everything we wanted, write this to the output and loop again
  48.                     baos.write(buf, 0, read);
  49.                 } else {
  50.                     baos.write(buf, 0, read - (totalRead - lengthToRead));
  51.                     break;
  52.                 }
  53.             }
  54.         } catch (IOException ex) {
  55.             throw new UtilsException(ex);
  56.         }
  57.         data = baos.toByteArray();

  58.         if (data.length != lengthToRead) {
  59.             String message = String.format("length of bytes read (%d) must be what we expected (%d)", data.length, lengthToRead);
  60.             throw new ForbiddenUseException(message);
  61.         }
  62.         return data;
  63.     }

  64.     @Override
  65.     public boolean equals(Object o) {
  66.         if (this == o) return true;
  67.         if (o == null || getClass() != o.getClass()) return false;
  68.         InputStreamUtils that = (InputStreamUtils) o;
  69.         return maxReadLineSizeBytes == that.maxReadLineSizeBytes;
  70.     }

  71.     @Override
  72.     public int hashCode() {
  73.         return Objects.hash(maxReadLineSizeBytes);
  74.     }
  75. }