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