InputStreamUtils.java
package com.renomad.minum.web;
import com.renomad.minum.security.ForbiddenUseException;
import com.renomad.minum.utils.UtilsException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* Handy helpful utilities for working with input streams.
*/
final class InputStreamUtils implements IInputStreamUtils {
private final int maxReadLineSizeBytes;
public InputStreamUtils(int maxReadLineSizeBytes) {
this.maxReadLineSizeBytes = maxReadLineSizeBytes;
}
@Override
public String readLine(InputStream inputStream) throws IOException {
final int NEWLINE_DECIMAL = 10;
final int CARRIAGE_RETURN_DECIMAL = 13;
final var result = new ByteArrayOutputStream(maxReadLineSizeBytes / 3);
for (int i = 0;; i++) {
if (i >= maxReadLineSizeBytes) {
inputStream.close();
throw new ForbiddenUseException("client sent more bytes than allowed for a single line. max: " + maxReadLineSizeBytes);
}
int a = inputStream.read();
if (a == -1) return result.toString(StandardCharsets.UTF_8);
if (a == CARRIAGE_RETURN_DECIMAL) continue;
if (a == NEWLINE_DECIMAL) break;
result.write(a);
}
return result.toString(StandardCharsets.UTF_8);
}
@Override
public byte[] read(int lengthToRead, InputStream inputStream) {
final int typicalBufferSize = 1024 * 8;
byte[] buf = new byte[Math.min(lengthToRead, typicalBufferSize)]; // 8k buffer is my understanding of a decent size. Fast, doesn't waste too much space.
byte[] data;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int read;
int totalRead = 0;
try {
while ((read = inputStream.read(buf)) >= 0) {
totalRead += read;
if (totalRead < lengthToRead) {
// if we haven't gotten everything we wanted, write this to the output and loop again
baos.write(buf, 0, read);
} else {
baos.write(buf, 0, read - (totalRead - lengthToRead));
break;
}
}
} catch (IOException ex) {
throw new UtilsException(ex);
}
data = baos.toByteArray();
if (data.length != lengthToRead) {
String message = String.format("length of bytes read (%d) must be what we expected (%d)", data.length, lengthToRead);
throw new ForbiddenUseException(message);
}
return data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InputStreamUtils that = (InputStreamUtils) o;
return maxReadLineSizeBytes == that.maxReadLineSizeBytes;
}
@Override
public int hashCode() {
return Objects.hash(maxReadLineSizeBytes);
}
}