SSLEepGet

This commit is contained in:
zzz
2010-01-13 22:33:43 +00:00
parent 3cd6520758
commit 188ac4f730
2 changed files with 332 additions and 15 deletions

View File

@ -44,21 +44,21 @@ public class EepGet {
protected String _actualURL; protected String _actualURL;
private String _postData; private String _postData;
private boolean _allowCaching; private boolean _allowCaching;
protected final List _listeners; protected final List<StatusListener> _listeners;
private boolean _keepFetching; protected boolean _keepFetching;
private Socket _proxy; protected Socket _proxy;
protected OutputStream _proxyOut; protected OutputStream _proxyOut;
protected InputStream _proxyIn; protected InputStream _proxyIn;
protected OutputStream _out; protected OutputStream _out;
protected long _alreadyTransferred; protected long _alreadyTransferred;
private long _bytesTransferred; protected long _bytesTransferred;
protected long _bytesRemaining; protected long _bytesRemaining;
protected int _currentAttempt; protected int _currentAttempt;
private String _etag; private String _etag;
private String _lastModified; private String _lastModified;
private boolean _encodingChunked; protected boolean _encodingChunked;
private boolean _notModified; protected boolean _notModified;
private String _contentType; private String _contentType;
protected boolean _transferFailed; protected boolean _transferFailed;
protected boolean _headersRead; protected boolean _headersRead;
@ -441,7 +441,7 @@ public class EepGet {
timeout.setTotalTimeoutPeriod(_fetchEndTime); timeout.setTotalTimeoutPeriod(_fetchEndTime);
try { try {
for (int i = 0; i < _listeners.size(); i++) for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).attempting(_url); _listeners.get(i).attempting(_url);
sendRequest(timeout); sendRequest(timeout);
timeout.resetTimer(); timeout.resetTimer();
doFetch(timeout); doFetch(timeout);
@ -452,7 +452,7 @@ public class EepGet {
} catch (IOException ioe) { } catch (IOException ioe) {
timeout.cancel(); timeout.cancel();
for (int i = 0; i < _listeners.size(); i++) for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe); _listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("ERR: doFetch failed " + ioe); _log.warn("ERR: doFetch failed " + ioe);
} finally { } finally {
@ -480,13 +480,13 @@ public class EepGet {
} }
for (int i = 0; i < _listeners.size(); i++) for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt); _listeners.get(i).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("All attempts failed for " + _url); _log.warn("All attempts failed for " + _url);
return false; return false;
} }
/** return true if the URL was completely retrieved */ /** single fetch */
protected void doFetch(SocketTimeout timeout) throws IOException { protected void doFetch(SocketTimeout timeout) throws IOException {
_headersRead = false; _headersRead = false;
_aborted = false; _aborted = false;
@ -586,7 +586,7 @@ public class EepGet {
_bytesRemaining -= read; _bytesRemaining -= read;
if (read > 0) { if (read > 0) {
for (int i = 0; i < _listeners.size(); i++) for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).bytesTransferred( _listeners.get(i).bytesTransferred(
_alreadyTransferred, _alreadyTransferred,
read, read,
_bytesTransferred, _bytesTransferred,
@ -615,12 +615,12 @@ public class EepGet {
if (_transferFailed) { if (_transferFailed) {
// 404, etc - transferFailed is called after all attempts fail, by fetch() above // 404, etc - transferFailed is called after all attempts fail, by fetch() above
for (int i = 0; i < _listeners.size(); i++) for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, new Exception("Attempt failed")); _listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, new Exception("Attempt failed"));
} else if ((_minSize > 0) && (_alreadyTransferred < _minSize)) { } else if ((_minSize > 0) && (_alreadyTransferred < _minSize)) {
throw new IOException("Bytes transferred " + _alreadyTransferred + " violates minimum of " + _minSize + " bytes"); throw new IOException("Bytes transferred " + _alreadyTransferred + " violates minimum of " + _minSize + " bytes");
} else if ( (_bytesRemaining == -1) || (remaining == 0) ) { } else if ( (_bytesRemaining == -1) || (remaining == 0) ) {
for (int i = 0; i < _listeners.size(); i++) for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).transferComplete( _listeners.get(i).transferComplete(
_alreadyTransferred, _alreadyTransferred,
_bytesTransferred, _bytesTransferred,
_encodingChunked?-1:_bytesRemaining, _encodingChunked?-1:_bytesRemaining,
@ -744,7 +744,7 @@ public class EepGet {
} }
} }
private long readChunkLength() throws IOException { protected long readChunkLength() throws IOException {
StringBuilder buf = new StringBuilder(8); StringBuilder buf = new StringBuilder(8);
int nl = 0; int nl = 0;
while (true) { while (true) {
@ -808,7 +808,7 @@ public class EepGet {
private void handle(String key, String val) { private void handle(String key, String val) {
for (int i = 0; i < _listeners.size(); i++) for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).headerReceived(_url, _currentAttempt, key.trim(), val.trim()); _listeners.get(i).headerReceived(_url, _currentAttempt, key.trim(), val.trim());
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Header line: [" + key + "] = [" + val + "]"); _log.debug("Header line: [" + key + "] = [" + val + "]");

View File

@ -0,0 +1,317 @@
package net.i2p.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.net.ssl.SSLSocketFactory;
// all part of the CA experiment below
//import java.io.FileInputStream;
//import java.io.InputStream;
//import java.util.Enumeration;
//import java.security.KeyStore;
//import java.security.GeneralSecurityException;
//import java.security.cert.CertificateExpiredException;
//import java.security.cert.CertificateNotYetValidException;
//import java.security.cert.CertificateFactory;
//import java.security.cert.X509Certificate;
//import javax.net.ssl.KeyManagerFactory;
//import javax.net.ssl.SSLContext;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
/**
* HTTPS only, non-proxied only, no retries, no min and max size options, no timeout option
* Fails on 301 or 302 (doesn't follow redirect)
* Fails on self-signed certs (must have a valid cert chain)
*
* @author zzz
* @since 0.7.10
*/
public class SSLEepGet extends EepGet {
//private static SSLContext _sslContext;
public SSLEepGet(I2PAppContext ctx, OutputStream outputStream, String url) {
// 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, null, outputStream, url, true, null, null);
}
/**
* SSLEepGet url
* no command line options supported
*/
public static void main(String args[]) {
String url = null;
try {
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
usage();
return;
} else {
url = args[i];
}
}
} catch (Exception e) {
e.printStackTrace();
usage();
return;
}
if (url == null) {
usage();
return;
}
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;
}
/******
* This is all an experiment to add a CA cert loaded from a file so we can use
* selfsigned certs on our servers.
* But it's failing.
* Run as java -Djava.security.debug=certpath -Djavax.net.debug=trustmanager -cp $I2P/lib/i2p.jar net.i2p.util.SSLEepGet "$@"
* to see the problems. It isn't including the added cert in the Trust Anchor list.
******/
/******
String foo = System.getProperty("javax.net.ssl.keyStore");
if (foo == null) {
File cacerts = new File(System.getProperty("java.home"), "lib/security/cacerts");
foo = cacerts.getAbsolutePath();
}
System.err.println("Location is: " + foo);
try {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
try {
InputStream fis = new FileInputStream(foo);
ks.load(fis, "changeit".toCharArray());
fis.close();
} catch (GeneralSecurityException gse) {
System.err.println("KS error, no default keys: " + gse);
ks.load(null, "changeit".toCharArray());
} catch (IOException ioe) {
System.err.println("IO error, no default keys: " + ioe);
ks.load(null, "changeit".toCharArray());
}
addCert(ks, "cacert");
for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
String alias = e.nextElement();
System.err.println("Aliases: " + alias + " isCert? " + ks.isCertificateEntry(alias));
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, "".toCharArray());
SSLContext sslc = SSLContext.getInstance("SSL");
sslc.init(kmf.getKeyManagers(), null, null);
_sslContext = sslc;
} catch (GeneralSecurityException gse) {
System.err.println("KS error: " + gse);
return;
} catch (IOException ioe) {
System.err.println("IO error: " + ioe);
return;
}
*******/
EepGet get = new SSLEepGet(I2PAppContext.getGlobalContext(), out, url);
get.addStatusListener(get.new CLIStatusListener(1024, 40));
get.fetch(45*1000, -1, 60*1000);
}
private static void usage() {
System.err.println("SSLEepGet url");
}
/******
private static boolean addCert(KeyStore ks, String file) {
try {
InputStream fis = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
fis.close();
System.err.println("Adding cert, Issuer: " + cert.getIssuerX500Principal());
try {
cert.checkValidity();
} catch (CertificateExpiredException cee) {
System.err.println("Warning - expired cert: " + cee);
} catch (CertificateNotYetValidException cnyve) {
System.err.println("Warning - not yet valid cert: " + cnyve);
}
// use file name as alias
ks.setCertificateEntry(file, cert);
} catch (GeneralSecurityException gse) {
System.err.println("Read cert error: " + gse);
return false;
} catch (IOException ioe) {
System.err.println("Read cert error: " + ioe);
return false;
}
return true;
}
*******/
@Override
protected void doFetch(SocketTimeout timeout) throws IOException {
_headersRead = false;
_aborted = false;
try {
readHeaders();
} finally {
_headersRead = true;
}
if (_aborted)
throw new IOException("Timed out reading the HTTP headers");
timeout.resetTimer();
if (_fetchInactivityTimeout > 0)
timeout.setInactivityTimeout(_fetchInactivityTimeout);
else
timeout.setInactivityTimeout(60*1000);
if (_redirectLocation != null) {
throw new IOException("Server redirect to " + _redirectLocation + " not allowed");
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Headers read completely, reading " + _bytesRemaining);
boolean strictSize = (_bytesRemaining >= 0);
int remaining = (int)_bytesRemaining;
byte buf[] = new byte[1024];
while (_keepFetching && ( (remaining > 0) || !strictSize ) && !_aborted) {
int toRead = buf.length;
if (strictSize && toRead > remaining)
toRead = remaining;
int read = _proxyIn.read(buf, 0, toRead);
if (read == -1)
break;
timeout.resetTimer();
_out.write(buf, 0, read);
_bytesTransferred += read;
remaining -= read;
if (remaining==0 && _encodingChunked) {
int char1 = _proxyIn.read();
if (char1 == '\r') {
int char2 = _proxyIn.read();
if (char2 == '\n') {
remaining = (int) readChunkLength();
} else {
_out.write(char1);
_out.write(char2);
_bytesTransferred += 2;
remaining -= 2;
read += 2;
}
} else {
_out.write(char1);
_bytesTransferred++;
remaining--;
read++;
}
}
timeout.resetTimer();
if (_bytesRemaining >= read) // else chunked?
_bytesRemaining -= read;
if (read > 0) {
for (int i = 0; i < _listeners.size(); i++)
_listeners.get(i).bytesTransferred(
_alreadyTransferred,
read,
_bytesTransferred,
_encodingChunked?-1:_bytesRemaining,
_url);
// This seems necessary to properly resume a partial download into a stream,
// as nothing else increments _alreadyTransferred, and there's no file length to check.
// Do this after calling the listeners to keep the total correct
_alreadyTransferred += read;
}
}
if (_out != null)
_out.close();
_out = null;
if (_aborted)
throw new IOException("Timed out reading the HTTP data");
timeout.cancel();
if (_transferFailed) {
// 404, etc - transferFailed is called after all attempts fail, by fetch() above
for (int i = 0; i < _listeners.size(); i++)
_listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, new Exception("Attempt failed"));
} else if ( (_bytesRemaining == -1) || (remaining == 0) ) {
for (int i = 0; i < _listeners.size(); i++)
_listeners.get(i).transferComplete(
_alreadyTransferred,
_bytesTransferred,
_encodingChunked?-1:_bytesRemaining,
_url,
_outputFile,
_notModified);
} else {
throw new IOException("Disconnection on attempt " + _currentAttempt + " after " + _bytesTransferred);
}
}
@Override
protected void sendRequest(SocketTimeout timeout) throws IOException {
if (_outputStream != null) {
// We are reading into a stream supplied by a caller,
// for which we cannot easily determine how much we've written.
// Assume that _alreadyTransferred holds the right value
// (we should never be restarted to work on an old stream).
} else {
File outFile = new File(_outputFile);
if (outFile.exists())
_alreadyTransferred = outFile.length();
}
String req = getRequest();
try {
URL url = new URL(_actualURL);
if ("https".equals(url.getProtocol())) {
String host = url.getHost();
int port = url.getPort();
if (port == -1)
port = 443;
// part of the experiment above
//if (_sslContext != null)
// _proxy = _sslContext.getSocketFactory().createSocket(host, port);
//else
_proxy = SSLSocketFactory.getDefault().createSocket(host, port);
} else {
throw new IOException("Only https supported: " + _actualURL);
}
} catch (MalformedURLException mue) {
throw new IOException("Request URL is invalid");
}
_proxyIn = _proxy.getInputStream();
_proxyOut = _proxy.getOutputStream();
_proxyOut.write(DataHelper.getUTF8(req));
_proxyOut.flush();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Request flushed");
}
}