com.isecpartners.gizmo.HttpRequest.java Source code

Java tutorial

Introduction

Here is the source code for com.isecpartners.gizmo.HttpRequest.java

Source

/*
Copyright (C) 2009 Rachel Engel
    
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package com.isecpartners.gizmo;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.util.Base64;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;

import cybervillains.ca.KeyStoreManager;
import com.isecpartners.gizmo.HttpResponse.FailedRequestException;

class HttpRequest extends HTTPMessage {

    private static int global_request_num = 0;
    private static char[] pass = "lalalala".toCharArray();
    private boolean canWakeUp = false;
    private String interrimContents;
    private String host;
    private boolean override_host = false;
    private String version;
    private boolean cached;
    private boolean isSSL = false;
    private int request_num = global_request_num++;
    HttpResponse resp;
    static SSLSocketFactory sslFactory;
    static SSLServerSocketFactory sslServerFactory;
    private Socket sock;
    private Socket outboundSock;
    private boolean sent = false;
    private static Hashtable<String, SSLSocketFactory> _factories = new Hashtable<String, SSLSocketFactory>();
    private StringBuffer workingContents = new StringBuffer();
    private int port = 80;
    private boolean user_defined_port = false;
    private boolean connect_protocol_handled = false;
    private String url = "";
    private boolean passthroughssl = false;
    private final String ACCEPT_ENCODING = "ACCEPT-ENCODING";

    public void setClientSocket(Socket sock) {
        this.sock = sock;
    }

    public Socket getClientSocket() {
        return this.sock;
    }

    public boolean passthroughssl() {
        return passthroughssl;
    }

    public HttpRequest clone() {
        HttpRequest ret = createRequest();
        ret.interrimContents = this.interrimContents;
        ret.host = this.host;
        ret.version = this.version;
        ret.isSSL = this.isSSL;
        ret.workingContents = this.workingContents;
        ret.url = this.url;
        ret.addContents(this.workingContents.toString());
        ret.header = header;
        return ret;
    }

    public String getURL() {
        String tmp = this.makeURL(workingContents);
        if (GizmoView.getView().intercepting()) {
            if (tmp.contains("//")) {
                tmp = tmp.substring(tmp.indexOf("/", tmp.indexOf("//") + 2));
            }
        }
        if (url.contains("?")) {
            tmp = tmp.substring(0, tmp.indexOf("?"));
        } else {
            tmp = tmp.substring(0, tmp.lastIndexOf("/"));
        }
        return (isSSL ? "https" : "http") + "://" + host + tmp + "/";
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    boolean isSSL() {
        return isSSL;
    }

    void setHost(String host) {
        this.host = host;
        if (host != null && host != "" && host.contains(":")) {
            this.host = host.substring(0, host.indexOf(":"));
            this.port = Integer.parseInt(host.substring(host.indexOf(":") + 1));
        }
        override_host = true;
    }

    void setSSL(boolean ssl) {
        this.isSSL = ssl;
        if (ssl)
            this.port = 443;
    }

    private String makeURL(StringBuffer workingContents) {
        String ret = "";
        if (header.contains("http")) {
            ret = mk_header(workingContents).substring(header.indexOf("http"), header.indexOf("HTTP") - 1);
        } else {
            ret = mk_header(workingContents).trim().substring(header.indexOf("/"), header.indexOf("HTTP") - 1);
        }
        return ret;
    }

    private String getUrlPath(String header) {
        if (header == null) {
            return "";
        }
        String tmp = header;
        String path = tmp.substring(tmp.indexOf(" ") + 1, tmp.lastIndexOf("HTTP")).trim();
        return path;
    }

    private String mk_header(StringBuffer workingContents) {
        return workingContents.substring(0, workingContents.indexOf("\r\n"));
    }

    private String[] parse_request_line(String header) {
        String pieces[] = header.trim().split(" ");
        return pieces;
    }

    private void readerToStringBuffer(DataInputStream buffered, StringBuffer contents) {

        try {

            byte ch_n = buffered.readByte();

            while (ch_n != -1) {
                contents.append((char) ch_n);

                if ((contents.indexOf("\r\n\r\n") != -1) || (buffered.available() == 0)) {
                    break;
                }
                ch_n = buffered.readByte();
            }
        } catch (Exception ex) {
            System.out.println(ex);
        }
    }

    private String rewriteMethodLine(StringBuffer workingContents) {
        int host_index = workingContents.toString().toUpperCase().indexOf("HOST:");

        if (host_index != -1) {
            int host_line_end_index = workingContents.toString().indexOf("\r\n", host_index);

            host = workingContents.toString().substring(host_index + 6, host_line_end_index).trim();
        } else {
            this.url = makeURL(workingContents);

            host = url.substring(url.indexOf("//") + 2, url.indexOf("/", url.indexOf("//") + 2));
        }
        if (host.contains(":")) {
            port = Integer.parseInt(host.substring(host.indexOf(":") + 1));
            user_defined_port = true;
            host = host.substring(0, host.indexOf(":"));
        }

        final String GET_HTTP = "^[a-zA-Z]+\\s+[hH][tT][tT][pP].*";

        if (header.matches(GET_HTTP)) {
            String tmp = workingContents.toString().substring(0, workingContents.indexOf("\r\n")).trim() + "\r\n";

            int start = tmp.indexOf("http://") + 8;
            String prefix = tmp.substring(0, start - 8);
            int end = tmp.indexOf("/", start);
            String postfix = tmp.substring(end, tmp.length());
            String whole = prefix + postfix;
            workingContents.replace(0, tmp.length(), whole);
        }

        return host;
    }

    public boolean isSent() {
        return this.sent;
    }

    void setInterrimContents(StringBuffer stringBuffer) {
        this.interrimContents = stringBuffer.toString();
        workingContents = new StringBuffer(this.interrimContents.toString());
        this.header = workingContents.substring(0, workingContents.indexOf("\r\n"));
        this.addContents(interrimContents);
    }

    void wakeupAndSend() {
        canWakeUp = true;
        synchronized (this) {
            this.notifyAll();
        }
    }

    public boolean awaitWakeup() throws InterruptedException {
        if (!canWakeUp) {
            synchronized (this) {
                this.wait();
            }
        }

        return canWakeUp;
    }

    private KeyManagerFactory createKeyManagerFactory(String cname) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException,
            InvalidKeyException, SignatureException, NoSuchProviderException, NoCertException {
        X509Certificate cert = KeyStoreManager.getCertificateByHostname(cname);
        cybervillains.ca.KeyStoreManager.getCertificateByHostname(cname);

        if (cert == null) {
            throw new NoCertException();
        }

        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(null, pass);

        ks.setCertificateEntry(cname, cert);
        ks.setKeyEntry(cname, KeyStoreManager.getPrivateKeyForLocalCert(cert), pass,
                new X509Certificate[] { cert });

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks, pass);

        return kmf;
    }

    private String getVersion(String header) {
        int begin = header.indexOf("HTTP/") + 5;
        return header.substring(begin).trim();
    }

    private SSLSocketFactory sloppySSL() {
        SSLUtilities.trustAllHostnames();
        return SSLUtilities.trustAllHttpsCertificates();
    }

    private synchronized SSLSocketFactory initSSL(String hostname)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException,
            IOException, KeyManagementException, InvalidKeyException, SignatureException, NoSuchProviderException,
            NoCertException {

        KeyManagerFactory kmf = null;
        SSLContext sslcontext = null;

        kmf = createKeyManagerFactory(hostname);
        sslcontext = SSLContext.getInstance("SSLv3", Security.getProvider("BC"));
        sslcontext.init(kmf.getKeyManagers(), null, null);
        SSLSocketFactory factory = sslcontext.getSocketFactory();

        _factories.put(hostname, factory);
        return factory;

    }

    private SSLSocket negotiateSSL(Socket sock, String hostname) throws KeyManagementException, KeyStoreException,
            NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, IOException,
            InvalidKeyException, SignatureException, NoSuchProviderException, NoCertException {
        synchronized (_factories) {
            SSLSocketFactory factory = _factories.get(hostname);

            if (factory == null) {
                factory = initSSL(hostname);
            }
            String inbound_hostname = sock.getInetAddress().getHostName();
            int inbound_port = sock.getPort();
            SSLSocket sslsock = (SSLSocket) factory.createSocket(sock, inbound_hostname, inbound_port, true);
            sslsock.setUseClientMode(false);
            if (!sslsock.isConnected()) {
                Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, "Couldn't negotiate SSL");
                System.exit(-1);
            }
            return sslsock;
        }
    }

    public void closeClientConnection() {
        try {
            sock.close();
        } catch (IOException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private HttpRequest() {
    }

    public HttpResponse getResponse() {
        return resp;
    }

    public boolean setDummyResponse(String s) {

        try {
            removeLine("PROXY-CONNECTION", workingContents); // removes line proxy-connection from the setInterimContents string
            if (mk_header(workingContents).contains("CONNECT") && !this.connect_protocol_handled) {
                handle_connect_protocol();
            }
        } catch (Exception e) {
            GizmoView.getView()
                    .setStatus("Unhandled protocol drop exception.  Probably shouldn't have excepted...");
        }

        host = rewriteMethodLine(workingContents);

        try {
            outboundSock = new Socket(host, port);
        } catch (Exception e) {
            GizmoView.getView().setStatus("Error creating outbound sock AV");
            return false;
        }

        this.resp = HttpResponse.create(s + "\r\n");
        resp.contentID = this.contentID;
        this.version = "1.0";

        try {
            this.sendDataToClient();
        } catch (Exception e) {
            GizmoView.getView().setStatus("Exception in closing connection.");
            return false;
        }
        this.wakeupAndSend();
        return true;
    }

    private int getPortFromHeader(String header) {
        int begin = header.indexOf("CONNECT") + "CONNECT".length();
        int end = header.indexOf(":", begin);
        String portString = header.substring(end + 1, header.indexOf(" ", end + 1));
        return Integer.parseInt(portString);
    }

    private String getHostFromHeader(String header) {
        int begin = header.indexOf("CONNECT") + "CONNECT".length();
        int end = header.indexOf(":", begin);
        return header.substring(begin, end).trim();
    }

    public static HttpRequest createRequest() {
        return new HttpRequest();
    }

    public boolean readRequest(Socket clientSock) {
        StringBuffer contents = new StringBuffer();

        try {
            InputStream input = clientSock.getInputStream();

            contents = readMessage(input);

            if (contents == null) {
                return false;
            }

            setContentEncodings(contents);
            this.interrimContents = contents.toString();
            this.sock = clientSock;

            this.header = contents.substring(0, contents.indexOf("\r\n"));

            workingContents = new StringBuffer(this.interrimContents.toString());

            if (header.contains("CONNECT") && GizmoView.getView().intercepting()) {
                handle_connect_protocol();
                if (!GizmoView.getView().config().terminateSSL()) {
                    return false;
                }
                this.header = workingContents.substring(0, workingContents.indexOf("\r\n"));
            }
            if (GizmoView.getView().intercepting()) {
                if (header.contains("http")) {
                    url = header.substring(header.indexOf("http"), header.indexOf("HTTP") - 1);
                } else {
                    url = header.substring(header.indexOf("/"), header.indexOf("HTTP") - 1);
                }
                if (url.contains("//")) {
                    host = url.substring(url.indexOf("//") + 2, url.indexOf("/", url.indexOf("//") + 2));
                } else {
                    String upper = workingContents.toString().toUpperCase();
                    int host_start = upper.indexOf("HOST:") + 5;
                    host = workingContents.substring(host_start, upper.indexOf("\n", host_start)).trim();
                }
            }

            this.addContents(workingContents.toString());

        } catch (Exception e) {
            return false;
        }

        return true;
    }

    public boolean fetchResponse(boolean cached) {
        this.cached = cached;

        OutputStream out = null;
        BufferedReader strBr = null;

        try {
            if (cached) {
                strBr = new BufferedReader(new StringReader(this.interrimContents.toString()));
            }

            removeLine("PROXY-CONNECTION", workingContents);
            updateContentLength();

            if (mk_header(workingContents).contains("CONNECT") && !this.connect_protocol_handled) {
                handle_connect_protocol();
                if (!GizmoView.getView().config().terminateSSL()) {
                    this.passthroughssl = true;
                    return false;
                }
            }

            if (isSSL || this.sock instanceof SSLSocket) {
                SSLSocket sslSock = (SSLSocket) this.sock;
                SSLSocket sslOut = null;
                if (workingContents == null) {
                    return false;
                }

                if (workingContents.indexOf("\r\n") == -1) {
                    return false;
                }

                if (!this.override_host)
                    host = rewriteMethodLine(workingContents);

                if (!user_defined_port) {
                    port = 443;
                }

                if (outboundSock == null || (!(outboundSock instanceof SSLSocket))) {

                    SSLSocketFactory sslsocketfactory = sloppySSL();
                    sslOut = (SSLSocket) sslsocketfactory.createSocket(host, port);
                } else {
                    sslOut = (SSLSocket) outboundSock;
                }

                sslOut.getOutputStream().write(workingContents.toString().getBytes());
                this.resp = HttpResponse.create(sslOut.getInputStream());
                if (resp == null) {
                    return false;
                }

            } else {
                //if (!this.override_host)
                host = rewriteMethodLine(workingContents);

                outboundSock = new Socket(host, port);

                outboundSock.getOutputStream().write(workingContents.toString().getBytes());
                this.resp = HttpResponse.create(outboundSock.getInputStream());

                if (resp == null) {
                    return false;
                }
            }

            this.addContents(workingContents.toString());

            this.header = workingContents.substring(0, this.workingContents.indexOf("\r\n"));
            this.url = getUrlPath(header);

            this.version = getVersion(this.header);

        } catch (SocketException e) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, e);
            return false;
        } catch (javax.net.ssl.SSLHandshakeException e) {
            try {
                GizmoView.getView().setStatus("couldn't connect with ssl.. cert issues?");
                sock.close();
            } catch (IOException ex) {
                Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            }
            return false;
        } catch (IOException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (FailedRequestException e) {
            GizmoView.getView().setStatus("malformed server response");
        } catch (Exception e) {
            try {
                Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, e);
                GizmoView.getView().setStatus("couldn't connect");
                this.sock.close();
                return false;
            } catch (IOException ex) {
                Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        this.wakeupAndSend();

        resp.setRequest(this);
        return true;
    }

    public Socket getOutboundSock() {
        return outboundSock;
    }

    private boolean handle_connect_protocol() {
        try {
            isSSL = true;

            /*                if (Configuration.getConfiguration().useProxy()) {
            outboundSock = new Socket(Configuration.getConfiguration().proxyHost(), Configuration.getConfiguration().proxyPort());
            out = outboundSock.getOutputStream();
            out.write(workingContents.toString().getBytes());
            BufferedReader in = new BufferedReader(new InputStreamReader(outboundSock.getInputStream()));
            in.readLine();
            while (in.ready()) {
            in.readLine();
            }
            }*/

            host = getHostFromHeader(mk_header(workingContents));
            port = getPortFromHeader(mk_header(workingContents));

            if (this.sock.isClosed()) {
                return false;
            }
            this.sock.getOutputStream().write("HTTP/1.0 200 Connection established\r\n\r\n".getBytes());

            if (!GizmoView.getView().config().terminateSSL()) {
                return false;
            }

            SSLSocket sslSock = negotiateSSL(this.sock, host);

            if (!cached) {
                workingContents = readMessage(sslSock.getInputStream());
                this.header = mk_header(workingContents);
                removeLine("accept-encoding", workingContents);
            }
            this.sock = sslSock;
        } catch (NoCertException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (KeyManagementException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (KeyStoreException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (CertificateException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (UnrecoverableKeyException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (InvalidKeyException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (SignatureException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (NoSuchProviderException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (IOException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        this.connect_protocol_handled = true;
        return true;
    }

    private StringBuffer readMessage(InputStream input) {
        StringBuffer contents = new StringBuffer();
        String CONTENT_LENGTH = "CONTENT-LENGTH";
        DataInputStream dinput = new DataInputStream(input);
        readerToStringBuffer(dinput, contents);
        int ii = 0;

        String uContents = contents.toString().toUpperCase();
        if (uContents.contains(CONTENT_LENGTH)) {
            int contentLengthIndex = uContents.indexOf(CONTENT_LENGTH) + CONTENT_LENGTH.length() + 2;
            String value = uContents.substring(uContents.indexOf(CONTENT_LENGTH) + CONTENT_LENGTH.length() + 2,
                    uContents.indexOf('\r', contentLengthIndex));
            int contentLength = Integer.parseInt(value.trim());
            StringBuffer body = new StringBuffer();

            try {
                for (; body.length() < contentLength; ii++) {
                    byte ch_n = dinput.readByte();
                    while (ch_n == -1) {
                        Thread.sleep(20);
                        ch_n = dinput.readByte();
                    }
                    body.append((char) ch_n);
                }
            } catch (InterruptedException ex) {
                Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
            }
            contents.append(body);
        }

        return contents;
    }

    public String toString() {
        return contents().toString();
    }

    void respond() {
        try {
            this.sock.getOutputStream().write(resp.byteContents());
            this.sock.close();

        } catch (IOException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void removeLine(String header, StringBuffer contents) {
        header = header.toUpperCase();

        String proxyUpper = contents.toString().toUpperCase();
        if (proxyUpper.contains(header)) {
            int begin = proxyUpper.indexOf(header);
            int lineEnd = proxyUpper.indexOf("\r\n", begin) + 2;
            contents.delete(begin, lineEnd);
        }
    }

    public void sendDataToClient() throws IOException {
        if (sock instanceof SSLSocket) {
            SSLSocket sslSock = (SSLSocket) sock;
            if (sslSock == null || resp == null) {
                return;
            }
            sslSock.getOutputStream().write(resp.byteContents());
        } else {
            this.sock.getOutputStream().write(resp.byteContents());
            this.sock.getOutputStream().flush();
        }
        if (version.equals("1.0") && !cached) {
            this.sock.close();
        }
        this.sent = true;
    }

    private void updateContentLength() {
        String proxyUpper = workingContents.toString().toUpperCase();
        final String CONTENTLENGTH = "CONTENT-LENGTH:";
        if (!proxyUpper.contains(CONTENTLENGTH)) {
            return;
        }

        int start = proxyUpper.indexOf(CONTENTLENGTH) + CONTENTLENGTH.length();
        int end = proxyUpper.indexOf("\r\n", start);

        int new_length = proxyUpper.substring(proxyUpper.indexOf("\r\n\r\n") + 4).length();

        workingContents.replace(start, end, " " + new_length);
    }

    void passThroughAllBits() {
        try {
            Socket destinationHost = new Socket(host, port);
            byte[] buf = new byte[1024];
            boolean didNothing = true;
            while (!this.sock.isClosed() && !destinationHost.isClosed()) {
                didNothing = true;
                if (sock.getInputStream().available() > 0) {
                    int b = sock.getInputStream().read(buf);
                    destinationHost.getOutputStream().write(buf, 0, b);
                    didNothing = false;
                }
                if (destinationHost.getInputStream().available() > 0) {
                    int b = destinationHost.getInputStream().read(buf);
                    sock.getOutputStream().write(buf, 0, b);
                    didNothing = false;
                }
                if (didNothing) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
            this.sock.close();
            destinationHost.close();
        } catch (UnknownHostException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(HttpRequest.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void setContentEncodings() {

    }

    private void setContentEncodings(StringBuffer contents) {
        if (contents.toString().toUpperCase().indexOf(ACCEPT_ENCODING) != -1) {
            int valStart = contents.toString().toUpperCase().indexOf(ACCEPT_ENCODING) + ACCEPT_ENCODING.length()
                    + 2;
            int valEnd = contents.indexOf("\r\n", valStart);
            contents.replace(valStart, valEnd, "gzip, deflate");
        }
    }

    class NoCertException extends Exception {
    }
}