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 
HTTPHandler(Charset charset, OSUSocketFactory socketFactory)44     public HTTPHandler(Charset charset, OSUSocketFactory socketFactory) throws IOException {
45         this(charset, socketFactory, null, null);
46     }
47 
HTTPHandler(Charset charset, OSUSocketFactory socketFactory, String user, byte[] password)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 
isHTTPAuthPerformed()59     public boolean isHTTPAuthPerformed() {
60         return mHTTPAuthPerformed;
61     }
62 
getOSUCertificate(URL osu)63     public X509Certificate getOSUCertificate(URL osu) throws GeneralSecurityException {
64         return mSocketFactory.getOSUCertificate(osu);
65     }
66 
renegotiate(Map<OSUCertType, List<X509Certificate>> certs, PrivateKey key)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 
getTLSUnique()78     public byte[] getTLSUnique() throws SSLException {
79         if (mSocket instanceof OpenSSLSocketImpl) {
80             return ((OpenSSLSocketImpl) mSocket).getChannelId();
81         }
82         return null;
83     }
84 
exchangeSOAP(URL url, String message)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 
exchangeBinary(URL url, String message, String contentType)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 
doGet(URL url)110     public InputStream doGet(URL url) throws IOException {
111         HTTPResponse response = exchangeWithRetry(url, null, HTTPMessage.Method.GET, null);
112         return response.getPayloadStream();
113     }
114 
doGetHTTP(URL url)115     public HTTPResponse doGetHTTP(URL url) throws IOException {
116         return exchangeWithRetry(url, null, HTTPMessage.Method.GET, null);
117     }
118 
exchangeWithRetry(URL url, String message, HTTPMessage.Method method, String contentType)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 
httpExchange(URL url, String message, HTTPMessage.Method method, String contentType)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 
close()173     public void close() throws IOException {
174         mIn.close();
175         mOut.close();
176         mSocket.close();
177     }
178 }
179