package org.bouncycastle.est;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;

import org.bouncycastle.util.Properties;
import org.bouncycastle.util.Strings;

A basic http response.
/** * A basic http response. */
public class ESTResponse { private final ESTRequest originalRequest; private final HttpUtil.Headers headers; private final byte[] lineBuffer; private final Source source; private String HttpVersion; private int statusCode; private String statusMessage; private InputStream inputStream; private Long contentLength; private long read = 0; private Long absoluteReadLimit; private static final Long ZERO = 0L; public ESTResponse(ESTRequest originalRequest, Source source) throws IOException { this.originalRequest = originalRequest; this.source = source; if (source instanceof LimitedSource) { this.absoluteReadLimit = ((LimitedSource)source).getAbsoluteReadLimit(); } Set<String> opts = Properties.asKeySet("org.bouncycastle.debug.est"); if (opts.contains("input") || opts.contains("all")) { this.inputStream = new PrintingInputStream(source.getInputStream()); } else { this.inputStream = source.getInputStream(); } this.headers = new HttpUtil.Headers(); this.lineBuffer = new byte[1024]; process(); } private void process() throws IOException { // // Status line. // HttpVersion = readStringIncluding(' '); this.statusCode = Integer.parseInt(readStringIncluding(' ')); this.statusMessage = readStringIncluding('\n'); // // Headers. // String line = readStringIncluding('\n'); int i; while (line.length() > 0) { i = line.indexOf(':'); if (i > -1) { String k = Strings.toLowerCase(line.substring(0, i).trim()); // Header keys are case insensitive headers.add(k, line.substring(i + 1).trim()); } line = readStringIncluding('\n'); } contentLength = getContentLength(); // // Concerned that different servers may or may not set a Content-length // for these success types. In this case we will arbitrarily set content length // to zero. // if (statusCode == 204 || statusCode == 202) { if (contentLength == null) { contentLength = 0L; } else { if (statusCode == 204 && contentLength > 0) { throw new IOException("Got HTTP status 204 but Content-length > 0."); } } } if (contentLength == null) { throw new IOException("No Content-length header."); } if (contentLength.equals(ZERO)) { // // The server is likely to hang up the socket and any attempt to read can // result in a broken pipe rather than an eof. // So we will return a dummy input stream that will return eof to anything that reads from it. // inputStream = new InputStream() { public int read() throws IOException { return -1; } }; } if (contentLength != null) { if (contentLength < 0) { throw new IOException("Server returned negative content length: " + absoluteReadLimit); } if (absoluteReadLimit != null && contentLength >= absoluteReadLimit) { throw new IOException("Content length longer than absolute read limit: " + absoluteReadLimit + " Content-Length: " + contentLength); } } inputStream = wrapWithCounter(inputStream, absoluteReadLimit); // // Observed that some // if ("base64".equalsIgnoreCase(getHeader("content-transfer-encoding"))) { inputStream = new CTEBase64InputStream(inputStream, getContentLength()); } } public String getHeader(String key) { return headers.getFirstValue(key); } protected InputStream wrapWithCounter(final InputStream in, final Long absoluteReadLimit) { return new InputStream() { public int read() throws IOException { int i = in.read(); if (i > -1) { read++; if (absoluteReadLimit != null && read >= absoluteReadLimit) { throw new IOException("Absolute Read Limit exceeded: " + absoluteReadLimit); } } return i; } public void close() throws IOException { if (contentLength != null && contentLength - 1 > read) { throw new IOException("Stream closed before limit fully read, Read: " + read + " ContentLength: " + contentLength); } if (in.available() > 0) { throw new IOException("Stream closed with extra content in pipe that exceeds content length."); } in.close(); } }; } protected String readStringIncluding(char until) throws IOException { int c = 0; int j; do { j = inputStream.read(); lineBuffer[c++] = (byte)j; if (c >= lineBuffer.length) { throw new IOException("Server sent line > " + lineBuffer.length); } } while (j != until && j > -1); if (j == -1) { throw new EOFException(); } return new String(lineBuffer, 0, c).trim(); } public ESTRequest getOriginalRequest() { return originalRequest; } public HttpUtil.Headers getHeaders() { return headers; } public String getHttpVersion() { return HttpVersion; } public int getStatusCode() { return statusCode; } public String getStatusMessage() { return statusMessage; } public InputStream getInputStream() { return inputStream; } public Source getSource() { return source; } public Long getContentLength() { String v = headers.getFirstValue("Content-Length"); if (v == null) { return null; } try { return Long.parseLong(v); } catch (RuntimeException nfe) { throw new RuntimeException("Content Length: '" + v + "' invalid. " + nfe.getMessage()); } } public void close() throws IOException { if (inputStream != null) { inputStream.close(); } this.source.close(); } private class PrintingInputStream extends InputStream { private final InputStream src; private PrintingInputStream(InputStream src) { this.src = src; } public int read() throws IOException { int i = src.read(); System.out.print(String.valueOf((char)i)); return i; } public int available() throws IOException { return src.available(); } public void close() throws IOException { src.close(); } } }