forked from I2P_Developers/i2p.i2p
SSLEepGet, Reseed: Implement HTTPS proxy option (ticket #423)
This commit is contained in:
@@ -48,8 +48,8 @@ public class EepGet {
|
||||
protected final I2PAppContext _context;
|
||||
protected final Log _log;
|
||||
protected final boolean _shouldProxy;
|
||||
private final String _proxyHost;
|
||||
private final int _proxyPort;
|
||||
protected final String _proxyHost;
|
||||
protected final int _proxyPort;
|
||||
protected final int _numRetries;
|
||||
private final long _minSize; // minimum and maximum acceptable response size, -1 signifies unlimited,
|
||||
private final long _maxSize; // applied both against whole responses and chunks
|
||||
@@ -85,7 +85,6 @@ public class EepGet {
|
||||
protected boolean _notModified;
|
||||
protected String _contentType;
|
||||
protected boolean _transferFailed;
|
||||
protected boolean _headersRead;
|
||||
protected boolean _aborted;
|
||||
protected int _fetchHeaderTimeout;
|
||||
private long _fetchEndTime;
|
||||
@@ -695,13 +694,8 @@ public class EepGet {
|
||||
* @param timeout may be null
|
||||
*/
|
||||
protected void doFetch(SocketTimeout timeout) throws IOException {
|
||||
_headersRead = false;
|
||||
_aborted = false;
|
||||
try {
|
||||
readHeaders();
|
||||
} finally {
|
||||
_headersRead = true;
|
||||
}
|
||||
readHeaders();
|
||||
if (_aborted)
|
||||
throw new IOException("Timed out reading the HTTP headers");
|
||||
|
||||
@@ -1079,11 +1073,14 @@ public class EepGet {
|
||||
|
||||
buf.setLength(0);
|
||||
byte lookahead[] = new byte[3];
|
||||
// "prime" the lookahead buffer with a '\n',
|
||||
// so it works if there's no header lines at all, like a HTTPS proxy
|
||||
increment(lookahead, '\n');
|
||||
while (true) {
|
||||
int cur = _proxyIn.read();
|
||||
switch (cur) {
|
||||
case -1:
|
||||
throw new IOException("Headers ended too soon");
|
||||
throw new IOException("EOF reading headers");
|
||||
case ':':
|
||||
if (key == null) {
|
||||
key = buf.toString();
|
||||
@@ -1105,7 +1102,7 @@ public class EepGet {
|
||||
increment(lookahead, cur);
|
||||
if (isEndOfHeaders(lookahead)) {
|
||||
if (!rcOk)
|
||||
throw new IOException("Invalid HTTP response code: " + _responseCode + ' ' + _responseText);
|
||||
throw new IOException("Invalid HTTP response: " + _responseCode + ' ' + _responseText);
|
||||
if (_encodingChunked) {
|
||||
_bytesRemaining = readChunkLength();
|
||||
}
|
||||
@@ -1502,6 +1499,8 @@ public class EepGet {
|
||||
* this will replace the etag or last-modified value given in the constructor.
|
||||
* Note that headers may be subsequently modified or removed in the I2PTunnel HTTP Client proxy.
|
||||
*
|
||||
* In proxied SSLEepGet, these headers are sent to the remote server, NOT the proxy.
|
||||
*
|
||||
* @since 0.8.8
|
||||
*/
|
||||
public void addHeader(String name, String value) {
|
||||
|
||||
@@ -160,13 +160,8 @@ public class EepHead extends EepGet {
|
||||
/** return true if the URL was completely retrieved */
|
||||
@Override
|
||||
protected void doFetch(SocketTimeout timeout) throws IOException {
|
||||
_headersRead = false;
|
||||
_aborted = false;
|
||||
try {
|
||||
readHeaders();
|
||||
} finally {
|
||||
_headersRead = true;
|
||||
}
|
||||
readHeaders();
|
||||
if (_aborted)
|
||||
throw new IOException("Timed out reading the HTTP headers");
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import net.i2p.I2PAppContext;
|
||||
* @author zzz
|
||||
*/
|
||||
public class PartialEepGet extends EepGet {
|
||||
long _fetchSize;
|
||||
private final long _fetchSize;
|
||||
|
||||
/**
|
||||
* Instantiate an EepGet that will fetch exactly size bytes when fetch() is called.
|
||||
|
||||
@@ -54,6 +54,7 @@ import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.net.Socket;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
@@ -72,13 +73,15 @@ import net.i2p.crypto.KeyStoreUtil;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* HTTPS only, non-proxied only, no retries, no min and max size options, no timeout option
|
||||
* HTTPS only, no retries, no min and max size options, no timeout option
|
||||
* Fails on 301 or 302 (doesn't follow redirect)
|
||||
* Fails on bad certs (must have a valid cert chain)
|
||||
* Self-signed certs or CAs not in the JVM key store must be loaded to be trusted.
|
||||
*
|
||||
* Since 0.8.2, loads additional trusted CA certs from $I2P/certificates/ssl/ and ~/.i2p/certificates/ssl/
|
||||
*
|
||||
* Since 0.9.33, HTTP proxies (CONNECT) supported. Proxy auth not supported.
|
||||
*
|
||||
* @author zzz
|
||||
* @since 0.7.10
|
||||
*/
|
||||
@@ -93,9 +96,17 @@ public class SSLEepGet extends EepGet {
|
||||
private final SSLContext _sslContext;
|
||||
/** may be null if init failed */
|
||||
private SavingTrustManager _stm;
|
||||
private final ProxyType _proxyType;
|
||||
|
||||
private static final String CERT_DIR = "certificates/ssl";
|
||||
|
||||
/**
|
||||
* Not all may be supported.
|
||||
* @since 0.9.33
|
||||
*/
|
||||
public enum ProxyType { NONE, HTTP, HTTPS, INTERNAL, SOCKS4, SOCKS5 }
|
||||
|
||||
|
||||
/**
|
||||
* A new SSLEepGet with a new SSLState
|
||||
*/
|
||||
@@ -131,6 +142,78 @@ public class SSLEepGet extends EepGet {
|
||||
this(ctx, outputFile, null, url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a proxy.
|
||||
*
|
||||
* @param proxyPort must be valid, -1 disallowed, no default
|
||||
* @since 0.9.33
|
||||
*/
|
||||
public SSLEepGet(I2PAppContext ctx, ProxyType type, String proxyHost, int proxyPort,
|
||||
OutputStream outputStream, String url) {
|
||||
this(ctx, type, proxyHost, proxyPort, outputStream, url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a proxy.
|
||||
*
|
||||
* @param proxyPort must be valid, -1 disallowed, no default
|
||||
* @param state an SSLState retrieved from a previous SSLEepGet with getSSLState(), or null.
|
||||
* This makes repeated fetches from the same host MUCH faster,
|
||||
* and prevents repeated key store loads even for different hosts.
|
||||
* @since 0.9.33
|
||||
*/
|
||||
public SSLEepGet(I2PAppContext ctx, ProxyType type, String proxyHost, int proxyPort,
|
||||
OutputStream outputStream, String url, SSLState state) {
|
||||
// we're using this constructor:
|
||||
// public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
|
||||
super(ctx, type != ProxyType.NONE, proxyHost, proxyPort, 0, -1, -1, null, outputStream, url, true, null, null);
|
||||
if (type != ProxyType.NONE && !_shouldProxy)
|
||||
throw new IllegalArgumentException("Bad proxy params");
|
||||
_proxyType = type;
|
||||
if (state != null && state.context != null)
|
||||
_sslContext = state.context;
|
||||
else
|
||||
_sslContext = initSSLContext();
|
||||
if (_sslContext == null)
|
||||
_log.error("Failed to initialize custom SSL context, using default context");
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a proxy.
|
||||
*
|
||||
* @param proxyPort must be valid, -1 disallowed, no default
|
||||
* @since 0.9.33
|
||||
*/
|
||||
public SSLEepGet(I2PAppContext ctx, ProxyType type, String proxyHost, int proxyPort,
|
||||
String outputFile, String url) {
|
||||
this(ctx, type, proxyHost, proxyPort, outputFile, url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a proxy.
|
||||
*
|
||||
* @param proxyPort must be valid, -1 disallowed, no default
|
||||
* @param state an SSLState retrieved from a previous SSLEepGet with getSSLState(), or null.
|
||||
* This makes repeated fetches from the same host MUCH faster,
|
||||
* and prevents repeated key store loads even for different hosts.
|
||||
* @since 0.9.33
|
||||
*/
|
||||
public SSLEepGet(I2PAppContext ctx, ProxyType type, String proxyHost, int proxyPort,
|
||||
String outputFile, String url, SSLState state) {
|
||||
// we're using this constructor:
|
||||
// public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
|
||||
super(ctx, type != ProxyType.NONE, proxyHost, proxyPort, 0, -1, -1, outputFile, null, url, true, null, null);
|
||||
if (type != ProxyType.NONE && !_shouldProxy)
|
||||
throw new IllegalArgumentException("Bad proxy params");
|
||||
_proxyType = type;
|
||||
if (state != null && state.context != null)
|
||||
_sslContext = state.context;
|
||||
else
|
||||
_sslContext = initSSLContext();
|
||||
if (_sslContext == null)
|
||||
_log.error("Failed to initialize custom SSL context, using default context");
|
||||
}
|
||||
|
||||
/**
|
||||
* outputFile, outputStream: One null, one non-null
|
||||
*
|
||||
@@ -143,6 +226,7 @@ public class SSLEepGet extends EepGet {
|
||||
// we're using this constructor:
|
||||
// public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
|
||||
super(ctx, false, null, -1, 0, -1, -1, outputFile, outputStream, url, true, null, null);
|
||||
_proxyType = ProxyType.NONE;
|
||||
if (state != null && state.context != null)
|
||||
_sslContext = state.context;
|
||||
else
|
||||
@@ -159,12 +243,28 @@ public class SSLEepGet extends EepGet {
|
||||
public static void main(String args[]) {
|
||||
int saveCerts = 0;
|
||||
boolean noVerify = false;
|
||||
String proxyHost = "127.0.0.1";
|
||||
int proxyPort = 80;
|
||||
boolean error = false;
|
||||
Getopt g = new Getopt("ssleepget", args, "sz");
|
||||
Getopt g = new Getopt("ssleepget", args, "p:sz");
|
||||
try {
|
||||
int c;
|
||||
while ((c = g.getopt()) != -1) {
|
||||
switch (c) {
|
||||
case 'p':
|
||||
String s = g.getOptarg();
|
||||
int colon = s.indexOf(':');
|
||||
if (colon >= 0) {
|
||||
// Todo IPv6 [a:b:c]:4444
|
||||
proxyHost = s.substring(0, colon);
|
||||
String port = s.substring(colon + 1);
|
||||
proxyPort = Integer.parseInt(port);
|
||||
} else {
|
||||
proxyHost = s;
|
||||
// proxyPort remains default
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
saveCerts++;
|
||||
break;
|
||||
@@ -192,16 +292,12 @@ public class SSLEepGet extends EepGet {
|
||||
String url = args[g.getOptind()];
|
||||
|
||||
String saveAs = suggestName(url);
|
||||
OutputStream out;
|
||||
try {
|
||||
// resume from a previous eepget won't work right doing it this way
|
||||
out = new FileOutputStream(saveAs);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Failed to create output file " + saveAs);
|
||||
return;
|
||||
}
|
||||
|
||||
SSLEepGet get = new SSLEepGet(I2PAppContext.getGlobalContext(), out, url);
|
||||
SSLEepGet get;
|
||||
if (proxyHost != null)
|
||||
get = new SSLEepGet(I2PAppContext.getGlobalContext(), ProxyType.HTTP, proxyHost, proxyPort, saveAs, url);
|
||||
else
|
||||
get = new SSLEepGet(I2PAppContext.getGlobalContext(), saveAs, url);
|
||||
if (saveCerts > 0)
|
||||
get._saveCerts = saveCerts;
|
||||
if (noVerify)
|
||||
@@ -213,7 +309,8 @@ public class SSLEepGet extends EepGet {
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.err.println("Usage: SSLEepGet [-sz] https://url\n" +
|
||||
System.err.println("Usage: SSLEepGet [-psz] https://url\n" +
|
||||
" -p proxyHost[:proxyPort] // default port 80\n" +
|
||||
" -s save unknown certs\n" +
|
||||
" -s -s save all certs\n" +
|
||||
" -z bypass hostname verification");
|
||||
@@ -410,13 +507,8 @@ public class SSLEepGet extends EepGet {
|
||||
|
||||
@Override
|
||||
protected void doFetch(SocketTimeout timeout) throws IOException {
|
||||
_headersRead = false;
|
||||
_aborted = false;
|
||||
try {
|
||||
readHeaders();
|
||||
} finally {
|
||||
_headersRead = true;
|
||||
}
|
||||
readHeaders();
|
||||
if (_aborted)
|
||||
throw new IOException("Timed out reading the HTTP headers");
|
||||
|
||||
@@ -573,17 +665,76 @@ public class SSLEepGet extends EepGet {
|
||||
port = url.getPort();
|
||||
if (port == -1)
|
||||
port = 443;
|
||||
// Warning, createSocket() followed by connect(InetSocketAddress)
|
||||
// disables SNI, at least on Java 7.
|
||||
// So we must do createSocket(host, port) and then setSoTimeout;
|
||||
// we can't crate a disconnected socket and then call setSoTimeout, sadly.
|
||||
if (_sslContext != null)
|
||||
_proxy = _sslContext.getSocketFactory().createSocket(host, port);
|
||||
else
|
||||
_proxy = SSLSocketFactory.getDefault().createSocket(host, port);
|
||||
if (_fetchHeaderTimeout > 0) {
|
||||
_proxy.setSoTimeout(_fetchHeaderTimeout);
|
||||
|
||||
if (_shouldProxy) {
|
||||
if (_proxyType != ProxyType.HTTP)
|
||||
throw new IOException("Unsupported proxy type " + _proxyType);
|
||||
|
||||
// connect to the proxy
|
||||
// _proxyPort validated in superconstrutor, no need to set default here
|
||||
if (_fetchHeaderTimeout > 0) {
|
||||
_proxy = new Socket();
|
||||
_proxy.setSoTimeout(_fetchHeaderTimeout);
|
||||
_proxy.connect(new InetSocketAddress(_proxyHost, _proxyPort), _fetchHeaderTimeout);
|
||||
} else {
|
||||
_proxy = new Socket(_proxyHost, _proxyPort);
|
||||
}
|
||||
_proxyIn = _proxy.getInputStream();
|
||||
_proxyOut = _proxy.getOutputStream();
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append("CONNECT ").append(host).append(':').append(port).append(" HTTP/1.1\r\n");
|
||||
// TODO if we need extra headers to the proxy, add a new method and list.
|
||||
// Standard extra headers go the server, not the proxy
|
||||
//if (_extraPHeaders != null) {
|
||||
// for (String hdr : _extraPHeaders) {
|
||||
// buf.append(hdr).append("\r\n");
|
||||
//}
|
||||
if (_authState != null && _authState.authMode != AUTH_MODE.NONE) {
|
||||
// TODO untested, is this right?
|
||||
buf.append("Proxy-Authorization: ");
|
||||
buf.append(_authState.getAuthHeader("CONNECT", host));
|
||||
buf.append("\r\n");
|
||||
}
|
||||
buf.append("\r\n");
|
||||
_proxyOut.write(DataHelper.getUTF8(buf.toString()));
|
||||
_proxyOut.flush();
|
||||
|
||||
// read the proxy response
|
||||
_aborted = false;
|
||||
readHeaders();
|
||||
if (_aborted)
|
||||
throw new IOException("Timed out reading the proxy headers");
|
||||
if (_responseCode == 407) {
|
||||
// TODO
|
||||
throw new IOException("Proxy auth unsupported");
|
||||
} else if (_responseCode != 200) {
|
||||
// readHeaders() will throw on most errors, but here we ensure it is 200
|
||||
throw new IOException("Invalid proxy response: " + _responseCode + ' ' + _responseText);
|
||||
}
|
||||
if (_redirectLocation != null)
|
||||
throw new IOException("Proxy redirect not allowed");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("proxy headers read completely");
|
||||
|
||||
// wrap the socket in an SSLSocket
|
||||
if (_sslContext != null)
|
||||
_proxy = _sslContext.getSocketFactory().createSocket(_proxy, host, port, true);
|
||||
else
|
||||
_proxy = ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(_proxy, host, port, true);
|
||||
} else {
|
||||
// Warning, createSocket() followed by connect(InetSocketAddress)
|
||||
// disables SNI, at least on Java 7.
|
||||
// So we must do createSocket(host, port) and then setSoTimeout;
|
||||
// we can't create a disconnected socket and then call setSoTimeout, sadly.
|
||||
if (_sslContext != null)
|
||||
_proxy = _sslContext.getSocketFactory().createSocket(host, port);
|
||||
else
|
||||
_proxy = SSLSocketFactory.getDefault().createSocket(host, port);
|
||||
if (_fetchHeaderTimeout > 0) {
|
||||
_proxy.setSoTimeout(_fetchHeaderTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
SSLSocket socket = (SSLSocket) _proxy;
|
||||
I2PSSLSocketFactory.setProtocolsAndCiphers(socket);
|
||||
if (!_bypassVerification) {
|
||||
|
||||
Reference in New Issue
Block a user