propagate from branch 'i2p.i2p.zzz.plugin' (head fafcd8c8c41873b4d106a9e06504dd7b48109ad8)
to branch 'i2p.i2p' (head 7eafbe18b0a1e26f09b9488d374f5fed4c278a78)
This commit is contained in:
@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@ -104,7 +105,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
|
||||
*/
|
||||
|
||||
private static final int VERSION_BYTES = 16;
|
||||
private static final int HEADER_BYTES = Signature.SIGNATURE_BYTES + VERSION_BYTES;
|
||||
public static final int HEADER_BYTES = Signature.SIGNATURE_BYTES + VERSION_BYTES;
|
||||
private static final String PROP_TRUSTED_KEYS = "router.trustedUpdateKeys";
|
||||
|
||||
private static I2PAppContext _context;
|
||||
@ -178,6 +179,22 @@ D8usM7Dxp5yrDrCYZ5AIijc=
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do we know about the following key?
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public boolean haveKey(String key) {
|
||||
if (key.length() != KEYSIZE_B64_BYTES)
|
||||
return false;
|
||||
SigningPublicKey signingPublicKey = new SigningPublicKey();
|
||||
try {
|
||||
signingPublicKey.fromBase64(key);
|
||||
} catch (DataFormatException dfe) {
|
||||
return false;
|
||||
}
|
||||
return _trustedKeys.containsKey(signingPublicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses command line arguments when this class is used from the command
|
||||
* line.
|
||||
@ -258,7 +275,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
|
||||
}
|
||||
|
||||
private static final void showVersionCLI(String signedFile) {
|
||||
String versionString = new TrustedUpdate().getVersionString(new File(signedFile));
|
||||
String versionString = getVersionString(new File(signedFile));
|
||||
|
||||
if (versionString.equals(""))
|
||||
System.out.println("No version string found in file '" + signedFile + "'");
|
||||
@ -331,7 +348,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
|
||||
* @return The version string read, or an empty string if no version string
|
||||
* is present.
|
||||
*/
|
||||
public String getVersionString(File signedFile) {
|
||||
public static String getVersionString(File signedFile) {
|
||||
FileInputStream fileInputStream = null;
|
||||
|
||||
try {
|
||||
@ -364,6 +381,45 @@ D8usM7Dxp5yrDrCYZ5AIijc=
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the version string from an input stream
|
||||
*
|
||||
* @param inputStream containing at least 56 bytes
|
||||
*
|
||||
* @return The version string read, or an empty string if no version string
|
||||
* is present.
|
||||
*/
|
||||
public static String getVersionString(InputStream inputStream) {
|
||||
try {
|
||||
long skipped = inputStream.skip(Signature.SIGNATURE_BYTES);
|
||||
if (skipped != Signature.SIGNATURE_BYTES)
|
||||
return "";
|
||||
byte[] data = new byte[VERSION_BYTES];
|
||||
int bytesRead = DataHelper.read(inputStream, data);
|
||||
|
||||
if (bytesRead != VERSION_BYTES) {
|
||||
return "";
|
||||
}
|
||||
|
||||
for (int i = 0; i < VERSION_BYTES; i++)
|
||||
if (data[i] == 0x00) {
|
||||
return new String(data, 0, i, "UTF-8");
|
||||
}
|
||||
|
||||
return new String(data, "UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + uee.getMessage());
|
||||
} catch (IOException ioe) {
|
||||
return "";
|
||||
} finally {
|
||||
if (inputStream != null)
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** version in the .sud file, valid only after calling migrateVerified() */
|
||||
public String newVersion() {
|
||||
@ -410,6 +466,22 @@ D8usM7Dxp5yrDrCYZ5AIijc=
|
||||
if (!verify(signedFile))
|
||||
return "Unknown signing key or corrupt file";
|
||||
|
||||
return migrateFile(signedFile, outputFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the file. Skips and ignores the signature and version. No verification.
|
||||
*
|
||||
* @param signedFile A signed update file.
|
||||
* @param outputFile The file to write the verified data to.
|
||||
*
|
||||
* @return <code>null</code> if the
|
||||
* data was moved, and an error <code>String</code> otherwise.
|
||||
*/
|
||||
public String migrateFile(File signedFile, File outputFile) {
|
||||
if (!signedFile.exists())
|
||||
return "File not found: " + signedFile.getAbsolutePath();
|
||||
|
||||
FileInputStream fileInputStream = null;
|
||||
FileOutputStream fileOutputStream = null;
|
||||
|
||||
@ -610,6 +682,23 @@ D8usM7Dxp5yrDrCYZ5AIijc=
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the DSA signature of a signed update file.
|
||||
*
|
||||
* @param signedFile The signed update file to check.
|
||||
*
|
||||
* @return signer (could be empty string) or null if invalid
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public String verifyAndGetSigner(File signedFile) {
|
||||
for (SigningPublicKey signingPublicKey : _trustedKeys.keySet()) {
|
||||
boolean isValidSignature = verify(signedFile, signingPublicKey);
|
||||
if (isValidSignature)
|
||||
return _trustedKeys.get(signingPublicKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the DSA signature of a signed update file.
|
||||
*
|
||||
|
130
core/java/src/net/i2p/util/PartialEepGet.java
Normal file
130
core/java/src/net/i2p/util/PartialEepGet.java
Normal file
@ -0,0 +1,130 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Fetch exactly the first 'size' bytes into a stream
|
||||
* Anything less or more will throw an IOException
|
||||
* No retries, no min and max size options, no timeout option
|
||||
* Useful for checking .sud versions
|
||||
*
|
||||
* @since 0.7.12
|
||||
* @author zzz
|
||||
*/
|
||||
public class PartialEepGet extends EepGet {
|
||||
long _fetchSize;
|
||||
|
||||
/** @param size fetch exactly this many bytes */
|
||||
public PartialEepGet(I2PAppContext ctx, String proxyHost, int proxyPort,
|
||||
OutputStream outputStream, String url, long size) {
|
||||
// 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, true, proxyHost, proxyPort, 0, size, size, null, outputStream, url, true, null, null);
|
||||
_fetchSize = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* PartialEepGet [-p 127.0.0.1:4444] [-l #bytes] url
|
||||
*
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String proxyHost = "127.0.0.1";
|
||||
int proxyPort = 4444;
|
||||
// 40 sig + 16 version for .suds
|
||||
long size = 56;
|
||||
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("-l")) {
|
||||
size = Long.parseLong(args[i+1]);
|
||||
i++;
|
||||
} else 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;
|
||||
}
|
||||
|
||||
EepGet get = new PartialEepGet(I2PAppContext.getGlobalContext(), proxyHost, proxyPort, out, url, size);
|
||||
get.addStatusListener(get.new CLIStatusListener(1024, 40));
|
||||
if (get.fetch(45*1000, -1, 60*1000)) {
|
||||
System.err.println("Last-Modified: " + get.getLastModified());
|
||||
System.err.println("Etag: " + get.getETag());
|
||||
} else {
|
||||
System.err.println("Failed " + url);
|
||||
}
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.err.println("PartialEepGet [-p 127.0.0.1:4444] [-l #bytes] url");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRequest() throws IOException {
|
||||
StringBuilder buf = new StringBuilder(2048);
|
||||
URL url = new URL(_actualURL);
|
||||
String proto = url.getProtocol();
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
String path = url.getPath();
|
||||
String query = url.getQuery();
|
||||
if (query != null)
|
||||
path = path + '?' + query;
|
||||
if (!path.startsWith("/"))
|
||||
path = "/" + path;
|
||||
if ( (port == 80) || (port == 443) || (port <= 0) ) path = proto + "://" + host + path;
|
||||
else path = proto + "://" + host + ":" + port + path;
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Requesting " + path);
|
||||
buf.append("GET ").append(_actualURL).append(" HTTP/1.1\r\n");
|
||||
buf.append("Host: ").append(url.getHost()).append("\r\n");
|
||||
buf.append("Range: bytes=");
|
||||
buf.append(_alreadyTransferred);
|
||||
buf.append('-');
|
||||
buf.append(_fetchSize - 1);
|
||||
buf.append("\r\n");
|
||||
|
||||
if (_shouldProxy)
|
||||
buf.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
|
||||
buf.append("Cache-control: no-cache\r\n" +
|
||||
"Pragma: no-cache\r\n");
|
||||
// This will be replaced if we are going through I2PTunnelHTTPClient
|
||||
buf.append("User-Agent: " + USER_AGENT + "\r\n" +
|
||||
"Accept-Encoding: \r\n" +
|
||||
"Connection: close\r\n\r\n");
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Request: [" + buf.toString() + "]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -126,4 +126,13 @@ public abstract class Translate {
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache.
|
||||
* Call this after adding new bundles to the classpath.
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public static void clearCache() {
|
||||
_missing.clear();
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Compares versions.
|
||||
* Characters other than [0-9.] are ignored.
|
||||
* Characters other than [0-9.-_] are ignored.
|
||||
* I2P only uses '.' but Sun Java uses '_' and plugins may use any of '.-_'
|
||||
* Moved from TrustedUpdate.java
|
||||
* @since 0.7.10
|
||||
*/
|
||||
@ -15,8 +16,8 @@ public class VersionComparator implements Comparator<String> {
|
||||
// try it the easy way first
|
||||
if (l.equals(r))
|
||||
return 0;
|
||||
StringTokenizer lTokens = new StringTokenizer(sanitize(l), ".");
|
||||
StringTokenizer rTokens = new StringTokenizer(sanitize(r), ".");
|
||||
StringTokenizer lTokens = new StringTokenizer(sanitize(l), VALID_SEPARATOR_CHARS);
|
||||
StringTokenizer rTokens = new StringTokenizer(sanitize(r), VALID_SEPARATOR_CHARS);
|
||||
|
||||
while (lTokens.hasMoreTokens() && rTokens.hasMoreTokens()) {
|
||||
String lNumber = lTokens.nextToken();
|
||||
@ -48,7 +49,8 @@ public class VersionComparator implements Comparator<String> {
|
||||
return left - right;
|
||||
}
|
||||
|
||||
private static final String VALID_VERSION_CHARS = "0123456789.";
|
||||
private static final String VALID_SEPARATOR_CHARS = ".-_";
|
||||
private static final String VALID_VERSION_CHARS = "0123456789" + VALID_SEPARATOR_CHARS;
|
||||
|
||||
private static final String sanitize(String versionString) {
|
||||
StringBuilder versionStringBuilder = new StringBuilder(versionString);
|
||||
|
Reference in New Issue
Block a user