// 
// Decompiled by Procyon v0.6.0
// 

package org.bouncycastle.est;

import java.io.EOFException;
import org.bouncycastle.util.Strings;
import java.io.IOException;
import java.util.Set;
import org.bouncycastle.util.Properties;
import java.io.InputStream;

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;
    private Long absoluteReadLimit;
    private static final Long ZERO;
    
    public long getAbsoluteReadLimit() {
        return (this.absoluteReadLimit == null) ? Long.MAX_VALUE : this.absoluteReadLimit;
    }
    
    public ESTResponse(final ESTRequest originalRequest, final Source source) throws IOException {
        this.read = 0L;
        this.originalRequest = originalRequest;
        this.source = source;
        if (source instanceof LimitedSource) {
            this.absoluteReadLimit = ((LimitedSource)source).getAbsoluteReadLimit();
        }
        final Set<String> keySet = Properties.asKeySet("org.bouncycastle.debug.est");
        if (keySet.contains("input") || keySet.contains("all")) {
            this.inputStream = new PrintingInputStream(source.getInputStream());
        }
        else {
            this.inputStream = source.getInputStream();
        }
        this.headers = new HttpUtil.Headers();
        this.lineBuffer = new byte[1024];
        this.process();
    }
    
    private void process() throws IOException {
        this.HttpVersion = this.readStringIncluding(' ');
        this.statusCode = Integer.parseInt(this.readStringIncluding(' '));
        this.statusMessage = this.readStringIncluding('\n');
        for (String s = this.readStringIncluding('\n'); s.length() > 0; s = this.readStringIncluding('\n')) {
            final int index = s.indexOf(58);
            if (index > -1) {
                this.headers.add(Strings.toLowerCase(s.substring(0, index).trim()), s.substring(index + 1).trim());
            }
        }
        final boolean equalsIgnoreCase = this.headers.getFirstValueOrEmpty("Transfer-Encoding").equalsIgnoreCase("chunked");
        if (equalsIgnoreCase) {
            this.contentLength = 0L;
        }
        else {
            this.contentLength = this.getContentLength();
        }
        if (this.statusCode == 204 || this.statusCode == 202) {
            if (this.contentLength == null) {
                this.contentLength = 0L;
            }
            else if (this.statusCode == 204 && this.contentLength > 0L) {
                throw new IOException("Got HTTP status 204 but Content-length > 0.");
            }
        }
        if (this.contentLength == null) {
            throw new IOException("No Content-length header.");
        }
        if (this.contentLength.equals(ESTResponse.ZERO) && !equalsIgnoreCase) {
            this.inputStream = new InputStream() {
                @Override
                public int read() throws IOException {
                    return -1;
                }
            };
        }
        if (this.contentLength < 0L) {
            throw new IOException("Server returned negative content length: " + this.absoluteReadLimit);
        }
        if (this.absoluteReadLimit != null && this.contentLength >= this.absoluteReadLimit) {
            throw new IOException("Content length longer than absolute read limit: " + this.absoluteReadLimit + " Content-Length: " + this.contentLength);
        }
        this.inputStream = this.wrapWithCounter(this.inputStream, this.absoluteReadLimit);
        if (equalsIgnoreCase) {
            this.inputStream = new CTEChunkedInputStream(this.inputStream);
        }
        if ("base64".equalsIgnoreCase(this.getHeader("content-transfer-encoding"))) {
            if (equalsIgnoreCase) {
                this.inputStream = new CTEBase64InputStream(this.inputStream);
            }
            else {
                this.inputStream = new CTEBase64InputStream(this.inputStream, this.contentLength);
            }
        }
    }
    
    public String getHeader(final String s) {
        return this.headers.getFirstValue(s);
    }
    
    public String getHeaderOrEmpty(final String s) {
        return this.headers.getFirstValueOrEmpty(s);
    }
    
    protected InputStream wrapWithCounter(final InputStream inputStream, final Long n) {
        return new InputStream() {
            @Override
            public int read() throws IOException {
                final int read = inputStream.read();
                if (read > -1) {
                    ESTResponse.this.read++;
                    if (n != null && ESTResponse.this.read >= n) {
                        throw new IOException("Absolute Read Limit exceeded: " + n);
                    }
                }
                return read;
            }
            
            @Override
            public void close() throws IOException {
                if (ESTResponse.this.contentLength != null && ESTResponse.this.contentLength - 1L > ESTResponse.this.read) {
                    throw new IOException("Stream closed before limit fully read, Read: " + ESTResponse.this.read + " ContentLength: " + ESTResponse.this.contentLength);
                }
                if (inputStream.available() > 0) {
                    throw new IOException("Stream closed with extra content in pipe that exceeds content length.");
                }
                inputStream.close();
            }
        };
    }
    
    protected String readStringIncluding(final char c) throws IOException {
        int length = 0;
        int read;
        do {
            read = this.inputStream.read();
            this.lineBuffer[length++] = (byte)read;
            if (length >= this.lineBuffer.length) {
                throw new IOException("Server sent line > " + this.lineBuffer.length);
            }
        } while (read != c && read > -1);
        if (read == -1) {
            throw new EOFException();
        }
        return new String(this.lineBuffer, 0, length).trim();
    }
    
    public ESTRequest getOriginalRequest() {
        return this.originalRequest;
    }
    
    public HttpUtil.Headers getHeaders() {
        return this.headers;
    }
    
    public String getHttpVersion() {
        return this.HttpVersion;
    }
    
    public int getStatusCode() {
        return this.statusCode;
    }
    
    public String getStatusMessage() {
        return this.statusMessage;
    }
    
    public InputStream getInputStream() {
        return this.inputStream;
    }
    
    public Source getSource() {
        return this.source;
    }
    
    public Long getContentLength() {
        final String firstValue = this.headers.getFirstValue("Content-Length");
        if (firstValue == null) {
            return null;
        }
        try {
            return Long.parseLong(firstValue);
        }
        catch (final RuntimeException ex) {
            throw new RuntimeException("Content Length: '" + firstValue + "' invalid. " + ex.getMessage());
        }
    }
    
    public void close() throws IOException {
        if (this.inputStream != null) {
            this.inputStream.close();
        }
        this.source.close();
    }
    
    static {
        ZERO = 0L;
    }
    
    private static class PrintingInputStream extends InputStream
    {
        private final InputStream src;
        
        private PrintingInputStream(final InputStream src) {
            this.src = src;
        }
        
        @Override
        public int read() throws IOException {
            return this.src.read();
        }
        
        @Override
        public int available() throws IOException {
            return this.src.available();
        }
        
        @Override
        public void close() throws IOException {
            this.src.close();
        }
    }
}
