forked from I2P_Developers/i2p.i2p
2005-03-23 jrandom
* New /configupdate.jsp page for controlling the update / notification
process, as well as various minor related updates. Note that not all
options are exposed yet, and the update detection code isn't in place
in this commit - it currently says there is always an update available.
* New EepGet component for reliable downloading, with a CLI exposed in
java -cp lib/i2p.jar net.i2p.util.EepGet url
* Added a default signing key to the TrustedUpdate component to be used
for verifying updates. This signing key can be authenticated via
gpg --verify i2p/core/java/src/net/i2p/crypto/TrustedUpdate.java
* New public domain SHA1 implementation for the DSA code so that we can
handle signing streams of arbitrary size without excess memory usage
(thanks P.Verdy!)
* Added some helpers to the TrustedUpdate to work off streams and to offer
a minimal CLI:
TrustedUpdate keygen pubKeyFile privKeyFile
TrustedUpdate sign origFile signedFile privKeyFile
TrustedUpdate verify signedFile
This commit is contained in:
513
core/java/src/net/i2p/util/EepGet.java
Normal file
513
core/java/src/net/i2p/util/EepGet.java
Normal file
@@ -0,0 +1,513 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* EepGet [-p localhost:4444]
|
||||
* [-n #retries]
|
||||
* [-o outputFile]
|
||||
* [-m markSize lineLen]
|
||||
* url
|
||||
*/
|
||||
public class EepGet {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private boolean _shouldProxy;
|
||||
private String _proxyHost;
|
||||
private int _proxyPort;
|
||||
private int _numRetries;
|
||||
private String _outputFile;
|
||||
private String _url;
|
||||
private List _listeners;
|
||||
|
||||
private boolean _keepFetching;
|
||||
private Socket _proxy;
|
||||
private OutputStream _proxyOut;
|
||||
private InputStream _proxyIn;
|
||||
private OutputStream _out;
|
||||
private long _alreadyTransferred;
|
||||
private long _bytesTransferred;
|
||||
private long _bytesRemaining;
|
||||
private int _currentAttempt;
|
||||
private String _etag;
|
||||
|
||||
public EepGet(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
|
||||
this(ctx, true, proxyHost, proxyPort, numRetries, outputFile, url);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, int numRetries, String outputFile, String url) {
|
||||
this(ctx, false, null, -1, numRetries, outputFile, url);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(EepGet.class);
|
||||
_shouldProxy = shouldProxy;
|
||||
_proxyHost = proxyHost;
|
||||
_proxyPort = proxyPort;
|
||||
_numRetries = numRetries;
|
||||
_outputFile = outputFile;
|
||||
_url = url;
|
||||
_alreadyTransferred = 0;
|
||||
_bytesTransferred = 0;
|
||||
_bytesRemaining = -1;
|
||||
_currentAttempt = 0;
|
||||
_listeners = new ArrayList(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] url
|
||||
*
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String proxyHost = "localhost";
|
||||
int proxyPort = 4444;
|
||||
int numRetries = 5;
|
||||
int markSize = 1024;
|
||||
int lineLen = 40;
|
||||
String saveAs = null;
|
||||
String url = null;
|
||||
try {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("-p")) {
|
||||
proxyHost = args[i+1].substring(0, args[i+1].indexOf(':'));
|
||||
String port = args[i+1].substring(args[i+1].indexOf(':')+1);
|
||||
proxyPort = Integer.parseInt(port);
|
||||
i++;
|
||||
} else if (args[i].equals("-n")) {
|
||||
numRetries = Integer.parseInt(args[i+1]);
|
||||
i++;
|
||||
} else if (args[i].equals("-o")) {
|
||||
saveAs = args[i+1];
|
||||
i++;
|
||||
} else if (args[i].equals("-m")) {
|
||||
markSize = Integer.parseInt(args[i+1]);
|
||||
lineLen = Integer.parseInt(args[i+2]);
|
||||
i += 2;
|
||||
} else {
|
||||
url = args[i];
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (url == null) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
if (saveAs == null)
|
||||
saveAs = suggestName(url);
|
||||
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), proxyHost, proxyPort, numRetries, saveAs, url);
|
||||
get.addStatusListener(get.new CLIStatusListener(markSize, lineLen));
|
||||
get.fetch();
|
||||
}
|
||||
|
||||
public static String suggestName(String url) {
|
||||
String name = null;
|
||||
if (url.lastIndexOf('/') >= 0)
|
||||
name = sanitize(url.substring(url.lastIndexOf('/')+1));
|
||||
if (name != null)
|
||||
return name;
|
||||
else
|
||||
return sanitize(url);
|
||||
}
|
||||
|
||||
private static final String _safeChars = "abcdefghijklmnopqrstuvwxyz" +
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"01234567890.,_=@#:";
|
||||
private static String sanitize(String name) {
|
||||
name = name.replace('/', '_');
|
||||
StringBuffer buf = new StringBuffer(name);
|
||||
for (int i = 0; i < name.length(); i++)
|
||||
if (_safeChars.indexOf(buf.charAt(i)) == -1)
|
||||
buf.setCharAt(i, '_');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.err.println("EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] url");
|
||||
}
|
||||
|
||||
public static interface StatusListener {
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url);
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile);
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause);
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt);
|
||||
}
|
||||
private class CLIStatusListener implements StatusListener {
|
||||
private int _markSize;
|
||||
private int _lineSize;
|
||||
private long _startedOn;
|
||||
private long _written;
|
||||
private long _lastComplete;
|
||||
private DecimalFormat _pct = new DecimalFormat("00.0%");
|
||||
private DecimalFormat _kbps = new DecimalFormat("###,000.00");
|
||||
public CLIStatusListener() {
|
||||
this(1024, 40);
|
||||
}
|
||||
public CLIStatusListener(int markSize, int lineSize) {
|
||||
_markSize = markSize;
|
||||
_lineSize = lineSize;
|
||||
_written = 0;
|
||||
_lastComplete = _context.clock().now();
|
||||
_startedOn = _lastComplete;
|
||||
}
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
for (int i = 0; i < currentWrite; i++) {
|
||||
_written++;
|
||||
if ( (_markSize > 0) && (_written % _markSize == 0) ) {
|
||||
System.out.print("#");
|
||||
|
||||
if ( (_lineSize > 0) && (_written % ((long)_markSize*(long)_lineSize) == 0l) ) {
|
||||
long now = _context.clock().now();
|
||||
long timeToSend = now - _lastComplete;
|
||||
if (timeToSend > 0) {
|
||||
StringBuffer buf = new StringBuffer(50);
|
||||
buf.append(" ");
|
||||
double pct = ((double)alreadyTransferred + (double)_written) / ((double)alreadyTransferred + (double)bytesRemaining);
|
||||
synchronized (_pct) {
|
||||
buf.append(_pct.format(pct));
|
||||
}
|
||||
buf.append(": ");
|
||||
buf.append(_written+alreadyTransferred);
|
||||
buf.append(" @ ");
|
||||
double lineKBytes = ((double)_markSize * (double)_lineSize)/1024.0d;
|
||||
double kbps = lineKBytes/((double)timeToSend/1000.0d);
|
||||
synchronized (_kbps) {
|
||||
buf.append(_kbps.format(kbps));
|
||||
}
|
||||
buf.append("KBps");
|
||||
|
||||
buf.append(" / ");
|
||||
long lifetime = _context.clock().now() - _startedOn;
|
||||
double lifetimeKBps = (1000.0d*(double)(_written+alreadyTransferred)/((double)lifetime*1024.0d));
|
||||
synchronized (_kbps) {
|
||||
buf.append(_kbps.format(lifetimeKBps));
|
||||
}
|
||||
buf.append("KBps");
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
_lastComplete = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
|
||||
System.out.println();
|
||||
System.out.println("== " + new Date());
|
||||
System.out.println("== Transfer of " + url + " completed with " + (alreadyTransferred+bytesTransferred)
|
||||
+ " and " + (bytesRemaining - bytesTransferred) + " remaining");
|
||||
System.out.println("== Output saved to " + outputFile);
|
||||
long timeToSend = _context.clock().now() - _startedOn;
|
||||
System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend));
|
||||
StringBuffer buf = new StringBuffer(50);
|
||||
buf.append("== Transfer rate: ");
|
||||
double kbps = (1000.0d*(double)(_written)/((double)timeToSend*1024.0d));
|
||||
synchronized (_kbps) {
|
||||
buf.append(_kbps.format(kbps));
|
||||
}
|
||||
buf.append("KBps");
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||
System.out.println();
|
||||
System.out.println("** " + new Date());
|
||||
System.out.println("** Attempt " + currentAttempt + " of " + url + " failed");
|
||||
System.out.println("** Transfered " + bytesTransferred
|
||||
+ " with " + (bytesRemaining < 0 ? "unknown" : ""+bytesRemaining) + " remaining");
|
||||
System.out.println("** " + cause.getMessage());
|
||||
_written = 0;
|
||||
}
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
System.out.println("== " + new Date());
|
||||
System.out.println("== Transfer of " + url + " failed after " + currentAttempt + " attempts");
|
||||
System.out.println("== Transfer size: " + bytesTransferred + " with "
|
||||
+ (bytesRemaining < 0 ? "unknown" : ""+bytesRemaining) + " remaining");
|
||||
long timeToSend = _context.clock().now() - _startedOn;
|
||||
System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend));
|
||||
double kbps = (timeToSend > 0 ? (1000.0d*(double)(bytesTransferred)/((double)timeToSend*1024.0d)) : 0);
|
||||
StringBuffer buf = new StringBuffer(50);
|
||||
buf.append("== Transfer rate: ");
|
||||
synchronized (_kbps) {
|
||||
buf.append(_kbps.format(kbps));
|
||||
}
|
||||
buf.append("KBps");
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void addStatusListener(StatusListener lsnr) {
|
||||
synchronized (_listeners) { _listeners.add(lsnr); }
|
||||
}
|
||||
|
||||
public void stopFetching() { _keepFetching = false; }
|
||||
public void fetch() {
|
||||
_keepFetching = true;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fetching (proxied? " + _shouldProxy + ") url=" + _url);
|
||||
while (_keepFetching) {
|
||||
try {
|
||||
sendRequest();
|
||||
doFetch();
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
|
||||
} finally {
|
||||
if (_out != null) {
|
||||
try {
|
||||
_out.close();
|
||||
} catch (IOException cioe) {}
|
||||
_out = null;
|
||||
}
|
||||
if (_proxy != null) {
|
||||
try {
|
||||
_proxy.close();
|
||||
_proxy = null;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
_currentAttempt++;
|
||||
if (_currentAttempt > _numRetries)
|
||||
break;
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
|
||||
}
|
||||
|
||||
/** return true if the URL was completely retrieved */
|
||||
private void doFetch() throws IOException {
|
||||
readHeaders();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Headers read completely, reading " + _bytesRemaining);
|
||||
|
||||
byte buf[] = new byte[1024];
|
||||
while (_keepFetching) {
|
||||
int read = _proxyIn.read(buf);
|
||||
if (read == -1)
|
||||
break;
|
||||
_out.write(buf, 0, read);
|
||||
_bytesTransferred += read;
|
||||
if (read > 0)
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).bytesTransferred(_alreadyTransferred, read, _bytesTransferred, _bytesRemaining, _url);
|
||||
}
|
||||
|
||||
if (_out != null)
|
||||
_out.close();
|
||||
_out = null;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Done transferring " + _bytesTransferred);
|
||||
|
||||
if (_bytesRemaining == _bytesTransferred) {
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).transferComplete(_alreadyTransferred, _bytesTransferred, _bytesRemaining, _url, _outputFile);
|
||||
} else {
|
||||
throw new IOException("Disconnection on attempt " + _currentAttempt + " after " + _bytesTransferred);
|
||||
}
|
||||
}
|
||||
|
||||
private void readHeaders() throws IOException {
|
||||
String key = null;
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
|
||||
boolean read = DataHelper.readLine(_proxyIn, buf);
|
||||
if (!read) throw new IOException("Unable to read the first line");
|
||||
int responseCode = handleStatus(buf.toString());
|
||||
|
||||
boolean rcOk = false;
|
||||
switch (responseCode) {
|
||||
case 200: // full
|
||||
_out = new FileOutputStream(_outputFile, false);
|
||||
rcOk = true;
|
||||
break;
|
||||
case 206: // partial
|
||||
_out = new FileOutputStream(_outputFile, true);
|
||||
rcOk = true;
|
||||
break;
|
||||
case 416: // completed (or range out of reach)
|
||||
_bytesRemaining = 0;
|
||||
_keepFetching = false;
|
||||
return;
|
||||
default:
|
||||
rcOk = false;
|
||||
}
|
||||
|
||||
byte lookahead[] = new byte[3];
|
||||
while (true) {
|
||||
int cur = _proxyIn.read();
|
||||
switch (cur) {
|
||||
case -1:
|
||||
throw new IOException("Headers ended too soon");
|
||||
case ':':
|
||||
if (key == null) {
|
||||
key = buf.toString();
|
||||
buf.setLength(0);
|
||||
increment(lookahead, cur);
|
||||
break;
|
||||
} else {
|
||||
buf.append((char)cur);
|
||||
increment(lookahead, cur);
|
||||
break;
|
||||
}
|
||||
case '\n':
|
||||
case '\r':
|
||||
if (key != null)
|
||||
handle(key, buf.toString());
|
||||
|
||||
buf.setLength(0);
|
||||
key = null;
|
||||
increment(lookahead, cur);
|
||||
if (isEndOfHeaders(lookahead)) {
|
||||
if (!rcOk)
|
||||
throw new IOException("Invalid HTTP response code: " + responseCode);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
buf.append((char)cur);
|
||||
increment(lookahead, cur);
|
||||
}
|
||||
|
||||
if (buf.length() > 1024)
|
||||
throw new IOException("Header line too long: " + buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse the first status line and grab the response code.
|
||||
* e.g. "HTTP/1.1 206 OK" vs "HTTP/1.1 200 OK" vs
|
||||
* "HTTP/1.1 404 NOT FOUND", etc.
|
||||
*
|
||||
* @return HTTP response code (200, 206, other)
|
||||
*/
|
||||
private int handleStatus(String line) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Status line: [" + line + "]");
|
||||
StringTokenizer tok = new StringTokenizer(line, " ");
|
||||
if (!tok.hasMoreTokens()) {
|
||||
System.err.println("ERR: status "+ line);
|
||||
return -1;
|
||||
}
|
||||
String protocol = tok.nextToken(); // ignored
|
||||
if (!tok.hasMoreTokens()) {
|
||||
System.err.println("ERR: status "+ line);
|
||||
return -1;
|
||||
}
|
||||
String rc = tok.nextToken();
|
||||
try {
|
||||
return Integer.parseInt(rc);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(String key, String val) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Header line: [" + key + "] = [" + val + "]");
|
||||
if (key.equalsIgnoreCase("Content-length")) {
|
||||
try {
|
||||
_bytesRemaining = Long.parseLong(val.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
}
|
||||
} else if (key.equalsIgnoreCase("ETag")) {
|
||||
_etag = val.trim();
|
||||
} else {
|
||||
// ignore the rest
|
||||
}
|
||||
}
|
||||
|
||||
private void increment(byte[] lookahead, int cur) {
|
||||
lookahead[0] = lookahead[1];
|
||||
lookahead[1] = lookahead[2];
|
||||
lookahead[2] = (byte)cur;
|
||||
}
|
||||
private boolean isEndOfHeaders(byte lookahead[]) {
|
||||
byte first = lookahead[0];
|
||||
byte second = lookahead[1];
|
||||
byte third = lookahead[2];
|
||||
return (isNL(second) && isNL(third)) || // \n\n
|
||||
(isNL(first) && isNL(third)); // \n\r\n
|
||||
}
|
||||
|
||||
/** we ignore any potential \r, since we trim it on write anyway */
|
||||
private static final byte NL = '\n';
|
||||
private boolean isNL(byte b) { return (b == NL); }
|
||||
|
||||
private void sendRequest() throws IOException {
|
||||
File outFile = new File(_outputFile);
|
||||
if (outFile.exists())
|
||||
_alreadyTransferred = outFile.length();
|
||||
|
||||
String req = getRequest();
|
||||
|
||||
if (_shouldProxy) {
|
||||
_proxy = new Socket(_proxyHost, _proxyPort);
|
||||
} else {
|
||||
try {
|
||||
URL url = new URL(_url);
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
if (port == -1)
|
||||
port = 80;
|
||||
_proxy = new Socket(host, port);
|
||||
} catch (MalformedURLException mue) {
|
||||
throw new IOException("Request URL is invalid");
|
||||
}
|
||||
}
|
||||
_proxyIn = _proxy.getInputStream();
|
||||
_proxyOut = _proxy.getOutputStream();
|
||||
|
||||
_proxyOut.write(req.toString().getBytes());
|
||||
_proxyOut.flush();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Request flushed");
|
||||
}
|
||||
|
||||
private String getRequest() {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("GET ").append(_url).append(" HTTP/1.1\n");
|
||||
try {
|
||||
URL url = new URL(_url);
|
||||
buf.append("Host: ").append(url.getHost()).append("\n");
|
||||
} catch (MalformedURLException mue) {
|
||||
mue.printStackTrace();
|
||||
}
|
||||
if (_alreadyTransferred > 0) {
|
||||
buf.append("Range: bytes=");
|
||||
buf.append(_alreadyTransferred);
|
||||
buf.append("-\n");
|
||||
}
|
||||
buf.append("Connection: close\n\n");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Request: [" + buf.toString() + "]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ public class LogManager {
|
||||
if (!_alreadyNoticedMissingConfig) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Log file " + _location + " does not exist");
|
||||
System.err.println("Log file " + _location + " does not exist");
|
||||
//System.err.println("Log file " + _location + " does not exist");
|
||||
_alreadyNoticedMissingConfig = true;
|
||||
}
|
||||
parseConfig(new Properties());
|
||||
@@ -644,7 +644,7 @@ public class LogManager {
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
_log.log(Log.CRIT, "Shutting down logger");
|
||||
_log.log(Log.WARN, "Shutting down logger");
|
||||
_writer.flushRecords();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user