Home | History | Annotate | Download | only in osu
      1 package com.android.hotspot2.osu;
      2 
      3 import android.util.Log;
      4 
      5 import com.android.hotspot2.utils.HTTPMessage;
      6 import com.android.hotspot2.utils.HTTPRequest;
      7 import com.android.hotspot2.utils.HTTPResponse;
      8 
      9 import com.android.org.conscrypt.OpenSSLSocketImpl;
     10 
     11 import org.xml.sax.SAXException;
     12 
     13 import java.io.BufferedInputStream;
     14 import java.io.BufferedOutputStream;
     15 import java.io.IOException;
     16 import java.io.InputStream;
     17 import java.net.Socket;
     18 import java.net.URL;
     19 import java.nio.ByteBuffer;
     20 import java.nio.charset.Charset;
     21 import java.nio.charset.StandardCharsets;
     22 import java.security.GeneralSecurityException;
     23 import java.security.PrivateKey;
     24 import java.security.cert.X509Certificate;
     25 import java.util.List;
     26 import java.util.Map;
     27 import java.util.concurrent.atomic.AtomicInteger;
     28 
     29 import javax.net.ssl.SSLException;
     30 import javax.net.ssl.SSLSocket;
     31 import javax.xml.parsers.ParserConfigurationException;
     32 
     33 public class HTTPHandler implements AutoCloseable {
     34     private final Charset mCharset;
     35     private final OSUSocketFactory mSocketFactory;
     36     private Socket mSocket;
     37     private BufferedOutputStream mOut;
     38     private BufferedInputStream mIn;
     39     private final String mUser;
     40     private final byte[] mPassword;
     41     private boolean mHTTPAuthPerformed;
     42     private static final AtomicInteger sSequence = new AtomicInteger();
     43 
     44     public HTTPHandler(Charset charset, OSUSocketFactory socketFactory) throws IOException {
     45         this(charset, socketFactory, null, null);
     46     }
     47 
     48     public HTTPHandler(Charset charset, OSUSocketFactory socketFactory,
     49                        String user, byte[] password) throws IOException {
     50         mCharset = charset;
     51         mSocketFactory = socketFactory;
     52         mSocket = mSocketFactory.createSocket();
     53         mOut = new BufferedOutputStream(mSocket.getOutputStream());
     54         mIn = new BufferedInputStream(mSocket.getInputStream());
     55         mUser = user;
     56         mPassword = password;
     57     }
     58 
     59     public boolean isHTTPAuthPerformed() {
     60         return mHTTPAuthPerformed;
     61     }
     62 
     63     public X509Certificate getOSUCertificate(URL osu) throws GeneralSecurityException {
     64         return mSocketFactory.getOSUCertificate(osu);
     65     }
     66 
     67     public void renegotiate(Map<OSUCertType, List<X509Certificate>> certs, PrivateKey key)
     68             throws IOException {
     69         if (!(mSocket instanceof SSLSocket)) {
     70             throw new IOException("Not a TLS connection");
     71         }
     72         if (certs != null) {
     73             mSocketFactory.reloadKeys(certs, key);
     74         }
     75         ((SSLSocket) mSocket).startHandshake();
     76     }
     77 
     78     public byte[] getTLSUnique() throws SSLException {
     79         if (mSocket instanceof OpenSSLSocketImpl) {
     80             return ((OpenSSLSocketImpl) mSocket).getChannelId();
     81         }
     82         return null;
     83     }
     84 
     85     public OSUResponse exchangeSOAP(URL url, String message) throws IOException {
     86         HTTPResponse response = exchangeWithRetry(url, message, HTTPMessage.Method.POST,
     87                 HTTPMessage.ContentTypeSOAP);
     88         if (response.getStatusCode() >= 300) {
     89             throw new IOException("Bad HTTP status code " + response.getStatusCode());
     90         }
     91         try {
     92             SOAPParser parser = new SOAPParser(response.getPayloadStream());
     93             return parser.getResponse();
     94         } catch (ParserConfigurationException | SAXException e) {
     95             ByteBuffer x = response.getPayload();
     96             byte[] b = new byte[x.remaining()];
     97             x.get(b);
     98             Log.w("XML", "Bad: '" + new String(b, StandardCharsets.ISO_8859_1));
     99             throw new IOException(e);
    100         }
    101     }
    102 
    103     public ByteBuffer exchangeBinary(URL url, String message, String contentType)
    104             throws IOException {
    105         HTTPResponse response =
    106                 exchangeWithRetry(url, message, HTTPMessage.Method.POST, contentType);
    107         return response.getBinaryPayload();
    108     }
    109 
    110     public InputStream doGet(URL url) throws IOException {
    111         HTTPResponse response = exchangeWithRetry(url, null, HTTPMessage.Method.GET, null);
    112         return response.getPayloadStream();
    113     }
    114 
    115     public HTTPResponse doGetHTTP(URL url) throws IOException {
    116         return exchangeWithRetry(url, null, HTTPMessage.Method.GET, null);
    117     }
    118 
    119     private HTTPResponse exchangeWithRetry(URL url, String message, HTTPMessage.Method method,
    120                                            String contentType) throws IOException {
    121         HTTPResponse response = null;
    122         int retry = 0;
    123         for (; ; ) {
    124             try {
    125                 response = httpExchange(url, message, method, contentType);
    126                 break;
    127             } catch (IOException ioe) {
    128                 close();
    129                 retry++;
    130                 if (retry > 3) {
    131                     break;
    132                 }
    133                 Log.d(OSUManager.TAG, "Failed HTTP exchange, retry " + retry);
    134                 mSocket = mSocketFactory.createSocket();
    135                 mOut = new BufferedOutputStream(mSocket.getOutputStream());
    136                 mIn = new BufferedInputStream(mSocket.getInputStream());
    137             }
    138         }
    139         if (response == null) {
    140             throw new IOException("Failed to establish connection to peer");
    141         }
    142         return response;
    143     }
    144 
    145     private HTTPResponse httpExchange(URL url, String message, HTTPMessage.Method method,
    146                                       String contentType)
    147             throws IOException {
    148         HTTPRequest request = new HTTPRequest(message, mCharset, method, url, contentType, false);
    149         request.send(mOut);
    150         HTTPResponse response = new HTTPResponse(mIn);
    151         Log.d(OSUManager.TAG, "HTTP code " + response.getStatusCode() + ", user " + mUser +
    152                 ", pw " + (mPassword != null ? '\'' + new String(mPassword) + '\'' : "-"));
    153         if (response.getStatusCode() == 401) {
    154             if (mUser == null) {
    155                 throw new IOException("Missing user name for HTTP authentication");
    156             }
    157             try {
    158                 request = new HTTPRequest(message, StandardCharsets.ISO_8859_1, method, url,
    159                         contentType, true);
    160                 request.doAuthenticate(response, mUser, mPassword, url,
    161                         sSequence.incrementAndGet());
    162                 request.send(mOut);
    163                 mHTTPAuthPerformed = true;
    164             } catch (GeneralSecurityException gse) {
    165                 throw new IOException(gse);
    166             }
    167 
    168             response = new HTTPResponse(mIn);
    169         }
    170         return response;
    171     }
    172 
    173     public void close() throws IOException {
    174         mIn.close();
    175         mOut.close();
    176         mSocket.close();
    177     }
    178 }
    179