Compare commits
166 Commits
i2p_0_6
...
i2p_0_6_1_
Author | SHA1 | Date | |
---|---|---|---|
cdee5b2c31 | |||
7f6e65c76f | |||
4dd628dbc8 | |||
3b5b48ad8a | |||
c4cac3f3f1 | |||
4a49e98c31 | |||
0c0e269e72 | |||
70b6f97abe | |||
0013677b83 | |||
a98ceda64d | |||
91ea1d0395 | |||
4aa65c3bb3 | |||
0a1f59940a | |||
f540dc798b | |||
30f6f26a68 | |||
ea3bf3ffc8 | |||
831d5ac70c | |||
1962867ad9 | |||
6019a03029 | |||
df5736f571 | |||
9a73c6defe | |||
9dfa87ba47 | |||
934a269753 | |||
1c0dfc242b | |||
3bc3e5d47e | |||
55869af2cc | |||
9f336dd05b | |||
411ca5e6c3 | |||
c528e4db03 | |||
848ead7683 | |||
1b8419b9b5 | |||
900420719e | |||
ef7d1ba964 | |||
ab1654c784 | |||
24bad8e4bb | |||
f6d8200bc8 | |||
aef33548b3 | |||
56ecdcce82 | |||
b9b59ff95f | |||
aa9dd3e5c6 | |||
30bd659149 | |||
3286ca49c8 | |||
7700d12178 | |||
557b7e3f2e | |||
3e1e9146e1 | |||
40d8d1aac1 | |||
1457b8efba | |||
3821e80ac8 | |||
d40bb459ea | |||
edf04f07c9 | |||
2bdea23986 | |||
6be0c4b694 | |||
2a272f465c | |||
a8ecd32b45 | |||
20c42a175d | |||
d6c3ffde87 | |||
177e0ae6a3 | |||
dab1b4d256 | |||
3aba12631b | |||
cfee6430d4 | |||
6b96df1cec | |||
deecfa5047 | |||
6ca3f01038 | |||
d89f589f2b | |||
8c1895e04f | |||
c3d0132a98 | |||
d955279d17 | |||
76266dce0d | |||
5694206b35 | |||
4293a18726 | |||
9865af4174 | |||
c8c109093d | |||
b5784d6025 | |||
31bdb8909a | |||
ee921c22ae | |||
172ffd0434 | |||
d9b4406c09 | |||
8ac0e85df4 | |||
249ccd5e3c | |||
727d76d43e | |||
44770b7c07 | |||
b5d571c75f | |||
da56d83716 | |||
f777e213ce | |||
79906f5a7d | |||
54074e76b5 | |||
c2ea8db683 | |||
744671a518 | |||
7f5b127bbc | |||
89eff0c628 | |||
177aeebb1c | |||
e0e6bde4a5 | |||
e6b145716f | |||
f958342704 | |||
5a1f738505 | |||
8147cdf40c | |||
6afc64ac39 | |||
61b8e3598b | |||
3bb445ff40 | |||
59a8037599 | |||
09cb5fad59 | |||
ee8e45ecf7 | |||
339868838d | |||
c5579fa349 | |||
d4a859547c | |||
779aa240d2 | |||
9aaad00383 | |||
6422f7ef78 | |||
3e51584b3c | |||
4ff8a53084 | |||
ccb73437c4 | |||
b43114f61b | |||
9bd87ab511 | |||
b6ea55f7ef | |||
5f18cec97d | |||
3ba921ec0e | |||
e313da254c | |||
8660cf0d74 | |||
e0bfdff152 | |||
c27aed3603 | |||
cdc6002f0e | |||
4cf3d9c1a2 | |||
0473e08e21 | |||
346faa3de2 | |||
5ec6dca64d | |||
1a6b49cfb8 | |||
c7b75df390 | |||
f97c09291b | |||
8f2a5b403c | |||
ea41a90eae | |||
b1dd29e64d | |||
46e47c47ac | |||
b7bf431f0d | |||
7f432122d9 | |||
e7be8c6097 | |||
adf56a16e1 | |||
11204b8a2b | |||
cade27dceb | |||
5597d28e59 | |||
0502fec432 | |||
a6714fc2de | |||
1219dadbd5 | |||
77b995f5ed | |||
2f53b9ff68 | |||
d84d045849 | |||
d8e72dfe48 | |||
88b9f7a74c | |||
6a19501214 | |||
ba30b56c5f | |||
a375e4b2ce | |||
44fd71e17f | |||
b41c378de9 | |||
4ce6b308b3 | |||
72c6e7d1c5 | |||
7ca3f22e77 | |||
59790dafef | |||
7227cae6ef | |||
03bba51c1e | |||
0637050cbc | |||
7f58a68c5a | |||
8120b0397c | |||
fbe42b7dce | |||
def24e34ad | |||
593253e6a3 | |||
56dd4cb8b5 | |||
10c6f67500 |
@ -6,8 +6,7 @@
|
||||
<property name="dist" location="dist"/>
|
||||
<property name="jar" value="addressbook.jar"/>
|
||||
<property name="war" value="addressbook.war"/>
|
||||
<property name="servlet" value="../jetty/jettylib/javax.servlet.jar"/>
|
||||
|
||||
|
||||
<target name="init">
|
||||
<mkdir dir="${build}"/>
|
||||
<mkdir dir="${dist}"/>
|
||||
@ -22,7 +21,12 @@
|
||||
|
||||
<target name="compile" depends="init">
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
srcdir="${src}" destdir="${build}" classpath="${servlet}"/>
|
||||
srcdir="${src}" destdir="${build}">
|
||||
<classpath>
|
||||
<pathelement location="../../core/java/build/i2p.jar" />
|
||||
<pathelement location="../jetty/jettylib/javax.servlet.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
|
@ -24,11 +24,12 @@ package addressbook;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.EepGet;
|
||||
|
||||
/**
|
||||
* An address book for storing human readable names mapped to base64 i2p
|
||||
* destinations. AddressBooks can be created from local and remote files, merged
|
||||
@ -65,14 +66,18 @@ public class AddressBook {
|
||||
* where key is a human readable name, and value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
public AddressBook(URL url) {
|
||||
this.location = url.getHost();
|
||||
|
||||
public AddressBook(String url, String proxyHost, int proxyPort) {
|
||||
this.location = url;
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp", url, true,
|
||||
null);
|
||||
get.fetch();
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(url);
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,44 +88,19 @@ public class AddressBook {
|
||||
* @param subscription
|
||||
* A Subscription instance pointing at a remote address book.
|
||||
*/
|
||||
public AddressBook(Subscription subscription) {
|
||||
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
|
||||
this.location = subscription.getLocation();
|
||||
|
||||
try {
|
||||
// EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true, )
|
||||
URL url = new URL(subscription.getLocation());
|
||||
HttpURLConnection connection = (HttpURLConnection) url
|
||||
.openConnection();
|
||||
if (subscription.getEtag() != null) {
|
||||
connection.addRequestProperty("If-None-Match", subscription
|
||||
.getEtag());
|
||||
}
|
||||
if (subscription.getLastModified() != null) {
|
||||
connection.addRequestProperty("If-Modified-Since", subscription
|
||||
.getLastModified());
|
||||
}
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
connection.disconnect();
|
||||
this.addresses = new HashMap();
|
||||
return;
|
||||
}
|
||||
if (connection.getHeaderField("ETag") != null) {
|
||||
subscription.setEtag(connection.getHeaderField("ETag"));
|
||||
}
|
||||
if (connection.getHeaderField("Last-Modified") != null) {
|
||||
subscription.setLastModified(connection
|
||||
.getHeaderField("Last-Modified"));
|
||||
}
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new URL(subscription
|
||||
.getLocation()));
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp",
|
||||
subscription.getLocation(), true, subscription.getEtag());
|
||||
get.fetch();
|
||||
subscription.setEtag(get.getETag());
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,7 +162,7 @@ public class AddressBook {
|
||||
* @param log
|
||||
* The log to write messages about new addresses or conflicts to.
|
||||
*/
|
||||
public void merge(AddressBook other, Log log) {
|
||||
public void merge(AddressBook other, boolean overwrite, Log log) {
|
||||
Iterator otherIter = other.addresses.keySet().iterator();
|
||||
|
||||
while (otherIter.hasNext()) {
|
||||
@ -190,7 +170,7 @@ public class AddressBook {
|
||||
String otherValue = (String) other.addresses.get(otherKey);
|
||||
|
||||
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
|
||||
if (this.addresses.containsKey(otherKey)) {
|
||||
if (this.addresses.containsKey(otherKey) && !overwrite) {
|
||||
if (!this.addresses.get(otherKey).equals(otherValue)
|
||||
&& log != null) {
|
||||
log.append("Conflict for " + otherKey + " from "
|
||||
@ -198,28 +178,19 @@ public class AddressBook {
|
||||
+ ". Destination in remote address book is "
|
||||
+ otherValue);
|
||||
}
|
||||
} else {
|
||||
} else if (!this.addresses.containsKey(otherKey)
|
||||
|| !this.addresses.get(otherKey).equals(otherValue)) {
|
||||
this.addresses.put(otherKey, otherValue);
|
||||
this.modified = true;
|
||||
if (log != null) {
|
||||
log.append("New address " + otherKey
|
||||
+ " added to address book.");
|
||||
+ " added to address book.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this AddressBook with other, without logging.
|
||||
*
|
||||
* @param other
|
||||
* An AddressBook to merge with.
|
||||
*/
|
||||
public void merge(AddressBook other) {
|
||||
this.merge(other, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the contents of this AddressBook out to the File file. If the file
|
||||
* cannot be writen to, this method will silently fail.
|
||||
@ -244,4 +215,4 @@ public class AddressBook {
|
||||
public void write() {
|
||||
this.write(new File(this.location));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Iterator;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Utility class providing methods to parse and write files in config file
|
||||
@ -86,24 +85,6 @@ public class ConfigParser {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the file at url. See
|
||||
* parseBufferedReader for details of the input format.
|
||||
*
|
||||
* @param url
|
||||
* A url pointing to a file to parse.
|
||||
* @return A Map containing the key, value pairs from url.
|
||||
* @throws IOException
|
||||
* if url cannot be read.
|
||||
*/
|
||||
public static Map parse(URL url) throws IOException {
|
||||
InputStream urlStream;
|
||||
urlStream = url.openConnection().getInputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
urlStream));
|
||||
return ConfigParser.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. See parseBufferedReader
|
||||
* for details of the input format.
|
||||
|
@ -36,6 +36,7 @@ import java.io.File;
|
||||
*/
|
||||
public class Daemon {
|
||||
public static final String VERSION = "2.0.3";
|
||||
private static final Daemon _instance = new Daemon();
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
@ -56,17 +57,16 @@ public class Daemon {
|
||||
* @param log
|
||||
* The log to write changes and conflicts to.
|
||||
*/
|
||||
public static void update(AddressBook master, AddressBook router,
|
||||
public void update(AddressBook master, AddressBook router,
|
||||
File published, SubscriptionList subscriptions, Log log) {
|
||||
String routerLocation = router.getLocation();
|
||||
master.merge(router);
|
||||
router.merge(master, true, null);
|
||||
Iterator iter = subscriptions.iterator();
|
||||
while (iter.hasNext()) {
|
||||
master.merge((AddressBook) iter.next(), log);
|
||||
router.merge((AddressBook) iter.next(), false, log);
|
||||
}
|
||||
master.write(new File(routerLocation));
|
||||
router.write();
|
||||
if (published != null)
|
||||
master.write(published);
|
||||
router.write(published);
|
||||
subscriptions.write();
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ public class Daemon {
|
||||
* @param home
|
||||
* The directory containing addressbook's configuration files.
|
||||
*/
|
||||
public static void update(Map settings, String home) {
|
||||
public void update(Map settings, String home) {
|
||||
File masterFile = new File(home, (String) settings
|
||||
.get("master_addressbook"));
|
||||
File routerFile = new File(home, (String) settings
|
||||
@ -101,10 +101,11 @@ public class Daemon {
|
||||
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, defaultSubs);
|
||||
etagsFile, lastModifiedFile, defaultSubs, (String) settings
|
||||
.get("proxy_host"), Integer.parseInt((String) settings.get("proxy_port")));
|
||||
Log log = new Log(logFile);
|
||||
|
||||
Daemon.update(master, router, published, subscriptions, log);
|
||||
update(master, router, published, subscriptions, log);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,6 +119,10 @@ public class Daemon {
|
||||
* others are ignored.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
_instance.run(args);
|
||||
}
|
||||
|
||||
public void run(String[] args) {
|
||||
String settingsLocation = "config.txt";
|
||||
Map settings = new HashMap();
|
||||
String home;
|
||||
@ -151,24 +156,36 @@ public class Daemon {
|
||||
|
||||
File settingsFile = new File(homeFile, settingsLocation);
|
||||
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
// wait
|
||||
try {
|
||||
Thread.currentThread().sleep(5*60*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
while (true) {
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
|
||||
System.setProperty("proxySet", "true");
|
||||
System.setProperty("http.proxyHost", (String) settings
|
||||
.get("proxy_host"));
|
||||
System.setProperty("http.proxyPort", (String) settings
|
||||
.get("proxy_port"));
|
||||
long delay = Long.parseLong((String) settings.get("update_delay"));
|
||||
if (delay < 1) {
|
||||
delay = 1;
|
||||
}
|
||||
|
||||
Daemon.update(settings, home);
|
||||
update(settings, home);
|
||||
try {
|
||||
Thread.sleep(delay * 60 * 60 * 1000);
|
||||
synchronized (this) {
|
||||
wait(delay * 60 * 60 * 1000);
|
||||
}
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to get the addressbook to reread its config and
|
||||
* refetch its subscriptions.
|
||||
*/
|
||||
public static void wakeup() {
|
||||
synchronized (_instance) {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
@ -44,10 +44,10 @@ public class DaemonThread extends Thread {
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(5 * 60 * 1000);
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
//try {
|
||||
// Thread.sleep(5 * 60 * 1000);
|
||||
//} catch (InterruptedException exp) {
|
||||
//}
|
||||
Daemon.main(this.args);
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@ import java.util.List;
|
||||
public class SubscriptionIterator implements Iterator {
|
||||
|
||||
private Iterator subIterator;
|
||||
private String proxyHost;
|
||||
private int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
|
||||
@ -40,8 +42,10 @@ public class SubscriptionIterator implements Iterator {
|
||||
* @param subscriptions
|
||||
* List of Subscription objects that represent address books.
|
||||
*/
|
||||
public SubscriptionIterator(List subscriptions) {
|
||||
public SubscriptionIterator(List subscriptions, String proxyHost, int proxyPort) {
|
||||
this.subIterator = subscriptions.iterator();
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
}
|
||||
|
||||
|
||||
@ -49,15 +53,15 @@ public class SubscriptionIterator implements Iterator {
|
||||
* @see java.util.Iterator#hasNext()
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return subIterator.hasNext();
|
||||
return this.subIterator.hasNext();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#next()
|
||||
*/
|
||||
public Object next() {
|
||||
Subscription sub = (Subscription) subIterator.next();
|
||||
return new AddressBook(sub);
|
||||
Subscription sub = (Subscription) this.subIterator.next();
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
@ -42,6 +42,10 @@ public class SubscriptionList {
|
||||
private File etagsFile;
|
||||
|
||||
private File lastModifiedFile;
|
||||
|
||||
private String proxyHost;
|
||||
|
||||
private int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionList using the urls from locationsFile and, if
|
||||
@ -58,15 +62,18 @@ public class SubscriptionList {
|
||||
* GET. The file is in the format "url=leastmodified".
|
||||
*/
|
||||
public SubscriptionList(File locationsFile, File etagsFile,
|
||||
File lastModifiedFile, List defaultSubs) {
|
||||
File lastModifiedFile, List defaultSubs, String proxyHost,
|
||||
int proxyPort) {
|
||||
this.subscriptions = new LinkedList();
|
||||
this.etagsFile = etagsFile;
|
||||
this.lastModifiedFile = lastModifiedFile;
|
||||
List locations;
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
Map etags;
|
||||
Map lastModified;
|
||||
String location;
|
||||
locations = ConfigParser.parseSubscriptions(locationsFile, defaultSubs);
|
||||
List locations = ConfigParser.parseSubscriptions(locationsFile,
|
||||
defaultSubs);
|
||||
try {
|
||||
etags = ConfigParser.parse(etagsFile);
|
||||
} catch (IOException exp) {
|
||||
@ -80,11 +87,9 @@ public class SubscriptionList {
|
||||
Iterator iter = locations.iterator();
|
||||
while (iter.hasNext()) {
|
||||
location = (String) iter.next();
|
||||
subscriptions.add(new Subscription(location, (String) etags
|
||||
this.subscriptions.add(new Subscription(location, (String) etags
|
||||
.get(location), (String) lastModified.get(location)));
|
||||
}
|
||||
|
||||
iter = this.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +99,8 @@ public class SubscriptionList {
|
||||
* @return A SubscriptionIterator.
|
||||
*/
|
||||
public SubscriptionIterator iterator() {
|
||||
return new SubscriptionIterator(this.subscriptions);
|
||||
return new SubscriptionIterator(this.subscriptions, this.proxyHost,
|
||||
this.proxyPort);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,12 +111,12 @@ public class Bogobot extends PircBot {
|
||||
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
|
||||
|
||||
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
|
||||
_ircServer = config.getProperty("ircServer", "irc.duck.i2p");
|
||||
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
|
||||
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
|
||||
|
||||
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
|
||||
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.duck.i2p.i2p-chat");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
|
||||
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
|
||||
|
||||
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();
|
||||
|
@ -8,36 +8,50 @@ package net.i2p.i2ptunnel;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple stream for delivering an HTTP response to
|
||||
* the client, trivially filtered to make sure "Connection: close"
|
||||
* is always in the response.
|
||||
* is always in the response. Perhaps add transparent handling of the
|
||||
* Content-encoding: x-i2p-gzip, adjusting the headers to say Content-encoding: identity?
|
||||
* Content-encoding: gzip is trivial as well, but Transfer-encoding: chunked makes it
|
||||
* more work than is worthwhile at the moment.
|
||||
*
|
||||
*/
|
||||
class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
private static final Log _log = new Log(HTTPResponseOutputStream.class);
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private ByteCache _cache;
|
||||
protected ByteArray _headerBuffer;
|
||||
private boolean _headerWritten;
|
||||
private byte _buf1[];
|
||||
protected boolean _gzip;
|
||||
private long _dataWritten;
|
||||
private InternalGZIPInputStream _in;
|
||||
private static final int CACHE_SIZE = 8*1024;
|
||||
|
||||
public HTTPResponseOutputStream(OutputStream raw) {
|
||||
super(raw);
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
_cache = ByteCache.getInstance(8, CACHE_SIZE);
|
||||
_headerBuffer = _cache.acquire();
|
||||
_headerWritten = false;
|
||||
_gzip = false;
|
||||
_dataWritten = 0;
|
||||
_buf1 = new byte[1];
|
||||
}
|
||||
|
||||
@ -51,6 +65,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
public void write(byte buf[], int off, int len) throws IOException {
|
||||
if (_headerWritten) {
|
||||
out.write(buf, off, len);
|
||||
_dataWritten += len;
|
||||
//out.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -62,8 +78,12 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
if (headerReceived()) {
|
||||
writeHeader();
|
||||
_headerWritten = true;
|
||||
if (i + 1 < len) // write out the remaining
|
||||
if (i + 1 < len) {
|
||||
// write out the remaining
|
||||
out.write(buf, off+i+1, len-i-1);
|
||||
_dataWritten += len-i-1;
|
||||
//out.flush();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -128,7 +148,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
if ( (keyLen <= 0) || (valLen <= 0) )
|
||||
throw new IOException("Invalid header @ " + j);
|
||||
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
|
||||
String val = new String(_headerBuffer.getData(), j+2, valLen);
|
||||
String val = new String(_headerBuffer.getData(), j+2, valLen).trim();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Response header [" + key + "] = [" + val + "]");
|
||||
|
||||
if ("Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Connection: close\n".getBytes());
|
||||
@ -136,6 +159,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
proxyConnectionSent = true;
|
||||
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
|
||||
_gzip = true;
|
||||
} else {
|
||||
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
|
||||
}
|
||||
@ -152,13 +177,107 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
if (!proxyConnectionSent)
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
finishHeaders();
|
||||
|
||||
boolean shouldCompress = shouldCompress();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After headers: gzip? " + _gzip + " compress? " + shouldCompress);
|
||||
|
||||
// done, shove off
|
||||
if (_headerBuffer.getData().length == CACHE_SIZE)
|
||||
_cache.release(_headerBuffer);
|
||||
else
|
||||
_headerBuffer = null;
|
||||
if (shouldCompress) {
|
||||
beginProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean shouldCompress() { return _gzip; }
|
||||
|
||||
protected void finishHeaders() throws IOException {
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
|
||||
protected void beginProcessing() throws IOException {
|
||||
//out.flush();
|
||||
PipedInputStream pi = new PipedInputStream();
|
||||
PipedOutputStream po = new PipedOutputStream(pi);
|
||||
new I2PThread(new Pusher(pi, out), "HTTP decompresser").start();
|
||||
out = po;
|
||||
}
|
||||
|
||||
private class Pusher implements Runnable {
|
||||
private InputStream _inRaw;
|
||||
private OutputStream _out;
|
||||
public Pusher(InputStream in, OutputStream out) {
|
||||
_inRaw = in;
|
||||
_out = out;
|
||||
}
|
||||
public void run() {
|
||||
OutputStream to = null;
|
||||
_in = null;
|
||||
long start = System.currentTimeMillis();
|
||||
long written = 0;
|
||||
try {
|
||||
_in = new InternalGZIPInputStream(_inRaw);
|
||||
byte buf[] = new byte[8192];
|
||||
int read = -1;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read " + read + " and writing it to the browser/streams");
|
||||
_out.write(buf, 0, read);
|
||||
_out.flush();
|
||||
written += read;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Decompressed: " + written + ", " + _in.getTotalRead() + "/" + _in.getTotalExpanded());
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error decompressing: " + written + ", " + (_in != null ? _in.getTotalRead() + "/" + _in.getTotalExpanded() : ""), ioe);
|
||||
} finally {
|
||||
if (_log.shouldLog(Log.WARN) && (_in != null))
|
||||
_log.warn("After decompression, written=" + written +
|
||||
(_in != null ?
|
||||
" read=" + _in.getTotalRead()
|
||||
+ ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining()
|
||||
+ ", finished=" + _in.getFinished()
|
||||
: ""));
|
||||
if (_out != null) try {
|
||||
_out.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
double compressed = (_in != null ? _in.getTotalRead() : 0);
|
||||
double expanded = (_in != null ? _in.getTotalExpanded() : 0);
|
||||
double ratio = 0;
|
||||
if (expanded > 0)
|
||||
ratio = compressed/expanded;
|
||||
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
|
||||
}
|
||||
}
|
||||
private class InternalGZIPInputStream extends GZIPInputStream {
|
||||
public InternalGZIPInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
public long getTotalRead() { return super.inf.getTotalIn(); }
|
||||
public long getTotalExpanded() { return super.inf.getTotalOut(); }
|
||||
public long getRemaining() { return super.inf.getRemaining(); }
|
||||
public boolean getFinished() { return super.inf.finished(); }
|
||||
public String toString() {
|
||||
return "Read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString() + ": " + _in;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
|
@ -109,8 +109,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
_tunnelId = ++__tunnelId;
|
||||
_log = _context.logManager().getLog(I2PTunnel.class);
|
||||
_event = new EventDispatcherImpl();
|
||||
_clientOptions = new Properties();
|
||||
_clientOptions.putAll(System.getProperties());
|
||||
Properties p = new Properties();
|
||||
p.putAll(System.getProperties());
|
||||
_clientOptions = p;
|
||||
_sessions = new ArrayList(1);
|
||||
|
||||
addConnectionEventListener(lsnr);
|
||||
|
@ -101,6 +101,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
this.l = l;
|
||||
this.handlerName = handlerName + _clientId;
|
||||
|
||||
// no need to load the netDb with leaseSets for destinations that will never
|
||||
// be looked up
|
||||
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
|
||||
|
||||
while (sockMgr == null) {
|
||||
synchronized (sockLock) {
|
||||
if (ownDest) {
|
||||
|
@ -96,6 +96,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_AHELPER_CONFLICT =
|
||||
("HTTP/1.1 409 Conflict\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: Destination key conflict</H1>"+
|
||||
"The addresshelper link you followed specifies a different destination key "+
|
||||
"than a host entry in your host database. "+
|
||||
"Someone could be trying to impersonate another eepsite, "+
|
||||
"or people have given two eepsites identical names.<P/>"+
|
||||
"You can resolve the conflict by considering which key you trust, "+
|
||||
"and either discarding the addresshelper link, "+
|
||||
"discarding the host entry from your host database, "+
|
||||
"or naming one of them differently.<P/>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
@ -179,6 +195,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static final boolean DEFAULT_GZIP = true;
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
OutputStream out = null;
|
||||
@ -243,50 +261,102 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
// Quick hack for foo.bar.i2p
|
||||
if (host.toLowerCase().endsWith(".i2p")) {
|
||||
// Destination gets the host name
|
||||
destination = host;
|
||||
// Host becomes the destination key
|
||||
host = getHostName(destination);
|
||||
if ( (host != null) && ("i2p".equals(host)) ) {
|
||||
int pos2;
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
if (fragment.startsWith("i2paddresshelper")) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
if (pos2 >= 0) {
|
||||
addressHelpers.put(destination,fragment.substring(pos2 + 1));
|
||||
}
|
||||
} else {
|
||||
// append each fragment unless it's the address helper
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
}
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
ahelper = 1;
|
||||
int pos2;
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
String ahelperKey = null;
|
||||
boolean ahelperConflict = false;
|
||||
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
String initialFragments = fragments;
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
|
||||
// Fragment looks like addresshelper key
|
||||
if (fragment.startsWith("i2paddresshelper=")) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
ahelperKey = fragment.substring(pos2 + 1);
|
||||
|
||||
// Key contains data, lets not ignore it
|
||||
if (ahelperKey != null) {
|
||||
|
||||
// Host resolvable only with addresshelper
|
||||
if ( (host == null) || ("i2p".equals(host)) )
|
||||
{
|
||||
// Cannot check, use addresshelper key
|
||||
addressHelpers.put(destination,ahelperKey);
|
||||
} else {
|
||||
// Host resolvable from database, verify addresshelper key
|
||||
// Silently bypass correct keys, otherwise alert
|
||||
if (!host.equals(ahelperKey))
|
||||
{
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + host + "], specified key [" + ahelperKey + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other fragments, just pass along
|
||||
// Append each fragment to urlEncoding
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
|
||||
// Did addresshelper key conflict?
|
||||
if (ahelperConflict)
|
||||
{
|
||||
String str;
|
||||
byte[] header;
|
||||
str = FileUtil.readTextFile("docs/ahelper-conflict-header.ht", 100, true);
|
||||
if (str != null) header = str.getBytes();
|
||||
else header = ERR_AHELPER_CONFLICT;
|
||||
|
||||
if (out != null) {
|
||||
long alias = I2PAppContext.getGlobalContext().random().nextLong();
|
||||
String trustedURL = protocol + uriPath + urlEncoding;
|
||||
String conflictURL = protocol + alias + ".i2p/?" + initialFragments;
|
||||
out.write(header);
|
||||
out.write(("To visit the destination in your host database, click <a href=\"" + trustedURL + "\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"" + conflictURL + "\">here</a>.<P/>").getBytes());
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
ahelper = 1;
|
||||
}
|
||||
|
||||
line = method + " " + request.substring(pos);
|
||||
} else if (host.indexOf(".") != -1) {
|
||||
// The request must be forwarded to a WWW proxy
|
||||
@ -369,6 +439,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
|
||||
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
|
||||
boolean gzip = DEFAULT_GZIP;
|
||||
if (ok != null)
|
||||
gzip = Boolean.valueOf(ok).booleanValue();
|
||||
if (gzip)
|
||||
newRequest.append("Accept-Encoding: x-i2p-gzip\r\n");
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
|
@ -3,16 +3,13 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -30,12 +27,38 @@ import net.i2p.util.Log;
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
|
||||
private Log _log;
|
||||
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
|
||||
super(s, i2ps, slock, initialI2PData, sockList, onTimeout);
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class);
|
||||
}
|
||||
|
||||
protected OutputStream getSocketOut() throws IOException {
|
||||
OutputStream raw = super.getSocketOut();
|
||||
return new HTTPResponseOutputStream(raw);
|
||||
}
|
||||
|
||||
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
|
||||
try {
|
||||
i2pin.close();
|
||||
i2pout.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unable to close the i2p socket output stream: " + i2pout, ioe);
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unable to close the browser output stream: " + out, ioe);
|
||||
}
|
||||
i2ps.close();
|
||||
s.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,14 +3,13 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@ -24,7 +23,9 @@ import net.i2p.util.Log;
|
||||
/**
|
||||
* Simple extension to the I2PTunnelServer that filters the HTTP
|
||||
* headers sent from the client to the server, replacing the Host
|
||||
* header with whatever this instance has been configured with.
|
||||
* header with whatever this instance has been configured with, and
|
||||
* if the browser set Accept-encoding: x-i2p-gzip, gzip the http
|
||||
* message body and set Content-encoding: x-i2p-gzip.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
@ -60,14 +61,48 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
socket.setReadTimeout(readTimeout);
|
||||
String modifiedHeader = getModifiedHeader(socket);
|
||||
// give them 5 seconds to send in the HTTP request
|
||||
socket.setReadTimeout(5*1000);
|
||||
|
||||
InputStream in = socket.getInputStream();
|
||||
|
||||
StringBuffer command = new StringBuffer(128);
|
||||
Properties headers = readHeaders(in, command);
|
||||
if ( (_spoofHost != null) && (_spoofHost.trim().length() > 0) )
|
||||
headers.setProperty("Host", _spoofHost);
|
||||
headers.setProperty("Connection", "close");
|
||||
// we keep the enc sent by the browser before clobbering it, since it may have
|
||||
// been x-i2p-gzip
|
||||
String enc = headers.getProperty("Accept-encoding");
|
||||
headers.setProperty("Accept-encoding", "identity;q=1, *;q=0");
|
||||
String modifiedHeader = formatHeaders(headers, command);
|
||||
|
||||
//String modifiedHeader = getModifiedHeader(socket);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Modified header: [" + modifiedHeader + "]");
|
||||
|
||||
socket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = getTunnel().getContext().clock().now();
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
|
||||
// instead of i2ptunnelrunner, use something that reads the HTTP
|
||||
// request from the socket, modifies the headers, sends the request to the
|
||||
// server, reads the response headers, rewriting to include Content-encoding: x-i2p-gzip
|
||||
// if it was one of the Accept-encoding: values, and gzip the payload
|
||||
Properties opts = getTunnel().getClientOptions();
|
||||
boolean allowGZIP = true;
|
||||
if (opts != null) {
|
||||
String val = opts.getProperty("i2ptunnel.gzip");
|
||||
if ( (val != null) && (!Boolean.valueOf(val).booleanValue()) )
|
||||
allowGZIP = false;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("HTTP server encoding header: " + enc);
|
||||
if ( allowGZIP && (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) ) {
|
||||
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
|
||||
req.start();
|
||||
} else {
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
socket.close();
|
||||
@ -86,17 +121,119 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
|
||||
private String getModifiedHeader(I2PSocket handleSocket) throws IOException {
|
||||
InputStream in = handleSocket.getInputStream();
|
||||
|
||||
StringBuffer command = new StringBuffer(128);
|
||||
Properties headers = readHeaders(in, command);
|
||||
headers.setProperty("Host", _spoofHost);
|
||||
headers.setProperty("Connection", "close");
|
||||
return formatHeaders(headers, command);
|
||||
}
|
||||
|
||||
private class CompressedRequestor implements Runnable {
|
||||
private Socket _webserver;
|
||||
private I2PSocket _browser;
|
||||
private String _headers;
|
||||
public CompressedRequestor(Socket webserver, I2PSocket browser, String headers) {
|
||||
_webserver = webserver;
|
||||
_browser = browser;
|
||||
_headers = headers;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Compressed requestor running");
|
||||
OutputStream serverout = null;
|
||||
OutputStream browserout = null;
|
||||
InputStream browserin = null;
|
||||
InputStream serverin = null;
|
||||
try {
|
||||
serverout = _webserver.getOutputStream();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("request headers: " + _headers);
|
||||
serverout.write(_headers.getBytes());
|
||||
browserin = _browser.getInputStream();
|
||||
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), "http compressed sender");
|
||||
sender.start();
|
||||
|
||||
browserout = _browser.getOutputStream();
|
||||
serverin = _webserver.getInputStream();
|
||||
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
|
||||
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Before pumping the compressed response");
|
||||
s.run(); // same thread
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After pumping the compressed response: " + compressedOut.getTotalRead() + "/" + compressedOut.getTotalCompressed());
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("error compressing", ioe);
|
||||
} finally {
|
||||
if (browserout != null) try { browserout.close(); } catch (IOException ioe) {}
|
||||
if (serverout != null) try { serverout.close(); } catch (IOException ioe) {}
|
||||
if (browserin != null) try { browserin.close(); } catch (IOException ioe) {}
|
||||
if (serverin != null) try { serverin.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
private class Sender implements Runnable {
|
||||
private OutputStream _out;
|
||||
private InputStream _in;
|
||||
private String _name;
|
||||
public Sender(OutputStream out, InputStream in, String name) {
|
||||
_out = out;
|
||||
_in = in;
|
||||
_name = name;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": Begin sending");
|
||||
try {
|
||||
byte buf[] = new byte[16*1024];
|
||||
int read = 0;
|
||||
int total = 0;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": read " + read + " and sending through the stream");
|
||||
_out.write(buf, 0, read);
|
||||
total += read;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": Done sending: " + total);
|
||||
//_out.flush();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error sending", ioe);
|
||||
} finally {
|
||||
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
|
||||
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
private class CompressedResponseOutputStream extends HTTPResponseOutputStream {
|
||||
private InternalGZIPOutputStream _gzipOut;
|
||||
public CompressedResponseOutputStream(OutputStream o) {
|
||||
super(o);
|
||||
}
|
||||
|
||||
protected boolean shouldCompress() { return true; }
|
||||
protected void finishHeaders() throws IOException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Including x-i2p-gzip as the content encoding in the response");
|
||||
out.write("Content-encoding: x-i2p-gzip\n".getBytes());
|
||||
super.finishHeaders();
|
||||
}
|
||||
|
||||
protected void beginProcessing() throws IOException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Beginning compression processing");
|
||||
//out.flush();
|
||||
_gzipOut = new InternalGZIPOutputStream(out);
|
||||
out = _gzipOut;
|
||||
}
|
||||
public long getTotalRead() { return _gzipOut.getTotalRead(); }
|
||||
public long getTotalCompressed() { return _gzipOut.getTotalCompressed(); }
|
||||
}
|
||||
private class InternalGZIPOutputStream extends GZIPOutputStream {
|
||||
public InternalGZIPOutputStream(OutputStream target) throws IOException {
|
||||
super(target);
|
||||
}
|
||||
public long getTotalRead() { return super.def.getTotalIn(); }
|
||||
public long getTotalCompressed() { return super.def.getTotalOut(); }
|
||||
}
|
||||
|
||||
private String formatHeaders(Properties headers, StringBuffer command) {
|
||||
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
|
||||
buf.append(command.toString()).append('\n');
|
||||
@ -131,6 +268,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
|
||||
String name = buf.substring(0, split);
|
||||
String value = buf.substring(split+2); // ": "
|
||||
if ("Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "Accept-encoding";
|
||||
headers.setProperty(name, value);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the header [" + name + "] = [" + value + "]");
|
||||
|
@ -153,11 +153,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
onTimeout.run();
|
||||
}
|
||||
|
||||
// now one connection is dead - kill the other as well.
|
||||
s.close();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
// now one connection is dead - kill the other as well, after making sure we flush
|
||||
close(out, in, i2pout, i2pin, s, i2ps, t1, t2);
|
||||
} catch (InterruptedException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Interrupted", ex);
|
||||
@ -188,6 +185,27 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
}
|
||||
|
||||
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
i2pout.flush();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
in.close();
|
||||
i2pin.close();
|
||||
// ok, yeah, there's a race here in theory, if data comes in after flushing and before
|
||||
// closing, but its better than before...
|
||||
s.close();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
}
|
||||
|
||||
public void errorOccurred() {
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
|
@ -11,6 +11,7 @@ import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.ConnectException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
@ -39,6 +40,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
protected InetAddress remoteHost;
|
||||
protected int remotePort;
|
||||
private boolean _usePool;
|
||||
|
||||
private Logging l;
|
||||
|
||||
@ -46,15 +48,27 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
/** default timeout to 3 minutes - override if desired */
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
private static final boolean DEFAULT_USE_POOL = false;
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
init(host, port, bais, privData, l);
|
||||
}
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, File privkey, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
try {
|
||||
init(host, port, new FileInputStream(privkey), privkeyname, l);
|
||||
} catch (IOException ioe) {
|
||||
@ -65,6 +79,11 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
init(host, port, privData, privkeyname, l);
|
||||
}
|
||||
|
||||
@ -159,22 +178,53 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String PROP_HANDLER_COUNT = "i2ptunnel.blockingHandlerCount";
|
||||
private static final int DEFAULT_HANDLER_COUNT = 10;
|
||||
|
||||
protected int getHandlerCount() {
|
||||
int rv = DEFAULT_HANDLER_COUNT;
|
||||
String cnt = getTunnel().getClientOptions().getProperty(PROP_HANDLER_COUNT);
|
||||
if (cnt != null) {
|
||||
try {
|
||||
rv = Integer.parseInt(cnt);
|
||||
if (rv <= 0)
|
||||
rv = DEFAULT_HANDLER_COUNT;
|
||||
} catch (NumberFormatException nfe) {
|
||||
rv = DEFAULT_HANDLER_COUNT;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (shouldUsePool()) {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int handlers = getHandlerCount();
|
||||
for (int i = 0; i < handlers; i++) {
|
||||
I2PThread handler = new I2PThread(new Handler(i2pss), "Handle Server " + i);
|
||||
handler.start();
|
||||
}
|
||||
/*
|
||||
} else {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
while (true) {
|
||||
I2PSocket i2ps = i2pss.accept();
|
||||
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
|
||||
I2PThread t = new I2PThread(new Handler(i2ps));
|
||||
t.start();
|
||||
try {
|
||||
final I2PSocket i2ps = i2pss.accept();
|
||||
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
|
||||
new I2PThread(new Runnable() { public void run() { blockingHandle(i2ps); } }).start();
|
||||
} catch (I2PException ipe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error accepting - KILLING THE TUNNEL SERVER", ipe);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error accepting", ce);
|
||||
// not killing the server..
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldUsePool() { return _usePool; }
|
||||
|
||||
/**
|
||||
* minor thread pool to pull off the accept() concurrently. there are still lots
|
||||
|
@ -479,9 +479,12 @@ public class IndexBean {
|
||||
public void setStartOnLoad(String moo) {
|
||||
_startOnLoad = true;
|
||||
}
|
||||
public void setSharedClient(String moo) {
|
||||
public void setShared(String moo) {
|
||||
_sharedClient=true;
|
||||
}
|
||||
public void setShared(boolean val) {
|
||||
_sharedClient=val;
|
||||
}
|
||||
public void setConnectDelay(String moo) {
|
||||
_connectDelay = true;
|
||||
}
|
||||
|
@ -166,9 +166,9 @@ if (curTunnel >= 0) {
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.isSharedClient(curTunnel)) { %>
|
||||
<input type="checkbox" value="true" name="sharedClient" checked="true" />
|
||||
<input type="checkbox" value="true" name="shared" checked="true" />
|
||||
<% } else { %>
|
||||
<input type="checkbox" value="true" name="sharedClient" />
|
||||
<input type="checkbox" value="true" name="shared" />
|
||||
<% } %>
|
||||
<i>(Share tunnels with other clients and httpclients? Change requires restart of client proxy)</i>
|
||||
</td>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
@ -14,4 +14,4 @@
|
||||
<welcome-file>index.html</welcome-file>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
</web-app>
|
||||
</web-app>
|
||||
|
@ -16,6 +16,7 @@ import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.time.Timestamper;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
|
||||
/**
|
||||
* Handler to deal with form submissions from the main config form and act
|
||||
@ -27,12 +28,16 @@ public class ConfigNetHandler extends FormHandler {
|
||||
private boolean _guessRequested;
|
||||
private boolean _reseedRequested;
|
||||
private boolean _saveRequested;
|
||||
private boolean _recheckReachabilityRequested;
|
||||
private boolean _timeSyncEnabled;
|
||||
private boolean _requireIntroductions;
|
||||
private String _tcpPort;
|
||||
private String _udpPort;
|
||||
private String _inboundRate;
|
||||
private String _inboundBurstRate;
|
||||
private String _inboundBurst;
|
||||
private String _outboundRate;
|
||||
private String _outboundBurstRate;
|
||||
private String _outboundBurst;
|
||||
private String _reseedFrom;
|
||||
private String _sharePct;
|
||||
@ -44,6 +49,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
reseed();
|
||||
} else if (_saveRequested) {
|
||||
saveChanges();
|
||||
} else if (_recheckReachabilityRequested) {
|
||||
recheckReachability();
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
@ -53,6 +60,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setReseed(String moo) { _reseedRequested = true; }
|
||||
public void setSave(String moo) { _saveRequested = true; }
|
||||
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
|
||||
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
||||
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
_hostname = (hostname != null ? hostname.trim() : null);
|
||||
@ -66,12 +75,18 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setInboundrate(String rate) {
|
||||
_inboundRate = (rate != null ? rate.trim() : null);
|
||||
}
|
||||
public void setInboundburstrate(String rate) {
|
||||
_inboundBurstRate = (rate != null ? rate.trim() : null);
|
||||
}
|
||||
public void setInboundburstfactor(String factor) {
|
||||
_inboundBurst = (factor != null ? factor.trim() : null);
|
||||
}
|
||||
public void setOutboundrate(String rate) {
|
||||
_outboundRate = (rate != null ? rate.trim() : null);
|
||||
}
|
||||
public void setOutboundburstrate(String rate) {
|
||||
_outboundBurstRate = (rate != null ? rate.trim() : null);
|
||||
}
|
||||
public void setOutboundburstfactor(String factor) {
|
||||
_outboundBurst = (factor != null ? factor.trim() : null);
|
||||
}
|
||||
@ -195,6 +210,11 @@ public class ConfigNetHandler extends FormHandler {
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private void recheckReachability() {
|
||||
_context.commSystem().recheckReachability();
|
||||
addFormNotice("Rechecking router reachability...");
|
||||
}
|
||||
|
||||
/**
|
||||
* The user made changes to the network config and wants to save them, so
|
||||
* lets go ahead and do so.
|
||||
@ -244,7 +264,14 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (_timeSyncEnabled) {
|
||||
if (_requireIntroductions) {
|
||||
_context.router().setConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS, "true");
|
||||
addFormNotice("Requiring SSU introduers");
|
||||
} else {
|
||||
_context.router().removeConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS);
|
||||
}
|
||||
|
||||
if (true || _timeSyncEnabled) {
|
||||
// Time sync enable, means NOT disabled
|
||||
_context.router().setConfigSetting(Timestamper.PROP_DISABLED, "false");
|
||||
} else {
|
||||
@ -274,14 +301,22 @@ public class ConfigNetHandler extends FormHandler {
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_OUTBOUND_KBPS, _outboundRate);
|
||||
updated = true;
|
||||
}
|
||||
if ( (_inboundBurstRate != null) && (_inboundBurstRate.length() > 0) ) {
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_INBOUND_BURST_KBPS, _inboundBurstRate);
|
||||
updated = true;
|
||||
}
|
||||
if ( (_outboundBurstRate != null) && (_outboundBurstRate.length() > 0) ) {
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_OUTBOUND_BURST_KBPS, _outboundBurstRate);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
String inRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_INBOUND_KBPS);
|
||||
String inBurstRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_INBOUND_BURST_KBPS);
|
||||
|
||||
if (_inboundBurst != null) {
|
||||
int rateKBps = 0;
|
||||
int burstSeconds = 0;
|
||||
try {
|
||||
rateKBps = Integer.parseInt(inRate);
|
||||
rateKBps = Integer.parseInt(inBurstRate);
|
||||
burstSeconds = Integer.parseInt(_inboundBurst);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore
|
||||
@ -293,13 +328,13 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
}
|
||||
|
||||
String outRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_OUTBOUND_KBPS);
|
||||
String outBurstRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_OUTBOUND_BURST_KBPS);
|
||||
|
||||
if (_outboundBurst != null) {
|
||||
int rateKBps = 0;
|
||||
int burstSeconds = 0;
|
||||
try {
|
||||
rateKBps = Integer.parseInt(outRate);
|
||||
rateKBps = Integer.parseInt(outBurstRate);
|
||||
burstSeconds = Integer.parseInt(_outboundBurst);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore
|
||||
|
@ -2,6 +2,10 @@ package net.i2p.router.web;
|
||||
|
||||
import net.i2p.time.Timestamper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.router.transport.udp.UDPAddress;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
|
||||
public class ConfigNetHelper {
|
||||
private RouterContext _context;
|
||||
@ -43,19 +47,12 @@ public class ConfigNetHelper {
|
||||
return "" + port;
|
||||
}
|
||||
|
||||
public String getUdpPort() {
|
||||
int port = 8887;
|
||||
String val = _context.getProperty(PROP_I2NP_UDP_PORT);
|
||||
if (val == null)
|
||||
val = _context.getProperty(PROP_I2NP_INTERNAL_UDP_PORT);
|
||||
if (val != null) {
|
||||
try {
|
||||
port = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore, use default from above
|
||||
}
|
||||
}
|
||||
return "" + port;
|
||||
public String getUdpAddress() {
|
||||
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
|
||||
if (addr == null)
|
||||
return "unknown";
|
||||
UDPAddress ua = new UDPAddress(addr);
|
||||
return ua.toString();
|
||||
}
|
||||
|
||||
public String getEnableTimeSyncChecked() {
|
||||
@ -66,8 +63,29 @@ public class ConfigNetHelper {
|
||||
return " checked ";
|
||||
}
|
||||
|
||||
public String getRequireIntroductionsChecked() {
|
||||
short status = _context.commSystem().getReachabilityStatus();
|
||||
switch (status) {
|
||||
case CommSystemFacade.STATUS_OK:
|
||||
if ("true".equalsIgnoreCase(_context.getProperty(UDPTransport.PROP_FORCE_INTRODUCERS, "false")))
|
||||
return "checked=\"true\"";
|
||||
return "";
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
return "checked=\"true\"";
|
||||
case CommSystemFacade.STATUS_UNKNOWN:
|
||||
if ("true".equalsIgnoreCase(_context.getProperty(UDPTransport.PROP_FORCE_INTRODUCERS, "false")))
|
||||
return "checked=\"true\"";
|
||||
return "";
|
||||
default:
|
||||
return "checked=\"true\"";
|
||||
}
|
||||
}
|
||||
|
||||
public static final String PROP_INBOUND_KBPS = "i2np.bandwidth.inboundKBytesPerSecond";
|
||||
public static final String PROP_OUTBOUND_KBPS = "i2np.bandwidth.outboundKBytesPerSecond";
|
||||
public static final String PROP_INBOUND_BURST_KBPS = "i2np.bandwidth.inboundBurstKBytesPerSecond";
|
||||
public static final String PROP_OUTBOUND_BURST_KBPS = "i2np.bandwidth.outboundBurstKBytesPerSecond";
|
||||
public static final String PROP_INBOUND_BURST = "i2np.bandwidth.inboundBurstKBytes";
|
||||
public static final String PROP_OUTBOUND_BURST = "i2np.bandwidth.outboundBurstKBytes";
|
||||
public static final String PROP_SHARE_PERCENTAGE = "router.sharePercentage";
|
||||
@ -78,14 +96,28 @@ public class ConfigNetHelper {
|
||||
if (rate != null)
|
||||
return rate;
|
||||
else
|
||||
return "-1";
|
||||
return "16";
|
||||
}
|
||||
public String getOutboundRate() {
|
||||
String rate = _context.getProperty(PROP_OUTBOUND_KBPS);
|
||||
if (rate != null)
|
||||
return rate;
|
||||
else
|
||||
return "-1";
|
||||
return "16";
|
||||
}
|
||||
public String getInboundBurstRate() {
|
||||
String rate = _context.getProperty(PROP_INBOUND_BURST_KBPS);
|
||||
if (rate != null)
|
||||
return rate;
|
||||
else
|
||||
return "32";
|
||||
}
|
||||
public String getOutboundBurstRate() {
|
||||
String rate = _context.getProperty(PROP_OUTBOUND_BURST_KBPS);
|
||||
if (rate != null)
|
||||
return rate;
|
||||
else
|
||||
return "32";
|
||||
}
|
||||
public String getInboundBurstFactorBox() {
|
||||
String rate = _context.getProperty(PROP_INBOUND_KBPS);
|
||||
|
@ -20,6 +20,7 @@ public class FormHandler {
|
||||
protected Log _log;
|
||||
private String _nonce;
|
||||
protected String _action;
|
||||
protected String _passphrase;
|
||||
private List _errors;
|
||||
private List _notices;
|
||||
private boolean _processed;
|
||||
@ -32,6 +33,7 @@ public class FormHandler {
|
||||
_processed = false;
|
||||
_valid = true;
|
||||
_nonce = null;
|
||||
_passphrase = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,6 +53,7 @@ public class FormHandler {
|
||||
|
||||
public void setNonce(String val) { _nonce = val; }
|
||||
public void setAction(String val) { _action = val; }
|
||||
public void setPassphrase(String val) { _passphrase = val; }
|
||||
|
||||
/**
|
||||
* Override this to perform the final processing (in turn, adding formNotice
|
||||
@ -119,8 +122,14 @@ public class FormHandler {
|
||||
String noncePrev = System.getProperty(getClass().getName() + ".noncePrev");
|
||||
if ( ( (nonce == null) || (!_nonce.equals(nonce)) ) &&
|
||||
( (noncePrev == null) || (!_nonce.equals(noncePrev)) ) ) {
|
||||
addFormError("Invalid nonce, are you being spoofed?");
|
||||
_valid = false;
|
||||
|
||||
String expected = _context.getProperty("consolePassword");
|
||||
if ( (expected != null) && (expected.trim().length() > 0) && (expected.equals(_passphrase)) ) {
|
||||
// ok
|
||||
} else {
|
||||
addFormError("Invalid nonce, are you being spoofed?");
|
||||
_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +198,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
// ignore
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
|
||||
|
||||
@ -224,4 +224,5 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
File temp = new File(TEMP_NEWS_FILE);
|
||||
temp.delete();
|
||||
}
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
}
|
||||
|
@ -28,14 +28,19 @@ public class ReseedHandler {
|
||||
if (nonce == null) return;
|
||||
if (nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.nonce")) ||
|
||||
nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.noncePrev"))) {
|
||||
synchronized (_reseedRunner) {
|
||||
if (_reseedRunner.isRunning()) {
|
||||
return;
|
||||
} else {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
|
||||
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
|
||||
reseed.start();
|
||||
}
|
||||
requestReseed();
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestReseed() {
|
||||
synchronized (_reseedRunner) {
|
||||
if (_reseedRunner.isRunning()) {
|
||||
return;
|
||||
} else {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
|
||||
System.out.println("Reseeding");
|
||||
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
|
||||
reseed.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,7 +51,8 @@ public class ReseedHandler {
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
reseed();
|
||||
reseed(false);
|
||||
System.out.println("Reseeding complete");
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false");
|
||||
_isRunning = false;
|
||||
}
|
||||
@ -59,7 +65,7 @@ public class ReseedHandler {
|
||||
* save them into this router's netDb dir.
|
||||
*
|
||||
*/
|
||||
private static void reseed() {
|
||||
private static void reseed(boolean echoStatus) {
|
||||
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
|
||||
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
|
||||
seedURL = DEFAULT_SEED_URL;
|
||||
@ -85,10 +91,16 @@ public class ReseedHandler {
|
||||
try {
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
if (echoStatus) {
|
||||
System.out.print(".");
|
||||
if (fetched % 60 == 0)
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if (echoStatus) System.out.println();
|
||||
} catch (Throwable t) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class).error("Error reseeding", t);
|
||||
}
|
||||
@ -172,7 +184,11 @@ public class ReseedHandler {
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
reseed();
|
||||
//System.out.println("Done reseeding");
|
||||
if ( (args != null) && (args.length == 1) && (!Boolean.valueOf(args[0]).booleanValue()) ) {
|
||||
System.out.println("Not reseeding, as requested");
|
||||
return; // not reseeding on request
|
||||
}
|
||||
System.out.println("Reseeding");
|
||||
reseed(true);
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,24 @@ public class RouterConsoleRunner {
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
// we check the i2p installation directory (.) for a flag telling us not to reseed,
|
||||
// but also check the home directory for that flag too, since new users installing i2p
|
||||
// don't have an installation directory that they can put the flag in yet.
|
||||
File noReseedFile = new File(new File(System.getProperty("user.home")), ".i2pnoreseed");
|
||||
File noReseedFileAlt1 = new File(new File(System.getProperty("user.home")), "noreseed.i2p");
|
||||
File noReseedFileAlt2 = new File(".i2pnoreseed");
|
||||
File noReseedFileAlt3 = new File("noreseed.i2p");
|
||||
if (!noReseedFile.exists() && !noReseedFileAlt1.exists() && !noReseedFileAlt2.exists() && !noReseedFileAlt3.exists()) {
|
||||
File netDb = new File("netDb");
|
||||
// sure, some of them could be "my.info" or various leaseSet- files, but chances are,
|
||||
// if someone has those files, they've already been seeded (at least enough to let them
|
||||
// get i2p started - they can reseed later in the web console)
|
||||
String names[] = (netDb.exists() ? netDb.list() : null);
|
||||
if ( (names == null) || (names.length < 15) ) {
|
||||
ReseedHandler.requestReseed();
|
||||
}
|
||||
}
|
||||
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
|
||||
I2PThread t = new I2PThread(fetcher, "NewsFetcher");
|
||||
|
@ -12,6 +12,7 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
@ -94,7 +95,24 @@ public class SummaryHelper {
|
||||
}
|
||||
|
||||
public boolean allowReseed() {
|
||||
return (_context.netDb().getKnownRouters() < 10);
|
||||
return (_context.netDb().getKnownRouters() < 30);
|
||||
}
|
||||
|
||||
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
|
||||
|
||||
public String getReachability() {
|
||||
int status = _context.commSystem().getReachabilityStatus();
|
||||
switch (status) {
|
||||
case CommSystemFacade.STATUS_OK:
|
||||
return "OK";
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
return "ERR-SymmetricNAT";
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
return "OK (NAT)";
|
||||
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,14 +205,9 @@ public class SummaryHelper {
|
||||
public String getInboundMinuteKBps() {
|
||||
if (_context == null)
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
|
||||
Rate rate = receiveRate.getRate(60*1000);
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(bps);
|
||||
double kbps = _context.bandwidthLimiter().getReceiveBps()/1024d;
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(kbps);
|
||||
}
|
||||
/**
|
||||
* How fast we have been sending data over the last minute (pretty printed
|
||||
@ -204,14 +217,9 @@ public class SummaryHelper {
|
||||
public String getOutboundMinuteKBps() {
|
||||
if (_context == null)
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
|
||||
Rate rate = receiveRate.getRate(60*1000);
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(bps);
|
||||
double kbps = _context.bandwidthLimiter().getSendBps()/1024d;
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(kbps);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,13 +231,12 @@ public class SummaryHelper {
|
||||
if (_context == null)
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
|
||||
RateStat receiveRate = _context.statManager().getRate("bw.recvRate");
|
||||
if (receiveRate == null) return "0.0";
|
||||
Rate rate = receiveRate.getRate(5*60*1000);
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(bps);
|
||||
double kbps = rate.getAverageValue()/1024;
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(kbps);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,13 +248,12 @@ public class SummaryHelper {
|
||||
if (_context == null)
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
|
||||
RateStat receiveRate = _context.statManager().getRate("bw.sendRate");
|
||||
if (receiveRate == null) return "0.0";
|
||||
Rate rate = receiveRate.getRate(5*60*1000);
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(bps);
|
||||
double kbps = rate.getAverageValue()/1024;
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
return fmt.format(kbps);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -259,20 +265,11 @@ public class SummaryHelper {
|
||||
if (_context == null)
|
||||
return "0.0";
|
||||
|
||||
long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes();
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("bw.recvRate");
|
||||
if (receiveRate == null) return "0.0";
|
||||
double kbps = receiveRate.getLifetimeAverageValue()/1024;
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
|
||||
// we use the unadjusted time, since thats what getWhenStarted is based off
|
||||
long lifetime = _context.clock().now()-_context.clock().getOffset()
|
||||
- _context.router().getWhenStarted();
|
||||
lifetime /= 1000;
|
||||
if (received > 0) {
|
||||
double receivedKBps = received / (lifetime*1024.0);
|
||||
return fmt.format(receivedKBps);
|
||||
} else {
|
||||
return "0.0";
|
||||
}
|
||||
return fmt.format(kbps);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,20 +281,11 @@ public class SummaryHelper {
|
||||
if (_context == null)
|
||||
return "0.0";
|
||||
|
||||
long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes();
|
||||
|
||||
RateStat sendRate = _context.statManager().getRate("bw.sendRate");
|
||||
if (sendRate == null) return "0.0";
|
||||
double kbps = sendRate.getLifetimeAverageValue()/1024;
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
|
||||
// we use the unadjusted time, since thats what getWhenStarted is based off
|
||||
long lifetime = _context.clock().now()-_context.clock().getOffset()
|
||||
- _context.router().getWhenStarted();
|
||||
lifetime /= 1000;
|
||||
if (sent > 0) {
|
||||
double sendKBps = sent / (lifetime*1024.0);
|
||||
return fmt.format(sendKBps);
|
||||
} else {
|
||||
return "0.0";
|
||||
}
|
||||
return fmt.format(kbps);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,6 +3,7 @@ package net.i2p.router.web;
|
||||
import java.io.File;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
@ -93,7 +94,7 @@ public class UpdateHandler {
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
update();
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.updateInProgress", "false");
|
||||
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
|
||||
_isRunning = false;
|
||||
}
|
||||
private void update() {
|
||||
@ -143,7 +144,7 @@ public class UpdateHandler {
|
||||
buf.append(" transferred<br />");
|
||||
_status = buf.toString();
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
_status = "<b>Update downloaded</b><br />";
|
||||
TrustedUpdate up = new TrustedUpdate(_context);
|
||||
boolean ok = up.migrateVerified(RouterVersion.VERSION, SIGNED_UPDATE_FILE, "i2pupdate.zip");
|
||||
@ -165,6 +166,7 @@ public class UpdateHandler {
|
||||
_status = "<b>Transfer failed</b><br />";
|
||||
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
|
||||
}
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2P Router Console - logs</title>
|
||||
<title>I2P Router Console - config networking</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
@ -28,58 +28,38 @@
|
||||
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigNetHandler.nonce")%>" />
|
||||
<input type="hidden" name="action" value="blah" />
|
||||
|
||||
UDP port: <i><jsp:getProperty name="nethelper" property="udpPort" /></i><br />
|
||||
<!-- <input name="udpPort" type="text" size="5" value="<jsp:getProperty name="nethelper" property="udpPort" />" /><br /> -->
|
||||
<b>You must poke a hole in your firewall or NAT (if applicable) to receive new inbound UDP packets on
|
||||
this port from arbitrary peers (this requirement will be removed in i2p 0.6.1, but is necessary now)</b><br />
|
||||
TCP port: <input name="tcpPort" type="text" size="5" value="<jsp:getProperty name="nethelper" property="tcpPort" />" /> <br />
|
||||
<b>You must poke a hole in your firewall or NAT (if applicable) so that you can receive inbound TCP
|
||||
connections on it (this requirement will be removed in i2p 0.6.1, but is necessary now)</b>
|
||||
<b>External UDP address:</b> <i><jsp:getProperty name="nethelper" property="udpAddress" /></i><br />
|
||||
<b>Require SSU introductions? </b>
|
||||
<input type="checkbox" name="requireIntroductions" value="true" <jsp:getProperty name="nethelper" property="requireIntroductionsChecked" /> /><br />
|
||||
<p>If you can, please poke a hole in your NAT or firewall to allow unsolicited UDP packets to reach
|
||||
you on your external UDP address. If you can't, I2P now includes supports UDP hole punching
|
||||
with "SSU introductions" - peers who will relay a request from someone you don't know to your
|
||||
router for your router so that you can make an outbound connection to them. I2P will use these
|
||||
introductions automatically if it detects that the port is not forwarded (as shown by
|
||||
the <i>Status: OK (NAT)</i> line), or you can manually require them here.
|
||||
Users behind symmetric NATs, such as OpenBSD's pf, are not currently supported.</p>
|
||||
<input type="submit" name="recheckReachability" value="Check network reachability..." />
|
||||
<hr />
|
||||
|
||||
<b>Bandwidth limiter</b><br />
|
||||
Inbound rate:
|
||||
<input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBytes per second
|
||||
<input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBps
|
||||
bursting up to
|
||||
<input name="inboundburstrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundBurstRate" />" /> KBps for
|
||||
<jsp:getProperty name="nethelper" property="inboundBurstFactorBox" /><br />
|
||||
Outbound rate:
|
||||
<input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBytes per second
|
||||
<input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBps
|
||||
bursting up to
|
||||
<input name="outboundburstrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundBurstRate" />" /> KBps for
|
||||
<jsp:getProperty name="nethelper" property="outboundBurstFactorBox" /><br />
|
||||
<i>A negative rate means there is no limit</i><br />
|
||||
<i>KBps = kilobytes per second = 1024 bytes per second.<br />
|
||||
A negative rate means a default limit of 16KBytes per second.</i><br />
|
||||
Bandwidth share percentage:
|
||||
<jsp:getProperty name="nethelper" property="sharePercentageBox" /><br />
|
||||
Sharing a higher percentage will improve your anonymity and help the network
|
||||
<hr />
|
||||
Enable internal time synchronization? <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
|
||||
<i>If disabled, your machine <b>must</b> be NTP synchronized - your clock must always
|
||||
be within a few seconds of "correct". You will need to be able to send outbound UDP
|
||||
packets on port 123 to one of the pool.ntp.org machines (or some other SNTP server).</i>
|
||||
<hr />
|
||||
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
|
||||
<i>Changing the TCP or UDP port will force a 'soft restart' - dropping your connections and clients as
|
||||
if the router was stopped and restarted. <b>Please be patient</b> - it may take
|
||||
a few seconds to complete.</i>
|
||||
</form>
|
||||
<hr />
|
||||
<b>Advanced network config:</b>
|
||||
<p>
|
||||
One advanced network option has to do with reseeding - you should never need to
|
||||
reseed your router as long as you can find at least one other peer on the network. However,
|
||||
when you do need to reseed, a link will show up on the left hand side which will
|
||||
fetch all of the routerInfo-* files from http://dev.i2p.net/i2pdb/. That URL is just an
|
||||
apache folder pointing at the netDb/ directory of a router - anyone can run one, and you can
|
||||
configure your router to seed off an alternate URL by adding the java environmental property
|
||||
"i2p.reseedURL=someURL" (e.g. java -Di2p.reseedURL=http://dev.i2p.net/i2pdb/ ...). You can
|
||||
also do it manually by getting routerInfo-*.dat files from someone (a friend, someone on IRC,
|
||||
whatever) and saving them to your netDb/ directory.</p>
|
||||
<p>
|
||||
With the SSU transport, the internal UDP port may be different from the external
|
||||
UDP port (in case of a firewall/NAT) - the UDP port field above specifies the
|
||||
external one and assumes they are the same, but if you want to set the internal
|
||||
port to something else, you can add "i2np.udp.internalPort=1234" to the
|
||||
<a href="configadvanced.jsp">advanced</a> config and restart the router.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2P Router Console - config clients</title>
|
||||
<title>I2P Router Console - config logging</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
</head><body>
|
||||
<jsp:useBean class="net.i2p.router.web.ConfigLoggingHelper" id="logginghelper" scope="request" />
|
||||
|
@ -3,7 +3,7 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2P Router Console - config clients</title>
|
||||
<title>I2P Router Console - config service</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
|
@ -45,7 +45,7 @@ more information).</p>
|
||||
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,
|
||||
which other client applications (such the <a href="http://duck.i2p/i2p-bt/">bittorrent port</a>) can use.
|
||||
There is also an optimized library for doing large number calculations - jbigi - which in turn uses the
|
||||
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. For
|
||||
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. Launchers for windows users are built with <a href="http://launch4j.sourceforge.net/">Launch4J</a>, and the installer is built with <a href="http://www.izforge.com/izpack/">IzPack</a>. For
|
||||
details on other applications available, as well as their licenses, please see the
|
||||
<a href="http://www.i2p.net/licenses">license policy</a>. Source for the I2P code and most bundled
|
||||
client applications can be found on our <a href="http://www.i2p.net/download">download page</a>, and is
|
||||
|
@ -15,14 +15,16 @@
|
||||
</div>
|
||||
|
||||
<h4>
|
||||
<a href="susimail/susimail">Susimail</a> |
|
||||
<a href="susidns/">SusiDNS</a> |
|
||||
<a href="syndie/">Syndie</a> |
|
||||
<a href="i2ptunnel/">I2PTunnel</a> |
|
||||
<a href="tunnels.jsp">Tunnels</a> |
|
||||
<a href="profiles.jsp">Profiles</a> |
|
||||
<a href="netdb.jsp">NetDB</a> |
|
||||
<a href="logs.jsp">Logs</a> |
|
||||
<a href="oldconsole.jsp">Internals</a> |
|
||||
<a href="oldstats.jsp">Stats</a> |
|
||||
<a href="i2ptunnel/" target="_blank">I2PTunnel</a> |
|
||||
<a href="susimail/susimail" target="_blank">Susimail</a>
|
||||
<a href="oldconsole.jsp">Internals</a>
|
||||
<jsp:useBean class="net.i2p.router.web.NavHelper" id="navhelper" scope="request" />
|
||||
<jsp:setProperty name="navhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:getProperty name="navhelper" property="clientAppLinks" />
|
||||
|
@ -3,7 +3,7 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2P Router Console - home</title>
|
||||
<title>I2P Router Console - internals</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2P Router Console - home</title>
|
||||
<title>I2P Router Console - statistics</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
|
@ -14,7 +14,8 @@
|
||||
<b>Version:</b> <jsp:getProperty name="helper" property="version" /><br />
|
||||
<b>Uptime:</b> <jsp:getProperty name="helper" property="uptime" /><br />
|
||||
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><br />
|
||||
<b>Memory:</b> <jsp:getProperty name="helper" property="memory" /><br /><%
|
||||
<b>Memory:</b> <jsp:getProperty name="helper" property="memory" /><br />
|
||||
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a><br /><%
|
||||
if (helper.updateAvailable()) {
|
||||
if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) {
|
||||
out.print(update.getStatus());
|
||||
@ -39,7 +40,8 @@
|
||||
<b>High capacity:</b> <jsp:getProperty name="helper" property="highCapacityPeers" /><br />
|
||||
<b>Well integrated:</b> <jsp:getProperty name="helper" property="wellIntegratedPeers" /><br />
|
||||
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
|
||||
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /><%
|
||||
<!-- <b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /> -->
|
||||
<b>Known:</b> <jsp:getProperty name="helper" property="allPeers" /><br /><%
|
||||
if (helper.getActivePeers() <= 0) {
|
||||
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
|
||||
}
|
||||
@ -62,7 +64,7 @@
|
||||
%><hr />
|
||||
|
||||
<u><b>Bandwidth in/out</b></u><br />
|
||||
<b>1m:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />
|
||||
<b>1s:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />
|
||||
<b>5m:</b> <jsp:getProperty name="helper" property="inboundFiveMinuteKBps" />/<jsp:getProperty name="helper" property="outboundFiveMinuteKBps" />KBps<br />
|
||||
<b>Total:</b> <jsp:getProperty name="helper" property="inboundLifetimeKBps" />/<jsp:getProperty name="helper" property="outboundLifetimeKBps" />KBps<br />
|
||||
<b>Used:</b> <jsp:getProperty name="helper" property="inboundTransferred" />/<jsp:getProperty name="helper" property="outboundTransferred" /><br />
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
@ -14,4 +14,4 @@
|
||||
<welcome-file>index.html</welcome-file>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
</web-app>
|
||||
</web-app>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<target name="bin" description="Builds assemblies from source">
|
||||
<mkdir dir="bin" />
|
||||
<csc target="dll" output="bin/sam-sharp.dll">
|
||||
<csc target="library" output="bin/sam-sharp.dll">
|
||||
<sources>
|
||||
<include name="src/**/*.cs" />
|
||||
</sources>
|
||||
|
@ -24,8 +24,8 @@ public class Connection {
|
||||
private Log _log;
|
||||
private ConnectionManager _connectionManager;
|
||||
private Destination _remotePeer;
|
||||
private byte _sendStreamId[];
|
||||
private byte _receiveStreamId[];
|
||||
private long _sendStreamId;
|
||||
private long _receiveStreamId;
|
||||
private long _lastSendTime;
|
||||
private long _lastSendId;
|
||||
private boolean _resetReceived;
|
||||
@ -72,7 +72,7 @@ public class Connection {
|
||||
private long _lifetimeDupMessageSent;
|
||||
private long _lifetimeDupMessageReceived;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 10*1000;
|
||||
public static final long MAX_RESEND_DELAY = 8*1000;
|
||||
public static final long MIN_RESEND_DELAY = 3*1000;
|
||||
|
||||
/** wait up to 5 minutes after disconnection so we can ack/close packets */
|
||||
@ -102,7 +102,7 @@ public class Connection {
|
||||
_closeSentOn = -1;
|
||||
_closeReceivedOn = -1;
|
||||
_unackedPacketsReceived = 0;
|
||||
_congestionWindowEnd = 0;
|
||||
_congestionWindowEnd = _options.getWindowSize()-1;
|
||||
_highestAckedThrough = -1;
|
||||
_lastCongestionSeenAt = MAX_WINDOW_SIZE*2; // lets allow it to grow
|
||||
_lastCongestionTime = -1;
|
||||
@ -153,8 +153,12 @@ public class Connection {
|
||||
synchronized (_outboundPackets) {
|
||||
if (!started)
|
||||
_context.statManager().addRateData("stream.chokeSizeBegin", _outboundPackets.size(), timeoutMs);
|
||||
if (!_connected)
|
||||
return false;
|
||||
|
||||
// no need to wait until the other side has ACKed us before sending the first few wsize
|
||||
// packets through
|
||||
// if (!_connected)
|
||||
// return false;
|
||||
|
||||
started = true;
|
||||
if ( (_outboundPackets.size() >= _options.getWindowSize()) || (_activeResends > 0) ||
|
||||
(_lastSendId - _highestAckedThrough > _options.getWindowSize()) ) {
|
||||
@ -205,7 +209,7 @@ public class Connection {
|
||||
_resetSent = true;
|
||||
if (_resetSentOn <= 0)
|
||||
_resetSentOn = _context.clock().now();
|
||||
if ( (_remotePeer == null) || (_sendStreamId == null) ) return;
|
||||
if ( (_remotePeer == null) || (_sendStreamId <= 0) ) return;
|
||||
PacketLocal reply = new PacketLocal(_context, _remotePeer);
|
||||
reply.setFlag(Packet.FLAG_RESET);
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
@ -256,14 +260,18 @@ public class Connection {
|
||||
remaining = 0;
|
||||
if (packet.isFlagSet(Packet.FLAG_CLOSE) || (remaining < 2)) {
|
||||
packet.setOptionalDelay(0);
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
} else {
|
||||
int delay = _options.getRTT() / 2;
|
||||
int delay = _options.getRTO() / 2;
|
||||
packet.setOptionalDelay(delay);
|
||||
_log.debug("Requesting ack delay of " + delay + "ms for packet " + packet);
|
||||
if (delay > 0)
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Requesting ack delay of " + delay + "ms for packet " + packet);
|
||||
}
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
|
||||
long timeout = _options.getRTT() + MIN_RESEND_DELAY;
|
||||
long timeout = _options.getRTO();
|
||||
if (timeout > MAX_RESEND_DELAY)
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -272,10 +280,13 @@ public class Connection {
|
||||
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet), timeout);
|
||||
}
|
||||
|
||||
_context.statManager().getStatLog().addData(Packet.toId(_sendStreamId), "stream.rtt", _options.getRTT(), _options.getWindowSize());
|
||||
|
||||
_lastSendTime = _context.clock().now();
|
||||
_outboundQueue.enqueue(packet);
|
||||
resetActivityTimer();
|
||||
|
||||
/*
|
||||
if (ackOnly) {
|
||||
// ACK only, don't schedule this packet for retries
|
||||
// however, if we are running low on sessionTags we want to send
|
||||
@ -286,6 +297,7 @@ public class Connection {
|
||||
_connectionManager.ping(_remotePeer, _options.getRTT()*2, false, packet.getKeyUsed(), packet.getTagsSent(), new PingNotifier());
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private class PingNotifier implements ConnectionManager.PingNotifier {
|
||||
@ -303,16 +315,20 @@ public class Connection {
|
||||
}
|
||||
|
||||
List ackPackets(long ackThrough, long nacks[]) {
|
||||
if (nacks == null) {
|
||||
_highestAckedThrough = ackThrough;
|
||||
if (ackThrough < _highestAckedThrough) {
|
||||
// dupack which won't tell us anything
|
||||
} else {
|
||||
long lowest = -1;
|
||||
for (int i = 0; i < nacks.length; i++) {
|
||||
if ( (lowest < 0) || (nacks[i] < lowest) )
|
||||
lowest = nacks[i];
|
||||
if (nacks == null) {
|
||||
_highestAckedThrough = ackThrough;
|
||||
} else {
|
||||
long lowest = -1;
|
||||
for (int i = 0; i < nacks.length; i++) {
|
||||
if ( (lowest < 0) || (nacks[i] < lowest) )
|
||||
lowest = nacks[i];
|
||||
}
|
||||
if (lowest - 1 > _highestAckedThrough)
|
||||
_highestAckedThrough = lowest - 1;
|
||||
}
|
||||
if (lowest - 1 > _highestAckedThrough)
|
||||
_highestAckedThrough = lowest - 1;
|
||||
}
|
||||
|
||||
List acked = null;
|
||||
@ -459,7 +475,9 @@ public class Connection {
|
||||
_receiver.destroy();
|
||||
if (_activityTimer != null)
|
||||
SimpleTimer.getInstance().removeEvent(_activityTimer);
|
||||
_activityTimer = null;
|
||||
//_activityTimer = null;
|
||||
if (_inputStream != null)
|
||||
_inputStream.streamErrorOccurred(new IOException("disconnected!"));
|
||||
|
||||
if (_disconnectScheduledOn < 0) {
|
||||
_disconnectScheduledOn = _context.clock().now();
|
||||
@ -510,17 +528,30 @@ public class Connection {
|
||||
synchronized (_connectLock) { _connectLock.notifyAll(); }
|
||||
}
|
||||
|
||||
private boolean _remotePeerSet = false;
|
||||
/** who are we talking with */
|
||||
public Destination getRemotePeer() { return _remotePeer; }
|
||||
public void setRemotePeer(Destination peer) { _remotePeer = peer; }
|
||||
public void setRemotePeer(Destination peer) {
|
||||
if (_remotePeerSet) throw new RuntimeException("Remote peer already set [" + _remotePeer + ", " + peer + "]");
|
||||
_remotePeerSet = true;
|
||||
_remotePeer = peer;
|
||||
}
|
||||
|
||||
private boolean _sendStreamIdSet = false;
|
||||
/** what stream do we send data to the peer on? */
|
||||
public byte[] getSendStreamId() { return _sendStreamId; }
|
||||
public void setSendStreamId(byte[] id) { _sendStreamId = id; }
|
||||
public long getSendStreamId() { return _sendStreamId; }
|
||||
public void setSendStreamId(long id) {
|
||||
if (_sendStreamIdSet) throw new RuntimeException("Send stream ID already set [" + _sendStreamId + ", " + id + "]");
|
||||
_sendStreamIdSet = true;
|
||||
_sendStreamId = id;
|
||||
}
|
||||
|
||||
private boolean _receiveStreamIdSet = false;
|
||||
/** stream the peer sends data to us on. (may be null) */
|
||||
public byte[] getReceiveStreamId() { return _receiveStreamId; }
|
||||
public void setReceiveStreamId(byte[] id) {
|
||||
public long getReceiveStreamId() { return _receiveStreamId; }
|
||||
public void setReceiveStreamId(long id) {
|
||||
if (_receiveStreamIdSet) throw new RuntimeException("Receive stream ID already set [" + _receiveStreamId + ", " + id + "]");
|
||||
_receiveStreamIdSet = true;
|
||||
_receiveStreamId = id;
|
||||
synchronized (_connectLock) { _connectLock.notifyAll(); }
|
||||
}
|
||||
@ -647,7 +678,7 @@ public class Connection {
|
||||
void waitForConnect() {
|
||||
long expiration = _context.clock().now() + _options.getConnectTimeout();
|
||||
while (true) {
|
||||
if (_connected && (_receiveStreamId != null) && (_sendStreamId != null) ) {
|
||||
if (_connected && (_receiveStreamId > 0) && (_sendStreamId > 0) ) {
|
||||
// w00t
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("waitForConnect(): Connected and we have stream IDs");
|
||||
@ -691,11 +722,19 @@ public class Connection {
|
||||
}
|
||||
|
||||
private void resetActivityTimer() {
|
||||
if (_options.getInactivityTimeout() <= 0) return;
|
||||
if (_activityTimer == null) return;
|
||||
if (_options.getInactivityTimeout() <= 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resetting the inactivity timer, but its gone!", new Exception("where did it go?"));
|
||||
return;
|
||||
}
|
||||
if (_activityTimer == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resetting the inactivity timer, but its gone!", new Exception("where did it go?"));
|
||||
return;
|
||||
}
|
||||
long howLong = _activityTimer.getTimeLeft();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resetting the inactivity timer to " + howLong);
|
||||
_log.debug("Resetting the inactivity timer to " + howLong, new Exception("Reset by"));
|
||||
// this will get rescheduled, and rescheduled, and rescheduled...
|
||||
SimpleTimer.getInstance().addEvent(_activityTimer, howLong);
|
||||
}
|
||||
@ -703,15 +742,34 @@ public class Connection {
|
||||
private class ActivityTimer implements SimpleTimer.TimedEvent {
|
||||
public void timeReached() {
|
||||
// uh, nothing more to do...
|
||||
if (!_connected) return;
|
||||
if (!_connected) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but we are already closed");
|
||||
return;
|
||||
}
|
||||
// we got rescheduled already
|
||||
if (getTimeLeft() > 0) return;
|
||||
long left = getTimeLeft();
|
||||
if (left > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is time left (" + left + ")");
|
||||
SimpleTimer.getInstance().addEvent(ActivityTimer.this, left);
|
||||
return;
|
||||
}
|
||||
// these are either going to time out or cause further rescheduling
|
||||
if (getUnackedPacketsSent() > 0) return;
|
||||
if (getUnackedPacketsSent() > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there are unacked packets");
|
||||
return;
|
||||
}
|
||||
// wtf, this shouldn't have been scheduled
|
||||
if (_options.getInactivityTimeout() <= 0) return;
|
||||
if (_options.getInactivityTimeout() <= 0) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is no timer...");
|
||||
return;
|
||||
}
|
||||
// if one of us can't talk...
|
||||
if ( (_closeSentOn > 0) || (_closeReceivedOn > 0) ) return;
|
||||
if ( (_closeSentOn > 0) || (_closeReceivedOn > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but we are closing");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, with action=" + _options.getInactivityAction());
|
||||
|
||||
// bugger it, might as well do the hard work now
|
||||
switch (_options.getInactivityAction()) {
|
||||
@ -737,7 +795,9 @@ public class Connection {
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
|
||||
disconnect(true);
|
||||
_inputStream.streamErrorOccurred(new IOException("Inactivity timeout"));
|
||||
_outputStream.streamErrorOccurred(new IOException("Inactivity timeout"));
|
||||
disconnect(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -758,18 +818,19 @@ public class Connection {
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("[Connection ");
|
||||
if (_receiveStreamId != null)
|
||||
buf.append(Base64.encode(_receiveStreamId));
|
||||
if (_receiveStreamId > 0)
|
||||
buf.append(Packet.toId(_receiveStreamId));
|
||||
else
|
||||
buf.append("unknown");
|
||||
buf.append("<-->");
|
||||
if (_sendStreamId != null)
|
||||
buf.append(Base64.encode(_sendStreamId));
|
||||
if (_sendStreamId > 0)
|
||||
buf.append(Packet.toId(_sendStreamId));
|
||||
else
|
||||
buf.append("unknown");
|
||||
buf.append(" wsize: ").append(_options.getWindowSize());
|
||||
buf.append(" cwin: ").append(_congestionWindowEnd - _highestAckedThrough);
|
||||
buf.append(" rtt: ").append(_options.getRTT());
|
||||
buf.append(" rto: ").append(_options.getRTO());
|
||||
// not synchronized to avoid some kooky races
|
||||
buf.append(" unacked outbound: ").append(_outboundPackets.size()).append(" ");
|
||||
/*
|
||||
@ -873,11 +934,16 @@ public class Connection {
|
||||
}
|
||||
// revamp various fields, in case we need to ack more, etc
|
||||
_inputStream.updateAcks(_packet);
|
||||
_packet.setOptionalDelay(getOptions().getChoke());
|
||||
int choke = getOptions().getChoke();
|
||||
_packet.setOptionalDelay(choke);
|
||||
if (choke > 0)
|
||||
_packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
_packet.setOptionalMaxSize(getOptions().getMaxMessageSize());
|
||||
_packet.setResendDelay(getOptions().getResendDelay());
|
||||
_packet.setReceiveStreamId(_receiveStreamId);
|
||||
_packet.setSendStreamId(_sendStreamId);
|
||||
if (_packet.getReceiveStreamId() <= 0)
|
||||
_packet.setReceiveStreamId(_receiveStreamId);
|
||||
if (_packet.getSendStreamId() <= 0)
|
||||
_packet.setSendStreamId(_sendStreamId);
|
||||
|
||||
int newWindowSize = getOptions().getWindowSize();
|
||||
|
||||
@ -946,10 +1012,10 @@ public class Connection {
|
||||
disconnect(false);
|
||||
} else {
|
||||
//long timeout = _options.getResendDelay() << numSends;
|
||||
long rtt = _options.getRTT();
|
||||
if (rtt < MIN_RESEND_DELAY)
|
||||
rtt = MIN_RESEND_DELAY;
|
||||
long timeout = rtt << (numSends-1);
|
||||
long rto = _options.getRTO();
|
||||
if (rto < MIN_RESEND_DELAY)
|
||||
rto = MIN_RESEND_DELAY;
|
||||
long timeout = rto << (numSends-1);
|
||||
if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) )
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
|
@ -2,6 +2,7 @@ package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -142,15 +143,18 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
data.setValid(size);
|
||||
data.setOffset(0);
|
||||
packet.setPayload(data);
|
||||
if ( (ackOnly && !forceIncrement) && (!isFirst) )
|
||||
packet.setSequenceNum(0);
|
||||
if ( (ackOnly && !forceIncrement) && (!isFirst) )
|
||||
packet.setSequenceNum(0);
|
||||
else
|
||||
packet.setSequenceNum(con.getNextOutboundPacketNum());
|
||||
packet.setSendStreamId(con.getSendStreamId());
|
||||
packet.setReceiveStreamId(con.getReceiveStreamId());
|
||||
|
||||
con.getInputStream().updateAcks(packet);
|
||||
packet.setOptionalDelay(con.getOptions().getChoke());
|
||||
int choke = con.getOptions().getChoke();
|
||||
packet.setOptionalDelay(choke);
|
||||
if (choke > 0)
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
packet.setResendDelay(con.getOptions().getResendDelay());
|
||||
|
||||
if (con.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
|
||||
@ -166,6 +170,9 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
packet.setOptionalFrom(con.getSession().getMyDestination());
|
||||
packet.setOptionalMaxSize(con.getOptions().getMaxMessageSize());
|
||||
}
|
||||
if (DataHelper.eq(con.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) {
|
||||
packet.setFlag(Packet.FLAG_NO_ACK);
|
||||
}
|
||||
|
||||
// don't set the closed flag if this is a plain ACK and there are outstanding
|
||||
// packets sent, otherwise the other side could receive the CLOSE prematurely,
|
||||
|
@ -127,7 +127,7 @@ class ConnectionHandler {
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
reply.setAckThrough(packet.getSequenceNum());
|
||||
reply.setSendStreamId(packet.getReceiveStreamId());
|
||||
reply.setReceiveStreamId(null);
|
||||
reply.setReceiveStreamId(0);
|
||||
reply.setOptionalFrom(_manager.getSession().getMyDestination());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending RST: " + reply + " because of " + packet);
|
||||
|
@ -31,9 +31,9 @@ public class ConnectionManager {
|
||||
private PacketQueue _outboundQueue;
|
||||
private SchedulerChooser _schedulerChooser;
|
||||
private ConnectionPacketHandler _conPacketHandler;
|
||||
/** Inbound stream ID (ByteArray) to Connection map */
|
||||
/** Inbound stream ID (Long) to Connection map */
|
||||
private Map _connectionByInboundId;
|
||||
/** Ping ID (ByteArray) to PingRequest */
|
||||
/** Ping ID (Long) to PingRequest */
|
||||
private Map _pendingPings;
|
||||
private boolean _allowIncoming;
|
||||
private int _maxConcurrentStreams;
|
||||
@ -71,16 +71,16 @@ public class ConnectionManager {
|
||||
_context.statManager().createRateStat("stream.receiveActive", "How many streams are active when a new one is received (period being not yet dropped)", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
Connection getConnectionByInboundId(byte[] id) {
|
||||
Connection getConnectionByInboundId(long id) {
|
||||
synchronized (_connectionLock) {
|
||||
return (Connection)_connectionByInboundId.get(new ByteArray(id));
|
||||
return (Connection)_connectionByInboundId.get(new Long(id));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* not guaranteed to be unique, but in case we receive more than one packet
|
||||
* on an inbound connection that we havent ack'ed yet...
|
||||
*/
|
||||
Connection getConnectionByOutboundId(byte[] id) {
|
||||
Connection getConnectionByOutboundId(long id) {
|
||||
synchronized (_connectionLock) {
|
||||
for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) {
|
||||
Connection con = (Connection)iter.next();
|
||||
@ -107,8 +107,7 @@ public class ConnectionManager {
|
||||
*/
|
||||
public Connection receiveConnection(Packet synPacket) {
|
||||
Connection con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, new ConnectionOptions(_defaultOptions));
|
||||
byte receiveId[] = new byte[4];
|
||||
_context.random().nextBytes(receiveId);
|
||||
long receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
|
||||
boolean reject = false;
|
||||
int active = 0;
|
||||
int total = 0;
|
||||
@ -122,16 +121,13 @@ public class ConnectionManager {
|
||||
reject = true;
|
||||
} else {
|
||||
while (true) {
|
||||
ByteArray ba = new ByteArray(receiveId);
|
||||
Connection oldCon = (Connection)_connectionByInboundId.put(ba, con);
|
||||
Connection oldCon = (Connection)_connectionByInboundId.put(new Long(receiveId), con);
|
||||
if (oldCon == null) {
|
||||
break;
|
||||
} else {
|
||||
_connectionByInboundId.put(ba, oldCon);
|
||||
_connectionByInboundId.put(new Long(receiveId), oldCon);
|
||||
// receiveId already taken, try another
|
||||
// (need to realloc receiveId, as ba.getData() points to the old value)
|
||||
receiveId = new byte[4];
|
||||
_context.random().nextBytes(receiveId);
|
||||
receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -148,7 +144,7 @@ public class ConnectionManager {
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
reply.setAckThrough(synPacket.getSequenceNum());
|
||||
reply.setSendStreamId(synPacket.getReceiveStreamId());
|
||||
reply.setReceiveStreamId(null);
|
||||
reply.setReceiveStreamId(0);
|
||||
reply.setOptionalFrom(_session.getMyDestination());
|
||||
// this just sends the packet - no retries or whatnot
|
||||
_outboundQueue.enqueue(reply);
|
||||
@ -160,7 +156,7 @@ public class ConnectionManager {
|
||||
con.getPacketHandler().receivePacket(synPacket, con);
|
||||
} catch (I2PException ie) {
|
||||
synchronized (_connectionLock) {
|
||||
_connectionByInboundId.remove(new ByteArray(receiveId));
|
||||
_connectionByInboundId.remove(new Long(receiveId));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -179,8 +175,7 @@ public class ConnectionManager {
|
||||
*/
|
||||
public Connection connect(Destination peer, ConnectionOptions opts) {
|
||||
Connection con = null;
|
||||
byte receiveId[] = new byte[4];
|
||||
_context.random().nextBytes(receiveId);
|
||||
long receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
|
||||
long expiration = _context.clock().now() + opts.getConnectTimeout();
|
||||
if (opts.getConnectTimeout() <= 0)
|
||||
expiration = _context.clock().now() + DEFAULT_STREAM_DELAY_MAX;
|
||||
@ -213,11 +208,10 @@ public class ConnectionManager {
|
||||
con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, opts);
|
||||
con.setRemotePeer(peer);
|
||||
|
||||
ByteArray ba = new ByteArray(receiveId);
|
||||
while (_connectionByInboundId.containsKey(ba)) {
|
||||
_context.random().nextBytes(receiveId);
|
||||
while (_connectionByInboundId.containsKey(new Long(receiveId))) {
|
||||
receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
|
||||
}
|
||||
_connectionByInboundId.put(ba, con);
|
||||
_connectionByInboundId.put(new Long(receiveId), con);
|
||||
break; // stop looping as a psuedo-wait
|
||||
}
|
||||
}
|
||||
@ -284,7 +278,7 @@ public class ConnectionManager {
|
||||
public void removeConnection(Connection con) {
|
||||
boolean removed = false;
|
||||
synchronized (_connectionLock) {
|
||||
Object o = _connectionByInboundId.remove(new ByteArray(con.getReceiveStreamId()));
|
||||
Object o = _connectionByInboundId.remove(new Long(con.getReceiveStreamId()));
|
||||
removed = (o == con);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Connection removed? " + removed + " remaining: "
|
||||
@ -320,11 +314,9 @@ public class ConnectionManager {
|
||||
return ping(peer, timeoutMs, blocking, null, null, null);
|
||||
}
|
||||
public boolean ping(Destination peer, long timeoutMs, boolean blocking, SessionKey keyToUse, Set tagsToSend, PingNotifier notifier) {
|
||||
byte id[] = new byte[4];
|
||||
_context.random().nextBytes(id);
|
||||
ByteArray ba = new ByteArray(id);
|
||||
Long id = new Long(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1);
|
||||
PacketLocal packet = new PacketLocal(_context, peer);
|
||||
packet.setSendStreamId(id);
|
||||
packet.setSendStreamId(id.longValue());
|
||||
packet.setFlag(Packet.FLAG_ECHO);
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
packet.setOptionalFrom(_session.getMyDestination());
|
||||
@ -336,7 +328,7 @@ public class ConnectionManager {
|
||||
PingRequest req = new PingRequest(peer, packet, notifier);
|
||||
|
||||
synchronized (_pendingPings) {
|
||||
_pendingPings.put(ba, req);
|
||||
_pendingPings.put(id, req);
|
||||
}
|
||||
|
||||
_outboundQueue.enqueue(packet);
|
||||
@ -349,10 +341,10 @@ public class ConnectionManager {
|
||||
}
|
||||
|
||||
synchronized (_pendingPings) {
|
||||
_pendingPings.remove(ba);
|
||||
_pendingPings.remove(id);
|
||||
}
|
||||
} else {
|
||||
SimpleTimer.getInstance().addEvent(new PingFailed(ba, notifier), timeoutMs);
|
||||
SimpleTimer.getInstance().addEvent(new PingFailed(id, notifier), timeoutMs);
|
||||
}
|
||||
|
||||
boolean ok = req.pongReceived();
|
||||
@ -364,17 +356,17 @@ public class ConnectionManager {
|
||||
}
|
||||
|
||||
private class PingFailed implements SimpleTimer.TimedEvent {
|
||||
private ByteArray _ba;
|
||||
private Long _id;
|
||||
private PingNotifier _notifier;
|
||||
public PingFailed(ByteArray ba, PingNotifier notifier) {
|
||||
_ba = ba;
|
||||
public PingFailed(Long id, PingNotifier notifier) {
|
||||
_id = id;
|
||||
_notifier = notifier;
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
boolean removed = false;
|
||||
synchronized (_pendingPings) {
|
||||
Object o = _pendingPings.remove(_ba);
|
||||
Object o = _pendingPings.remove(_id);
|
||||
if (o != null)
|
||||
removed = true;
|
||||
}
|
||||
@ -411,11 +403,10 @@ public class ConnectionManager {
|
||||
public boolean pongReceived() { return _ponged; }
|
||||
}
|
||||
|
||||
void receivePong(byte pingId[]) {
|
||||
ByteArray ba = new ByteArray(pingId);
|
||||
void receivePong(long pingId) {
|
||||
PingRequest req = null;
|
||||
synchronized (_pendingPings) {
|
||||
req = (PingRequest)_pendingPings.remove(ba);
|
||||
req = (PingRequest)_pendingPings.remove(new Long(pingId));
|
||||
}
|
||||
if (req != null)
|
||||
req.pong();
|
||||
|
@ -13,6 +13,9 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
private int _receiveWindow;
|
||||
private int _profile;
|
||||
private int _rtt;
|
||||
private int _rttDev;
|
||||
private int _rto;
|
||||
private int _trend[];
|
||||
private int _resendDelay;
|
||||
private int _sendAckDelay;
|
||||
private int _maxMessageSize;
|
||||
@ -50,6 +53,10 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
public static final String PROP_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR = "i2p.streaming.congestionAvoidanceGrowthRateFactor";
|
||||
public static final String PROP_SLOW_START_GROWTH_RATE_FACTOR = "i2p.streaming.slowStartGrowthRateFactor";
|
||||
|
||||
private static final int TREND_COUNT = 3;
|
||||
static final int INITIAL_WINDOW_SIZE = 4;
|
||||
static final int DEFAULT_MAX_SENDS = 8;
|
||||
|
||||
public ConnectionOptions() {
|
||||
super();
|
||||
}
|
||||
@ -65,6 +72,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
public ConnectionOptions(ConnectionOptions opts) {
|
||||
super(opts);
|
||||
if (opts != null) {
|
||||
setMaxWindowSize(opts.getMaxWindowSize());
|
||||
setConnectDelay(opts.getConnectDelay());
|
||||
setProfile(opts.getProfile());
|
||||
setRTT(opts.getRTT());
|
||||
@ -77,7 +85,6 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
setInactivityTimeout(opts.getInactivityTimeout());
|
||||
setInactivityAction(opts.getInactivityAction());
|
||||
setInboundBufferSize(opts.getInboundBufferSize());
|
||||
setMaxWindowSize(opts.getMaxWindowSize());
|
||||
setCongestionAvoidanceGrowthRateFactor(opts.getCongestionAvoidanceGrowthRateFactor());
|
||||
setSlowStartGrowthRateFactor(opts.getSlowStartGrowthRateFactor());
|
||||
}
|
||||
@ -85,6 +92,9 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
|
||||
protected void init(Properties opts) {
|
||||
super.init(opts);
|
||||
_trend = new int[TREND_COUNT];
|
||||
|
||||
setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
|
||||
setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
|
||||
setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK));
|
||||
setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, 4*1024));
|
||||
@ -92,22 +102,23 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
|
||||
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000));
|
||||
setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 500));
|
||||
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, 1));
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
|
||||
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, INITIAL_WINDOW_SIZE));
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, DEFAULT_MAX_SENDS));
|
||||
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
|
||||
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
|
||||
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 2*60*1000));
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
|
||||
setInboundBufferSize(getMaxMessageSize() * (Connection.MAX_WINDOW_SIZE + 2));
|
||||
setCongestionAvoidanceGrowthRateFactor(getInt(opts, PROP_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR, 1));
|
||||
setSlowStartGrowthRateFactor(getInt(opts, PROP_SLOW_START_GROWTH_RATE_FACTOR, 1));
|
||||
|
||||
setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DISCONNECT_TIMEOUT));
|
||||
setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
|
||||
}
|
||||
|
||||
public void setProperties(Properties opts) {
|
||||
super.setProperties(opts);
|
||||
if (opts == null) return;
|
||||
if (opts.containsKey(PROP_MAX_WINDOW_SIZE))
|
||||
setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
|
||||
if (opts.containsKey(PROP_CONNECT_DELAY))
|
||||
setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
|
||||
if (opts.containsKey(PROP_PROFILE))
|
||||
@ -119,17 +130,17 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
if (opts.containsKey(PROP_INITIAL_RECEIVE_WINDOW))
|
||||
setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
|
||||
if (opts.containsKey(PROP_INITIAL_RESEND_DELAY))
|
||||
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 500));
|
||||
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000));
|
||||
if (opts.containsKey(PROP_INITIAL_ACK_DELAY))
|
||||
setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 500));
|
||||
if (opts.containsKey(PROP_INITIAL_WINDOW_SIZE))
|
||||
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, 1));
|
||||
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, INITIAL_WINDOW_SIZE));
|
||||
if (opts.containsKey(PROP_MAX_RESENDS))
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, DEFAULT_MAX_SENDS));
|
||||
if (opts.containsKey(PROP_WRITE_TIMEOUT))
|
||||
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
|
||||
if (opts.containsKey(PROP_INACTIVITY_TIMEOUT))
|
||||
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
|
||||
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 2*60*1000));
|
||||
if (opts.containsKey(PROP_INACTIVITY_ACTION))
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
|
||||
setInboundBufferSize(getMaxMessageSize() * (Connection.MAX_WINDOW_SIZE + 2));
|
||||
@ -140,8 +151,6 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
|
||||
if (opts.containsKey(PROP_CONNECT_TIMEOUT))
|
||||
setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DISCONNECT_TIMEOUT));
|
||||
if (opts.containsKey(PROP_MAX_WINDOW_SIZE))
|
||||
setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,16 +195,55 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
*/
|
||||
public int getRTT() { return _rtt; }
|
||||
public void setRTT(int ms) {
|
||||
if (_rto == 0) {
|
||||
_rttDev = ms;
|
||||
_rto = (int)Connection.MAX_RESEND_DELAY;
|
||||
}
|
||||
synchronized (_trend) {
|
||||
_trend[0] = _trend[1];
|
||||
_trend[1] = _trend[2];
|
||||
if (ms > _rtt)
|
||||
_trend[2] = 1;
|
||||
else if (ms < _rtt)
|
||||
_trend[2] = -1;
|
||||
else
|
||||
_trend[2] = 0;
|
||||
}
|
||||
|
||||
_rtt = ms;
|
||||
if (_rtt > 60*1000)
|
||||
_rtt = 60*1000;
|
||||
}
|
||||
public int getRTO() { return _rto; }
|
||||
|
||||
/**
|
||||
* If we have 3 consecutive rtt increases, we are trending upwards (1), or if we have
|
||||
* 3 consecutive rtt decreases, we are trending downwards (-1), else we're stable.
|
||||
*
|
||||
*/
|
||||
public int getRTTTrend() {
|
||||
synchronized (_trend) {
|
||||
for (int i = 0; i < TREND_COUNT - 1; i++) {
|
||||
if (_trend[i] != _trend[i+1])
|
||||
return 0;
|
||||
}
|
||||
return _trend[0];
|
||||
}
|
||||
}
|
||||
|
||||
/** rtt = rtt*RTT_DAMPENING + (1-RTT_DAMPENING)*currentPacketRTT */
|
||||
private static final double RTT_DAMPENING = 0.9;
|
||||
|
||||
public void updateRTT(int measuredValue) {
|
||||
setRTT((int)(RTT_DAMPENING*_rtt + (1-RTT_DAMPENING)*measuredValue));
|
||||
_rttDev = _rttDev + (int)(0.25d*(Math.abs(measuredValue-_rtt)-_rttDev));
|
||||
int smoothed = (int)(RTT_DAMPENING*_rtt + (1-RTT_DAMPENING)*measuredValue);
|
||||
_rto = smoothed + (_rttDev<<2);
|
||||
if (_rto < Connection.MIN_RESEND_DELAY)
|
||||
_rto = (int)Connection.MIN_RESEND_DELAY;
|
||||
else if (_rto > Connection.MAX_RESEND_DELAY)
|
||||
_rto = (int)Connection.MAX_RESEND_DELAY;
|
||||
|
||||
setRTT(smoothed);
|
||||
}
|
||||
|
||||
/** How long after sending a packet will we wait before resending? */
|
||||
|
@ -26,6 +26,7 @@ public class ConnectionPacketHandler {
|
||||
_context.statManager().createRateStat("stream.con.packetsAckedPerMessageReceived", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.sendsBeforeAck", "How many times a message was sent before it was ACKed?", "Stream", new long[] { 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.resetReceived", "How many messages had we sent successfully before receiving a RESET?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.trend", "What direction the RTT is trending in (with period = windowsize)", "Stream", new long[] { 60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
/** distribute a packet to the connection specified */
|
||||
@ -61,7 +62,7 @@ public class ConnectionPacketHandler {
|
||||
con.getOutputStream().setBufferSize(packet.getOptionalMaxSize());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
con.packetReceived();
|
||||
|
||||
boolean choke = false;
|
||||
@ -91,7 +92,20 @@ public class ConnectionPacketHandler {
|
||||
|
||||
_context.statManager().addRateData("stream.con.receiveMessageSize", packet.getPayloadSize(), 0);
|
||||
|
||||
boolean isNew = con.getInputStream().messageReceived(packet.getSequenceNum(), packet.getPayload());
|
||||
boolean isNew = false;
|
||||
boolean allowAck = true;
|
||||
|
||||
if ( (!packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) &&
|
||||
( (packet.getSendStreamId() <= 0) ||
|
||||
(packet.getReceiveStreamId() <= 0) ) )
|
||||
allowAck = false;
|
||||
|
||||
if (allowAck) {
|
||||
isNew = con.getInputStream().messageReceived(packet.getSequenceNum(), packet.getPayload());
|
||||
} else {
|
||||
con.getInputStream().notifyActivity();
|
||||
isNew = false;
|
||||
}
|
||||
|
||||
if ( (packet.getSequenceNum() == 0) && (packet.getPayloadSize() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -146,9 +160,7 @@ public class ConnectionPacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE) &&
|
||||
((packet.getSendStreamId() == null) ||
|
||||
DataHelper.eq(packet.getSendStreamId(), Packet.STREAM_ID_UNKNOWN) ) ) {
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE) && (packet.getSendStreamId() <= 0) ) {
|
||||
// don't honor the ACK 0 in SYN packets received when the other side
|
||||
// has obviously not seen our messages
|
||||
} else {
|
||||
@ -156,10 +168,16 @@ public class ConnectionPacketHandler {
|
||||
}
|
||||
con.eventOccurred();
|
||||
if (fastAck) {
|
||||
if (con.getLastSendTime() + 2000 < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
if (!isNew) {
|
||||
// if we're congested (fastAck) but this is also a new packet,
|
||||
// we've already scheduled an ack above, so there is no need to schedule
|
||||
// a fast ack (we can wait a few ms)
|
||||
} else {
|
||||
if (con.getLastSendTime() + 2000 < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,11 +191,26 @@ public class ConnectionPacketHandler {
|
||||
}
|
||||
|
||||
private boolean ack(Connection con, long ackThrough, long nacks[], Packet packet, boolean isNew, boolean choke) {
|
||||
if (ackThrough < 0) return false;
|
||||
//if ( (nacks != null) && (nacks.length > 0) )
|
||||
// con.getOptions().setRTT(con.getOptions().getRTT() + nacks.length*1000);
|
||||
|
||||
int numResends = 0;
|
||||
List acked = con.ackPackets(ackThrough, nacks);
|
||||
List acked = null;
|
||||
// if we don't know the streamIds for both sides of the connection, there's no way we
|
||||
// could actually be acking data (this fixes the buggered up ack of packet 0 problem).
|
||||
// this is called after packet verification, which places the stream IDs as necessary if
|
||||
// the SYN verifies (so if we're acking w/out stream IDs, no SYN has been received yet)
|
||||
if ( (packet != null) && (packet.getSendStreamId() > 0) && (packet.getReceiveStreamId() > 0) &&
|
||||
(con != null) && (con.getSendStreamId() > 0) && (con.getReceiveStreamId() > 0) &&
|
||||
(!DataHelper.eq(packet.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
||||
(!DataHelper.eq(packet.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
||||
(!DataHelper.eq(con.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
||||
(!DataHelper.eq(con.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) )
|
||||
acked = con.ackPackets(ackThrough, nacks);
|
||||
else
|
||||
return false;
|
||||
|
||||
if ( (acked != null) && (acked.size() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(acked.size() + " of our packets acked with " + packet);
|
||||
@ -247,8 +280,13 @@ public class ConnectionPacketHandler {
|
||||
int oldWindow = con.getOptions().getWindowSize();
|
||||
int newWindowSize = oldWindow;
|
||||
|
||||
int trend = con.getOptions().getRTTTrend();
|
||||
|
||||
_context.statManager().addRateData("stream.trend", trend, newWindowSize);
|
||||
|
||||
if ( (!congested) && (acked > 0) && (numResends <= 0) ) {
|
||||
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
if ( (newWindowSize > con.getLastCongestionSeenAt() / 2) ||
|
||||
(trend > 0) ) { // tcp vegas: avoidance if rtt is increasing, even if we arent at ssthresh/2 yet
|
||||
// congestion avoidance
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
@ -282,6 +320,12 @@ public class ConnectionPacketHandler {
|
||||
return congested;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we don't know the send stream id yet (we're just creating a connection), allow
|
||||
* the first three packets to come in. The first of those should be the SYN, of course...
|
||||
*/
|
||||
private static final int MAX_INITIAL_PACKETS = ConnectionOptions.INITIAL_WINDOW_SIZE;
|
||||
|
||||
/**
|
||||
* Make sure this packet is ok and that we can continue processing its data.
|
||||
*
|
||||
@ -295,14 +339,14 @@ public class ConnectionPacketHandler {
|
||||
} else {
|
||||
verifySignature(packet, con);
|
||||
|
||||
if (con.getSendStreamId() == null) {
|
||||
if (con.getSendStreamId() <= 0) {
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
con.setSendStreamId(packet.getReceiveStreamId());
|
||||
con.setRemotePeer(packet.getOptionalFrom());
|
||||
return true;
|
||||
} else {
|
||||
// neither RST nor SYN and we dont have the stream id yet?
|
||||
if (packet.getSequenceNum() <= 2) {
|
||||
if (packet.getSequenceNum() < MAX_INITIAL_PACKETS) {
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
|
@ -44,6 +44,7 @@ public class MessageHandler implements I2PSessionListener {
|
||||
_log.warn("Error receiving the message", ise);
|
||||
return;
|
||||
}
|
||||
if (data == null) return;
|
||||
Packet packet = new Packet();
|
||||
try {
|
||||
packet.readPacket(data, 0, data.length);
|
||||
|
@ -193,6 +193,8 @@ public class MessageInputStream extends InputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyActivity() { synchronized (_dataLock) { _dataLock.notifyAll(); } }
|
||||
|
||||
/**
|
||||
* A new message has arrived - toss it on the appropriate queue (moving
|
||||
* previously pending messages to the ready queue if it fills the gap, etc).
|
||||
@ -202,7 +204,7 @@ public class MessageInputStream extends InputStream {
|
||||
public boolean messageReceived(long messageId, ByteArray payload) {
|
||||
synchronized (_dataLock) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("received " + messageId + " with " + payload.getValid());
|
||||
_log.debug("received " + messageId + " with " + (payload != null ? payload.getValid()+"" : "no payload"));
|
||||
if (messageId <= _highestReadyBlockId) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("ignoring dup message " + messageId);
|
||||
@ -435,7 +437,9 @@ public class MessageInputStream extends InputStream {
|
||||
*
|
||||
*/
|
||||
void streamErrorOccurred(IOException ioe) {
|
||||
_streamError = ioe;
|
||||
if (_streamError == null)
|
||||
_streamError = ioe;
|
||||
_locallyClosed = true;
|
||||
synchronized (_dataLock) {
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
|
@ -312,11 +312,16 @@ public class MessageOutputStream extends OutputStream {
|
||||
/** nonblocking close */
|
||||
public void closeInternal() {
|
||||
_closed = true;
|
||||
_streamError = new IOException("Closed internally");
|
||||
if (_streamError == null)
|
||||
_streamError = new IOException("Closed internally");
|
||||
clearData(true);
|
||||
}
|
||||
|
||||
private void clearData(boolean shouldFlush) {
|
||||
ByteArray ba = null;
|
||||
synchronized (_dataLock) {
|
||||
// flush any data, but don't wait for it
|
||||
if (_dataReceiver != null)
|
||||
if ( (_dataReceiver != null) && (_valid > 0) && shouldFlush)
|
||||
_dataReceiver.writeData(_buf, 0, _valid);
|
||||
_written += _valid;
|
||||
_valid = 0;
|
||||
@ -345,7 +350,9 @@ public class MessageOutputStream extends OutputStream {
|
||||
}
|
||||
|
||||
void streamErrorOccurred(IOException ioe) {
|
||||
_streamError = ioe;
|
||||
if (_streamError == null)
|
||||
_streamError = ioe;
|
||||
clearData(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,6 +10,7 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Contain a single packet transferred as part of a streaming connection.
|
||||
@ -51,8 +52,8 @@ import net.i2p.util.ByteCache;
|
||||
*
|
||||
*/
|
||||
public class Packet {
|
||||
private byte _sendStreamId[];
|
||||
private byte _receiveStreamId[];
|
||||
private long _sendStreamId;
|
||||
private long _receiveStreamId;
|
||||
private long _sequenceNum;
|
||||
private long _ackThrough;
|
||||
private long _nacks[];
|
||||
@ -64,7 +65,6 @@ public class Packet {
|
||||
private Destination _optionFrom;
|
||||
private int _optionDelay;
|
||||
private int _optionMaxSize;
|
||||
private ByteCache _cache;
|
||||
|
||||
/**
|
||||
* The receiveStreamId will be set to this when the packet doesn't know
|
||||
@ -72,7 +72,9 @@ public class Packet {
|
||||
* synchronize packet)
|
||||
*
|
||||
*/
|
||||
public static final byte STREAM_ID_UNKNOWN[] = new byte[] { 0x00, 0x00, 0x00, 0x00 };
|
||||
public static final long STREAM_ID_UNKNOWN = 0l;
|
||||
|
||||
public static final long MAX_STREAM_ID = 0xffffffffl;
|
||||
|
||||
/**
|
||||
* This packet is creating a new socket connection (if the receiveStreamId
|
||||
@ -135,43 +137,38 @@ public class Packet {
|
||||
* ping reply (if receiveStreamId is set).
|
||||
*/
|
||||
public static final int FLAG_ECHO = (1 << 9);
|
||||
|
||||
/**
|
||||
* If set, this packet doesn't really want to ack anything
|
||||
*/
|
||||
public static final int FLAG_NO_ACK = (1 << 10);
|
||||
|
||||
public static final int DEFAULT_MAX_SIZE = 32*1024;
|
||||
private static final int MAX_DELAY_REQUEST = 65535;
|
||||
|
||||
public Packet() {
|
||||
_cache = ByteCache.getInstance(128, MAX_PAYLOAD_SIZE);
|
||||
}
|
||||
public Packet() { }
|
||||
|
||||
/** what stream is this packet a part of? */
|
||||
public byte[] getSendStreamId() {
|
||||
if ( (_sendStreamId == null) || (DataHelper.eq(_sendStreamId, STREAM_ID_UNKNOWN)) )
|
||||
return null;
|
||||
else
|
||||
return _sendStreamId;
|
||||
}
|
||||
public void setSendStreamId(byte[] id) {
|
||||
private boolean _sendStreamIdSet = false;
|
||||
/** what stream do we send data to the peer on? */
|
||||
public long getSendStreamId() { return _sendStreamId; }
|
||||
public void setSendStreamId(long id) {
|
||||
if ( (_sendStreamIdSet) && (_sendStreamId > 0) )
|
||||
throw new RuntimeException("Send stream ID already set [" + _sendStreamId + ", " + id + "]");
|
||||
_sendStreamIdSet = true;
|
||||
_sendStreamId = id;
|
||||
if ( (id != null) && (DataHelper.eq(id, STREAM_ID_UNKNOWN)) )
|
||||
_sendStreamId = null;
|
||||
}
|
||||
|
||||
private boolean _receiveStreamIdSet = false;
|
||||
/**
|
||||
* Stream that replies should be sent on. if the
|
||||
* connection is still being built, this should be
|
||||
* null.
|
||||
*
|
||||
* stream the replies should be sent on. this should be 0 if the
|
||||
* connection is still being built.
|
||||
*/
|
||||
public byte[] getReceiveStreamId() {
|
||||
if ( (_receiveStreamId == null) || (DataHelper.eq(_receiveStreamId, STREAM_ID_UNKNOWN)) )
|
||||
return null;
|
||||
else
|
||||
return _receiveStreamId;
|
||||
}
|
||||
public void setReceiveStreamId(byte[] id) {
|
||||
public long getReceiveStreamId() { return _receiveStreamId; }
|
||||
public void setReceiveStreamId(long id) {
|
||||
if ( (_receiveStreamIdSet) && (_receiveStreamId > 0) )
|
||||
throw new RuntimeException("Receive stream ID already set [" + _receiveStreamId + ", " + id + "]");
|
||||
_receiveStreamIdSet = true;
|
||||
_receiveStreamId = id;
|
||||
if ( (id != null) && (DataHelper.eq(id, STREAM_ID_UNKNOWN)) )
|
||||
_receiveStreamId = null;
|
||||
}
|
||||
|
||||
/** 0-indexed sequence number for this Packet in the sendStream */
|
||||
@ -181,11 +178,21 @@ public class Packet {
|
||||
/**
|
||||
* The highest packet sequence number that received
|
||||
* on the receiveStreamId. This field is ignored on the initial
|
||||
* connection packet (where receiveStreamId is the unknown id).
|
||||
* connection packet (where receiveStreamId is the unknown id) or
|
||||
* if FLAG_NO_ACK is set.
|
||||
*
|
||||
*/
|
||||
public long getAckThrough() { return _ackThrough; }
|
||||
public void setAckThrough(long id) { _ackThrough = id; }
|
||||
public long getAckThrough() {
|
||||
if (isFlagSet(FLAG_NO_ACK))
|
||||
return -1;
|
||||
else
|
||||
return _ackThrough;
|
||||
}
|
||||
public void setAckThrough(long id) {
|
||||
if (id < 0)
|
||||
setFlag(FLAG_NO_ACK);
|
||||
_ackThrough = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of packet sequence numbers below the getAckThrough() value
|
||||
@ -209,8 +216,6 @@ public class Packet {
|
||||
/** get the actual payload of the message. may be null */
|
||||
public ByteArray getPayload() { return _payload; }
|
||||
public void setPayload(ByteArray payload) {
|
||||
//if ( (_payload != null) && (_payload != payload) )
|
||||
// _cache.release(_payload);
|
||||
_payload = payload;
|
||||
if ( (payload != null) && (payload.getValid() > MAX_PAYLOAD_SIZE) )
|
||||
throw new IllegalArgumentException("Too large payload: " + payload.getValid());
|
||||
@ -219,15 +224,11 @@ public class Packet {
|
||||
return (_payload == null ? 0 : _payload.getValid());
|
||||
}
|
||||
public void releasePayload() {
|
||||
//if (_payload != null)
|
||||
// _cache.release(_payload);
|
||||
_payload = null;
|
||||
//_payload = null;
|
||||
}
|
||||
public ByteArray acquirePayload() {
|
||||
ByteArray old = _payload;
|
||||
_payload = new ByteArray(new byte[Packet.MAX_PAYLOAD_SIZE]); //_cache.acquire();
|
||||
//if (old != null)
|
||||
// _cache.release(old);
|
||||
_payload = new ByteArray(new byte[Packet.MAX_PAYLOAD_SIZE]);
|
||||
return _payload;
|
||||
}
|
||||
|
||||
@ -240,6 +241,7 @@ public class Packet {
|
||||
else
|
||||
_flags &= ~flag;
|
||||
}
|
||||
public void setFlags(int flags) { _flags = flags; }
|
||||
|
||||
/** the signature on the packet (only included if the flag for it is set) */
|
||||
public Signature getOptionalSignature() { return _optionSignature; }
|
||||
@ -263,7 +265,6 @@ public class Packet {
|
||||
*/
|
||||
public int getOptionalDelay() { return _optionDelay; }
|
||||
public void setOptionalDelay(int delayMs) {
|
||||
setFlag(FLAG_DELAY_REQUESTED, delayMs > 0);
|
||||
if (delayMs > MAX_DELAY_REQUEST)
|
||||
_optionDelay = MAX_DELAY_REQUEST;
|
||||
else if (delayMs < 0)
|
||||
@ -297,15 +298,9 @@ public class Packet {
|
||||
*/
|
||||
private int writePacket(byte buffer[], int offset, boolean includeSig) throws IllegalStateException {
|
||||
int cur = offset;
|
||||
if ( (_sendStreamId != null) && (_sendStreamId.length == 4) )
|
||||
System.arraycopy(_sendStreamId, 0, buffer, cur, _sendStreamId.length);
|
||||
else
|
||||
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
|
||||
DataHelper.toLong(buffer, cur, 4, (_sendStreamId >= 0 ? _sendStreamId : STREAM_ID_UNKNOWN));
|
||||
cur += 4;
|
||||
if ( (_receiveStreamId != null) && (_receiveStreamId.length == 4) )
|
||||
System.arraycopy(_receiveStreamId, 0, buffer, cur, _receiveStreamId.length);
|
||||
else
|
||||
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
|
||||
DataHelper.toLong(buffer, cur, 4, (_receiveStreamId >= 0 ? _receiveStreamId : STREAM_ID_UNKNOWN));
|
||||
cur += 4;
|
||||
DataHelper.toLong(buffer, cur, 4, _sequenceNum > 0 ? _sequenceNum : 0);
|
||||
cur += 4;
|
||||
@ -383,7 +378,7 @@ public class Packet {
|
||||
size += 4; // sequenceNum
|
||||
size += 4; // ackThrough
|
||||
if (_nacks != null) {
|
||||
size++; // nacks length
|
||||
size++; // nacks length
|
||||
size += 4 * _nacks.length;
|
||||
} else {
|
||||
size++; // nacks length
|
||||
@ -425,32 +420,31 @@ public class Packet {
|
||||
if (length < 22) // min header size
|
||||
throw new IllegalArgumentException("Too small: len=" + buffer.length);
|
||||
int cur = offset;
|
||||
_sendStreamId = new byte[4];
|
||||
System.arraycopy(buffer, cur, _sendStreamId, 0, 4);
|
||||
setSendStreamId(DataHelper.fromLong(buffer, cur, 4));
|
||||
cur += 4;
|
||||
_receiveStreamId = new byte[4];
|
||||
System.arraycopy(buffer, cur, _receiveStreamId, 0, 4);
|
||||
setReceiveStreamId(DataHelper.fromLong(buffer, cur, 4));
|
||||
cur += 4;
|
||||
_sequenceNum = DataHelper.fromLong(buffer, cur, 4);
|
||||
setSequenceNum(DataHelper.fromLong(buffer, cur, 4));
|
||||
cur += 4;
|
||||
_ackThrough = DataHelper.fromLong(buffer, cur, 4);
|
||||
setAckThrough(DataHelper.fromLong(buffer, cur, 4));
|
||||
cur += 4;
|
||||
int numNacks = (int)DataHelper.fromLong(buffer, cur, 1);
|
||||
cur++;
|
||||
if (length < 22 + numNacks*4)
|
||||
throw new IllegalArgumentException("Too small with " + numNacks + " nacks: " + length);
|
||||
if (numNacks > 0) {
|
||||
_nacks = new long[numNacks];
|
||||
long nacks[] = new long[numNacks];
|
||||
for (int i = 0; i < numNacks; i++) {
|
||||
_nacks[i] = DataHelper.fromLong(buffer, cur, 4);
|
||||
nacks[i] = DataHelper.fromLong(buffer, cur, 4);
|
||||
cur += 4;
|
||||
}
|
||||
setNacks(nacks);
|
||||
} else {
|
||||
_nacks = null;
|
||||
setNacks(null);
|
||||
}
|
||||
_resendDelay = (int)DataHelper.fromLong(buffer, cur, 1);
|
||||
setResendDelay((int)DataHelper.fromLong(buffer, cur, 1));
|
||||
cur++;
|
||||
_flags = (int)DataHelper.fromLong(buffer, cur, 2);
|
||||
setFlags((int)DataHelper.fromLong(buffer, cur, 2));
|
||||
cur += 2;
|
||||
|
||||
int optionSize = (int)DataHelper.fromLong(buffer, cur, 2);
|
||||
@ -466,33 +460,36 @@ public class Packet {
|
||||
throw new IllegalArgumentException("length: " + length + " offset: " + offset + " begin: " + payloadBegin);
|
||||
|
||||
// skip ahead to the payload
|
||||
_payload = new ByteArray(new byte[payloadSize]); //_cache.acquire();
|
||||
System.arraycopy(buffer, payloadBegin, _payload.getData(), 0, payloadSize);
|
||||
_payload.setValid(payloadSize);
|
||||
_payload.setOffset(0);
|
||||
//_payload = new ByteArray(new byte[payloadSize]);
|
||||
_payload = new ByteArray(buffer, payloadBegin, payloadSize);
|
||||
//System.arraycopy(buffer, payloadBegin, _payload.getData(), 0, payloadSize);
|
||||
//_payload.setValid(payloadSize);
|
||||
//_payload.setOffset(0);
|
||||
|
||||
// ok now lets go back and deal with the options
|
||||
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
|
||||
_optionDelay = (int)DataHelper.fromLong(buffer, cur, 2);
|
||||
setOptionalDelay((int)DataHelper.fromLong(buffer, cur, 2));
|
||||
cur += 2;
|
||||
}
|
||||
if (isFlagSet(FLAG_FROM_INCLUDED)) {
|
||||
_optionFrom = new Destination();
|
||||
Destination optionFrom = new Destination();
|
||||
try {
|
||||
cur += _optionFrom.readBytes(buffer, cur);
|
||||
cur += optionFrom.readBytes(buffer, cur);
|
||||
setOptionalFrom(optionFrom);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IllegalArgumentException("Bad from field: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
|
||||
_optionMaxSize = (int)DataHelper.fromLong(buffer, cur, 2);
|
||||
setOptionalMaxSize((int)DataHelper.fromLong(buffer, cur, 2));
|
||||
cur += 2;
|
||||
}
|
||||
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
|
||||
_optionSignature = new Signature();
|
||||
Signature optionSignature = new Signature();
|
||||
byte buf[] = new byte[Signature.SIGNATURE_BYTES];
|
||||
System.arraycopy(buffer, cur, buf, 0, Signature.SIGNATURE_BYTES);
|
||||
_optionSignature.setData(buf);
|
||||
optionSignature.setData(buf);
|
||||
setOptionalSignature(optionSignature);
|
||||
cur += Signature.SIGNATURE_BYTES;
|
||||
}
|
||||
}
|
||||
@ -518,7 +515,12 @@ public class Packet {
|
||||
}
|
||||
boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, from.getSigningPublicKey());
|
||||
if (!ok) {
|
||||
ctx.logManager().getLog(Packet.class).error("Signature failed on " + toString(), new Exception("moo"));
|
||||
Log l = ctx.logManager().getLog(Packet.class);
|
||||
l.error("Signature failed on " + toString(), new Exception("moo"));
|
||||
if (false) {
|
||||
l.error(Base64.encode(buffer, 0, size));
|
||||
l.error("Signature: " + Base64.encode(_optionSignature.getData()));
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@ -533,6 +535,12 @@ public class Packet {
|
||||
setFlag(FLAG_SIGNATURE_INCLUDED);
|
||||
int size = writePacket(buffer, offset, false);
|
||||
_optionSignature = ctx.dsa().sign(buffer, offset, size, key);
|
||||
if (false) {
|
||||
Log l = ctx.logManager().getLog(Packet.class);
|
||||
l.error("Signing: " + toString());
|
||||
l.error(Base64.encode(buffer, 0, size));
|
||||
l.error("Signature: " + Base64.encode(_optionSignature.getData()));
|
||||
}
|
||||
// jump into the signed data and inject the signature where we
|
||||
// previously placed a bunch of zeroes
|
||||
int signatureOffset = offset
|
||||
@ -566,7 +574,7 @@ public class Packet {
|
||||
else
|
||||
buf.append('\t');
|
||||
buf.append(toFlagString());
|
||||
buf.append(" ACK ").append(_ackThrough);
|
||||
buf.append(" ACK ").append(getAckThrough());
|
||||
if (_nacks != null) {
|
||||
buf.append(" NACK");
|
||||
for (int i = 0; i < _nacks.length; i++) {
|
||||
@ -578,11 +586,8 @@ public class Packet {
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static final String toId(byte id[]) {
|
||||
if (id == null)
|
||||
return Base64.encode(STREAM_ID_UNKNOWN);
|
||||
else
|
||||
return Base64.encode(id);
|
||||
static final String toId(long id) {
|
||||
return Base64.encode(DataHelper.toLong(4, id));
|
||||
}
|
||||
|
||||
private final String toFlagString() {
|
||||
|
@ -22,19 +22,26 @@ public class PacketHandler {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private int _lastDelay;
|
||||
private int _dropped;
|
||||
|
||||
public PacketHandler(I2PAppContext ctx, ConnectionManager mgr) {
|
||||
_manager = mgr;
|
||||
_context = ctx;
|
||||
_dropped = 0;
|
||||
_log = ctx.logManager().getLog(PacketHandler.class);
|
||||
_lastDelay = _context.random().nextInt(30*1000);
|
||||
}
|
||||
|
||||
private boolean choke(Packet packet) {
|
||||
if (false) {
|
||||
// artificial choke: 2% random drop and a 0-30s
|
||||
private boolean choke(Packet packet) {
|
||||
if (true) return true;
|
||||
//if ( (_dropped == 0) && true ) { //&& (_manager.getSent() <= 0) ) {
|
||||
// _dropped++;
|
||||
// return false;
|
||||
//}
|
||||
if (true) {
|
||||
// artificial choke: 2% random drop and a 0-5s
|
||||
// random tiered delay from 0-30s
|
||||
if (_context.random().nextInt(100) >= 95) {
|
||||
if (_context.random().nextInt(100) >= 98) {
|
||||
displayPacket(packet, "DROP", null);
|
||||
return false;
|
||||
} else {
|
||||
@ -42,7 +49,7 @@ public class PacketHandler {
|
||||
/*
|
||||
int delay = _context.random().nextInt(5*1000);
|
||||
*/
|
||||
int delay = _context.random().nextInt(6*1000);
|
||||
int delay = _context.random().nextInt(1*1000);
|
||||
int delayFactor = _context.random().nextInt(100);
|
||||
if (delayFactor > 80) {
|
||||
if (delayFactor > 98)
|
||||
@ -90,14 +97,12 @@ public class PacketHandler {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("packet received: " + packet);
|
||||
|
||||
byte sendId[] = packet.getSendStreamId();
|
||||
if (!isNonZero(sendId))
|
||||
sendId = null;
|
||||
long sendId = packet.getSendStreamId();
|
||||
|
||||
Connection con = (sendId != null ? _manager.getConnectionByInboundId(sendId) : null);
|
||||
Connection con = (sendId > 0 ? _manager.getConnectionByInboundId(sendId) : null);
|
||||
if (con != null) {
|
||||
receiveKnownCon(con, packet);
|
||||
displayPacket(packet, "RECV", "wsize " + con.getOptions().getWindowSize());
|
||||
displayPacket(packet, "RECV", "wsize " + con.getOptions().getWindowSize() + " rto " + con.getOptions().getRTO());
|
||||
} else {
|
||||
receiveUnknownCon(packet, sendId);
|
||||
displayPacket(packet, "UNKN", null);
|
||||
@ -120,9 +125,9 @@ public class PacketHandler {
|
||||
|
||||
private void receiveKnownCon(Connection con, Packet packet) {
|
||||
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
|
||||
if (packet.getSendStreamId() != null) {
|
||||
if (packet.getSendStreamId() > 0) {
|
||||
receivePing(packet);
|
||||
} else if (packet.getReceiveStreamId() != null) {
|
||||
} else if (packet.getReceiveStreamId() > 0) {
|
||||
receivePong(packet);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -155,17 +160,29 @@ public class PacketHandler {
|
||||
_log.warn("Received forged reset for " + con, ie);
|
||||
}
|
||||
} else {
|
||||
if ( (con.getSendStreamId() == null) ||
|
||||
(DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) ) {
|
||||
byte oldId[] =con.getSendStreamId();
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) // con fully established, w00t
|
||||
con.setSendStreamId(packet.getReceiveStreamId());
|
||||
if ( (con.getSendStreamId() <= 0) ||
|
||||
(DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) ||
|
||||
(packet.getSequenceNum() <= 5) ) { // its in flight from the first batch
|
||||
long oldId = con.getSendStreamId();
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
if (oldId <= 0) {
|
||||
// con fully established, w00t
|
||||
con.setSendStreamId(packet.getReceiveStreamId());
|
||||
} else if (oldId == packet.getReceiveStreamId()) {
|
||||
// ok, as expected...
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Received a syn with the wrong IDs, con=" + con + " packet=" + packet);
|
||||
packet.releasePayload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
con.getPacketHandler().receivePacket(packet, con);
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received forged packet for " + con + ": " + packet, ie);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Received forged packet for " + con + "/" + oldId + ": " + packet, ie);
|
||||
con.setSendStreamId(oldId);
|
||||
}
|
||||
} else if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
@ -207,11 +224,11 @@ public class PacketHandler {
|
||||
_manager.getPacketQueue().enqueue(reply);
|
||||
}
|
||||
|
||||
private void receiveUnknownCon(Packet packet, byte sendId[]) {
|
||||
private void receiveUnknownCon(Packet packet, long sendId) {
|
||||
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
|
||||
if (packet.getSendStreamId() != null) {
|
||||
if (packet.getSendStreamId() > 0) {
|
||||
receivePing(packet);
|
||||
} else if (packet.getReceiveStreamId() != null) {
|
||||
} else if (packet.getReceiveStreamId() > 0) {
|
||||
receivePong(packet);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -221,10 +238,10 @@ public class PacketHandler {
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received on an unknown stream (and not an ECHO): " + packet);
|
||||
if (sendId == null) {
|
||||
if (sendId <= 0) {
|
||||
Connection con = _manager.getConnectionByOutboundId(packet.getReceiveStreamId());
|
||||
if (con != null) {
|
||||
if (con.getAckedPackets() <= 0) {
|
||||
if ( (con.getHighestAckedThrough() <= 5) && (packet.getSequenceNum() <= 5) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received additional packets before the syn on " + con + ": " + packet);
|
||||
receiveKnownCon(con, packet);
|
||||
@ -250,7 +267,7 @@ public class PacketHandler {
|
||||
}
|
||||
_log.warn("Packet belongs to no other cons: " + packet + " connections: "
|
||||
+ buf.toString() + " sendId: "
|
||||
+ (sendId != null ? Base64.encode(sendId) : " unknown"));
|
||||
+ (sendId > 0 ? Packet.toId(sendId) : " unknown"));
|
||||
}
|
||||
packet.releasePayload();
|
||||
}
|
||||
@ -282,25 +299,7 @@ public class PacketHandler {
|
||||
_manager.receivePong(packet.getReceiveStreamId());
|
||||
}
|
||||
|
||||
private static final boolean isValidMatch(byte conStreamId[], byte packetStreamId[]) {
|
||||
if ( (conStreamId == null) || (packetStreamId == null) ||
|
||||
(conStreamId.length != packetStreamId.length) )
|
||||
return false;
|
||||
|
||||
boolean nonZeroFound = false;
|
||||
for (int i = 0; i < conStreamId.length; i++) {
|
||||
if (conStreamId[i] != packetStreamId[i]) return false;
|
||||
if (conStreamId[i] != 0x0) nonZeroFound = true;
|
||||
}
|
||||
return nonZeroFound;
|
||||
}
|
||||
|
||||
private static final boolean isNonZero(byte[] b) {
|
||||
boolean nonZeroFound = false;
|
||||
for (int i = 0; b != null && i < b.length; i++) {
|
||||
if (b[i] != 0x0)
|
||||
nonZeroFound = true;
|
||||
}
|
||||
return nonZeroFound;
|
||||
private static final boolean isValidMatch(long conStreamId, long packetStreamId) {
|
||||
return ( (conStreamId == packetStreamId) && (conStreamId != 0) );
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import java.util.Set;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
@ -27,7 +26,6 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
private long _ackOn;
|
||||
private long _cancelledOn;
|
||||
private SimpleTimer.TimedEvent _resendEvent;
|
||||
private ByteCache _cache = ByteCache.getInstance(128, MAX_PAYLOAD_SIZE);
|
||||
|
||||
public PacketLocal(I2PAppContext ctx, Destination to) {
|
||||
this(ctx, to, null);
|
||||
@ -71,8 +69,11 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public void prepare() {
|
||||
if (_connection != null)
|
||||
_connection.getInputStream().updateAcks(this);
|
||||
if (_numSends > 0) // so we can debug to differentiate resends
|
||||
if (_numSends > 0) {
|
||||
// so we can debug to differentiate resends
|
||||
setOptionalDelay(_numSends * 1000);
|
||||
setFlag(FLAG_DELAY_REQUESTED);
|
||||
}
|
||||
}
|
||||
|
||||
public long getCreatedOn() { return _createdOn; }
|
||||
|
@ -125,7 +125,7 @@ class PacketQueue {
|
||||
_log.debug(msg);
|
||||
}
|
||||
Connection c = packet.getConnection();
|
||||
String suffix = (c != null ? "wsize " + c.getOptions().getWindowSize() : null);
|
||||
String suffix = (c != null ? "wsize " + c.getOptions().getWindowSize() + " rto " + c.getOptions().getRTO() : null);
|
||||
_connectionManager.getPacketHandler().displayPacket(packet, "SEND", suffix);
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ class SchedulerClosed extends SchedulerImpl {
|
||||
(!con.getResetReceived()) &&
|
||||
(timeSinceClose < Connection.DISCONNECT_TIMEOUT);
|
||||
boolean conTimeout = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
|
||||
con.getSendStreamId() == null &&
|
||||
con.getSendStreamId() <= 0 &&
|
||||
con.getLifetime() < Connection.DISCONNECT_TIMEOUT;
|
||||
return (ok || conTimeout);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class SchedulerDead extends SchedulerImpl {
|
||||
boolean nothingLeftToDo = (con.getDisconnectScheduledOn() > 0) &&
|
||||
(timeSinceClose >= Connection.DISCONNECT_TIMEOUT);
|
||||
boolean timedOut = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
|
||||
con.getSendStreamId() == null &&
|
||||
con.getSendStreamId() <= 0 &&
|
||||
con.getLifetime() >= Connection.DISCONNECT_TIMEOUT;
|
||||
return nothingLeftToDo || timedOut;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class SchedulerPreconnect extends SchedulerImpl {
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
return (con != null) &&
|
||||
(con.getSendStreamId() == null) &&
|
||||
(con.getSendStreamId() <= 0) &&
|
||||
(con.getLastSendId() < 0);
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ class SchedulerReceived extends SchedulerImpl {
|
||||
public boolean accept(Connection con) {
|
||||
return (con != null) &&
|
||||
(con.getLastSendId() < 0) &&
|
||||
(con.getSendStreamId() != null);
|
||||
(con.getSendStreamId() > 0);
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
|
4
apps/susidns/readme.txt
Normal file
@ -0,0 +1,4 @@
|
||||
The src/ dir contains susidns 0.13 retrieved from http://susi.i2p/ on 2005/09/15
|
||||
The contents are released under GPL. Please see http://susi.i2p/ for more info
|
||||
The paths in the src/build.xml were updated to reference jars in the i2p
|
||||
source tree.
|
BIN
apps/susidns/src/WEB-INF/lib/jstl.jar
Normal file
BIN
apps/susidns/src/WEB-INF/lib/standard.jar
Normal file
17
apps/susidns/src/WEB-INF/web-template.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
<web-app>
|
||||
<display-name>susidns</display-name>
|
||||
<!-- precompiled servlets -->
|
||||
<session-config>
|
||||
<session-timeout>
|
||||
30
|
||||
</session-timeout>
|
||||
</session-config>
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.html</welcome-file>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
</web-app>
|
80
apps/susidns/src/build.xml
Normal file
@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project name="susidns" default="all" basedir=".">
|
||||
<property name="jetty" value="../../jetty/" />
|
||||
<property name="project" value="susidns" />
|
||||
<property name="src" value="java/src" />
|
||||
<property name="bin" value="./WEB-INF/classes" />
|
||||
<property name="lib" value="${jetty}/jettylib" />
|
||||
<property name="tmp" value="./tmp" />
|
||||
<property name="jsp" value="./jsp" />
|
||||
<path id="cp">
|
||||
<pathelement path="${classpath}" />
|
||||
<pathelement location="${bin}" />
|
||||
<pathelement location="${lib}/javax.servlet.jar"/>
|
||||
<pathelement location="${lib}/org.mortbay.jetty.jar"/>
|
||||
<pathelement location="WEB-INF/lib/jstl.jar" />
|
||||
<pathelement location="WEB-INF/lib/standard.jar" />
|
||||
<pathelement location="${lib}/jasper-compiler.jar" />
|
||||
<pathelement location="${lib}/jasper-runtime.jar" />
|
||||
<pathelement location="${lib}/javax.servlet.jar" />
|
||||
<pathelement location="${lib}/commons-logging.jar" />
|
||||
<pathelement location="${lib}/commons-el.jar" />
|
||||
<pathelement location="${lib}/ant.jar" />
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</path>
|
||||
<target name="compile">
|
||||
<mkdir dir="${bin}" />
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
classpathref="cp" destdir="${bin}" srcdir="${src}" includes="**/*.java" />
|
||||
</target>
|
||||
<target name="precompilejsp">
|
||||
<delete file="WEB-INF/web-fragment.xml" />
|
||||
<delete file="WEB-INF/web-out.xml" />
|
||||
<mkdir dir="${tmp}" />
|
||||
<java classname="org.apache.jasper.JspC" fork="true" classpathref="cp">
|
||||
<arg value="-d" />
|
||||
<arg value="WEB-INF/classes" />
|
||||
<arg value="-v" />
|
||||
<arg value="-p" />
|
||||
<arg value="i2p.susi.dns.jsp" />
|
||||
<arg value="-webinc" />
|
||||
<arg value="WEB-INF/web-fragment.xml" />
|
||||
<arg value="-webapp" />
|
||||
<arg value="./jsp" />
|
||||
</java>
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="${bin}" srcdir="./WEB-INF/classes" includes="**/*.java" classpathref="cp">
|
||||
</javac>
|
||||
<copy file="WEB-INF/web-template.xml" tofile="WEB-INF/web-out.xml" />
|
||||
<loadfile property="jspc.web.fragment" srcfile="WEB-INF/web-fragment.xml" />
|
||||
<replace file="WEB-INF//web-out.xml">
|
||||
<replacefilter token="<!-- precompiled servlets -->" value="${jspc.web.fragment}" />
|
||||
</replace>
|
||||
</target>
|
||||
<target name="all" depends="compile,precompilejsp,war"/>
|
||||
<target name="war">
|
||||
<war destfile="${project}.war" webxml="WEB-INF/web-out.xml">
|
||||
<fileset dir=".">
|
||||
<include name="WEB-INF/**/*.class"/>
|
||||
<include name="WEB-INF/lib/*.jar"/>
|
||||
<include name="${src}/**/*.java"/>
|
||||
<include name="jsp/*.jsp"/>
|
||||
<include name="images/*.png"/>
|
||||
<include name="css.css"/>
|
||||
<include name="index.html"/>
|
||||
<include name="build.xml"/>
|
||||
<include name="WEB-INF/web-template.xml"/>
|
||||
<include name="WEB-INF/web-out.xml"/>
|
||||
<include name="WEB-INF/classes/${project}.properties"/>
|
||||
</fileset>
|
||||
</war>
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete file="susidns.war" />
|
||||
<delete>
|
||||
<fileset dir="." includes="**/*.class" />
|
||||
<fileset dir="." includes="tmp" />
|
||||
</delete>
|
||||
</target>
|
||||
<target name="distclean" depends="clean" />
|
||||
</project>
|
94
apps/susidns/src/css.css
Normal file
@ -0,0 +1,94 @@
|
||||
p {
|
||||
font-family:Verdana,Tahoma,Arial,Helvetica;
|
||||
color:black;
|
||||
line-height:12pt;
|
||||
margin-left:5mm;
|
||||
margin-right:5mm;
|
||||
font-size:10pt;
|
||||
}
|
||||
|
||||
span.addrhlpr {
|
||||
font-size:7pt;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family:Verdana,Tahoma,Arial,Helvetica;
|
||||
color:black;
|
||||
font-size:12pt;
|
||||
letter-spacing:2pt;
|
||||
line-height:18pt;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
color:black;
|
||||
}
|
||||
|
||||
a {
|
||||
color:#327BBF;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
th {
|
||||
font-family:Verdana,Tahoma,Arial,Helvetica;
|
||||
color:black;
|
||||
line-height:12pt;
|
||||
margin-left:5mm;
|
||||
margin-right:5mm;
|
||||
font-size:10pt;
|
||||
}
|
||||
|
||||
td {
|
||||
font-family:Verdana,Tahoma,Arial,Helvetica;
|
||||
color:black;
|
||||
line-height:12pt;
|
||||
margin-left:5mm;
|
||||
margin-right:5mm;
|
||||
font-size:10pt;
|
||||
vertical-align:center;
|
||||
}
|
||||
|
||||
li {
|
||||
font-family:Verdana,Tahoma,Arial,Helvetica;
|
||||
color:black;
|
||||
line-height:12pt;
|
||||
margin-left:5mm;
|
||||
margin-right:5mm;
|
||||
font-size:10pt;
|
||||
}
|
||||
|
||||
tr.list1 {
|
||||
background-color:#E0E0E0;
|
||||
}
|
||||
|
||||
tr.list0 {
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
p.messages {
|
||||
background-color:#92CAFF;
|
||||
color:#327BBF;
|
||||
color:black;
|
||||
border-style:dotted;
|
||||
padding-top: 5mm;
|
||||
padding-right: 5mm;
|
||||
padding-bottom: 5mm;
|
||||
padding-left: 5mm;
|
||||
}
|
||||
|
||||
#help {
|
||||
border-style:dotted;
|
||||
padding-top: 5mm;
|
||||
padding-right: 5mm;
|
||||
padding-bottom: 5mm;
|
||||
padding-left: 5mm;
|
||||
}
|
||||
|
||||
p.footer {
|
||||
font-size:7pt;
|
||||
}
|
BIN
apps/susidns/src/images/add.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
apps/susidns/src/images/delete.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
apps/susidns/src/images/how.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
apps/susidns/src/images/logo.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
apps/susidns/src/images/reload.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
apps/susidns/src/images/save.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
apps/susidns/src/images/search.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
11
apps/susidns/src/index.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url=index.jsp" />
|
||||
<title>susidns</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="index.jsp">Enter</a>
|
||||
</body>
|
||||
</html>
|
||||
|
61
apps/susidns/src/java/src/i2p/susi/dns/AddressBean.java
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
|
||||
public class AddressBean
|
||||
{
|
||||
private String name, destination;
|
||||
|
||||
public AddressBean()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AddressBean(String name, String destination)
|
||||
{
|
||||
this.name = name;
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public String getDestination()
|
||||
{
|
||||
return destination;
|
||||
}
|
||||
|
||||
public void setDestination(String destination)
|
||||
{
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.2 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class AddressByNameSorter implements Comparator
|
||||
{
|
||||
public int compare(Object arg0, Object arg1)
|
||||
{
|
||||
AddressBean a = (AddressBean)arg0;
|
||||
AddressBean b = (AddressBean)arg1;
|
||||
|
||||
if( a == null )
|
||||
return 1;
|
||||
|
||||
if( b == null )
|
||||
return -1;
|
||||
|
||||
return a.getName().compareToIgnoreCase(b.getName());
|
||||
}
|
||||
}
|
262
apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.7 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Properties;
|
||||
|
||||
public class AddressbookBean
|
||||
{
|
||||
private String book, action, serial, lastSerial, filter, search, hostname, destination;
|
||||
private Properties properties, addressbook;
|
||||
private int trClass;
|
||||
private LinkedList deletionMarks;
|
||||
private static Comparator sorter;
|
||||
|
||||
static {
|
||||
sorter = new AddressByNameSorter();
|
||||
}
|
||||
public String getSearch() {
|
||||
return search;
|
||||
}
|
||||
public void setSearch(String search) {
|
||||
this.search = search;
|
||||
}
|
||||
public boolean isHasFilter()
|
||||
{
|
||||
return filter != null && filter.length() > 0;
|
||||
}
|
||||
public void setTrClass(int trClass) {
|
||||
this.trClass = trClass;
|
||||
}
|
||||
public int getTrClass() {
|
||||
trClass = 1 - trClass;
|
||||
return trClass;
|
||||
}
|
||||
public boolean isIsEmpty()
|
||||
{
|
||||
return ! isNotEmpty();
|
||||
}
|
||||
public boolean isNotEmpty()
|
||||
{
|
||||
return addressbook != null && addressbook.size() > 0;
|
||||
}
|
||||
public AddressbookBean()
|
||||
{
|
||||
properties = new Properties();
|
||||
deletionMarks = new LinkedList();
|
||||
}
|
||||
private long configLastLoaded = 0;
|
||||
private void loadConfig()
|
||||
{
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
if( properties.size() > 0 && currentTime - configLastLoaded < 10000 )
|
||||
return;
|
||||
|
||||
try {
|
||||
properties.clear();
|
||||
properties.load( new FileInputStream( ConfigBean.configFileName ) );
|
||||
configLastLoaded = currentTime;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
|
||||
}
|
||||
}
|
||||
public String getFileName()
|
||||
{
|
||||
loadConfig();
|
||||
String filename = properties.getProperty( getBook() + "_addressbook" );
|
||||
return ConfigBean.addressbookPrefix + filename;
|
||||
}
|
||||
private Object[] entries;
|
||||
|
||||
public Object[] getEntries()
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
public String getBook()
|
||||
{
|
||||
if( book == null || ( book.compareToIgnoreCase( "master" ) != 0 &&
|
||||
book.compareToIgnoreCase( "router" ) != 0 ) &&
|
||||
book.compareToIgnoreCase( "published" ) != 0 )
|
||||
book = "master";
|
||||
|
||||
return book;
|
||||
}
|
||||
public void setBook(String book) {
|
||||
this.book = book;
|
||||
}
|
||||
public String getSerial() {
|
||||
lastSerial = "" + Math.random();
|
||||
action = null;
|
||||
return lastSerial;
|
||||
}
|
||||
public void setSerial(String serial) {
|
||||
this.serial = serial;
|
||||
}
|
||||
public String getMessages()
|
||||
{
|
||||
loadConfig();
|
||||
|
||||
String message = "";
|
||||
|
||||
if( action != null ) {
|
||||
if( lastSerial != null && serial != null && serial.compareTo( lastSerial ) == 0 ) {
|
||||
boolean changed = false;
|
||||
if( action.compareToIgnoreCase( "add") == 0 ) {
|
||||
if( addressbook != null && hostname != null && destination != null ) {
|
||||
addressbook.put( hostname, destination );
|
||||
changed = true;
|
||||
message += "Destination added.<br/>";
|
||||
}
|
||||
}
|
||||
if( action.compareToIgnoreCase( "delete" ) == 0 ) {
|
||||
Iterator it = deletionMarks.iterator();
|
||||
int deleted = 0;
|
||||
while( it.hasNext() ) {
|
||||
String name = (String)it.next();
|
||||
addressbook.remove( name );
|
||||
changed = true;
|
||||
deleted++;
|
||||
}
|
||||
if( changed ) {
|
||||
message += "" + deleted + " destination(s) deleted.<br/>";
|
||||
}
|
||||
}
|
||||
if( changed ) {
|
||||
try {
|
||||
save();
|
||||
message += "Addressbook saved.<br/>";
|
||||
} catch (Exception e) {
|
||||
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
|
||||
message += "ERROR: Could not write addressbook file.<br/>";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
message += "Invalid nonce. Are you being spoofed?";
|
||||
}
|
||||
}
|
||||
|
||||
action = null;
|
||||
|
||||
addressbook = new Properties();
|
||||
|
||||
try {
|
||||
addressbook.load( new FileInputStream( getFileName() ) );
|
||||
LinkedList list = new LinkedList();
|
||||
Enumeration e = addressbook.keys();
|
||||
while( e.hasMoreElements() ) {
|
||||
String name = (String)e.nextElement();
|
||||
String destination = addressbook.getProperty( name );
|
||||
if( filter != null && filter.length() > 0 ) {
|
||||
if( filter.compareTo( "0-9" ) == 0 ) {
|
||||
char first = name.charAt(0);
|
||||
if( first < '0' || first > '9' )
|
||||
continue;
|
||||
}
|
||||
else if( ! name.toLowerCase().startsWith( filter.toLowerCase() ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( search != null && search.length() > 0 ) {
|
||||
if( name.indexOf( search ) == -1 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
list.addLast( new AddressBean( name, destination ) );
|
||||
}
|
||||
|
||||
Object array[] = list.toArray();
|
||||
Arrays.sort( array, sorter );
|
||||
entries = array;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
|
||||
}
|
||||
|
||||
if( message.length() > 0 )
|
||||
message = "<p class=\"messages\">" + message + "</p>";
|
||||
return message;
|
||||
}
|
||||
|
||||
private void save() throws IOException
|
||||
{
|
||||
String filename = properties.getProperty( getBook() + "_addressbook" );
|
||||
|
||||
addressbook.store( new FileOutputStream( ConfigBean.addressbookPrefix + filename ), null );
|
||||
}
|
||||
public String getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public boolean isMaster()
|
||||
{
|
||||
return getBook().compareToIgnoreCase( "master" ) == 0;
|
||||
}
|
||||
public boolean isRouter()
|
||||
{
|
||||
return getBook().compareToIgnoreCase( "router" ) == 0;
|
||||
}
|
||||
public void setFilter(String filter) {
|
||||
if( filter != null && ( filter.length() == 0 || filter.compareToIgnoreCase( "none" ) == 0 ) ) {
|
||||
filter = null;
|
||||
search = null;
|
||||
}
|
||||
this.filter = filter;
|
||||
}
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
public void setDestination(String destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
public void setResetDeletionMarks( String dummy ) {
|
||||
deletionMarks.clear();
|
||||
}
|
||||
public void setMarkedForDeletion( String name ) {
|
||||
deletionMarks.addLast( name );
|
||||
}
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
}
|
157
apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.3 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ConfigBean implements Serializable {
|
||||
|
||||
/*
|
||||
* as this is not provided as constant in addressbook, we define it here
|
||||
*/
|
||||
public static String addressbookPrefix = "addressbook/";
|
||||
public static String configFileName = addressbookPrefix + "config.txt";
|
||||
|
||||
private String action, config;
|
||||
private String serial, lastSerial;
|
||||
private boolean saved;
|
||||
|
||||
public static String getConfigFileName() {
|
||||
return configFileName;
|
||||
}
|
||||
|
||||
public String getfileName() {
|
||||
return getConfigFileName();
|
||||
}
|
||||
|
||||
public boolean isSaved() {
|
||||
return saved;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getConfig()
|
||||
{
|
||||
if( config != null )
|
||||
return config;
|
||||
|
||||
reload();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private void reload()
|
||||
{
|
||||
File file = new File( configFileName );
|
||||
if( file != null && file.isFile() ) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
try {
|
||||
BufferedReader br = new BufferedReader( new FileReader( file ) );
|
||||
String line;
|
||||
while( ( line = br.readLine() ) != null ) {
|
||||
buf.append( line );
|
||||
buf.append( "\n" );
|
||||
}
|
||||
config = buf.toString();
|
||||
saved = true;
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void save()
|
||||
{
|
||||
File file = new File( configFileName );
|
||||
try {
|
||||
PrintWriter out = new PrintWriter( new FileOutputStream( file ) );
|
||||
out.print( config );
|
||||
out.flush();
|
||||
out.close();
|
||||
saved = true;
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public void setConfig(String config) {
|
||||
this.config = config;
|
||||
this.saved = false;
|
||||
|
||||
/*
|
||||
* as this is a property file we need a newline at the end of the last line!
|
||||
*/
|
||||
if( ! this.config.endsWith( "\n" ) ) {
|
||||
this.config += "\n";
|
||||
}
|
||||
}
|
||||
public String getMessages() {
|
||||
String message = "";
|
||||
if( action != null ) {
|
||||
if( lastSerial != null && serial != null && serial.compareTo( lastSerial ) == 0 ) {
|
||||
if( action.compareToIgnoreCase( "save") == 0 ) {
|
||||
save();
|
||||
message = "Configuration saved.";
|
||||
}
|
||||
else if( action.compareToIgnoreCase( "reload") == 0 ) {
|
||||
reload();
|
||||
message = "Configuration reloaded.";
|
||||
}
|
||||
}
|
||||
else {
|
||||
message = "Invalid nonce. Are you being spoofed?";
|
||||
}
|
||||
}
|
||||
if( message.length() > 0 )
|
||||
message = "<p class=\"messages\">" + message + "</p>";
|
||||
return message;
|
||||
}
|
||||
public String getSerial()
|
||||
{
|
||||
lastSerial = "" + Math.random();
|
||||
action = null;
|
||||
return lastSerial;
|
||||
}
|
||||
public void setSerial(String serial ) {
|
||||
this.serial = serial;
|
||||
}
|
||||
}
|
56
apps/susidns/src/java/src/i2p/susi/dns/Debug.java
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class Debug
|
||||
{
|
||||
private static Log _log;
|
||||
private static I2PAppContext _context;
|
||||
|
||||
static
|
||||
{
|
||||
try {
|
||||
_context = I2PAppContext.getGlobalContext(); // new I2PAppContext();
|
||||
_log = _context.logManager().getLog(Debug.class);
|
||||
}
|
||||
catch( NoClassDefFoundError e ) {
|
||||
_context = null;
|
||||
_log = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void debug( String msg )
|
||||
{
|
||||
if( _log != null ) {
|
||||
_log.debug( msg );
|
||||
}
|
||||
else {
|
||||
System.err.println( "DEBUG: [susidns] " + msg );
|
||||
}
|
||||
}
|
||||
}
|
171
apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.3 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Properties;
|
||||
|
||||
public class SubscriptionsBean
|
||||
{
|
||||
private String action, fileName, content, serial, lastSerial;
|
||||
private boolean saved;
|
||||
|
||||
Properties properties;
|
||||
|
||||
public SubscriptionsBean()
|
||||
{
|
||||
properties = new Properties();
|
||||
}
|
||||
private long configLastLoaded = 0;
|
||||
private void loadConfig()
|
||||
{
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
if( properties.size() > 0 && currentTime - configLastLoaded < 10000 )
|
||||
return;
|
||||
|
||||
try {
|
||||
properties.clear();
|
||||
properties.load( new FileInputStream( ConfigBean.configFileName ) );
|
||||
configLastLoaded = currentTime;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
|
||||
}
|
||||
}
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getFileName()
|
||||
{
|
||||
loadConfig();
|
||||
|
||||
fileName = ConfigBean.addressbookPrefix + properties.getProperty( "subscriptions", "subscriptions.txt" );
|
||||
|
||||
return fileName;
|
||||
}
|
||||
private void reload()
|
||||
{
|
||||
File file = new File( getFileName() );
|
||||
if( file != null && file.isFile() ) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
try {
|
||||
BufferedReader br = new BufferedReader( new FileReader( file ) );
|
||||
String line;
|
||||
while( ( line = br.readLine() ) != null ) {
|
||||
buf.append( line );
|
||||
buf.append( "\n" );
|
||||
}
|
||||
content = buf.toString();
|
||||
saved = true;
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void save()
|
||||
{
|
||||
File file = new File( getFileName() );
|
||||
try {
|
||||
PrintWriter out = new PrintWriter( new FileOutputStream( file ) );
|
||||
out.print( content );
|
||||
out.flush();
|
||||
out.close();
|
||||
saved = true;
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public String getMessages() {
|
||||
String message = "";
|
||||
if( action != null ) {
|
||||
if( lastSerial != null && serial != null && serial.compareTo( lastSerial ) == 0 ) {
|
||||
if( action.compareToIgnoreCase( "save") == 0 ) {
|
||||
save();
|
||||
message = "Subscriptions saved.";
|
||||
}
|
||||
else if( action.compareToIgnoreCase( "reload") == 0 ) {
|
||||
reload();
|
||||
message = "Subscriptions reloaded.";
|
||||
}
|
||||
}
|
||||
else {
|
||||
message = "Invalid nonce. Are you being spoofed?";
|
||||
}
|
||||
}
|
||||
if( message.length() > 0 )
|
||||
message = "<p class=\"messages\">" + message + "</p>";
|
||||
return message;
|
||||
}
|
||||
public String getSerial()
|
||||
{
|
||||
lastSerial = "" + Math.random();
|
||||
action = null;
|
||||
return lastSerial;
|
||||
}
|
||||
public void setSerial(String serial ) {
|
||||
this.serial = serial;
|
||||
}
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
this.saved = false;
|
||||
|
||||
/*
|
||||
* as this is a property file we need a newline at the end of the last line!
|
||||
*/
|
||||
if( ! this.content.endsWith( "\n" ) ) {
|
||||
this.content += "\n";
|
||||
}
|
||||
}
|
||||
public String getContent()
|
||||
{
|
||||
if( content != null )
|
||||
return content;
|
||||
|
||||
reload();
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
39
apps/susidns/src/java/src/i2p/susi/dns/VersionBean.java
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.4 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
|
||||
public class VersionBean {
|
||||
|
||||
private static String version = "0.4";
|
||||
private static String url = "http://susi.i2p/?i2paddresshelper=T2DU1KAz3meB0B53U8Y06-I7vHR7XmC0qXAJfLW6b-1L1FVKoySRZz4xazHAwyv2xtRpvKrv6ukLm1tThEW0zQWtZPtX8G6KkzMibD8t7IS~4yw-9VkBtUydyYfsX08AK3v~-egSW8HCXTdyIJVtrETJb337VDUHW-7D4L1JLbwSH4if2ooks6yFTrljK5aVMi-16dZOVvmoyJc3jBqSdK6kraO4gW5-vHTmbLwL498p9nug1KOg1DqgN2GeU5X1QlVrlpFb~IIfdP~O8NT7u-LAjW3jSJsMbLDHMSYTIhC7xmJIiBoi-qk8p6TLynAmvJ7HRvbx4N1EB-uJHyD16wsZkkHyEOfmXbj0ZqLyKEGb3thPwCz-M9v~c2Qt3WbwjXJAtHpjlHkdJ4Fg91cX2oak~JoapnPf6Syw8hko5syf6VVoCYLnrrYyM8oGl8mLclHkj~VCidQNqMSM74IhrHfK6HmRikqtZBexb5M6wfMTTqBvaHURdD21GOpFKYBUAAAA";
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
168
apps/susidns/src/jsp/addressbook.jsp
Normal file
@ -0,0 +1,168 @@
|
||||
<%
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application" />
|
||||
<jsp:useBean id="book" class="i2p.susi.dns.AddressbookBean" scope="session" />
|
||||
<jsp:setProperty name="book" property="*" />
|
||||
<jsp:setProperty name="book" property="resetDeletionMarks" value="1"/>
|
||||
<c:forEach items="${paramValues.checked}" var="checked">
|
||||
<jsp:setProperty name="book" property="markedForDeletion" value="${checked}"/>
|
||||
</c:forEach>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>${book.book} addressbook - susidns v${version.version}</title>
|
||||
<link rel="stylesheet" type="text/css" href="css.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="logo">
|
||||
<img src="images/logo.png" alt="susidns logo" border="0"/>
|
||||
</div>
|
||||
|
||||
<div id="navi">
|
||||
<p>addressbooks
|
||||
<a href="addressbook.jsp?book=master">master</a> |
|
||||
<a href="addressbook.jsp?book=router">router</a> |
|
||||
<a href="addressbook.jsp?book=published">published</a> *
|
||||
<a href="subscriptions.jsp">subscriptions</a> *
|
||||
<a href="config.jsp">configuration</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="headline">
|
||||
<h3>${book.book} addressbook at ${book.fileName}</h3>
|
||||
</div>
|
||||
|
||||
<div id="messages">${book.messages}</div>
|
||||
|
||||
<div id="filter">
|
||||
<p>Filter: <a href="addressbook.jsp?filter=a">a</a>
|
||||
<a href="addressbook.jsp?filter=b">b</a>
|
||||
<a href="addressbook.jsp?filter=c">c</a>
|
||||
<a href="addressbook.jsp?filter=d">d</a>
|
||||
<a href="addressbook.jsp?filter=e">e</a>
|
||||
<a href="addressbook.jsp?filter=f">f</a>
|
||||
<a href="addressbook.jsp?filter=g">g</a>
|
||||
<a href="addressbook.jsp?filter=h">h</a>
|
||||
<a href="addressbook.jsp?filter=i">i</a>
|
||||
<a href="addressbook.jsp?filter=j">j</a>
|
||||
<a href="addressbook.jsp?filter=k">k</a>
|
||||
<a href="addressbook.jsp?filter=l">l</a>
|
||||
<a href="addressbook.jsp?filter=m">m</a>
|
||||
<a href="addressbook.jsp?filter=n">n</a>
|
||||
<a href="addressbook.jsp?filter=o">o</a>
|
||||
<a href="addressbook.jsp?filter=p">p</a>
|
||||
<a href="addressbook.jsp?filter=q">q</a>
|
||||
<a href="addressbook.jsp?filter=r">r</a>
|
||||
<a href="addressbook.jsp?filter=s">s</a>
|
||||
<a href="addressbook.jsp?filter=t">t</a>
|
||||
<a href="addressbook.jsp?filter=u">u</a>
|
||||
<a href="addressbook.jsp?filter=v">v</a>
|
||||
<a href="addressbook.jsp?filter=w">w</a>
|
||||
<a href="addressbook.jsp?filter=x">x</a>
|
||||
<a href="addressbook.jsp?filter=y">y</a>
|
||||
<a href="addressbook.jsp?filter=z">z</a>
|
||||
<a href="addressbook.jsp?filter=0-9">0-9</a>
|
||||
<a href="addressbook.jsp?filter=none">all</a></p>
|
||||
<c:if test="${book.hasFilter}">
|
||||
<p>Current filter: ${book.filter}</p>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="addressbook.jsp">
|
||||
<div id="search">
|
||||
<table><tr>
|
||||
<td class="search">Search: <input type="text" name="search" value="${book.search}" size="20" /></td>
|
||||
<td class="search"><input type="image" src="images/search.png" name="submitsearch" value="search" alt="Search" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<form method="POST" action="addressbook.jsp">
|
||||
<input type="hidden" name="serial" value="${book.serial}"/>
|
||||
|
||||
<c:if test="${book.notEmpty}">
|
||||
|
||||
<div id="book">
|
||||
<jsp:setProperty name="book" property="trClass" value="0" />
|
||||
<table class="book" cellspacing="0" cellpadding="5">
|
||||
<tr class="head">
|
||||
|
||||
<c:if test="${book.master || book.router}">
|
||||
<th> </th>
|
||||
</c:if>
|
||||
|
||||
<th>Name</th>
|
||||
<th>Destination</th>
|
||||
</tr>
|
||||
<c:forEach items="${book.entries}" var="addr">
|
||||
<tr class="list${book.trClass}">
|
||||
<c:if test="${book.master || book.router}">
|
||||
<td class="checkbox"><input type="checkbox" name="checked" value="${addr.name}" alt="Mark for deletion"></td>
|
||||
</c:if>
|
||||
<td class="names"><a href="http://${addr.name}/">${addr.name}</a> -
|
||||
<span class="addrhlpr"><a href="http://${addr.name}/?i2paddresshelper=${addr.destination}">(addrhlpr)</a></span>
|
||||
</td>
|
||||
<td class="destinations"><input type="text" name="dest_${addr.name}" value="${addr.destination}" size="20"></td>
|
||||
</tr>
|
||||
</c:forEach>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<c:if test="${book.master || book.router}">
|
||||
<div id="buttons">
|
||||
<p class="buttons"><input type="image" name="action" value="delete" src="images/delete.png" alt="Delete checked" />
|
||||
</p>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
</c:if>
|
||||
|
||||
<c:if test="${book.isEmpty}">
|
||||
<div id="book">
|
||||
<p class="book">The ${book.book} addressbook is empty.</p>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<div id="add">
|
||||
<p class="add">
|
||||
<h3>Add new destination:</h3>
|
||||
Hostname: <input type="text" name="hostname" value="" size="20"> Destination: <input type="text" name="destination" value="" size="20"><br/>
|
||||
<input type="image" name="action" value="add" src="images/add.png" alt="Add destination" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<div id="footer">
|
||||
<p class="footer">susidns v${version.version} © <a href="${version.url}">susi</a> 2005</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
96
apps/susidns/src/jsp/config.jsp
Normal file
@ -0,0 +1,96 @@
|
||||
<%
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.14 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application"/>
|
||||
<jsp:useBean id="cfg" class="i2p.susi.dns.ConfigBean" scope="session"/>
|
||||
<jsp:setProperty name="cfg" property="*" />
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>configuration - susidns v${version.version}</title>
|
||||
<link rel="stylesheet" type="text/css" href="css.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="logo">
|
||||
<img src="images/logo.png" alt="susidns logo" border="0"/>
|
||||
</div>
|
||||
<div id="navi">
|
||||
<p>
|
||||
addressbooks
|
||||
<a href="addressbook.jsp?book=master">master</a> |
|
||||
<a href="addressbook.jsp?book=router">router</a> |
|
||||
<a href="addressbook.jsp?book=published">published</a> *
|
||||
<a href="subscriptions.jsp">subscriptions</a> *
|
||||
<a href="config.jsp">configuration</a>
|
||||
</p>
|
||||
</div>
|
||||
<div id="headline">
|
||||
<h3>${cfg.fileName}</h3>
|
||||
</div>
|
||||
<div id="messages">${cfg.messages}</div>
|
||||
<form method="POST" action="config.jsp">
|
||||
<div id="config">
|
||||
<input type="hidden" name="serial" value="${cfg.serial}" />
|
||||
<textarea name="config" rows="10" cols="80">${cfg.config}</textarea>
|
||||
</div>
|
||||
<div id="buttons">
|
||||
<input type="image" src="images/save.png" name="action" value="save" alt="Save Config"/>
|
||||
<input type="image" src="images/reload.png" name="action" value="reload" alt="Reload Config"/>
|
||||
</div>
|
||||
</form>
|
||||
<div id="help">
|
||||
<h3>Hints</h3>
|
||||
<ol>
|
||||
<li>All file or directory paths here are relative to the addressbooks working directory, which normally
|
||||
is located at $I2P/addressbook/.</li>
|
||||
<li>If you want to manually add lines to an addressbook, add them to the master addressbook. The router
|
||||
addressbook and the published addressbook are overwritten by the addressbook application.</li>
|
||||
<li><b>Important:</b>When you publish your addressbook, <b>ALL</b> destinations appear there, even those
|
||||
from your master addressbook. Unfortunately the master addressbook points to your userhosts.txt, which was
|
||||
used for private destinations before. So if you want to keep the destinations in your userhosts.txt secret,
|
||||
please change the master addressbook to a different file before turning on addressbook publishing.</li>
|
||||
</ol>
|
||||
<h3>Options</h3>
|
||||
<ul>
|
||||
<li><b>subscriptions</b> - file containing the list of subscriptions URLs (no need to change)</li>
|
||||
<li><b>update_delay</b> - update interval in hours (no need to change)</li>
|
||||
<li><b>published_addressbook</b> - your public hosts.txt file (choose a path within your webserver document root)</li>
|
||||
<li><b>router_addressbook</b> - your hosts.txt (no need to change)</li>
|
||||
<li><b>master_addressbook</b> - your personal addressbook, it gets never overwritten by the addressbook</li>
|
||||
<li><b>proxy_port</b> - http port for your eepProxy (no need to change)</li>
|
||||
<li><b>proxy_host</b> - hostname for your eepProxy (no need to change)</li>
|
||||
<li><b>should_publish</b> - true/false whether to write the published addressbook</li>
|
||||
<li><b>etags</b> - file containing the etags header from the fetched subscription URLs (no need to change)</li>
|
||||
<li><b>last_modified</b> - file containing the modification timestamp for each fetched subscription URL (no need to change)</li>
|
||||
<li><b>log</b> - file to log activity to (change to /dev/null if you like)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<p class="footer">susidns v${version.version} © <a href="${version.url}">susi</a> 2005 </p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
80
apps/susidns/src/jsp/index.jsp
Normal file
@ -0,0 +1,80 @@
|
||||
<%
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application" />
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>introduction - susidns v${version.version}</title>
|
||||
<link rel="stylesheet" type="text/css" href="css.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="logo">
|
||||
<img src="images/logo.png" alt="susidns logo" border="0"/>
|
||||
</div>
|
||||
|
||||
<div id="navi">
|
||||
<p>addressbooks
|
||||
<a href="addressbook.jsp?book=master">master</a> |
|
||||
<a href="addressbook.jsp?book=router">router</a> |
|
||||
<a href="addressbook.jsp?book=published">published</a> *
|
||||
<a href="subscriptions.jsp">subscriptions</a> *
|
||||
<a href="config.jsp">configuration</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h3>Huh? what addressbook?</h3>
|
||||
<p>
|
||||
The addressbook application is part of your i2p installation. It regularly updates your hosts.txt file
|
||||
from distributed sources. It keeps your hosts.txt up to date, so it automatically contains all new
|
||||
eepsites announced on <a href="http://orion.i2p">orion</a>
|
||||
or in the <a href="http://forum.i2p/viewforum.php?f=16">forum</a>.
|
||||
</p>
|
||||
<p>
|
||||
(To speak the truth: In its default configuration the addressbook does not poll
|
||||
orion, but dev.i2p only. Subscribing to <a href="http://orion.i2p">orion</a> is an easy task,
|
||||
just add <a href="http://orion.i2p/hosts.txt">http://orion.i2p/hosts.txt</a> to your <a href="subscriptions.jsp">subscriptions</a> file.)
|
||||
</p>
|
||||
<p>If you have questions about naming in i2p, there is an excellent <a href="http://forum.i2p.net/viewtopic.php?t=134">introduction</a>
|
||||
from duck in the forum.</p>
|
||||
<h3>How does the addressbook work?</h3>
|
||||
<p>The addressbook application regularly (normally once per hour) polls your subscriptions and merges their content
|
||||
into your so called router addressbook (normally your plain hosts.txt). Then it merges your so called master addressbook (normally
|
||||
your userhosts.txt) into the router addressbook as well. If configured the router addressbook is now written to the so published addressbook,
|
||||
which is a publicly available copy of your hosts.txt somewhere in your eepsites document root. (Yes, this means that, with activated publication,
|
||||
your once private keys from userhosts.txt now are publicly available for everybody.)
|
||||
</p>
|
||||
<p><img src="images/how.png" border="0" alt="addressbook working scheme"/></p>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p class="footer">susidns v${version.version} © <a href="${version.url}">susi</a> 2005</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
78
apps/susidns/src/jsp/subscriptions.jsp
Normal file
@ -0,0 +1,78 @@
|
||||
<%
|
||||
/*
|
||||
* Created on Sep 02, 2005
|
||||
*
|
||||
* This file is part of susidns project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.7 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application" />
|
||||
<jsp:useBean id="subs" class="i2p.susi.dns.SubscriptionsBean" scope="session" />
|
||||
<jsp:setProperty name="subs" property="*" />
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>subscriptions - susidns v${version.version}</title>
|
||||
<link rel="stylesheet" type="text/css" href="css.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="logo">
|
||||
<img src="images/logo.png" alt="susidns logo" border="0"/>
|
||||
</div>
|
||||
<div id="navi">
|
||||
<p>addressbooks
|
||||
<a href="addressbook.jsp?book=master">master</a> |
|
||||
<a href="addressbook.jsp?book=router">router</a> |
|
||||
<a href="addressbook.jsp?book=published">published</a> *
|
||||
<a href="subscriptions.jsp">subscriptions</a> *
|
||||
<a href="config.jsp">configuration</a>
|
||||
</p>
|
||||
</div>
|
||||
<div id="headline">
|
||||
<h3>${subs.fileName}</h3>
|
||||
</div>
|
||||
<div id="messages">${subs.messages}</div>
|
||||
<form method="POST" action="subscriptions.jsp">
|
||||
<div id="content">
|
||||
<input type="hidden" name="serial" value="${subs.serial}" />
|
||||
<textarea name="content" rows="10" cols="80">${subs.content}</textarea>
|
||||
</div>
|
||||
<div id="buttons">
|
||||
<input type="image" src="images/save.png" name="action" value="save" alt="Save Subscriptions" />
|
||||
<input type="image" src="images/reload.png" name="action" value="reload" alt="Reload Subscriptions" />
|
||||
</div>
|
||||
</form>
|
||||
<div id="help">
|
||||
<h3>Explanation</h3>
|
||||
<p class="help">
|
||||
The subscription file contains a list of (i2p) URLs. The addressbook application
|
||||
regularly (once per hour) checks this list for new eepsites. Those URLs simply contain the published hosts.txt
|
||||
file of other people. Default subscription is the hosts.txt from dev.i2p. The most
|
||||
popular collaboration site for eepsite is orion.i2p. So its a good idea to add http://orion.i2p/hosts.txt
|
||||
as a 2nd subscription.
|
||||
</p>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<p class="footer">susidns v${version.version} © <a href="${version.url}">susi</a> 2005</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
31
apps/syndie/doc/intro.sml
Normal file
@ -0,0 +1,31 @@
|
||||
Syndie is a new effort to build a user friendly secure blogging tool, exploiting the capabilities offered by anonymity and security systems such as [link schema="web" location="http://www.i2p.net/"]I2P[/link], [link schema="web" location="http://tor.eff.org/"]TOR[/link], [link schema="web" location="http://www.freenetproject.org/"]Freenet[/link], [link schema="web" location="http://www.mnetproject.org/"]MNet[/link], and others. Abstracting away the content distribution side, Syndie allows people to [b]build content and communities[/b] that span technologies rather than tying oneself down to the ups and downs of any particular network.
|
||||
|
||||
[cut][/cut]Syndie is working to take the technologies of the security, anonymity, and cryptography worlds and merge them with the simplicity and user focus of the blogging world. From the user's standpoint, you could perhaps view Syndie as a distributed [link schema="web" location="http://www.livejournal.com"]LiveJournal[/link], while technically Syndie is much, much simpler.
|
||||
|
||||
[b]How Syndie works[/b][hr][/hr]The [i]magic[/i] behind Syndie's abstraction is to ignore any content distribution issues and merely assume data moves around as necessary. Each Syndie instance runs agains the filesystem, verifying and indexing blogs and offering up what it knows to the user through a web interface. The core idea in Syndie, therefore, is the [b]archive[/b]- a collection of blogs categorized and ready for consumption.
|
||||
|
||||
Whenever someone reads or posts to a Syndie instance, it is working with the [b]local archive[/b]. However, as Syndie's development progresses, people will be able to read [b]remote archives[/b] - pulling the archive summary from an I2P [i]eepsite[/i], TOR [i]hosted service[/i], Freenet [i]Freesite[/i], MNet [i]key[/i], or (with a little less glamor) usenet, filesharing apps, or the web. The first thing Syndie needs to use a remote archive is the archive's index - a plain text file summarizing what the archive contains ([attachment id="0"]an example[/attachment]). From that, Syndie will let the user browse through the blogs, pulling the individual blog posts into the local archive when necessary.
|
||||
|
||||
[b]Posting[/b][hr][/hr]Creating and posting to blogs with Syndie is trivial - simply log in to Syndie, click on the [i]Post[/i] button, and fill out the form offered. Syndie handles all of the encryption and formatting details - packaging up the post with any attached files into a single signed, compressed, and potentially encrypted bundle, storing it in the local archive and capable of being shared with other Syndie users. Every blog is identified by its public key behind the scenes, so there is no need for a central authority to require that your blogs are all named uniquely or any other such thing.
|
||||
|
||||
While each blog is run by a single author, they can in turn allow other authors to post to the blog while still letting readers know that the post is authorized (though created by a different author). Of course, if multiple people wanted to run a single blog and make it look like only one person wrote it, they could share the blog's private keys.
|
||||
|
||||
[b]Tags[/b][hr][/hr]Following the lessons from the last few years, every Syndie entry has any number of tags associated with it by the author, allowing trivial categorization and filtering.
|
||||
|
||||
[b]Hosting[/b][hr][/hr]While in many scenarios it is best for people to run Syndie locally on their machine, Syndie is a fully multiuser system so anyone can be a Syndie hosting provider by simply exposing the web interface to the public. The Syndie host's operator can password protect the blog registration interface so only authorized people can create a blog, and the operator can technically go through and delete blog posts or even entire blogs from their local archive. A public Syndie host can be a general purpose blog repository, letting anyone sign up (following the blogger and geocities path), be a more community oriented blog repository, requiring people to introduce you to the host to sign up (following the livejournal/orkut path), be a more focused blog repository, requiring posts to stay within certain guidelines (following the indymedia path), or even to fit specialized needs by picking and choosing among the best blogs and posts out there, offering the operator's editorial flare into a comprehensive collection.
|
||||
|
||||
[b]Syndication[/b][hr][/hr]By itself, Syndie is a nice blogging community system, but its real strength as a tool for individual and community empowerment comes when blogs are shared. While Syndie does not aim to be a content distribution network, it does want to exploit them to allow those who require their message to get out to do so. By design, syndicating Syndie can be done with some of the most basic tools - simply pass around the self authenticating files written to the archive and you're done. The archive itself is organized so that you can expose it as an indexed directory in some webserver and let people wget against it, picking to pull individual posts, all posts within a blog, all posts since a given date, or all posts in all blogs. With a very small shell script, you could parse the plain text archive summary to pull posts by size and tag as well. People could offer up their archives as rsync repositories or package up tarballs/zipfiles of blogs or entries - simply grabbing them and extracting them into your local Syndie archive would instantly give you access to all of the content therein.
|
||||
|
||||
Of course, manual syndication as described above has... limits. When appropriate, Syndie will tie in to content syndication systems such as [link schema="eep" location="http://feedspace.i2p/"]Feedspace[/link] (or even good ol' Usenet) to automatically import (and export) posts. Integration with content distribution networks like Freenet and MNet will allow the user to periodically grab a published archive index and pull down blogs as necessary. Posting archives and blogs to those networks will be done trivially as well, though they do still depend upon a polling paradigm.
|
||||
|
||||
[b]SML[/b][hr][/hr]Syndie is meant to work securely with any browser regardless of the browser's security. Blog entries are written in [b]SML[/b] [i](Syndie or Secure Markup Language)[/i] with a bbcode-linke syntax, extended to exploit some of Syndie's capabilities and context. In addition to the SML content in a blog entry, there can be any number of attachments, references to other blogs/posts/tags, nym<->public key mappings (useful for I2P host distribution), references to archives of blogs (on eepsites, freesites, etc), links to various resources, and more.
|
||||
|
||||
[b]Future[/b][hr][/hr]Down the road, there are lots of things to improve with Syndie. The interface, of course, is critical, as are tools for SML authoring and improvements to SML itself to offer a more engaging user experience. Integration with a search engine like Lucene would allow full text search through entire archives, and Atom/RSS interfaces would allow trivial import and export to existing clients. Even further, blogs could be transparently encrypted, allowing only authorized users (those with the key) to read entries posted to them (or even know what attachments are included). Integration with existing blogging services (such as [link schema="web" location="http://www.anonyblog.com"]anonyblog[/link], [link schema="web" location="http://blo.gs"]blo.gs[/link], and [link schema="web" location="http://livejournal.com"]livejournal[/link]) may also be explored. Of course, bundling with I2P and other anonymity, security, and community systems will be pursued.
|
||||
|
||||
[b]Who/where/when/why[/b][hr][/hr]The base Syndie system was written in a few days by [blog name="jrandom" bloghash="ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" archive0="eep://dev.i2p/~jrandom" archive1="http://dev.i2p.net/~jrandom" archive2="mailto://jrandom@i2p.net"][/blog], though comes out of discussions with [link schema="eep" location="http://frosk.i2p"]Frosk[/link] and many others in the I2P community. Yes, this is an incarnation of [b]MyI2P[/b] (or for those who remember jrand0m's flog, [b]Flogger[/b]).
|
||||
|
||||
All of the Syndie code is of course open source and released into the public domain (the [i]real[/i] "free as in freedom"), though it does use some BSD licensed cryptographic routines and an Apache licensed file upload component. Contributions of code are very much welcome - the source is located within the [link schema="web" location="http://www.i2p.net/cvs"]I2P codebase[/link]. Of course, those who cannot or choose not to contribute code are encouraged to [b]use[/b] Syndie - create a blog, create some content, read some content! For those who really want to though, financial contributions to the Syndie development effort can be channeled through the [link schema="web" location="http://www.i2p.net/donate"]I2P fund[/link] (donations for Syndie are distributed to Syndie developers from time to time).
|
||||
|
||||
The "why" of Syndie is a much bigger question, though is hopefully self-evident. We need kickass anonymity-aware client applications so that we can get better anonymity (since without kickass clients, we don't have many users). We also need kickass tools for safe blogging, since there are limits to the strength offered by low latency anonymity systems like I2P and TOR - Syndie goes beyond them to offer an interface to mid and high latency anonymous systems while exploiting their capabilities for fast and efficient syndication.
|
||||
|
||||
Oh, and jrandom also lost his blog's private key, so needed something to blog with again.
|
27
apps/syndie/doc/readme.txt
Normal file
@ -0,0 +1,27 @@
|
||||
To install this base instance:
|
||||
|
||||
mkdir lib
|
||||
cp ../lib/i2p.jar lib/
|
||||
cp ../lib/commons-el.jar lib/
|
||||
cp ../lib/commons-logging.jar lib/
|
||||
cp ../lib/jasper-compiler.jar lib/
|
||||
cp ../lib/jasper-runtime.jar lib/
|
||||
cp ../lib/javax.servlet.jar lib/
|
||||
cp ../lib/jbigi.jar lib/
|
||||
cp ../lib/org.mortbay.jetty.jar lib/
|
||||
cp ../lib/xercesImpl.jar lib/
|
||||
|
||||
To run it:
|
||||
sh run.sh
|
||||
firefox http://localhost:7653/syndie/
|
||||
|
||||
You can share your archive at http://localhost:7653/ so
|
||||
that people can syndicate off you via
|
||||
cd archive ; wget -m -nH http://yourmachine:7653/
|
||||
|
||||
You may want to add a password on the registration form
|
||||
so that you have control over who can create blogs via /syndie/.
|
||||
To do so, set the password in the run.sh script.
|
||||
|
||||
Windows users:
|
||||
write your own instructions. We're alpha, here ;)
|
41
apps/syndie/doc/sml.sml
Normal file
@ -0,0 +1,41 @@
|
||||
[cut]A brief glance at SML[/cut]
|
||||
[b]General rules[/b]
|
||||
|
||||
Newlines are newlines are newlines. If you include a newline in your SML, you'll get a newline in the rendered HTML.
|
||||
|
||||
All < and > characters are replaced by their HTML entity counterparts.
|
||||
|
||||
All SML tags are enclosed with [[ and ]] (e.g. [[b]]bold stuff[[/b]]). ([[ and ]] characters are quoted by [[[[ and ]]]], respectively)
|
||||
|
||||
Nesting SML tags is [b]not[/b] currently supported (though will be at a later date).
|
||||
|
||||
All SML tags must have a beginning and end tag (even for ones without any 'body', such as [[hr]][[/hr]]). This restriction may be removed later.
|
||||
|
||||
Simple formatting tags behave as expected: [[b]], [[i]], [[u]], [[h1]] through [[h5]], [[hr]], [[pre]].
|
||||
[hr][/hr]
|
||||
[b]Tag details[/b]
|
||||
|
||||
* To cut an entry so that the summary is before while the details are afterwards:
|
||||
[[cut]]more inside...[[/cut]]
|
||||
|
||||
* To load an attachment as an image with "syndie's logo" as the alternate text:
|
||||
[[img attachment="0"]]syndie's logo[[/img]]
|
||||
|
||||
* To add a download link to an attachment:
|
||||
[[attachment id="0"]]anchor text[[/img]]
|
||||
|
||||
* To quote someone:
|
||||
[[quote author="who you are quoting" location="blog://ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/1234567890"]]stuff they said[[/quote]]
|
||||
|
||||
* To sample some code:
|
||||
[[code location="eep://dev.i2p/cgi-bin/cvsweb.cgi/i2p/index.html"]]<html>[[/code]]
|
||||
|
||||
* To link to a [blog name="jrandom" bloghash="ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" blogentry="1124402137773" archive0="eep://dev.i2p/~jrandom/archive" archive1="irc2p://jrandom@irc.postman.i2p/#i2p"]bitchin' blog[/blog]:
|
||||
[[blog name="the blogs name" bloghash="ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" blogtag="tag" blogentry="123456789" archive0="eep://dev.i2p/~jrandom/archive/" archive1="freenet://SSK@blah/archive//"]]description of the blog[[/blog]]. blogentry and blogtag are optional and there can be any number of archiveN locations specified.
|
||||
|
||||
* To link to an [link schema="eep" location="http://dev.i2p/"]external resource[/link]:
|
||||
[[link schema="eep" location="http://dev.i2p/"]]link to it[[/link]].
|
||||
[i]The schema should be a network selection tool, such as "eep" for an eepsite, "tor" for a tor hidden service, "web" for a normal website, "freenet" for a freenet key, etc. The local user's Syndie configuration should include information necessary for the user to access the content referenced through the given schemas.[/i]
|
||||
|
||||
* To pass an [address name="dev.i2p" schema="eep" location="NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA"]addressbook entry[/address]:
|
||||
[[address name="dev.i2p" schema="eep" location="NF2...AAAA"]]add it[[/address]].
|
101
apps/syndie/java/build.xml
Normal file
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="syndie">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../jetty/" target="build" />
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/syndie.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.syndie.CLI" />
|
||||
<attribute name="Class-Path" value="i2p.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<ant target="war" />
|
||||
</target>
|
||||
<target name="war" depends="builddep, compile, precompilejsp">
|
||||
<war destfile="../syndie.war" webxml="../jsp/web-out.xml">
|
||||
<fileset dir="../jsp/" includes="**/*" excludes=".nbintdb, web.xml, web-out.xml, web-fragment.xml, **/*.java, **/*.jsp" />
|
||||
<classes dir="./build/obj" />
|
||||
</war>
|
||||
</target>
|
||||
<target name="precompilejsp">
|
||||
<delete dir="../jsp/WEB-INF/" />
|
||||
<delete file="../jsp/web-fragment.xml" />
|
||||
<delete file="../jsp/web-out.xml" />
|
||||
<mkdir dir="../jsp/WEB-INF/" />
|
||||
<mkdir dir="../jsp/WEB-INF/classes" />
|
||||
<!-- there are various jspc ant tasks, but they all seem a bit flakey -->
|
||||
<java classname="org.apache.jasper.JspC" fork="true" >
|
||||
<classpath>
|
||||
<pathelement location="../../jetty/jettylib/jasper-compiler.jar" />
|
||||
<pathelement location="../../jetty/jettylib/jasper-runtime.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/ant.jar" />
|
||||
<pathelement location="build/obj" />
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</classpath>
|
||||
<arg value="-d" />
|
||||
<arg value="../jsp/WEB-INF/classes" />
|
||||
<arg value="-p" />
|
||||
<arg value="net.i2p.syndie.jsp" />
|
||||
<arg value="-webinc" />
|
||||
<arg value="../jsp/web-fragment.xml" />
|
||||
<arg value="-webapp" />
|
||||
<arg value="../jsp/" />
|
||||
</java>
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="../jsp/WEB-INF/classes/" srcdir="../jsp/WEB-INF/classes" includes="**/*.java" >
|
||||
<classpath>
|
||||
<pathelement location="../../jetty/jettylib/jasper-runtime.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="build/obj" />
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
<copy file="../jsp/web.xml" tofile="../jsp/web-out.xml" />
|
||||
<loadfile property="jspc.web.fragment" srcfile="../jsp/web-fragment.xml" />
|
||||
<replace file="../jsp/web-out.xml">
|
||||
<replacefilter token="<!-- precompiled servlets -->" value="${jspc.web.fragment}" />
|
||||
</replace>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="syndie" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
428
apps/syndie/java/src/net/i2p/syndie/Archive.java
Normal file
@ -0,0 +1,428 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.text.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
|
||||
/**
|
||||
* Store blog info in the local filesystem.
|
||||
*
|
||||
* Entries are stored under:
|
||||
* $rootDir/$h(blogKey)/$entryId.snd (the index lists them as YYYYMMDD_n_jKB)
|
||||
* Blog info is stored under:
|
||||
* $rootDir/$h(blogKey)/meta.snm
|
||||
* Archive summary is stored under
|
||||
* $rootDir/archive.txt
|
||||
* Any key=value pairs in
|
||||
* $rootDir/archiveHeaders.txt
|
||||
* are injected into the archive.txt on regeneration.
|
||||
*
|
||||
* When entries are loaded for extraction/verification/etc, their contents are written to
|
||||
* $cacheDir/$h(blogKey)/$entryId/ (e.g. $cacheDir/$h(blogKey)/$entryId/entry.sml)
|
||||
*/
|
||||
public class Archive {
|
||||
private I2PAppContext _context;
|
||||
private File _rootDir;
|
||||
private File _cacheDir;
|
||||
private Map _blogInfo;
|
||||
private ArchiveIndex _index;
|
||||
private EntryExtractor _extractor;
|
||||
private String _defaultSelector;
|
||||
|
||||
public static final String METADATA_FILE = "meta.snm";
|
||||
public static final String INDEX_FILE = "archive.txt";
|
||||
public static final String HEADER_FILE = "archiveHeaders.txt";
|
||||
|
||||
private static final FilenameFilter _entryFilenameFilter = new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) { return name.endsWith(".snd"); }
|
||||
};
|
||||
|
||||
public Archive(I2PAppContext ctx, String rootDir, String cacheDir) {
|
||||
_context = ctx;
|
||||
_rootDir = new File(rootDir);
|
||||
if (!_rootDir.exists())
|
||||
_rootDir.mkdirs();
|
||||
_cacheDir = new File(cacheDir);
|
||||
if (!_cacheDir.exists())
|
||||
_cacheDir.mkdirs();
|
||||
_blogInfo = new HashMap();
|
||||
_index = null;
|
||||
_extractor = new EntryExtractor(ctx);
|
||||
_defaultSelector = ctx.getProperty("syndie.defaultSelector");
|
||||
if (_defaultSelector == null) _defaultSelector = "";
|
||||
reloadInfo();
|
||||
}
|
||||
|
||||
public void reloadInfo() {
|
||||
File f[] = _rootDir.listFiles();
|
||||
List info = new ArrayList();
|
||||
for (int i = 0; i < f.length; i++) {
|
||||
if (f[i].isDirectory()) {
|
||||
File meta = new File(f[i], METADATA_FILE);
|
||||
if (meta.exists()) {
|
||||
BlogInfo bi = new BlogInfo();
|
||||
try {
|
||||
bi.load(new FileInputStream(meta));
|
||||
if (bi.verify(_context)) {
|
||||
info.add(bi);
|
||||
} else {
|
||||
System.err.println("Invalid blog (but we're storing it anyway): " + bi);
|
||||
new Exception("foo").printStackTrace();
|
||||
info.add(bi);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (_blogInfo) {
|
||||
_blogInfo.clear();
|
||||
for (int i = 0; i < info.size(); i++) {
|
||||
BlogInfo bi = (BlogInfo)info.get(i);
|
||||
_blogInfo.put(bi.getKey().calculateHash(), bi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getDefaultSelector() { return _defaultSelector; }
|
||||
public void setDefaultSelector(String sel) {
|
||||
if (sel == null)
|
||||
_defaultSelector = "";
|
||||
else
|
||||
_defaultSelector = sel;
|
||||
}
|
||||
|
||||
public BlogInfo getBlogInfo(BlogURI uri) {
|
||||
if (uri == null) return null;
|
||||
synchronized (_blogInfo) {
|
||||
return (BlogInfo)_blogInfo.get(uri.getKeyHash());
|
||||
}
|
||||
}
|
||||
public BlogInfo getBlogInfo(Hash key) {
|
||||
synchronized (_blogInfo) {
|
||||
return (BlogInfo)_blogInfo.get(key);
|
||||
}
|
||||
}
|
||||
public boolean storeBlogInfo(BlogInfo info) {
|
||||
if (!info.verify(_context)) {
|
||||
System.err.println("Not storing the invalid blog " + info);
|
||||
new Exception("foo!").printStackTrace();
|
||||
return false;
|
||||
}
|
||||
boolean isNew = true;
|
||||
synchronized (_blogInfo) {
|
||||
BlogInfo old = (BlogInfo)_blogInfo.get(info.getKey().calculateHash());
|
||||
if ( (old == null) || (old.getEdition() < info.getEdition()) )
|
||||
_blogInfo.put(info.getKey().calculateHash(), info);
|
||||
else
|
||||
isNew = false;
|
||||
}
|
||||
if (!isNew) return true; // valid entry, but not stored, since its old
|
||||
try {
|
||||
File blogDir = new File(_rootDir, info.getKey().calculateHash().toBase64());
|
||||
blogDir.mkdirs();
|
||||
File blogFile = new File(blogDir, "meta.snm");
|
||||
FileOutputStream out = new FileOutputStream(blogFile);
|
||||
info.write(out);
|
||||
out.close();
|
||||
System.out.println("Blog info written to " + blogFile.getPath());
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List listBlogs() {
|
||||
synchronized (_blogInfo) {
|
||||
return new ArrayList(_blogInfo.values());
|
||||
}
|
||||
}
|
||||
|
||||
private File getEntryDir(File entryFile) {
|
||||
String name = entryFile.getName();
|
||||
if (!name.endsWith(".snd")) throw new RuntimeException("hmm, why are we trying to get an entry dir for " + entryFile.getAbsolutePath());
|
||||
String blog = entryFile.getParentFile().getName();
|
||||
File blogDir = new File(_cacheDir, blog);
|
||||
return new File(blogDir, name.substring(0, name.length()-4));
|
||||
//return new File(entryFile.getParentFile(), "." + name.substring(0, name.length()-4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expensive operation, reading all entries within the blog and parsing out the tags.
|
||||
* Whenever possible, query the index instead of the archive
|
||||
*
|
||||
*/
|
||||
public List listTags(Hash blogKeyHash) {
|
||||
List rv = new ArrayList();
|
||||
BlogInfo info = getBlogInfo(blogKeyHash);
|
||||
if (info == null)
|
||||
return rv;
|
||||
|
||||
File blogDir = new File(_rootDir, Base64.encode(blogKeyHash.getData()));
|
||||
File entries[] = blogDir.listFiles(_entryFilenameFilter);
|
||||
for (int j = 0; j < entries.length; j++) {
|
||||
try {
|
||||
File entryDir = getEntryDir(entries[j]);
|
||||
EntryContainer entry = null;
|
||||
if (entryDir.exists())
|
||||
entry = getCachedEntry(entryDir);
|
||||
if ( (entry == null) || (!entryDir.exists()) ) {
|
||||
if (!extractEntry(entries[j], entryDir, info)) {
|
||||
System.err.println("Entry " + entries[j].getPath() + " is not valid");
|
||||
new Exception("foo!!").printStackTrace();
|
||||
continue;
|
||||
}
|
||||
entry = getCachedEntry(entryDir);
|
||||
}
|
||||
String tags[] = entry.getTags();
|
||||
for (int t = 0; t < tags.length; t++) {
|
||||
if (!rv.contains(tags[t])) {
|
||||
System.out.println("Found a new tag in cached " + entry.getURI() + ": " + tags[t]);
|
||||
rv.add(tags[t]);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
} // end iterating over the entries
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the entry to the given dir, returning true if it was verified properly
|
||||
*
|
||||
*/
|
||||
private boolean extractEntry(File entryFile, File entryDir, BlogInfo info) throws IOException {
|
||||
if (!entryDir.exists())
|
||||
entryDir.mkdirs();
|
||||
|
||||
boolean ok = _extractor.extract(entryFile, entryDir, null, info);
|
||||
if (!ok) {
|
||||
File files[] = entryDir.listFiles();
|
||||
for (int i = 0; i < files.length; i++)
|
||||
files[i].delete();
|
||||
entryDir.delete();
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private EntryContainer getCachedEntry(File entryDir) {
|
||||
try {
|
||||
CachedEntry ce = new CachedEntry(entryDir);
|
||||
if (ce.isValid())
|
||||
return ce;
|
||||
return null;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
||||
File files[] = entryDir.listFiles();
|
||||
for (int i = 0; i < files.length; i++)
|
||||
files[i].delete();
|
||||
entryDir.delete();
|
||||
return null;
|
||||
}
|
||||
|
||||
public EntryContainer getEntry(BlogURI uri) { return getEntry(uri, null); }
|
||||
public EntryContainer getEntry(BlogURI uri, SessionKey blogKey) {
|
||||
List entries = listEntries(uri, null, blogKey);
|
||||
if (entries.size() > 0)
|
||||
return (EntryContainer)entries.get(0);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public List listEntries(BlogURI uri, String tag, SessionKey blogKey) {
|
||||
return listEntries(uri.getKeyHash(), uri.getEntryId(), tag, blogKey);
|
||||
}
|
||||
public List listEntries(Hash blog, long entryId, String tag, SessionKey blogKey) {
|
||||
List rv = new ArrayList();
|
||||
BlogInfo info = getBlogInfo(blog);
|
||||
if (info == null)
|
||||
return rv;
|
||||
|
||||
File blogDir = new File(_rootDir, blog.toBase64());
|
||||
File entries[] = blogDir.listFiles(_entryFilenameFilter);
|
||||
if (entries == null)
|
||||
return rv;
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
try {
|
||||
EntryContainer entry = null;
|
||||
if (blogKey == null) {
|
||||
// no key, cache.
|
||||
File entryDir = getEntryDir(entries[i]);
|
||||
if (entryDir.exists())
|
||||
entry = getCachedEntry(entryDir);
|
||||
if ((entry == null) || !entryDir.exists()) {
|
||||
if (!extractEntry(entries[i], entryDir, info)) {
|
||||
System.err.println("Entry " + entries[i].getPath() + " is not valid");
|
||||
new Exception("foo!!!!").printStackTrace();
|
||||
continue;
|
||||
}
|
||||
entry = getCachedEntry(entryDir);
|
||||
}
|
||||
} else {
|
||||
// we have an explicit key - no caching
|
||||
entry = new EntryContainer();
|
||||
entry.load(new FileInputStream(entries[i]));
|
||||
boolean ok = entry.verifySignature(_context, info);
|
||||
if (!ok) {
|
||||
System.err.println("Keyed entry " + entries[i].getPath() + " is not valid");
|
||||
new Exception("foo!!!!!!").printStackTrace();
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.parseRawData(_context, blogKey);
|
||||
|
||||
entry.setCompleteSize((int)entries[i].length());
|
||||
}
|
||||
|
||||
if (entryId >= 0) {
|
||||
if (entry.getURI().getEntryId() == entryId) {
|
||||
rv.add(entry);
|
||||
return rv;
|
||||
}
|
||||
} else if (tag != null) {
|
||||
String tags[] = entry.getTags();
|
||||
for (int j = 0; j < tags.length; j++) {
|
||||
if (tags[j].equals(tag)) {
|
||||
rv.add(entry);
|
||||
System.out.println("cached entry matched requested tag [" + tag + "]: " + entry.getURI());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("cached entry is ok and no id or tag was requested: " + entry.getURI());
|
||||
rv.add(entry);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public boolean storeEntry(EntryContainer container) {
|
||||
if (container == null) return false;
|
||||
BlogURI uri = container.getURI();
|
||||
if (uri == null) return false;
|
||||
|
||||
File blogDir = new File(_rootDir, uri.getKeyHash().toBase64());
|
||||
blogDir.mkdirs();
|
||||
File entryFile = new File(blogDir, getEntryFilename(uri.getEntryId()));
|
||||
if (entryFile.exists()) return true;
|
||||
|
||||
|
||||
BlogInfo info = getBlogInfo(uri);
|
||||
if (info == null) {
|
||||
System.out.println("no blog metadata for the uri " + uri);
|
||||
return false;
|
||||
}
|
||||
if (!container.verifySignature(_context, info)) {
|
||||
System.out.println("Not storing the invalid blog entry at " + uri);
|
||||
return false;
|
||||
} else {
|
||||
//System.out.println("Signature is valid: " + container.getSignature() + " for info " + info);
|
||||
}
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
container.write(baos, true);
|
||||
byte data[] = baos.toByteArray();
|
||||
FileOutputStream out = new FileOutputStream(entryFile);
|
||||
out.write(data);
|
||||
out.close();
|
||||
container.setCompleteSize(data.length);
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getEntryFilename(long entryId) { return entryId + ".snd"; }
|
||||
|
||||
private static SimpleDateFormat _dateFmt = new SimpleDateFormat("yyyyMMdd", Locale.UK);
|
||||
public static String getIndexName(long entryId, int numBytes) {
|
||||
try {
|
||||
synchronized (_dateFmt) {
|
||||
String yy = _dateFmt.format(new Date(entryId));
|
||||
long begin = _dateFmt.parse(yy).getTime();
|
||||
long n = entryId - begin;
|
||||
int kb = numBytes / 1024;
|
||||
return yy + '_' + n + '_' + kb + "KB";
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return "UNKNOWN";
|
||||
} catch (ParseException pe) {
|
||||
pe.printStackTrace();
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
public static long getEntryIdFromIndexName(String entryIndexName) {
|
||||
if (entryIndexName == null) return -1;
|
||||
if (entryIndexName.endsWith(".snd"))
|
||||
entryIndexName = entryIndexName.substring(0, entryIndexName.length() - 4);
|
||||
int endYY = entryIndexName.indexOf('_');
|
||||
if (endYY <= 0) return -1;
|
||||
int endN = entryIndexName.indexOf('_', endYY+1);
|
||||
if (endN <= 0) return -1;
|
||||
String yy = entryIndexName.substring(0, endYY);
|
||||
String n = entryIndexName.substring(endYY+1, endN);
|
||||
try {
|
||||
synchronized (_dateFmt) {
|
||||
long dayBegin = _dateFmt.parse(yy).getTime();
|
||||
long dayEntry = Long.parseLong(n);
|
||||
return dayBegin + dayEntry;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
} catch (ParseException pe) {
|
||||
pe.printStackTrace();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public static int getSizeFromIndexName(String entryIndexName) {
|
||||
if (entryIndexName == null) return -1;
|
||||
if (entryIndexName.endsWith(".snd"))
|
||||
entryIndexName = entryIndexName.substring(0, entryIndexName.length() - 4);
|
||||
int beginSize = entryIndexName.lastIndexOf('_');
|
||||
if ( (beginSize <= 0) || (beginSize >= entryIndexName.length()-3) )
|
||||
return -1;
|
||||
try {
|
||||
String sz = entryIndexName.substring(beginSize+1, entryIndexName.length()-2);
|
||||
return Integer.parseInt(sz);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public ArchiveIndex getIndex() {
|
||||
if (_index == null)
|
||||
regenerateIndex();
|
||||
return _index;
|
||||
}
|
||||
|
||||
public File getArchiveDir() { return _rootDir; }
|
||||
public File getIndexFile() { return new File(_rootDir, INDEX_FILE); }
|
||||
public void regenerateIndex() {
|
||||
reloadInfo();
|
||||
_index = ArchiveIndexer.index(_context, this);
|
||||
try {
|
||||
FileOutputStream out = new FileOutputStream(new File(_rootDir, INDEX_FILE));
|
||||
out.write(DataHelper.getUTF8(_index.toString()));
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
190
apps/syndie/java/src/net/i2p/syndie/ArchiveIndexer.java
Normal file
@ -0,0 +1,190 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
|
||||
/**
|
||||
* Dig through the archive to build an index
|
||||
*/
|
||||
class ArchiveIndexer {
|
||||
private static final int RECENT_BLOG_COUNT = 10;
|
||||
private static final int RECENT_ENTRY_COUNT = 10;
|
||||
|
||||
public static ArchiveIndex index(I2PAppContext ctx, Archive source) {
|
||||
LocalArchiveIndex rv = new LocalArchiveIndex(ctx);
|
||||
rv.setGeneratedOn(ctx.clock().now());
|
||||
|
||||
File rootDir = source.getArchiveDir();
|
||||
|
||||
File headerFile = new File(rootDir, Archive.HEADER_FILE);
|
||||
if (headerFile.exists()) {
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(headerFile), "UTF-8"));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
StringTokenizer tok = new StringTokenizer(line, ":");
|
||||
if (tok.countTokens() == 2)
|
||||
rv.setHeader(tok.nextToken(), tok.nextToken());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// things are new if we just received them in the last day
|
||||
long newSince = ctx.clock().now() - 24*60*60*1000;
|
||||
|
||||
rv.setVersion(Version.INDEX_VERSION);
|
||||
|
||||
/** 0-lowestEntryId --> blog Hash */
|
||||
Map blogsByAge = new TreeMap();
|
||||
/** 0-entryId --> BlogURI */
|
||||
Map entriesByAge = new TreeMap();
|
||||
List blogs = source.listBlogs();
|
||||
rv.setAllBlogs(blogs.size());
|
||||
|
||||
int newEntries = 0;
|
||||
int allEntries = 0;
|
||||
long newSize = 0;
|
||||
long totalSize = 0;
|
||||
int newBlogs = 0;
|
||||
|
||||
SMLParser parser = new SMLParser(ctx);
|
||||
|
||||
for (int i = 0; i < blogs.size(); i++) {
|
||||
BlogInfo cur = (BlogInfo)blogs.get(i);
|
||||
Hash key = cur.getKey().calculateHash();
|
||||
String keyStr = Base64.encode(key.getData());
|
||||
File blogDir = new File(rootDir, Base64.encode(key.getData()));
|
||||
|
||||
File metaFile = new File(blogDir, Archive.METADATA_FILE);
|
||||
long metadate = metaFile.lastModified();
|
||||
|
||||
List entries = source.listEntries(key, -1, null, null);
|
||||
System.out.println("Entries under " + key + ": " + entries);
|
||||
/** tag name --> ordered map of entryId to EntryContainer */
|
||||
Map tags = new TreeMap();
|
||||
|
||||
for (int j = 0; j < entries.size(); j++) {
|
||||
EntryContainer entry = (EntryContainer)entries.get(j);
|
||||
entriesByAge.put(new Long(0-entry.getURI().getEntryId()), entry.getURI());
|
||||
allEntries++;
|
||||
totalSize += entry.getCompleteSize();
|
||||
String entryTags[] = entry.getTags();
|
||||
for (int t = 0; t < entryTags.length; t++) {
|
||||
if (!tags.containsKey(entryTags[t])) {
|
||||
tags.put(entryTags[t], new TreeMap());
|
||||
//System.err.println("New tag [" + entryTags[t] + "]");
|
||||
}
|
||||
Map entriesByTag = (Map)tags.get(entryTags[t]);
|
||||
entriesByTag.put(new Long(0-entry.getURI().getEntryId()), entry);
|
||||
System.out.println("Entries under tag " + entryTags[t] + ":" + entriesByTag.values());
|
||||
}
|
||||
|
||||
if (entry.getURI().getEntryId() >= newSince) {
|
||||
newEntries++;
|
||||
newSize += entry.getCompleteSize();
|
||||
}
|
||||
HeaderReceiver rec = new HeaderReceiver();
|
||||
parser.parse(entry.getEntry().getText(), rec);
|
||||
String reply = rec.getHeader(HTMLRenderer.HEADER_IN_REPLY_TO);
|
||||
if (reply != null) {
|
||||
BlogURI parent = new BlogURI(reply.trim());
|
||||
if ( (parent.getKeyHash() != null) && (parent.getEntryId() >= 0) )
|
||||
rv.addReply(parent, entry.getURI());
|
||||
else
|
||||
System.err.println("Parent of " + entry.getURI() + " is not valid: [" + reply.trim() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
long lowestEntryId = -1;
|
||||
for (Iterator iter = tags.keySet().iterator(); iter.hasNext(); ) {
|
||||
String tagName = (String)iter.next();
|
||||
Map tagEntries = (Map)tags.get(tagName);
|
||||
long highestId = -1;
|
||||
if (tagEntries.size() <= 0) break;
|
||||
Long id = (Long)tagEntries.keySet().iterator().next();
|
||||
highestId = 0 - id.longValue();
|
||||
|
||||
rv.addBlog(key, tagName, highestId);
|
||||
for (Iterator entryIter = tagEntries.values().iterator(); entryIter.hasNext(); ) {
|
||||
EntryContainer entry = (EntryContainer)entryIter.next();
|
||||
String indexName = Archive.getIndexName(entry.getURI().getEntryId(), entry.getCompleteSize());
|
||||
rv.addBlogEntry(key, tagName, indexName);
|
||||
if (!entryIter.hasNext())
|
||||
lowestEntryId = entry.getURI().getEntryId();
|
||||
}
|
||||
}
|
||||
|
||||
if (lowestEntryId > newSince)
|
||||
newBlogs++;
|
||||
|
||||
blogsByAge.put(new Long(0-lowestEntryId), key);
|
||||
}
|
||||
|
||||
rv.setAllEntries(allEntries);
|
||||
rv.setNewBlogs(newBlogs);
|
||||
rv.setNewEntries(newEntries);
|
||||
rv.setTotalSize(totalSize);
|
||||
rv.setNewSize(newSize);
|
||||
|
||||
int i = 0;
|
||||
for (Iterator iter = blogsByAge.keySet().iterator(); iter.hasNext() && i < RECENT_BLOG_COUNT; i++) {
|
||||
Long when = (Long)iter.next();
|
||||
Hash key = (Hash)blogsByAge.get(when);
|
||||
rv.addNewestBlog(key);
|
||||
}
|
||||
i = 0;
|
||||
for (Iterator iter = entriesByAge.keySet().iterator(); iter.hasNext() && i < RECENT_ENTRY_COUNT; i++) {
|
||||
Long when = (Long)iter.next();
|
||||
BlogURI uri = (BlogURI)entriesByAge.get(when);
|
||||
rv.addNewestEntry(uri);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static class HeaderReceiver implements SMLParser.EventReceiver {
|
||||
private Properties _headers;
|
||||
public HeaderReceiver() { _headers = null; }
|
||||
public String getHeader(String name) { return (_headers != null ? _headers.getProperty(name) : null); }
|
||||
public void receiveHeader(String header, String value) {
|
||||
if (_headers == null) _headers = new Properties();
|
||||
_headers.setProperty(header, value);
|
||||
}
|
||||
|
||||
public void receiveAddress(String name, String schema, String protocol, String location, String anchorText) {}
|
||||
public void receiveArchive(String name, String description, String locationSchema, String location, String postingKey, String anchorText) {}
|
||||
public void receiveAttachment(int id, String anchorText) {}
|
||||
public void receiveBegin() {}
|
||||
public void receiveBlog(String name, String blogKeyHash, String blogPath, long blogEntryId, List blogArchiveLocations, String anchorText) {}
|
||||
public void receiveBold(String text) {}
|
||||
public void receiveCode(String text, String codeLocationSchema, String codeLocation) {}
|
||||
public void receiveCut(String summaryText) {}
|
||||
public void receiveEnd() {}
|
||||
public void receiveGT() {}
|
||||
public void receiveH1(String text) {}
|
||||
public void receiveH2(String text) {}
|
||||
public void receiveH3(String text) {}
|
||||
public void receiveH4(String text) {}
|
||||
public void receiveH5(String text) {}
|
||||
public void receiveHR() {}
|
||||
public void receiveHeaderEnd() {}
|
||||
public void receiveImage(String alternateText, int attachmentId) {}
|
||||
public void receiveItalic(String text) {}
|
||||
public void receiveLT() {}
|
||||
public void receiveLeftBracket() {}
|
||||
public void receiveLink(String schema, String location, String text) {}
|
||||
public void receiveNewline() {}
|
||||
public void receivePlain(String text) {}
|
||||
public void receivePre(String text) {}
|
||||
public void receiveQuote(String text, String whoQuoted, String quoteLocationSchema, String quoteLocation) {}
|
||||
public void receiveRightBracket() {}
|
||||
public void receiveUnderline(String text) {}
|
||||
}
|
||||
}
|
683
apps/syndie/java/src/net/i2p/syndie/BlogManager.java
Normal file
@ -0,0 +1,683 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.PetName;
|
||||
import net.i2p.client.naming.PetNameDB;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BlogManager {
|
||||
private I2PAppContext _context;
|
||||
private static BlogManager _instance;
|
||||
private File _blogKeyDir;
|
||||
private File _privKeyDir;
|
||||
private File _archiveDir;
|
||||
private File _userDir;
|
||||
private File _cacheDir;
|
||||
private File _tempDir;
|
||||
private File _rootDir;
|
||||
private Archive _archive;
|
||||
|
||||
static {
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
|
||||
String rootDir = I2PAppContext.getGlobalContext().getProperty("syndie.rootDir");
|
||||
if (false) {
|
||||
if (rootDir == null)
|
||||
rootDir = System.getProperty("user.home");
|
||||
rootDir = rootDir + File.separatorChar + ".syndie";
|
||||
} else {
|
||||
if (rootDir == null)
|
||||
rootDir = "./syndie";
|
||||
}
|
||||
_instance = new BlogManager(I2PAppContext.getGlobalContext(), rootDir);
|
||||
}
|
||||
public static BlogManager instance() { return _instance; }
|
||||
|
||||
public BlogManager(I2PAppContext ctx, String rootDir) {
|
||||
_context = ctx;
|
||||
_rootDir = new File(rootDir);
|
||||
_rootDir.mkdirs();
|
||||
readConfig();
|
||||
_blogKeyDir = new File(_rootDir, "blogkeys");
|
||||
_privKeyDir = new File(_rootDir, "privkeys");
|
||||
String archiveDir = _context.getProperty("syndie.archiveDir");
|
||||
if (archiveDir != null)
|
||||
_archiveDir = new File(archiveDir);
|
||||
else
|
||||
_archiveDir = new File(_rootDir, "archive");
|
||||
_userDir = new File(_rootDir, "users");
|
||||
_cacheDir = new File(_rootDir, "cache");
|
||||
_tempDir = new File(_rootDir, "temp");
|
||||
_blogKeyDir.mkdirs();
|
||||
_privKeyDir.mkdirs();
|
||||
_archiveDir.mkdirs();
|
||||
_cacheDir.mkdirs();
|
||||
_userDir.mkdirs();
|
||||
_tempDir.mkdirs();
|
||||
_archive = new Archive(ctx, _archiveDir.getAbsolutePath(), _cacheDir.getAbsolutePath());
|
||||
_archive.regenerateIndex();
|
||||
}
|
||||
|
||||
private File getConfigFile() { return new File(_rootDir, "syndie.config"); }
|
||||
private void readConfig() {
|
||||
File config = getConfigFile();
|
||||
if (config.exists()) {
|
||||
try {
|
||||
Properties p = new Properties();
|
||||
DataHelper.loadProps(p, config);
|
||||
for (Iterator iter = p.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
System.setProperty(key, p.getProperty(key));
|
||||
System.out.println("Read config prop [" + key + "] = [" + p.getProperty(key) + "]");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
System.out.println("Config doesn't exist: " + config.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
public void writeConfig() {
|
||||
File config = new File(_rootDir, "syndie.config");
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(config);
|
||||
for (Iterator iter = _context.getPropertyNames().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (name.startsWith("syndie."))
|
||||
out.write(DataHelper.getUTF8(name + '=' + _context.getProperty(name) + '\n'));
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public BlogInfo createBlog(String name, String description, String contactURL, String archives[]) {
|
||||
return createBlog(name, null, description, contactURL, archives);
|
||||
}
|
||||
public BlogInfo createBlog(String name, SigningPublicKey posters[], String description, String contactURL, String archives[]) {
|
||||
Object keys[] = _context.keyGenerator().generateSigningKeypair();
|
||||
SigningPublicKey pub = (SigningPublicKey)keys[0];
|
||||
SigningPrivateKey priv = (SigningPrivateKey)keys[1];
|
||||
|
||||
try {
|
||||
FileOutputStream out = new FileOutputStream(new File(_privKeyDir, Base64.encode(pub.calculateHash().getData()) + ".priv"));
|
||||
pub.writeBytes(out);
|
||||
priv.writeBytes(out);
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
return createInfo(pub, priv, name, posters, description, contactURL, archives, 0);
|
||||
}
|
||||
|
||||
public BlogInfo createInfo(SigningPublicKey pub, SigningPrivateKey priv, String name, SigningPublicKey posters[],
|
||||
String description, String contactURL, String archives[], int edition) {
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty("Name", name);
|
||||
opts.setProperty("Description", description);
|
||||
opts.setProperty("Edition", Integer.toString(edition));
|
||||
opts.setProperty("ContactURL", contactURL);
|
||||
for (int i = 0; archives != null && i < archives.length; i++)
|
||||
opts.setProperty("Archive." + i, archives[i]);
|
||||
|
||||
BlogInfo info = new BlogInfo(pub, posters, opts);
|
||||
info.sign(_context, priv);
|
||||
|
||||
_archive.storeBlogInfo(info);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public boolean updateMetadata(User user, Hash blog, Properties opts) {
|
||||
if (!user.getAuthenticated()) return false;
|
||||
BlogInfo oldInfo = getArchive().getBlogInfo(blog);
|
||||
if (oldInfo == null) return false;
|
||||
if (!user.getBlog().equals(oldInfo.getKey().calculateHash())) return false;
|
||||
int oldEdition = 0;
|
||||
try {
|
||||
String ed = oldInfo.getProperty("Edition");
|
||||
if (ed != null)
|
||||
oldEdition = Integer.parseInt(ed);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
opts.setProperty("Edition", oldEdition + 1 + "");
|
||||
BlogInfo info = new BlogInfo(oldInfo.getKey(), oldInfo.getPosters(), opts);
|
||||
SigningPrivateKey key = getMyPrivateKey(oldInfo);
|
||||
info.sign(_context, key);
|
||||
getArchive().storeBlogInfo(info);
|
||||
user.setLastMetaEntry(oldEdition+1);
|
||||
saveUser(user);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Archive getArchive() { return _archive; }
|
||||
public File getTempDir() { return _tempDir; }
|
||||
public File getRootDir() { return _rootDir; }
|
||||
|
||||
public List listMyBlogs() {
|
||||
File files[] = _privKeyDir.listFiles();
|
||||
List rv = new ArrayList();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
if (files[i].isFile() && !files[i].isHidden()) {
|
||||
try {
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
pub.readBytes(new FileInputStream(files[i]));
|
||||
BlogInfo info = _archive.getBlogInfo(pub.calculateHash());
|
||||
if (info != null)
|
||||
rv.add(info);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public SigningPrivateKey getMyPrivateKey(BlogInfo blog) {
|
||||
if (blog == null) return null;
|
||||
File keyFile = new File(_privKeyDir, Base64.encode(blog.getKey().calculateHash().getData()) + ".priv");
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(keyFile);
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
pub.readBytes(in);
|
||||
SigningPrivateKey priv = new SigningPrivateKey();
|
||||
priv.readBytes(in);
|
||||
return priv;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String login(User user, String login, String pass) {
|
||||
if ( (login == null) || (pass == null) ) return "<span class=\"b_loginMsgErr\">Login not specified</span>";
|
||||
Hash userHash = _context.sha().calculateHash(DataHelper.getUTF8(login));
|
||||
Hash passHash = _context.sha().calculateHash(DataHelper.getUTF8(pass));
|
||||
File userFile = new File(_userDir, Base64.encode(userHash.getData()));
|
||||
System.out.println("Attempting to login to " + login + " w/ pass = " + pass
|
||||
+ ": file = " + userFile.getAbsolutePath() + " passHash = "
|
||||
+ Base64.encode(passHash.getData()));
|
||||
if (userFile.exists()) {
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
FileInputStream fin = new FileInputStream(userFile);
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(fin, "UTF-8"));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
int split = line.indexOf('=');
|
||||
if (split <= 0) continue;
|
||||
String key = line.substring(0, split);
|
||||
String val = line.substring(split+1);
|
||||
props.setProperty(key.trim(), val.trim());
|
||||
}
|
||||
return user.login(login, pass, props);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return "<span class=\"b_loginMsgErr\">Error logging in - corrupt userfile</span>";
|
||||
}
|
||||
} else {
|
||||
return "<span class=\"b_loginMsgErr\">User does not exist</span>";
|
||||
}
|
||||
}
|
||||
|
||||
/** hash of the password required to register and create a new blog (null means no password required) */
|
||||
public String getRegistrationPasswordHash() {
|
||||
String pass = _context.getProperty("syndie.registrationPassword");
|
||||
if ( (pass == null) || (pass.trim().length() <= 0) ) return null;
|
||||
return pass;
|
||||
}
|
||||
|
||||
/** Password required to access the remote syndication functinoality (null means no password required) */
|
||||
public String getRemotePasswordHash() {
|
||||
String pass = _context.getProperty("syndie.remotePassword");
|
||||
|
||||
System.out.println("Remote password? [" + pass + "]");
|
||||
if ( (pass == null) || (pass.trim().length() <= 0) ) return null;
|
||||
return pass;
|
||||
}
|
||||
public String getAdminPasswordHash() {
|
||||
String pass = _context.getProperty("syndie.adminPassword");
|
||||
if ( (pass == null) || (pass.trim().length() <= 0) ) return "";
|
||||
return pass;
|
||||
}
|
||||
|
||||
public boolean isConfigured() {
|
||||
File cfg = getConfigFile();
|
||||
return (cfg.exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, this syndie instance is meant for just one local user, so we don't need
|
||||
* to password protect registration, remote.jsp, or admin.jsp
|
||||
*
|
||||
*/
|
||||
public boolean isSingleUser() {
|
||||
String isSingle = _context.getProperty("syndie.singleUser");
|
||||
return ( (isSingle != null) && (Boolean.valueOf(isSingle).booleanValue()) );
|
||||
}
|
||||
|
||||
public String getDefaultProxyHost() { return _context.getProperty("syndie.defaultProxyHost", ""); }
|
||||
public String getDefaultProxyPort() { return _context.getProperty("syndie.defaultProxyPort", ""); }
|
||||
public int getUpdateDelay() { return Integer.parseInt(_context.getProperty("syndie.updateDelay", "12")); }
|
||||
public String[] getUpdateArchives() { return _context.getProperty("syndie.updateArchives", "").split(","); }
|
||||
|
||||
public boolean authorizeAdmin(String pass) {
|
||||
if (isSingleUser()) return true;
|
||||
String admin = getAdminPasswordHash();
|
||||
if ( (admin == null) || (admin.trim().length() <= 0) )
|
||||
return false;
|
||||
String hash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(pass.trim())).getData());
|
||||
return (hash.equals(admin));
|
||||
}
|
||||
public boolean authorizeRemote(String pass) {
|
||||
if (isSingleUser()) return true;
|
||||
String rem = getRemotePasswordHash();
|
||||
if ( (rem == null) || (rem.trim().length() <= 0) )
|
||||
return false;
|
||||
String hash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(pass.trim())).getData());
|
||||
return (hash.equals(rem));
|
||||
}
|
||||
public boolean authorizeRemote(User user) {
|
||||
if (isSingleUser()) return true;
|
||||
return (user.getAuthenticated() && user.getAllowAccessRemote());
|
||||
}
|
||||
|
||||
public void configure(String registrationPassword, String remotePassword, String adminPass, String defaultSelector,
|
||||
String defaultProxyHost, int defaultProxyPort, boolean isSingleUser, Properties opts) {
|
||||
File cfg = getConfigFile();
|
||||
Writer out = null;
|
||||
try {
|
||||
out = new OutputStreamWriter(new FileOutputStream(cfg), "UTF-8");
|
||||
if (registrationPassword != null)
|
||||
out.write("syndie.registrationPassword="+Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(registrationPassword.trim())).getData()) + "\n");
|
||||
if (remotePassword != null)
|
||||
out.write("syndie.remotePassword="+Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(remotePassword.trim())).getData()) + "\n");
|
||||
if (adminPass != null)
|
||||
out.write("syndie.adminPassword="+Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(adminPass.trim())).getData()) + "\n");
|
||||
if (defaultSelector != null)
|
||||
out.write("syndie.defaultSelector="+defaultSelector.trim() + "\n");
|
||||
if (defaultProxyHost != null)
|
||||
out.write("syndie.defaultProxyHost="+defaultProxyHost.trim() + "\n");
|
||||
if (defaultProxyPort > 0)
|
||||
out.write("syndie.defaultProxyPort="+defaultProxyPort + "\n");
|
||||
out.write("syndie.singleUser=" + isSingleUser + "\n");
|
||||
if (opts != null) {
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = opts.getProperty(key);
|
||||
out.write(key.trim() + "=" + val.trim() + "\n");
|
||||
}
|
||||
}
|
||||
_archive.setDefaultSelector(defaultSelector);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
readConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public String authorizeRemoteAccess(User user, String password) {
|
||||
if (!user.getAuthenticated()) return "<span class=\"b_remoteMsgErr\">Not logged in</span>";
|
||||
String remPass = getRemotePasswordHash();
|
||||
if (remPass == null)
|
||||
return "<span class=\"b_remoteMsgErr\">Remote access password not configured - please <a href=\"admin.jsp\">specify</a> a remote " +
|
||||
"archive password</span>";
|
||||
|
||||
if (authorizeRemote(password)) {
|
||||
user.setAllowAccessRemote(true);
|
||||
saveUser(user);
|
||||
return "<span class=\"b_remoteMsgOk\">Remote access authorized</span>";
|
||||
} else {
|
||||
return "<span class=\"b_remoteMsgErr\">Remote access denied</span>";
|
||||
}
|
||||
}
|
||||
|
||||
public void saveUser(User user) {
|
||||
if (!user.getAuthenticated()) return;
|
||||
String userHash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(user.getUsername())).getData());
|
||||
File userFile = new File(_userDir, userHash);
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(userFile);
|
||||
out.write(DataHelper.getUTF8(user.export()));
|
||||
user.getPetNameDB().store(user.getAddressbookLocation());
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe){}
|
||||
}
|
||||
}
|
||||
public String register(User user, String login, String password, String registrationPassword, String blogName, String blogDescription, String contactURL) {
|
||||
System.err.println("Register [" + login + "] pass [" + password + "] name [" + blogName + "] descr [" + blogDescription + "] contact [" + contactURL + "] regPass [" + registrationPassword + "]");
|
||||
String hashedRegistrationPassword = getRegistrationPasswordHash();
|
||||
if ( (hashedRegistrationPassword != null) && (!isSingleUser()) ) {
|
||||
try {
|
||||
if (!hashedRegistrationPassword.equals(Base64.encode(_context.sha().calculateHash(registrationPassword.getBytes("UTF-8")).getData())))
|
||||
return "<span class=\"b_regMsgErr\">Invalid registration password</span>";
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
return "<span class=\"b_regMsgErr\">Error registering</span>";
|
||||
}
|
||||
}
|
||||
String userHash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(login)).getData());
|
||||
File userFile = new File(_userDir, userHash);
|
||||
if (userFile.exists()) {
|
||||
return "<span class=\"b_regMsgErr\">Cannot register the login " + login + ": it already exists</span>";
|
||||
} else {
|
||||
BlogInfo info = createBlog(blogName, blogDescription, contactURL, null);
|
||||
String hashedPassword = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(password)).getData());
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(userFile);
|
||||
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||
bw.write("password=" + hashedPassword + "\n");
|
||||
bw.write("blog=" + Base64.encode(info.getKey().calculateHash().getData()) + "\n");
|
||||
bw.write("lastid=-1\n");
|
||||
bw.write("lastmetaedition=0\n");
|
||||
bw.write("addressbook=userhosts-"+userHash + ".txt\n");
|
||||
bw.write("showimages=false\n");
|
||||
bw.write("showexpanded=false\n");
|
||||
bw.flush();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return "<span class=\"b_regMsgErr\">Internal error registering - " + ioe.getMessage() + "</span>";
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
String loginResult = login(user, login, password);
|
||||
_archive.regenerateIndex();
|
||||
return loginResult;
|
||||
}
|
||||
}
|
||||
|
||||
public String exportHosts(User user) {
|
||||
if (!user.getAuthenticated() || !user.getAllowAccessRemote())
|
||||
return "<span class=\"b_addrMsgErr\">Not authorized to export the hosts</span>";
|
||||
PetNameDB userDb = user.getPetNameDB();
|
||||
PetNameDB routerDb = _context.petnameDb();
|
||||
// horribly inefficient...
|
||||
for (Iterator names = userDb.getNames().iterator(); names.hasNext();) {
|
||||
PetName pn = userDb.get((String)names.next());
|
||||
if (pn == null) continue;
|
||||
Destination existing = _context.namingService().lookup(pn.getName());
|
||||
if (existing == null && pn.getNetwork().equalsIgnoreCase("i2p")) {
|
||||
routerDb.set(pn.getName(), pn);
|
||||
try {
|
||||
routerDb.store();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return "<span class=\"b_addrMsgErr\">Error exporting the hosts: " + ioe.getMessage() + "</span>";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "<span class=\"b_addrMsgOk\">Hosts exported</span>";
|
||||
}
|
||||
|
||||
public BlogURI createBlogEntry(User user, String subject, String tags, String entryHeaders, String sml) {
|
||||
return createBlogEntry(user, subject, tags, entryHeaders, sml, null, null, null);
|
||||
}
|
||||
public BlogURI createBlogEntry(User user, String subject, String tags, String entryHeaders, String sml, List fileNames, List fileStreams, List fileTypes) {
|
||||
if (!user.getAuthenticated()) return null;
|
||||
BlogInfo info = getArchive().getBlogInfo(user.getBlog());
|
||||
if (info == null) return null;
|
||||
SigningPrivateKey privkey = getMyPrivateKey(info);
|
||||
if (privkey == null) return null;
|
||||
|
||||
long entryId = -1;
|
||||
long now = _context.clock().now();
|
||||
long dayBegin = getDayBegin(now);
|
||||
if (user.getMostRecentEntry() >= dayBegin)
|
||||
entryId = user.getMostRecentEntry() + 1;
|
||||
else
|
||||
entryId = dayBegin;
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(tags, " ,\n\t");
|
||||
String tagList[] = new String[tok.countTokens()];
|
||||
for (int i = 0; i < tagList.length; i++)
|
||||
tagList[i] = tok.nextToken().trim();
|
||||
|
||||
BlogURI uri = new BlogURI(user.getBlog(), entryId);
|
||||
|
||||
try {
|
||||
StringBuffer raw = new StringBuffer(sml.length() + 128);
|
||||
raw.append("Subject: ").append(subject).append('\n');
|
||||
raw.append("Tags: ");
|
||||
for (int i = 0; i < tagList.length; i++)
|
||||
raw.append(tagList[i]).append('\t');
|
||||
raw.append('\n');
|
||||
if ( (entryHeaders != null) && (entryHeaders.trim().length() > 0) ) {
|
||||
System.out.println("Entry headers: " + entryHeaders);
|
||||
BufferedReader userHeaders = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(DataHelper.getUTF8(entryHeaders)), "UTF-8"));
|
||||
String line = null;
|
||||
while ( (line = userHeaders.readLine()) != null) {
|
||||
line = line.trim();
|
||||
System.out.println("Line: " + line);
|
||||
if (line.length() <= 0) continue;
|
||||
int split = line.indexOf('=');
|
||||
int split2 = line.indexOf(':');
|
||||
if ( (split < 0) || ( (split2 > 0) && (split2 < split) ) ) split = split2;
|
||||
String key = line.substring(0,split).trim();
|
||||
String val = line.substring(split+1).trim();
|
||||
raw.append(key).append(": ").append(val).append('\n');
|
||||
}
|
||||
}
|
||||
raw.append('\n');
|
||||
raw.append(sml);
|
||||
|
||||
EntryContainer c = new EntryContainer(uri, tagList, DataHelper.getUTF8(raw));
|
||||
if ((fileNames != null) && (fileStreams != null) && (fileNames.size() == fileStreams.size()) ) {
|
||||
for (int i = 0; i < fileNames.size(); i++) {
|
||||
String name = (String)fileNames.get(i);
|
||||
InputStream in = (InputStream)fileStreams.get(i);
|
||||
String fileType = (fileTypes != null ? (String)fileTypes.get(i) : "application/octet-stream");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
byte buf[] = new byte[1024];
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read == -1) break;
|
||||
baos.write(buf, 0, read);
|
||||
}
|
||||
byte att[] = baos.toByteArray();
|
||||
if ( (att != null) && (att.length > 0) )
|
||||
c.addAttachment(att, new File(name).getName(), null, fileType);
|
||||
}
|
||||
}
|
||||
//for (int i = 7; i < args.length; i++) {
|
||||
// c.addAttachment(read(args[i]), new File(args[i]).getName(),
|
||||
// "Attached file", "application/octet-stream");
|
||||
//}
|
||||
SessionKey entryKey = null;
|
||||
//if (!"NONE".equals(args[5]))
|
||||
// entryKey = new SessionKey(Base64.decode(args[5]));
|
||||
c.seal(_context, privkey, null);
|
||||
boolean ok = getArchive().storeEntry(c);
|
||||
if (ok) {
|
||||
getArchive().regenerateIndex();
|
||||
user.setMostRecentEntry(entryId);
|
||||
saveUser(user);
|
||||
return uri;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read in the syndie blog metadata file from the stream, verifying it and adding it to
|
||||
* the archive if necessary
|
||||
*
|
||||
*/
|
||||
public boolean importBlogMetadata(InputStream metadataStream) throws IOException {
|
||||
try {
|
||||
BlogInfo info = new BlogInfo();
|
||||
info.load(metadataStream);
|
||||
return _archive.storeBlogInfo(info);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read in the syndie entry file from the stream, verifying it and adding it to
|
||||
* the archive if necessary
|
||||
*
|
||||
*/
|
||||
public boolean importBlogEntry(InputStream entryStream) throws IOException {
|
||||
try {
|
||||
EntryContainer c = new EntryContainer();
|
||||
c.load(entryStream);
|
||||
return _archive.storeEntry(c);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String addAddress(User user, String name, String protocol, String location, String schema) {
|
||||
if (!user.getAuthenticated()) return "<span class=\"b_addrMsgErr\">Not logged in</span>";
|
||||
boolean ok = validateAddressName(name);
|
||||
if (!ok) return "<span class=\"b_addrMsgErr\">Invalid name: " + HTMLRenderer.sanitizeString(name) + "</span>";
|
||||
ok = validateAddressLocation(location);
|
||||
if (!ok) return "<span class=\"b_addrMsgErr\">Invalid location: " + HTMLRenderer.sanitizeString(location) + "</span>";
|
||||
if (!validateAddressSchema(schema)) return "<span class=\"b_addrMsgErr\">Unsupported schema: " + HTMLRenderer.sanitizeString(schema) + "</span>";
|
||||
// no need to quote user/location further, as they've been sanitized
|
||||
|
||||
PetNameDB names = user.getPetNameDB();
|
||||
if (names.exists(name))
|
||||
return "<span class=\"b_addrMsgErr\">Name is already in use</span>";
|
||||
PetName pn = new PetName(name, schema, protocol, location);
|
||||
names.set(name, pn);
|
||||
|
||||
try {
|
||||
names.store(user.getAddressbookLocation());
|
||||
return "<span class=\"b_addrMsgOk\">Address " + name + " written to your addressbook</span>";
|
||||
} catch (IOException ioe) {
|
||||
return "<span class=\"b_addrMsgErr\">Error writing out the name: " + ioe.getMessage() + "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
public Properties getKnownHosts(User user, boolean includePublic) throws IOException {
|
||||
Properties rv = new Properties();
|
||||
if ( (user != null) && (user.getAuthenticated()) ) {
|
||||
File userHostsFile = new File(user.getAddressbookLocation());
|
||||
rv.putAll(getKnownHosts(userHostsFile));
|
||||
}
|
||||
if (includePublic) {
|
||||
rv.putAll(getKnownHosts(new File("hosts.txt")));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
private Properties getKnownHosts(File filename) throws IOException {
|
||||
Properties rv = new Properties();
|
||||
if (filename.exists()) {
|
||||
rv.load(new FileInputStream(filename));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private boolean validateAddressName(String name) {
|
||||
if ( (name == null) || (name.trim().length() <= 0) ) return false;
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (!Character.isLetterOrDigit(c) && ('.' != c) && ('-' != c) && ('_' != c) )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateAddressLocation(String location) {
|
||||
if ( (location == null) || (location.trim().length() <= 0) ) return false;
|
||||
if (false) {
|
||||
try {
|
||||
Destination d = new Destination(location);
|
||||
return (d.getPublicKey() != null);
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// not everything is an i2p destination...
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateAddressSchema(String schema) {
|
||||
if ( (schema == null) || (schema.trim().length() <= 0) ) return false;
|
||||
if (true) {
|
||||
return true;
|
||||
} else {
|
||||
return "eep".equals(schema) || "i2p".equals(schema);
|
||||
}
|
||||
}
|
||||
|
||||
private final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd", Locale.UK);
|
||||
private final long getDayBegin(long now) {
|
||||
synchronized (_dateFormat) {
|
||||
try {
|
||||
String str = _dateFormat.format(new Date(now));
|
||||
return _dateFormat.parse(str).getTime();
|
||||
} catch (ParseException pe) {
|
||||
pe.printStackTrace();
|
||||
// wtf
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleSyndication(String location) {
|
||||
String archives[] = getUpdateArchives();
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
if ( (archives != null) && (archives.length > 0) ) {
|
||||
for (int i = 0; i < archives.length; i++)
|
||||
if ( (!archives[i].equals(location)) && (archives[i].trim().length() > 0) )
|
||||
buf.append(archives[i]).append(",");
|
||||
}
|
||||
if ( (location != null) && (location.trim().length() > 0) )
|
||||
buf.append(location.trim());
|
||||
System.setProperty("syndie.updateArchives", buf.toString());
|
||||
Updater.wakeup();
|
||||
}
|
||||
public void unscheduleSyndication(String location) {
|
||||
String archives[] = getUpdateArchives();
|
||||
if ( (archives != null) && (archives.length > 0) ) {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
for (int i = 0; i < archives.length; i++)
|
||||
if ( (!archives[i].equals(location)) && (archives[i].trim().length() > 0) )
|
||||
buf.append(archives[i]).append(",");
|
||||
System.setProperty("syndie.updateArchives", buf.toString());
|
||||
}
|
||||
}
|
||||
public boolean syndicationScheduled(String location) {
|
||||
String archives[] = getUpdateArchives();
|
||||
if ( (location == null) || (archives == null) || (archives.length <= 0) )
|
||||
return false;
|
||||
for (int i = 0; i < archives.length; i++)
|
||||
if (location.equals(archives[i]))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
188
apps/syndie/java/src/net/i2p/syndie/CLI.java
Normal file
@ -0,0 +1,188 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class CLI {
|
||||
public static final String USAGE = "Usage: \n" +
|
||||
"rootDir regenerateIndex\n" +
|
||||
"rootDir createBlog name description contactURL[ archiveURL]*\n" +
|
||||
"rootDir createEntry blogPublicKeyHash tag[,tag]* (NOW|entryId) (NONE|entryKeyBase64) smlFile[ attachmentFile]*\n" +
|
||||
"rootDir listMyBlogs\n" +
|
||||
"rootDir listTags blogPublicKeyHash\n" +
|
||||
"rootDir listEntries blogPublicKeyHash blogTag\n" +
|
||||
"rootDir renderEntry blogPublicKeyHash entryId (NONE|entryKeyBase64) summaryOnly includeImages\n";
|
||||
|
||||
public static void main(String args[]) {
|
||||
//args = new String[] { "~/.syndie/", "listEntries", "9qXCJUyUBCCaiIShURo02ckxjrMvrtiDYENv2ATL3-Y=", "/" };
|
||||
//args = new String[] { "~/.syndie/", "renderEntry", "Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=", "/", "20050811001", "NONE", "true", "false" };
|
||||
if (args.length < 2) {
|
||||
System.err.print(USAGE);
|
||||
return;
|
||||
}
|
||||
String command = args[1];
|
||||
if ("createBlog".equals(command))
|
||||
createBlog(args);
|
||||
else if ("listMyBlogs".equals(command))
|
||||
listMyBlogs(args);
|
||||
else if ("createEntry".equals(command))
|
||||
createEntry(args);
|
||||
else if ("listTags".equals(command))
|
||||
listPaths(args);
|
||||
else if ("listEntries".equals(command))
|
||||
listEntries(args);
|
||||
else if ("regenerateIndex".equals(command))
|
||||
regenerateIndex(args);
|
||||
else if ("renderEntry".equals(command))
|
||||
renderEntry(args);
|
||||
else
|
||||
System.out.print(USAGE);
|
||||
}
|
||||
|
||||
private static void createBlog(String args[]) {
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
String archives[] = new String[args.length - 5];
|
||||
System.arraycopy(args, 5, archives, 0, archives.length);
|
||||
BlogInfo info = mgr.createBlog(args[2], args[3], args[4], archives);
|
||||
System.out.println("Blog created: " + info);
|
||||
mgr.getArchive().regenerateIndex();
|
||||
}
|
||||
private static void listMyBlogs(String args[]) {
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
List info = mgr.listMyBlogs();
|
||||
for (int i = 0; i < info.size(); i++)
|
||||
System.out.println(info.get(i).toString());
|
||||
}
|
||||
|
||||
private static void listPaths(String args[]) {
|
||||
// "rootDir listTags blogPublicKeyHash\n";
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
List tags = mgr.getArchive().listTags(new Hash(Base64.decode(args[2])));
|
||||
System.out.println("tag count: " + tags.size());
|
||||
for (int i = 0; i < tags.size(); i++)
|
||||
System.out.println("Tag " + i + ": " + tags.get(i).toString());
|
||||
}
|
||||
|
||||
private static void regenerateIndex(String args[]) {
|
||||
// "rootDir regenerateIndex\n";
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
mgr.getArchive().regenerateIndex();
|
||||
System.out.println("Index regenerated");
|
||||
}
|
||||
|
||||
private static void listEntries(String args[]) {
|
||||
// "rootDir listEntries blogPublicKeyHash tag\n";
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
List entries = mgr.getArchive().listEntries(new Hash(Base64.decode(args[2])), -1, args[3], null);
|
||||
System.out.println("Entry count: " + entries.size());
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
EntryContainer entry = (EntryContainer)entries.get(i);
|
||||
System.out.println("***************************************************");
|
||||
System.out.println("Entry " + i + ": " + entry.getURI().toString());
|
||||
System.out.println("===================================================");
|
||||
System.out.println(entry.getEntry().getText());
|
||||
System.out.println("===================================================");
|
||||
Attachment attachments[] = entry.getAttachments();
|
||||
for (int j = 0; j < attachments.length; j++) {
|
||||
System.out.println("Attachment " + j + ": " + attachments[j]);
|
||||
}
|
||||
System.out.println("===================================================");
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderEntry(String args[]) {
|
||||
//"rootDir renderEntry blogPublicKeyHash entryId (NONE|entryKeyBase64) summaryOnly includeImages\n";
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
long id = -1;
|
||||
try {
|
||||
id = Long.parseLong(args[3]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return;
|
||||
}
|
||||
SessionKey entryKey = null;
|
||||
if (!("NONE".equals(args[4])))
|
||||
entryKey = new SessionKey(Base64.decode(args[5]));
|
||||
EntryContainer entry = mgr.getArchive().getEntry(new BlogURI(new Hash(Base64.decode(args[2])), id), entryKey);
|
||||
if (entry != null) {
|
||||
HTMLRenderer renderer = new HTMLRenderer(I2PAppContext.getGlobalContext());
|
||||
boolean summaryOnly = "true".equalsIgnoreCase(args[5]);
|
||||
boolean showImages = "true".equalsIgnoreCase(args[6]);
|
||||
try {
|
||||
File f = File.createTempFile("syndie", ".html");
|
||||
Writer out = new OutputStreamWriter(new FileOutputStream(f), "UTF-8");
|
||||
renderer.render(null, mgr.getArchive(), entry, out, summaryOnly, showImages);
|
||||
out.flush();
|
||||
out.close();
|
||||
System.out.println("Rendered to " + f.getAbsolutePath() + ": " + f.length());
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
System.err.println("Entry does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
private static void createEntry(String args[]) {
|
||||
// "rootDir createEntry blogPublicKey tag[,tag]* (NOW|entryId) (NONE|entryKeyBase64) smlFile[ attachmentFile]*\n" +
|
||||
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
BlogManager mgr = new BlogManager(ctx, args[0]);
|
||||
long entryId = -1;
|
||||
if ("NOW".equals(args[4])) {
|
||||
entryId = ctx.clock().now();
|
||||
} else {
|
||||
try {
|
||||
entryId = Long.parseLong(args[4]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
StringTokenizer tok = new StringTokenizer(args[3], ",");
|
||||
String tags[] = new String[tok.countTokens()];
|
||||
for (int i = 0; i < tags.length; i++)
|
||||
tags[i] = tok.nextToken();
|
||||
BlogURI uri = new BlogURI(new Hash(Base64.decode(args[2])), entryId);
|
||||
BlogInfo blog = mgr.getArchive().getBlogInfo(uri);
|
||||
if (blog == null) {
|
||||
System.err.println("Blog does not exist: " + uri);
|
||||
return;
|
||||
}
|
||||
SigningPrivateKey key = mgr.getMyPrivateKey(blog);
|
||||
|
||||
try {
|
||||
byte smlData[] = read(args[6]);
|
||||
EntryContainer c = new EntryContainer(uri, tags, smlData);
|
||||
for (int i = 7; i < args.length; i++) {
|
||||
c.addAttachment(read(args[i]), new File(args[i]).getName(),
|
||||
"Attached file", "application/octet-stream");
|
||||
}
|
||||
SessionKey entryKey = null;
|
||||
if (!"NONE".equals(args[5]))
|
||||
entryKey = new SessionKey(Base64.decode(args[5]));
|
||||
c.seal(ctx, key, entryKey);
|
||||
boolean ok = mgr.getArchive().storeEntry(c);
|
||||
System.out.println("Blog entry created: " + c+ "? " + ok);
|
||||
if (ok)
|
||||
mgr.getArchive().regenerateIndex();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] read(String file) throws IOException {
|
||||
File f = new File(file);
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
byte rv[] = new byte[(int)f.length()];
|
||||
if (rv.length != DataHelper.read(in, rv))
|
||||
throw new IOException("File not read completely");
|
||||
return rv;
|
||||
}
|
||||
}
|
250
apps/syndie/java/src/net/i2p/syndie/CachedEntry.java
Normal file
@ -0,0 +1,250 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
|
||||
/**
|
||||
* Lazy loading wrapper for an entry, pulling data out of a cached & extracted dir,
|
||||
* rather than dealing with the crypto, zip, etc.
|
||||
*
|
||||
*/
|
||||
class CachedEntry extends EntryContainer {
|
||||
private File _entryDir;
|
||||
|
||||
private int _format;
|
||||
private int _size;
|
||||
private BlogURI _blog;
|
||||
private Properties _headers;
|
||||
private Entry _entry;
|
||||
private Attachment _attachments[];
|
||||
|
||||
public CachedEntry(File entryDir) throws IOException {
|
||||
_entryDir = entryDir;
|
||||
importMeta();
|
||||
_entry = new CachedEntryDetails();
|
||||
_attachments = null;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return (_entry != null) && (_blog != null);
|
||||
}
|
||||
|
||||
// always available, loaded from meta
|
||||
public int getFormat() { return _format; }
|
||||
public BlogURI getURI() { return _blog; }
|
||||
public int getCompleteSize() { return _size; }
|
||||
|
||||
// dont need to override it, as it works off getHeader
|
||||
//public String[] getTags() { return super.getTags(); }
|
||||
|
||||
public Entry getEntry() { return _entry; }
|
||||
public Attachment[] getAttachments() {
|
||||
importAttachments();
|
||||
return _attachments;
|
||||
}
|
||||
public String getHeader(String key) {
|
||||
importHeaders();
|
||||
return _headers.getProperty(key);
|
||||
}
|
||||
|
||||
public String toString() { return getURI().toString(); }
|
||||
public boolean verifySignature(I2PAppContext ctx, BlogInfo info) { return true; }
|
||||
|
||||
// not supported...
|
||||
public void parseRawData(I2PAppContext ctx) throws IOException {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public void parseRawData(I2PAppContext ctx, SessionKey zipKey) throws IOException {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public void setHeader(String name, String val) {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public void addAttachment(byte data[], String name, String description, String mimeType) {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public void write(OutputStream out, boolean includeRealSignature) throws IOException {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public Signature getSignature() {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
|
||||
// now the actual lazy loading code
|
||||
private void importMeta() throws IOException {
|
||||
Properties meta = readProps(new File(_entryDir, EntryExtractor.META));
|
||||
_format = getInt(meta, "format");
|
||||
_size = getInt(meta, "size");
|
||||
_blog = new BlogURI(new Hash(Base64.decode(meta.getProperty("blog"))), getLong(meta, "entry"));
|
||||
}
|
||||
|
||||
private Properties importHeaders() {
|
||||
if (_headers == null) {
|
||||
try {
|
||||
_headers = readProps(new File(_entryDir, EntryExtractor.HEADERS));
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
_headers = new Properties();
|
||||
}
|
||||
}
|
||||
return _headers;
|
||||
}
|
||||
|
||||
private void importAttachments() {
|
||||
if (_attachments == null) {
|
||||
List attachments = new ArrayList();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
File meta = new File(_entryDir, EntryExtractor.ATTACHMENT_PREFIX + i + EntryExtractor.ATTACHMENT_META_SUFFIX);
|
||||
if (meta.exists())
|
||||
attachments.add(new CachedAttachment(i, meta));
|
||||
else
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
Attachment a[] = new Attachment[attachments.size()];
|
||||
for (i = 0; i < a.length; i++)
|
||||
a[i] = (Attachment)attachments.get(i);
|
||||
_attachments = a;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private static Properties readProps(File propsFile) throws IOException {
|
||||
Properties rv = new Properties();
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
in = new BufferedReader(new InputStreamReader(new FileInputStream(propsFile), "UTF-8"));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
int split = line.indexOf('=');
|
||||
if ( (split <= 0) || (split >= line.length()) ) continue;
|
||||
rv.setProperty(line.substring(0, split).trim(), line.substring(split+1).trim());
|
||||
}
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static final int getInt(Properties props, String key) {
|
||||
String val = props.getProperty(key);
|
||||
try { return Integer.parseInt(val); } catch (NumberFormatException nfe) {}
|
||||
return -1;
|
||||
}
|
||||
private static final long getLong(Properties props, String key) {
|
||||
String val = props.getProperty(key);
|
||||
try { return Long.parseLong(val); } catch (NumberFormatException nfe) {}
|
||||
return -1l;
|
||||
}
|
||||
|
||||
|
||||
private class CachedEntryDetails extends Entry {
|
||||
private String _text;
|
||||
public CachedEntryDetails() {
|
||||
super(null);
|
||||
}
|
||||
public String getText() {
|
||||
importText();
|
||||
return _text;
|
||||
}
|
||||
private void importText() {
|
||||
if (_text == null) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
File f = new File(_entryDir, EntryExtractor.ENTRY);
|
||||
byte buf[] = new byte[(int)f.length()]; // hmm
|
||||
in = new FileInputStream(f);
|
||||
int read = DataHelper.read(in, buf);
|
||||
if (read != buf.length) throw new IOException("read: " + read + " file size: " + buf.length + " for " + f.getPath());
|
||||
_text = DataHelper.getUTF8(buf);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CachedAttachment extends Attachment {
|
||||
private int _attachmentId;
|
||||
private File _metaFile;
|
||||
private Properties _attachmentHeaders;
|
||||
private int _dataSize;
|
||||
|
||||
public CachedAttachment(int id, File meta) {
|
||||
super(null, null);
|
||||
_attachmentId = id;
|
||||
_metaFile = meta;
|
||||
_attachmentHeaders = null;
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
importAttachmentHeaders();
|
||||
return _dataSize;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public InputStream getDataStream() throws IOException {
|
||||
String name = EntryExtractor.ATTACHMENT_PREFIX + _attachmentId + EntryExtractor.ATTACHMENT_DATA_SUFFIX;
|
||||
File f = new File(_entryDir, name);
|
||||
return new FileInputStream(f);
|
||||
}
|
||||
|
||||
public byte[] getRawMetadata() {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
|
||||
public String getMeta(String key) {
|
||||
importAttachmentHeaders();
|
||||
return _attachmentHeaders.getProperty(key);
|
||||
}
|
||||
|
||||
//public String getName() { return getMeta(NAME); }
|
||||
//public String getDescription() { return getMeta(DESCRIPTION); }
|
||||
//public String getMimeType() { return getMeta(MIMETYPE); }
|
||||
|
||||
public void setMeta(String key, String val) {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
|
||||
public Map getMeta() {
|
||||
importAttachmentHeaders();
|
||||
return _attachmentHeaders;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
importAttachmentHeaders();
|
||||
int len = _dataSize;
|
||||
return getName()
|
||||
+ (getDescription() != null ? ": " + getDescription() : "")
|
||||
+ (getMimeType() != null ? ", type: " + getMimeType() : "")
|
||||
+ ", size: " + len;
|
||||
}
|
||||
|
||||
private void importAttachmentHeaders() {
|
||||
if (_attachmentHeaders == null) {
|
||||
try {
|
||||
Properties props = readProps(_metaFile);
|
||||
String sz = (String)props.remove(EntryExtractor.ATTACHMENT_DATA_SIZE);
|
||||
if (sz != null) {
|
||||
try {
|
||||
_dataSize = Integer.parseInt(sz);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
_attachmentHeaders = props;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
_attachmentHeaders = new Properties();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
132
apps/syndie/java/src/net/i2p/syndie/EntryExtractor.java
Normal file
@ -0,0 +1,132 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* To cut down on unnecessary IO/cpu load, extract entries onto the disk for
|
||||
* faster access later. Individual entries are stored in subdirectories based on
|
||||
* their name - $archiveDir/$blogDir/$entryId.snd extracts its files into various
|
||||
* files under $cacheDir/$blogDir/$entryId/:
|
||||
* headers.txt: name=value pairs for attributes of the entry container itself
|
||||
* info.txt: name=value pairs for implicit attributes of the container (blog, id, format, size)
|
||||
* entry.sml: raw sml file
|
||||
* attachmentN_data.dat: raw binary data for attachment N
|
||||
* attachmentN_meta.dat: name=value pairs for attributes of attachment N
|
||||
*
|
||||
*/
|
||||
public class EntryExtractor {
|
||||
private I2PAppContext _context;
|
||||
|
||||
static final String HEADERS = "headers.txt";
|
||||
static final String META = "meta.txt";
|
||||
static final String ENTRY = "entry.sml";
|
||||
static final String ATTACHMENT_PREFIX = "attachment";
|
||||
static final String ATTACHMENT_DATA_SUFFIX = "_data.dat";
|
||||
static final String ATTACHMENT_META_SUFFIX = "_meta.txt";
|
||||
static final String ATTACHMENT_DATA_SIZE = "EntryExtractor__dataSize";
|
||||
|
||||
public EntryExtractor(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public boolean extract(File entryFile, File entryDir, SessionKey entryKey, BlogInfo info) throws IOException {
|
||||
EntryContainer entry = new EntryContainer();
|
||||
entry.load(new FileInputStream(entryFile));
|
||||
boolean ok = entry.verifySignature(_context, info);
|
||||
if (!ok) {
|
||||
return false;
|
||||
} else {
|
||||
entry.setCompleteSize((int)entryFile.length());
|
||||
if (entryKey != null)
|
||||
entry.parseRawData(_context, entryKey);
|
||||
else
|
||||
entry.parseRawData(_context);
|
||||
extract(entry, entryDir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void extract(EntryContainer entry, File entryDir) throws IOException {
|
||||
extractHeaders(entry, entryDir);
|
||||
extractMeta(entry, entryDir);
|
||||
extractEntry(entry, entryDir);
|
||||
Attachment attachments[] = entry.getAttachments();
|
||||
if (attachments != null) {
|
||||
for (int i = 0; i < attachments.length; i++) {
|
||||
extractAttachmentData(i, attachments[i], entryDir);
|
||||
extractAttachmentMetadata(i, attachments[i], entryDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void extractHeaders(EntryContainer entry, File entryDir) throws IOException {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(new File(entryDir, HEADERS));
|
||||
Map headers = entry.getHeaders();
|
||||
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String k = (String)iter.next();
|
||||
String v = (String)headers.get(k);
|
||||
out.write(DataHelper.getUTF8(k.trim() + '=' + v.trim() + '\n'));
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
private void extractMeta(EntryContainer entry, File entryDir) throws IOException {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(new File(entryDir, META));
|
||||
out.write(DataHelper.getUTF8("format=" + entry.getFormat() + '\n'));
|
||||
out.write(DataHelper.getUTF8("size=" + entry.getCompleteSize() + '\n'));
|
||||
out.write(DataHelper.getUTF8("blog=" + entry.getURI().getKeyHash().toBase64() + '\n'));
|
||||
out.write(DataHelper.getUTF8("entry=" + entry.getURI().getEntryId() + '\n'));
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
private void extractEntry(EntryContainer entry, File entryDir) throws IOException {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(new File(entryDir, ENTRY));
|
||||
out.write(DataHelper.getUTF8(entry.getEntry().getText()));
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
private void extractAttachmentData(int num, Attachment attachment, File entryDir) throws IOException {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(new File(entryDir, ATTACHMENT_PREFIX + num + ATTACHMENT_DATA_SUFFIX));
|
||||
//out.write(attachment.getData());
|
||||
InputStream data = attachment.getDataStream();
|
||||
byte buf[] = new byte[1024];
|
||||
int read = 0;
|
||||
while ( (read = data.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
data.close();
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
private void extractAttachmentMetadata(int num, Attachment attachment, File entryDir) throws IOException {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(new File(entryDir, ATTACHMENT_PREFIX + num + ATTACHMENT_META_SUFFIX));
|
||||
Map meta = attachment.getMeta();
|
||||
for (Iterator iter = meta.keySet().iterator(); iter.hasNext(); ) {
|
||||
String k = (String)iter.next();
|
||||
String v = (String)meta.get(k);
|
||||
out.write(DataHelper.getUTF8(k + '=' + v + '\n'));
|
||||
}
|
||||
out.write(DataHelper.getUTF8(ATTACHMENT_DATA_SIZE + '=' + attachment.getDataLength()));
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
71
apps/syndie/java/src/net/i2p/syndie/Updater.java
Normal file
@ -0,0 +1,71 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.syndie.web.RemoteArchiveBean;
|
||||
|
||||
public class Updater {
|
||||
public static final String VERSION = "1.0";
|
||||
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(Updater.class);
|
||||
private static final Updater _instance = new Updater();
|
||||
private long _lastUpdate;
|
||||
|
||||
public void update() {
|
||||
BlogManager bm = BlogManager.instance();
|
||||
if (_lastUpdate + bm.getUpdateDelay()*60*60*1000 > System.currentTimeMillis()) {
|
||||
return;
|
||||
}
|
||||
_lastUpdate = System.currentTimeMillis();
|
||||
_log.debug("Update started.");
|
||||
User user = new User();
|
||||
RemoteArchiveBean rab = new RemoteArchiveBean();
|
||||
String[] archives = bm.getUpdateArchives();
|
||||
for (int i = 0; i < archives.length; i++) {
|
||||
_log.debug("Fetching " + archives[i]);
|
||||
rab.fetchIndex(user, "web", archives[i], bm.getDefaultProxyHost(), bm.getDefaultProxyPort());
|
||||
if (rab.getRemoteIndex() != null) {
|
||||
_log.debug("Index fetched, getting new entries.");
|
||||
HashMap parameters = new HashMap();
|
||||
parameters.put("action", "Fetch all new entries");
|
||||
//rab.fetchSelectedBulk(user, parameters);
|
||||
rab.fetchAllEntries(user, parameters);
|
||||
_log.debug("Update finished.");
|
||||
} else {
|
||||
_log.debug("Index fetch failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
_instance.run();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
// wait
|
||||
try {
|
||||
Thread.currentThread().sleep(5*60*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
while (true) {
|
||||
int delay = BlogManager.instance().getUpdateDelay();
|
||||
if (delay < 1) delay = 1;
|
||||
update();
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait(delay * 60 * 60 * 1000);
|
||||
}
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void wakeup() {
|
||||
synchronized (_instance) {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
37
apps/syndie/java/src/net/i2p/syndie/UpdaterServlet.java
Normal file
@ -0,0 +1,37 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import javax.servlet.GenericServlet;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
/**
|
||||
* A wrapper for syndie updater to allow it to be started as a web application.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class UpdaterServlet extends GenericServlet {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
|
||||
*/
|
||||
public void service(ServletRequest request, ServletResponse response) {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
|
||||
*/
|
||||
public void init(ServletConfig config) {
|
||||
try {
|
||||
super.init(config);
|
||||
} catch (ServletException exp) {
|
||||
}
|
||||
UpdaterThread thread = new UpdaterThread();
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
System.out.println("INFO: Starting Syndie Updater " + Updater.VERSION);
|
||||
}
|
||||
|
||||
}
|
27
apps/syndie/java/src/net/i2p/syndie/UpdaterThread.java
Normal file
@ -0,0 +1,27 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
/**
|
||||
* A thread that runs the updater.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class UpdaterThread extends Thread {
|
||||
|
||||
/**
|
||||
* Construct an UpdaterThread.
|
||||
*/
|
||||
public UpdaterThread() {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
//try {
|
||||
// Thread.sleep(5 * 60 * 1000);
|
||||
//} catch (InterruptedException exp) {
|
||||
//}
|
||||
Updater.main();
|
||||
}
|
||||
}
|
244
apps/syndie/java/src/net/i2p/syndie/User.java
Normal file
@ -0,0 +1,244 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.PetNameDB;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* User session state and preferences.
|
||||
*
|
||||
*/
|
||||
public class User {
|
||||
private I2PAppContext _context;
|
||||
private String _username;
|
||||
private String _hashedPassword;
|
||||
private Hash _blog;
|
||||
private long _mostRecentEntry;
|
||||
/** Group name to List of blog selectors, where the selectors are of the form
|
||||
* blog://$key, entry://$key/$entryId, blogtag://$key/$tag, tag://$tag
|
||||
*/
|
||||
private Map _blogGroups;
|
||||
/** list of blogs (Hash) we never want to see entries from */
|
||||
private List _shitlistedBlogs;
|
||||
/** where our userhosts.txt is */
|
||||
private String _addressbookLocation;
|
||||
private boolean _showImagesByDefault;
|
||||
private boolean _showExpandedByDefault;
|
||||
private String _defaultSelector;
|
||||
private long _lastLogin;
|
||||
private long _lastMetaEntry;
|
||||
private boolean _allowAccessRemote;
|
||||
private boolean _authenticated;
|
||||
private String _eepProxyHost;
|
||||
private int _eepProxyPort;
|
||||
private String _webProxyHost;
|
||||
private int _webProxyPort;
|
||||
private String _torProxyHost;
|
||||
private int _torProxyPort;
|
||||
private PetNameDB _petnames;
|
||||
|
||||
public User() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
init();
|
||||
}
|
||||
private void init() {
|
||||
_authenticated = false;
|
||||
_username = null;
|
||||
_hashedPassword = null;
|
||||
_blog = null;
|
||||
_mostRecentEntry = -1;
|
||||
_blogGroups = new HashMap();
|
||||
_shitlistedBlogs = new ArrayList();
|
||||
_defaultSelector = null;
|
||||
_addressbookLocation = "userhosts.txt";
|
||||
_showImagesByDefault = true;
|
||||
_showExpandedByDefault = false;
|
||||
_allowAccessRemote = false;
|
||||
_eepProxyHost = null;
|
||||
_webProxyHost = null;
|
||||
_torProxyHost = null;
|
||||
_eepProxyPort = -1;
|
||||
_webProxyPort = -1;
|
||||
_torProxyPort = -1;
|
||||
_lastLogin = -1;
|
||||
_lastMetaEntry = 0;
|
||||
_petnames = new PetNameDB();
|
||||
}
|
||||
|
||||
public boolean getAuthenticated() { return _authenticated; }
|
||||
public String getUsername() { return _username; }
|
||||
public Hash getBlog() { return _blog; }
|
||||
public String getBlogStr() { return Base64.encode(_blog.getData()); }
|
||||
public long getMostRecentEntry() { return _mostRecentEntry; }
|
||||
public Map getBlogGroups() { return _blogGroups; }
|
||||
public List getShitlistedBlogs() { return _shitlistedBlogs; }
|
||||
public String getAddressbookLocation() { return _addressbookLocation; }
|
||||
public boolean getShowImages() { return _showImagesByDefault; }
|
||||
public boolean getShowExpanded() { return _showExpandedByDefault; }
|
||||
public long getLastLogin() { return _lastLogin; }
|
||||
public String getHashedPassword() { return _hashedPassword; }
|
||||
public long getLastMetaEntry() { return _lastMetaEntry; }
|
||||
public String getDefaultSelector() { return _defaultSelector; }
|
||||
public void setDefaultSelector(String sel) { _defaultSelector = sel; }
|
||||
public boolean getAllowAccessRemote() { return _allowAccessRemote; }
|
||||
public void setAllowAccessRemote(boolean allow) { _allowAccessRemote = true; }
|
||||
|
||||
public void setMostRecentEntry(long id) { _mostRecentEntry = id; }
|
||||
public void setLastMetaEntry(long id) { _lastMetaEntry = id; }
|
||||
|
||||
public String getEepProxyHost() { return _eepProxyHost; }
|
||||
public int getEepProxyPort() { return _eepProxyPort; }
|
||||
public String getWebProxyHost() { return _webProxyHost; }
|
||||
public int getWebProxyPort() { return _webProxyPort; }
|
||||
public String getTorProxyHost() { return _torProxyHost; }
|
||||
public int getTorProxyPort() { return _torProxyPort; }
|
||||
|
||||
public PetNameDB getPetNameDB() { return _petnames; }
|
||||
|
||||
public void invalidate() {
|
||||
if (_authenticated)
|
||||
BlogManager.instance().saveUser(this);
|
||||
init();
|
||||
}
|
||||
|
||||
public String login(String login, String pass, Properties props) {
|
||||
String expectedPass = props.getProperty("password");
|
||||
String hpass = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(pass)).getData());
|
||||
if (!hpass.equals(expectedPass)) {
|
||||
_authenticated = false;
|
||||
return "<span class=\"b_loginMsgErr\">Incorrect password</span>";
|
||||
}
|
||||
|
||||
_username = login;
|
||||
_hashedPassword = expectedPass;
|
||||
|
||||
// blog=luS9d3uaf....HwAE=
|
||||
String b = props.getProperty("blog");
|
||||
if (b != null) _blog = new Hash(Base64.decode(b));
|
||||
// lastid=12345
|
||||
String id = props.getProperty("lastid");
|
||||
if (id != null) try { _mostRecentEntry = Long.parseLong(id); } catch (NumberFormatException nfe) {}
|
||||
// lastmetaedition=12345
|
||||
id = props.getProperty("lastmetaedition");
|
||||
if (id != null) try { _lastMetaEntry = Long.parseLong(id); } catch (NumberFormatException nfe) {}
|
||||
// groups=abc:selector,selector,selector,selector def:selector,selector,selector
|
||||
StringTokenizer tok = new StringTokenizer(props.getProperty("groups", ""), " ");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String group = tok.nextToken();
|
||||
int endName = group.indexOf(':');
|
||||
if (endName <= 0)
|
||||
continue;
|
||||
String groupName = group.substring(0, endName);
|
||||
String sel = group.substring(endName+1);
|
||||
List selectors = new ArrayList();
|
||||
while ( (sel != null) && (sel.length() > 0) ) {
|
||||
int end = sel.indexOf(',');
|
||||
if (end < 0) {
|
||||
selectors.add(sel);
|
||||
sel = null;
|
||||
} else {
|
||||
if (end + 1 >= sel.length()) {
|
||||
selectors.add(sel.substring(0,end));
|
||||
sel = null;
|
||||
} else if (end == 0) {
|
||||
sel = sel.substring(1);
|
||||
} else {
|
||||
selectors.add(sel.substring(0, end));
|
||||
sel = sel.substring(end+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
_blogGroups.put(groupName.trim(), selectors);
|
||||
}
|
||||
// shitlist=hash,hash,hash
|
||||
tok = new StringTokenizer(props.getProperty("shitlistedblogs", ""), ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String blog = tok.nextToken();
|
||||
byte bl[] = Base64.decode(blog);
|
||||
if ( (bl != null) && (bl.length == Hash.HASH_LENGTH) )
|
||||
_shitlistedBlogs.add(new Hash(bl));
|
||||
}
|
||||
|
||||
String addr = props.getProperty("addressbook", "userhosts.txt");
|
||||
if (addr != null) {
|
||||
_addressbookLocation = addr;
|
||||
try {
|
||||
_petnames.load(addr);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
String show = props.getProperty("showimages", "true");
|
||||
_showImagesByDefault = (show != null) && (show.equals("true"));
|
||||
show = props.getProperty("showexpanded", "false");
|
||||
_showExpandedByDefault = (show != null) && (show.equals("true"));
|
||||
_defaultSelector = props.getProperty("defaultselector");
|
||||
String allow = props.getProperty("allowaccessremote", "false");
|
||||
_allowAccessRemote = (allow != null) && (allow.equals("true"));
|
||||
_eepProxyPort = getInt(props.getProperty("eepproxyport"));
|
||||
_webProxyPort = getInt(props.getProperty("webproxyport"));
|
||||
_torProxyPort = getInt(props.getProperty("torproxyport"));
|
||||
_eepProxyHost = props.getProperty("eepproxyhost");
|
||||
_webProxyHost = props.getProperty("webproxyhost");
|
||||
_torProxyHost = props.getProperty("torproxyhost");
|
||||
_lastLogin = _context.clock().now();
|
||||
_authenticated = true;
|
||||
return LOGIN_OK;
|
||||
}
|
||||
|
||||
private int getInt(String val) {
|
||||
if (val == null) return -1;
|
||||
try { return Integer.parseInt(val); } catch (NumberFormatException nfe) { return -1; }
|
||||
}
|
||||
|
||||
public static final String LOGIN_OK = "<span class=\"b_loginMsgOk\">Logged in</span>";
|
||||
|
||||
public String export() {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("password=" + getHashedPassword() + "\n");
|
||||
buf.append("blog=" + getBlog().toBase64() + "\n");
|
||||
buf.append("lastid=" + getMostRecentEntry() + "\n");
|
||||
buf.append("lastmetaedition=" + getLastMetaEntry() + "\n");
|
||||
buf.append("lastlogin=" + getLastLogin() + "\n");
|
||||
buf.append("addressbook=" + getAddressbookLocation() + "\n");
|
||||
buf.append("showimages=" + getShowImages() + "\n");
|
||||
buf.append("showexpanded=" + getShowExpanded() + "\n");
|
||||
buf.append("defaultselector=" + getDefaultSelector() + "\n");
|
||||
buf.append("allowaccessremote=" + _allowAccessRemote + "\n");
|
||||
|
||||
buf.append("groups=");
|
||||
Map groups = getBlogGroups();
|
||||
for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
List selectors = (List)groups.get(name);
|
||||
buf.append(name).append(':');
|
||||
for (int i = 0; i < selectors.size(); i++) {
|
||||
buf.append(selectors.get(i));
|
||||
if (i + 1 < selectors.size())
|
||||
buf.append(",");
|
||||
}
|
||||
if (iter.hasNext())
|
||||
buf.append(' ');
|
||||
}
|
||||
buf.append('\n');
|
||||
// shitlist=hash,hash,hash
|
||||
List shitlistedBlogs = getShitlistedBlogs();
|
||||
if (shitlistedBlogs.size() > 0) {
|
||||
buf.setLength(0);
|
||||
buf.append("shitlistedblogs=");
|
||||
for (int i = 0; i < shitlistedBlogs.size(); i++) {
|
||||
Hash blog = (Hash)shitlistedBlogs.get(i);
|
||||
buf.append(blog.toBase64());
|
||||
if (i + 1 < shitlistedBlogs.size())
|
||||
buf.append(',');
|
||||
}
|
||||
buf.append('\n');
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
11
apps/syndie/java/src/net/i2p/syndie/Version.java
Normal file
@ -0,0 +1,11 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Version {
|
||||
public static final String VERSION = "0-alpha";
|
||||
public static final String BUILD = "0";
|
||||
public static final String INDEX_VERSION = "1.0";
|
||||
public static final String ID = "$Id$";
|
||||
}
|
470
apps/syndie/java/src/net/i2p/syndie/data/ArchiveIndex.java
Normal file
@ -0,0 +1,470 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.Archive;
|
||||
import net.i2p.syndie.BlogManager;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple read-only summary of an archive
|
||||
*/
|
||||
public class ArchiveIndex {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
protected String _version;
|
||||
protected long _generatedOn;
|
||||
protected int _allBlogs;
|
||||
protected int _newBlogs;
|
||||
protected int _allEntries;
|
||||
protected int _newEntries;
|
||||
protected long _totalSize;
|
||||
protected long _newSize;
|
||||
/** list of BlogSummary objects */
|
||||
protected List _blogs;
|
||||
/** list of Hash objects */
|
||||
protected List _newestBlogs;
|
||||
/** list of BlogURI objects */
|
||||
protected List _newestEntries;
|
||||
/** parent message to a set of replies, ordered with the oldest first */
|
||||
protected Map _replies;
|
||||
protected Properties _headers;
|
||||
|
||||
public ArchiveIndex() {
|
||||
this(I2PAppContext.getGlobalContext(), false);
|
||||
}
|
||||
public ArchiveIndex(I2PAppContext ctx) {
|
||||
this(ctx, false); //true);
|
||||
}
|
||||
public ArchiveIndex(I2PAppContext ctx, boolean shouldLoad) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(ArchiveIndex.class);
|
||||
_blogs = new ArrayList();
|
||||
_newestBlogs = new ArrayList();
|
||||
_newestEntries = new ArrayList();
|
||||
_headers = new Properties();
|
||||
_replies = Collections.synchronizedMap(new HashMap());
|
||||
_generatedOn = -1;
|
||||
if (shouldLoad)
|
||||
setIsLocal("true");
|
||||
}
|
||||
|
||||
public String getVersion() { return _version; }
|
||||
public Properties getHeaders() { return _headers; }
|
||||
public int getAllBlogs() { return _allBlogs; }
|
||||
public int getNewBlogs() { return _newBlogs; }
|
||||
public int getAllEntries() { return _allEntries; }
|
||||
public int getNewEntries() { return _newEntries; }
|
||||
public long getTotalSize() { return _totalSize; }
|
||||
public long getNewSize() { return _newSize; }
|
||||
public long getGeneratedOn() { return _generatedOn; }
|
||||
|
||||
public String getNewSizeStr() {
|
||||
if (_newSize < 1024) return _newSize + "";
|
||||
if (_newSize < 1024*1024) return _newSize/1024 + "KB";
|
||||
else return _newSize/(1024*1024) + "MB";
|
||||
}
|
||||
public String getTotalSizeStr() {
|
||||
if (_totalSize < 1024) return _totalSize + "";
|
||||
if (_totalSize < 1024*1024) return _totalSize/1024 + "KB";
|
||||
else return _totalSize/(1024*1024) + "MB";
|
||||
}
|
||||
|
||||
/** how many blogs/tags are indexed */
|
||||
public int getIndexBlogs() { return _blogs.size(); }
|
||||
/** get the blog used for the given blog/tag pair */
|
||||
public Hash getBlog(int index) { return ((BlogSummary)_blogs.get(index)).blog; }
|
||||
/** get the tag used for the given blog/tag pair */
|
||||
public String getBlogTag(int index) { return ((BlogSummary)_blogs.get(index)).tag; }
|
||||
/** get the highest entry ID for the given blog/tag pair */
|
||||
public long getBlogLastUpdated(int index) { return ((BlogSummary)_blogs.get(index)).lastUpdated; }
|
||||
/** get the entry count for the given blog/tag pair */
|
||||
public int getBlogEntryCount(int index) { return ((BlogSummary)_blogs.get(index)).entries.size(); }
|
||||
/** get the entry from the given blog/tag pair */
|
||||
public BlogURI getBlogEntry(int index, int entryIndex) { return ((EntrySummary)((BlogSummary)_blogs.get(index)).entries.get(entryIndex)).entry; }
|
||||
/** get the raw entry size (including attachments) from the given blog/tag pair */
|
||||
public long getBlogEntrySizeKB(int index, int entryIndex) { return ((EntrySummary)((BlogSummary)_blogs.get(index)).entries.get(entryIndex)).size; }
|
||||
|
||||
public boolean getEntryIsKnown(BlogURI uri) { return getEntry(uri) != null; }
|
||||
public long getBlogEntrySizeKB(BlogURI uri) {
|
||||
EntrySummary entry = getEntry(uri);
|
||||
if (entry == null) return -1;
|
||||
return entry.size;
|
||||
}
|
||||
private EntrySummary getEntry(BlogURI uri) {
|
||||
if ( (uri == null) || (uri.getKeyHash() == null) || (uri.getEntryId() < 0) ) return null;
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
BlogSummary summary = (BlogSummary)_blogs.get(i);
|
||||
if (summary.blog.equals(uri.getKeyHash())) {
|
||||
for (int j = 0; j < summary.entries.size(); j++) {
|
||||
EntrySummary entry = (EntrySummary)summary.entries.get(j);
|
||||
if (entry.entry.equals(uri))
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public Set getBlogEntryTags(BlogURI uri) {
|
||||
Set tags = new HashSet();
|
||||
if ( (uri == null) || (uri.getKeyHash() == null) || (uri.getEntryId() < 0) ) return tags;
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
BlogSummary summary = (BlogSummary)_blogs.get(i);
|
||||
if (summary.blog.equals(uri.getKeyHash())) {
|
||||
for (int j = 0; j < summary.entries.size(); j++) {
|
||||
EntrySummary entry = (EntrySummary)summary.entries.get(j);
|
||||
if (entry.entry.equals(uri)) {
|
||||
tags.add(summary.tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
public int getBlogEntryCount(Hash blog) {
|
||||
Set uris = new HashSet(64);
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
BlogSummary summary = (BlogSummary)_blogs.get(i);
|
||||
if (summary.blog.equals(blog)) {
|
||||
uris.addAll(summary.entries);
|
||||
//for (int j = 0; j < summary.entries.size(); j++) {
|
||||
// EntrySummary entry = (EntrySummary)summary.entries.get(j);
|
||||
// uris.add(entry.entry);
|
||||
//}
|
||||
}
|
||||
}
|
||||
return uris.size();
|
||||
}
|
||||
|
||||
/** how many 'new' blogs are listed */
|
||||
public int getNewestBlogCount() { return _newestBlogs.size(); }
|
||||
public Hash getNewestBlog(int index) { return (Hash)_newestBlogs.get(index); }
|
||||
/** how many 'new' entries are listed */
|
||||
public int getNewestBlogEntryCount() { return _newestEntries.size(); }
|
||||
public BlogURI getNewestBlogEntry(int index) { return (BlogURI)_newestEntries.get(index); }
|
||||
|
||||
/** list of locally known tags (String) under the given blog */
|
||||
public List getBlogTags(Hash blog) {
|
||||
List rv = new ArrayList();
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
if (getBlog(i).equals(blog))
|
||||
rv.add(getBlogTag(i));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
/** list of unique blogs locally known (set of Hash) */
|
||||
public Set getUniqueBlogs() {
|
||||
Set rv = new HashSet();
|
||||
for (int i = 0; i < _blogs.size(); i++)
|
||||
rv.add(getBlog(i));
|
||||
return rv;
|
||||
}
|
||||
public List getReplies(BlogURI uri) {
|
||||
Set replies = (Set)_replies.get(uri);
|
||||
if (replies == null) return Collections.EMPTY_LIST;
|
||||
synchronized (replies) {
|
||||
return new ArrayList(replies);
|
||||
}
|
||||
}
|
||||
public void setLocation(String location) {
|
||||
try {
|
||||
File l = new File(location);
|
||||
if (l.exists())
|
||||
load(l);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
public void setIsLocal(String val) {
|
||||
if ("true".equals(val)) {
|
||||
try {
|
||||
File dir = BlogManager.instance().getArchive().getArchiveDir();
|
||||
load(new File(dir, Archive.INDEX_FILE));
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void load(File location) throws IOException {
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(location);
|
||||
load(in);
|
||||
} finally {
|
||||
if (in != null)
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/** load up the index from an archive.txt */
|
||||
public void load(InputStream index) throws IOException {
|
||||
_allBlogs = 0;
|
||||
_allEntries = 0;
|
||||
_newBlogs = 0;
|
||||
_newEntries = 0;
|
||||
_newSize = 0;
|
||||
_totalSize = 0;
|
||||
_version = null;
|
||||
_blogs = new ArrayList();
|
||||
_newestBlogs = new ArrayList();
|
||||
_newestEntries = new ArrayList();
|
||||
_headers = new Properties();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(index, "UTF-8"));
|
||||
String line = null;
|
||||
line = in.readLine();
|
||||
if (line == null)
|
||||
return;
|
||||
if (!line.startsWith("SyndieVersion:"))
|
||||
throw new IOException("Index is invalid - it starts with " + line);
|
||||
_version = line.substring("SyndieVersion:".length()).trim();
|
||||
if (!_version.startsWith("1."))
|
||||
throw new IOException("Index is not supported, we only handle versions 1.*, but it is " + _version);
|
||||
while ( (line = in.readLine()) != null) {
|
||||
if (line.length() <= 0)
|
||||
break;
|
||||
if (line.startsWith("Blog:")) break;
|
||||
int split = line.indexOf(':');
|
||||
if (split <= 0) continue;
|
||||
if (split >= line.length()-1) continue;
|
||||
_headers.setProperty(line.substring(0, split), line.substring(split+1));
|
||||
}
|
||||
if (line != null) {
|
||||
do {
|
||||
if (!line.startsWith("Blog:"))
|
||||
break;
|
||||
loadBlog(line);
|
||||
} while ( (line = in.readLine()) != null);
|
||||
}
|
||||
|
||||
// ignore the first line that doesnt start with blog - its blank
|
||||
while ( (line = in.readLine()) != null) {
|
||||
int split = line.indexOf(':');
|
||||
if (split <= 0) continue;
|
||||
if (split >= line.length()-1) continue;
|
||||
String key = line.substring(0, split);
|
||||
String val = line.substring(split+1);
|
||||
if (key.equals("AllBlogs"))
|
||||
_allBlogs = getInt(val);
|
||||
else if (key.equals("NewBlogs"))
|
||||
_newBlogs = getInt(val);
|
||||
else if (key.equals("AllEntries"))
|
||||
_allEntries = getInt(val);
|
||||
else if (key.equals("NewEntries"))
|
||||
_newEntries = getInt(val);
|
||||
else if (key.equals("TotalSize"))
|
||||
_totalSize = getInt(val);
|
||||
else if (key.equals("NewSize"))
|
||||
_newSize = getInt(val);
|
||||
else if (key.equals("NewestBlogs"))
|
||||
_newestBlogs = parseNewestBlogs(val);
|
||||
else if (key.equals("NewestEntries"))
|
||||
_newestEntries = parseNewestEntries(val);
|
||||
//else
|
||||
// System.err.println("Key: " + key + " val: " + val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dig through the index for BlogURIs matching the given criteria, ordering the results by
|
||||
* their own entryIds.
|
||||
*
|
||||
* @param out where to store the matches
|
||||
* @param blog if set, what blog key must the entries be under
|
||||
* @param tag if set, what tag must the entry be in
|
||||
*
|
||||
*/
|
||||
public void selectMatchesOrderByEntryId(List out, Hash blog, String tag) {
|
||||
TreeMap ordered = new TreeMap();
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
BlogSummary summary = (BlogSummary)_blogs.get(i);
|
||||
if (blog != null) {
|
||||
if (!blog.equals(summary.blog))
|
||||
continue;
|
||||
}
|
||||
if (tag != null) {
|
||||
if (!tag.equals(summary.tag)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tag [" + summary.tag + "] does not match the requested [" + tag + "] in " + summary.blog.toBase64());
|
||||
if (false) {
|
||||
StringBuffer b = new StringBuffer(tag.length()*2);
|
||||
for (int j = 0; j < tag.length(); j++) {
|
||||
b.append((int)tag.charAt(j));
|
||||
b.append('.');
|
||||
if (summary.tag.length() > j+1)
|
||||
b.append((int)summary.tag.charAt(j));
|
||||
else
|
||||
b.append('_');
|
||||
b.append(' ');
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("tag.summary: " + b.toString());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < summary.entries.size(); j++) {
|
||||
EntrySummary entry = (EntrySummary)summary.entries.get(j);
|
||||
String k = (Long.MAX_VALUE-entry.entry.getEntryId()) + "-" + entry.entry.getKeyHash().toBase64();
|
||||
ordered.put(k, entry.entry);
|
||||
//System.err.println("Including match: " + k);
|
||||
}
|
||||
}
|
||||
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); ) {
|
||||
BlogURI entry = (BlogURI)iter.next();
|
||||
if (!out.contains(entry))
|
||||
out.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int getInt(String val) {
|
||||
try {
|
||||
return Integer.parseInt(val.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private List parseNewestBlogs(String vals) {
|
||||
List rv = new ArrayList();
|
||||
StringTokenizer tok = new StringTokenizer(vals, " \t\n");
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(new Hash(Base64.decode(tok.nextToken())));
|
||||
return rv;
|
||||
}
|
||||
private List parseNewestEntries(String vals) {
|
||||
List rv = new ArrayList();
|
||||
StringTokenizer tok = new StringTokenizer(vals, " \t\n");
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(new BlogURI(tok.nextToken()));
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void loadBlog(String line) throws IOException {
|
||||
// Blog: hash YYYYMMDD tag\t[ yyyymmdd_n_sizeKB]*
|
||||
StringTokenizer tok = new StringTokenizer(line.trim(), " \n\t");
|
||||
if (tok.countTokens() < 4)
|
||||
return;
|
||||
tok.nextToken();
|
||||
String keyStr = tok.nextToken();
|
||||
Hash keyHash = new Hash(Base64.decode(keyStr));
|
||||
String whenStr = tok.nextToken();
|
||||
long when = getIndexDate(whenStr);
|
||||
String tag = tok.nextToken();
|
||||
BlogSummary summary = new BlogSummary();
|
||||
summary.blog = keyHash;
|
||||
summary.tag = tag.trim();
|
||||
summary.lastUpdated = when;
|
||||
summary.entries = new ArrayList();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String entry = tok.nextToken();
|
||||
long id = Archive.getEntryIdFromIndexName(entry);
|
||||
int kb = Archive.getSizeFromIndexName(entry);
|
||||
summary.entries.add(new EntrySummary(new BlogURI(keyHash, id), kb));
|
||||
}
|
||||
_blogs.add(summary);
|
||||
}
|
||||
|
||||
private SimpleDateFormat _dateFmt = new SimpleDateFormat("yyyyMMdd", Locale.UK);
|
||||
private long getIndexDate(String yyyymmdd) {
|
||||
synchronized (_dateFmt) {
|
||||
try {
|
||||
return _dateFmt.parse(yyyymmdd).getTime();
|
||||
} catch (ParseException pe) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
private String getIndexDate(long when) {
|
||||
synchronized (_dateFmt) {
|
||||
return _dateFmt.format(new Date(when));
|
||||
}
|
||||
}
|
||||
|
||||
protected class BlogSummary {
|
||||
Hash blog;
|
||||
String tag;
|
||||
long lastUpdated;
|
||||
/** list of EntrySummary objects */
|
||||
List entries;
|
||||
|
||||
public BlogSummary() {
|
||||
entries = new ArrayList();
|
||||
}
|
||||
}
|
||||
protected class EntrySummary {
|
||||
BlogURI entry;
|
||||
long size;
|
||||
public EntrySummary(BlogURI uri, long kb) {
|
||||
size = kb;
|
||||
entry = uri;
|
||||
}
|
||||
public int hashCode() {
|
||||
return entry.hashCode();
|
||||
}
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj instanceof EntrySummary) && (((EntrySummary)obj).entry.equals(entry)) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** export the index into an archive.txt */
|
||||
public String toString() {
|
||||
StringBuffer rv = new StringBuffer(1024);
|
||||
rv.append("SyndieVersion: ").append(_version).append('\n');
|
||||
for (Iterator iter = _headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = _headers.getProperty(key);
|
||||
rv.append(key).append(": ").append(val).append('\n');
|
||||
}
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
rv.append("Blog: ");
|
||||
Hash blog = getBlog(i);
|
||||
String tag = getBlogTag(i);
|
||||
rv.append(Base64.encode(blog.getData())).append(' ');
|
||||
rv.append(getIndexDate(getBlogLastUpdated(i))).append(' ');
|
||||
rv.append(tag).append('\t');
|
||||
int entries = getBlogEntryCount(i);
|
||||
for (int j = 0; j < entries; j++) {
|
||||
BlogURI entry = getBlogEntry(i, j);
|
||||
long kb = getBlogEntrySizeKB(i, j);
|
||||
rv.append(Archive.getIndexName(entry.getEntryId(), (int)kb*1024)).append(' ');
|
||||
}
|
||||
rv.append('\n');
|
||||
}
|
||||
|
||||
rv.append('\n');
|
||||
rv.append("AllBlogs: ").append(_allBlogs).append('\n');
|
||||
rv.append("NewBlogs: ").append(_newBlogs).append('\n');
|
||||
rv.append("AllEntries: ").append(_allEntries).append('\n');
|
||||
rv.append("NewEntries: ").append(_newEntries).append('\n');
|
||||
rv.append("TotalSize: ").append(_totalSize).append('\n');
|
||||
rv.append("NewSize: ").append(_newSize).append('\n');
|
||||
|
||||
rv.append("NewestBlogs: ");
|
||||
for (int i = 0; i < _newestBlogs.size(); i++)
|
||||
rv.append(((Hash)(_newestBlogs.get(i))).toBase64()).append(' ');
|
||||
rv.append('\n');
|
||||
|
||||
rv.append("NewestEntries: ");
|
||||
for (int i = 0; i < _newestEntries.size(); i++)
|
||||
rv.append(((BlogURI)_newestEntries.get(i)).toString()).append(' ');
|
||||
rv.append('\n');
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
|
||||
/** Usage: ArchiveIndex archive.txt */
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
ArchiveIndex i = new ArchiveIndex();
|
||||
i.load(new File(args[0]));
|
||||
System.out.println(i.toString());
|
||||
} catch (IOException ioe) { ioe.printStackTrace(); }
|
||||
}
|
||||
}
|
122
apps/syndie/java/src/net/i2p/syndie/data/Attachment.java
Normal file
@ -0,0 +1,122 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Attachment {
|
||||
private byte _data[];
|
||||
private byte _rawMetadata[];
|
||||
private List _keys;
|
||||
private List _values;
|
||||
|
||||
public Attachment(byte data[], byte metadata[]) {
|
||||
_data = data;
|
||||
_rawMetadata = metadata;
|
||||
_keys = new ArrayList();
|
||||
_values = new ArrayList();
|
||||
parseMeta();
|
||||
}
|
||||
|
||||
public static final String NAME = "Name";
|
||||
public static final String DESCRIPTION = "Description";
|
||||
public static final String MIMETYPE = "MimeType";
|
||||
|
||||
public Attachment(byte data[], String name, String description, String mimeType) {
|
||||
_data = data;
|
||||
_keys = new ArrayList();
|
||||
_values = new ArrayList();
|
||||
_keys.add(NAME);
|
||||
_values.add(name);
|
||||
if ( (description != null) && (description.trim().length() > 0) ) {
|
||||
_keys.add(DESCRIPTION);
|
||||
_values.add(description);
|
||||
}
|
||||
if ( (mimeType != null) && (mimeType.trim().length() > 0) ) {
|
||||
_keys.add(MIMETYPE);
|
||||
_values.add(mimeType);
|
||||
}
|
||||
createMeta();
|
||||
}
|
||||
|
||||
public byte[] getData() { return _data; }
|
||||
public int getDataLength() { return _data.length; }
|
||||
public byte[] getRawMetadata() { return _rawMetadata; }
|
||||
|
||||
public InputStream getDataStream() throws IOException { return new ByteArrayInputStream(_data); }
|
||||
|
||||
public String getMeta(String key) {
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
if (key.equals(_keys.get(i)))
|
||||
return (String)_values.get(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getName() { return getMeta(NAME); }
|
||||
public String getDescription() { return getMeta(DESCRIPTION); }
|
||||
public String getMimeType() { return getMeta(MIMETYPE); }
|
||||
|
||||
public void setMeta(String key, String val) {
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
if (key.equals(_keys.get(i))) {
|
||||
_values.set(i, val);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_keys.add(key);
|
||||
_values.add(val);
|
||||
}
|
||||
|
||||
public Map getMeta() {
|
||||
Map rv = new HashMap(_keys.size());
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
String k = (String)_keys.get(i);
|
||||
String v = (String)_values.get(i);
|
||||
rv.put(k,v);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void createMeta() {
|
||||
StringBuffer meta = new StringBuffer(64);
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
meta.append(_keys.get(i)).append(':').append(_values.get(i)).append('\n');
|
||||
}
|
||||
_rawMetadata = DataHelper.getUTF8(meta);
|
||||
}
|
||||
|
||||
private void parseMeta() {
|
||||
if (_rawMetadata == null) return;
|
||||
String key = null;
|
||||
String val = null;
|
||||
int keyBegin = 0;
|
||||
int valBegin = -1;
|
||||
for (int i = 0; i < _rawMetadata.length; i++) {
|
||||
if (_rawMetadata[i] == ':') {
|
||||
key = DataHelper.getUTF8(_rawMetadata, keyBegin, i - keyBegin);
|
||||
valBegin = i + 1;
|
||||
} else if (_rawMetadata[i] == '\n') {
|
||||
val = DataHelper.getUTF8(_rawMetadata, valBegin, i - valBegin);
|
||||
_keys.add(key);
|
||||
_values.add(val);
|
||||
keyBegin = i + 1;
|
||||
key = null;
|
||||
val = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
int len = 0;
|
||||
if (_data != null)
|
||||
len = _data.length;
|
||||
return getName()
|
||||
+ (getDescription() != null ? ": " + getDescription() : "")
|
||||
+ (getMimeType() != null ? ", type: " + getMimeType() : "")
|
||||
+ ", size: " + len;
|
||||
}
|
||||
}
|
280
apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java
Normal file
@ -0,0 +1,280 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Blog metadata. Formatted as: <pre>
|
||||
* [key:val\n]*
|
||||
* </pre>
|
||||
*
|
||||
* Required keys:
|
||||
* Owner: base64 of their signing public key
|
||||
* Signature: base64 of the DSA signature of the rest of the ordered metadata
|
||||
* Edition: base10 unique identifier for this metadata (higher clobbers lower)
|
||||
*
|
||||
* Optional keys:
|
||||
* Posters: comma delimited list of base64 signing public keys that
|
||||
* can post to the blog
|
||||
* Name: name of the blog
|
||||
* Description: brief description of the blog
|
||||
*
|
||||
*/
|
||||
public class BlogInfo {
|
||||
private SigningPublicKey _key;
|
||||
private SigningPublicKey _posters[];
|
||||
private String _optionNames[];
|
||||
private String _optionValues[];
|
||||
private Signature _signature;
|
||||
|
||||
public BlogInfo() {}
|
||||
|
||||
public BlogInfo(SigningPublicKey key, SigningPublicKey posters[], Properties opts) {
|
||||
_optionNames = new String[0];
|
||||
_optionValues = new String[0];
|
||||
setKey(key);
|
||||
setPosters(posters);
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String k = (String)iter.next();
|
||||
String v = opts.getProperty(k);
|
||||
setProperty(k.trim(), v.trim());
|
||||
}
|
||||
}
|
||||
|
||||
public SigningPublicKey getKey() { return _key; }
|
||||
public void setKey(SigningPublicKey key) {
|
||||
_key = key;
|
||||
setProperty(OWNER_KEY, Base64.encode(key.getData()));
|
||||
}
|
||||
|
||||
public static final String OWNER_KEY = "Owner";
|
||||
public static final String POSTERS = "Posters";
|
||||
public static final String SIGNATURE = "Signature";
|
||||
public static final String NAME = "Name";
|
||||
public static final String DESCRIPTION = "Description";
|
||||
public static final String EDITION = "Edition";
|
||||
|
||||
public void load(InputStream in) throws IOException {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
|
||||
List names = new ArrayList();
|
||||
List vals = new ArrayList();
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Read info line [" + line + "]");
|
||||
line = line.trim();
|
||||
int len = line.length();
|
||||
int split = line.indexOf(':');
|
||||
if ( (len <= 0) || (split <= 0) ) {
|
||||
continue;
|
||||
} else if (split >= len - 1) {
|
||||
names.add(line.substring(0, split).trim());
|
||||
vals.add("");
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = line.substring(0, split).trim();
|
||||
String val = line.substring(split+1).trim();
|
||||
names.add(key);
|
||||
vals.add(val);
|
||||
}
|
||||
_optionNames = new String[names.size()];
|
||||
_optionValues = new String[names.size()];
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
_optionNames[i] = (String)names.get(i);
|
||||
_optionValues[i] = (String)vals.get(i);
|
||||
//System.out.println("Loaded info: [" + _optionNames[i] + "] = [" + _optionValues[i] + "]");
|
||||
}
|
||||
|
||||
String keyStr = getProperty(OWNER_KEY);
|
||||
if (keyStr == null) throw new IOException("Owner not found");
|
||||
_key = new SigningPublicKey(Base64.decode(keyStr));
|
||||
|
||||
String postersStr = getProperty(POSTERS);
|
||||
if (postersStr != null) {
|
||||
StringTokenizer tok = new StringTokenizer(postersStr, ", \t");
|
||||
_posters = new SigningPublicKey[tok.countTokens()];
|
||||
for (int i = 0; tok.hasMoreTokens(); i++)
|
||||
_posters[i] = new SigningPublicKey(Base64.decode(tok.nextToken()));
|
||||
}
|
||||
|
||||
String sigStr = getProperty(SIGNATURE);
|
||||
if (sigStr == null) throw new IOException("Signature not found");
|
||||
_signature = new Signature(Base64.decode(sigStr));
|
||||
}
|
||||
|
||||
public void write(OutputStream out) throws IOException { write(out, true); }
|
||||
public void write(OutputStream out, boolean includeRealSignature) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
if ( (includeRealSignature) || (!SIGNATURE.equals(_optionNames[i])) )
|
||||
buf.append(_optionNames[i]).append(':').append(_optionValues[i]).append('\n');
|
||||
}
|
||||
String s = buf.toString();
|
||||
out.write(s.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
public String getProperty(String name) {
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
if (_optionNames[i].equals(name)) {
|
||||
String val = _optionValues[i];
|
||||
//System.out.println("getProperty[" + name + "] = [" + val + "] [sz=" + val.length() +"]");
|
||||
//for (int j = 0; j < val.length(); j++) {
|
||||
// char c = (char)val.charAt(j);
|
||||
// if (c != (c & 0x7F))
|
||||
// System.out.println("char " + j + ": " + (int)c);
|
||||
//}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setProperty(String name, String val) {
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
if (_optionNames[i].equals(name)) {
|
||||
_optionValues[i] = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String names[] = new String[_optionNames.length + 1];
|
||||
String values[] = new String[_optionValues.length + 1];
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
names[i] = _optionNames[i];
|
||||
values[i] = _optionValues[i];
|
||||
}
|
||||
names[names.length-1] = name;
|
||||
values[values.length-1] = val;
|
||||
_optionNames = names;
|
||||
_optionValues = values;
|
||||
}
|
||||
|
||||
public int getEdition() {
|
||||
String e = getProperty(EDITION);
|
||||
if (e != null) {
|
||||
try {
|
||||
return Integer.parseInt(e);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public String[] getProperties() { return _optionNames; }
|
||||
|
||||
public SigningPublicKey[] getPosters() { return _posters; }
|
||||
public void setPosters(SigningPublicKey posters[]) {
|
||||
_posters = posters;
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; posters != null && i < posters.length; i++) {
|
||||
buf.append(Base64.encode(posters[i].getData()));
|
||||
if (i + 1 < posters.length)
|
||||
buf.append(',');
|
||||
}
|
||||
setProperty(POSTERS, buf.toString());
|
||||
}
|
||||
|
||||
public boolean verify(I2PAppContext ctx) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||
write(out, false);
|
||||
out.close();
|
||||
byte data[] = out.toByteArray();
|
||||
return ctx.dsa().verifySignature(_signature, data, _key);
|
||||
} catch (IOException ioe) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void sign(I2PAppContext ctx, SigningPrivateKey priv) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||
write(out, false);
|
||||
byte data[] = out.toByteArray();
|
||||
Signature sig = ctx.dsa().sign(data, priv);
|
||||
if (sig == null)
|
||||
throw new IOException("wtf, why is the signature null? data.len = " + data.length + " priv: " + priv);
|
||||
setProperty(SIGNATURE, Base64.encode(sig.getData()));
|
||||
_signature = sig;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("Blog ").append(getKey().calculateHash().toBase64());
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
if ( (!SIGNATURE.equals(_optionNames[i])) &&
|
||||
(!OWNER_KEY.equals(_optionNames[i])) &&
|
||||
(!SIGNATURE.equals(_optionNames[i])) )
|
||||
buf.append(' ').append(_optionNames[i]).append(": ").append(_optionValues[i]);
|
||||
}
|
||||
|
||||
if ( (_posters != null) && (_posters.length > 0) ) {
|
||||
buf.append(" additional posts by");
|
||||
for (int i = 0; i < _posters.length; i++) {
|
||||
buf.append(' ').append(_posters[i].calculateHash().toBase64());
|
||||
if (i + 1 < _posters.length)
|
||||
buf.append(',');
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final String TEST_STRING = "\u20AC\u00DF\u6771\u10400\u00F6";
|
||||
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
if (true) {
|
||||
try {
|
||||
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
|
||||
SigningPublicKey pub = (SigningPublicKey)keys[0];
|
||||
SigningPrivateKey priv = (SigningPrivateKey)keys[1];
|
||||
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty("Name", TEST_STRING);
|
||||
opts.setProperty("Description", TEST_STRING);
|
||||
opts.setProperty("Edition", "0");
|
||||
opts.setProperty("ContactURL", TEST_STRING);
|
||||
|
||||
String nameOrig = opts.getProperty("Name");
|
||||
BlogInfo info = new BlogInfo(pub, null, opts);
|
||||
info.sign(ctx, priv);
|
||||
boolean ok = info.verify(ctx);
|
||||
System.err.println("sign&verify: " + ok);
|
||||
|
||||
FileOutputStream o = new FileOutputStream("bloginfo-test.dat");
|
||||
info.write(o, true);
|
||||
o.close();
|
||||
FileInputStream i = new FileInputStream("bloginfo-test.dat");
|
||||
byte buf[] = new byte[4096];
|
||||
int sz = DataHelper.read(i, buf);
|
||||
BlogInfo read = new BlogInfo();
|
||||
read.load(new ByteArrayInputStream(buf, 0, sz));
|
||||
ok = read.verify(ctx);
|
||||
System.err.println("write to disk, verify read: " + ok);
|
||||
System.err.println("Data: " + Base64.encode(buf, 0, sz));
|
||||
System.err.println("Str : " + new String(buf, 0, sz));
|
||||
|
||||
System.err.println("Name ok? " + read.getProperty("Name").equals(TEST_STRING));
|
||||
System.err.println("Desc ok? " + read.getProperty("Description").equals(TEST_STRING));
|
||||
System.err.println("Name ok? " + read.getProperty("ContactURL").equals(TEST_STRING));
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
} else {
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(args[0]);
|
||||
BlogInfo info = new BlogInfo();
|
||||
info.load(in);
|
||||
boolean ok = info.verify(I2PAppContext.getGlobalContext());
|
||||
System.out.println("OK? " + ok + " :" + info);
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
}
|
||||
}
|
96
apps/syndie/java/src/net/i2p/syndie/data/BlogURI.java
Normal file
@ -0,0 +1,96 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BlogURI {
|
||||
private Hash _blogHash;
|
||||
private long _entryId;
|
||||
|
||||
public BlogURI() {
|
||||
this(null, -1);
|
||||
}
|
||||
public BlogURI(Hash blogHash, long entryId) {
|
||||
_blogHash = blogHash;
|
||||
_entryId = entryId;
|
||||
}
|
||||
public BlogURI(String uri) {
|
||||
if (uri.startsWith("blog://")) {
|
||||
int off = "blog://".length();
|
||||
_blogHash = new Hash(Base64.decode(uri.substring(off, off+44))); // 44 chars == base64(32 bytes)
|
||||
int entryStart = uri.indexOf('/', off+1);
|
||||
if (entryStart < 0) {
|
||||
_entryId = -1;
|
||||
} else {
|
||||
try {
|
||||
_entryId = Long.parseLong(uri.substring(entryStart+1).trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
_entryId = -1;
|
||||
}
|
||||
}
|
||||
} else if (uri.startsWith("entry://")) {
|
||||
int off = "entry://".length();
|
||||
_blogHash = new Hash(Base64.decode(uri.substring(off, off+44))); // 44 chars == base64(32 bytes)
|
||||
int entryStart = uri.indexOf('/', off+1);
|
||||
if (entryStart < 0) {
|
||||
_entryId = -1;
|
||||
} else {
|
||||
try {
|
||||
_entryId = Long.parseLong(uri.substring(entryStart+1).trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
_entryId = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_blogHash = null;
|
||||
_entryId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public Hash getKeyHash() { return _blogHash; }
|
||||
public long getEntryId() { return _entryId; }
|
||||
|
||||
public void setKeyHash(Hash hash) { _blogHash = hash; }
|
||||
public void setEntryId(long id) { _entryId = id; }
|
||||
|
||||
public String toString() {
|
||||
if ( (_blogHash == null) || (_blogHash.getData() == null) )
|
||||
return "";
|
||||
StringBuffer rv = new StringBuffer(64);
|
||||
rv.append("blog://").append(Base64.encode(_blogHash.getData()));
|
||||
rv.append('/');
|
||||
if (_entryId >= 0)
|
||||
rv.append(_entryId);
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (obj.getClass() != getClass()) return false;
|
||||
return DataHelper.eq(_entryId, ((BlogURI)obj)._entryId) &&
|
||||
DataHelper.eq(_blogHash, ((BlogURI)obj)._blogHash);
|
||||
}
|
||||
public int hashCode() {
|
||||
int rv = (int)_entryId;
|
||||
if (_blogHash != null)
|
||||
rv += _blogHash.hashCode();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
test("http://asdf/");
|
||||
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=");
|
||||
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/");
|
||||
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/123456789");
|
||||
test("entry://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/");
|
||||
test("entry://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/123456789");
|
||||
}
|
||||
private static void test(String uri) {
|
||||
BlogURI u = new BlogURI(uri);
|
||||
if (!u.toString().equals(uri))
|
||||
System.err.println("Not a match: [" + uri + "] != [" + u.toString() + "]");
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Create a new blog metadata & set of entries using some crazy UTF8 encoded chars,
|
||||
* then make sure they're always valid. These blogs & entries can then be fed into
|
||||
* jetty/syndie/etc to see how and where they are getting b0rked.
|
||||
*/
|
||||
public class EncodingTestGenerator {
|
||||
public EncodingTestGenerator() {}
|
||||
public static final String TEST_STRING = "\u20AC\u00DF\u6771\u10400\u00F6";
|
||||
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
try {
|
||||
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
|
||||
SigningPublicKey pub = (SigningPublicKey)keys[0];
|
||||
SigningPrivateKey priv = (SigningPrivateKey)keys[1];
|
||||
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty("Name", TEST_STRING);
|
||||
opts.setProperty("Description", TEST_STRING);
|
||||
opts.setProperty("Edition", "0");
|
||||
opts.setProperty("ContactURL", TEST_STRING);
|
||||
|
||||
String nameOrig = opts.getProperty("Name");
|
||||
BlogInfo info = new BlogInfo(pub, null, opts);
|
||||
info.sign(ctx, priv);
|
||||
boolean ok = info.verify(ctx);
|
||||
System.err.println("sign&verify: " + ok);
|
||||
|
||||
FileOutputStream o = new FileOutputStream("encodedMeta.dat");
|
||||
info.write(o, true);
|
||||
o.close();
|
||||
FileInputStream i = new FileInputStream("encodedMeta.dat");
|
||||
byte buf[] = new byte[4096];
|
||||
int sz = DataHelper.read(i, buf);
|
||||
BlogInfo read = new BlogInfo();
|
||||
read.load(new ByteArrayInputStream(buf, 0, sz));
|
||||
ok = read.verify(ctx);
|
||||
System.err.println("write to disk, verify read: " + ok);
|
||||
System.err.println("Name ok? " + read.getProperty("Name").equals(TEST_STRING));
|
||||
System.err.println("Desc ok? " + read.getProperty("Description").equals(TEST_STRING));
|
||||
System.err.println("Name ok? " + read.getProperty("ContactURL").equals(TEST_STRING));
|
||||
|
||||
// ok now lets create some entries
|
||||
BlogURI uri = new BlogURI(read.getKey().calculateHash(), 0);
|
||||
String tags[] = new String[4];
|
||||
for (int j = 0; j < tags.length; j++)
|
||||
tags[j] = TEST_STRING + "_" + j;
|
||||
StringBuffer smlOrig = new StringBuffer(512);
|
||||
smlOrig.append("Subject: ").append(TEST_STRING).append("\n\n");
|
||||
smlOrig.append("Hi with ").append(TEST_STRING);
|
||||
EntryContainer container = new EntryContainer(uri, tags, DataHelper.getUTF8(smlOrig));
|
||||
container.seal(ctx, priv, null);
|
||||
ok = container.verifySignature(ctx, read);
|
||||
System.err.println("Sealed and verified entry: " + ok);
|
||||
FileOutputStream fos = new FileOutputStream("encodedEntry.dat");
|
||||
container.write(fos, true);
|
||||
fos.close();
|
||||
System.out.println("Written to " + new File("encodedEntry.dat").getAbsolutePath());
|
||||
|
||||
FileInputStream fis = new FileInputStream("encodedEntry.dat");
|
||||
EntryContainer read2 = new EntryContainer();
|
||||
read2.load(fis);
|
||||
ok = read2.verifySignature(ctx, read);
|
||||
System.out.println("Read ok? " + ok);
|
||||
|
||||
read2.parseRawData(ctx);
|
||||
String tagsRead[] = read2.getTags();
|
||||
for (int j = 0; j < tagsRead.length; j++) {
|
||||
if (!tags[j].equals(tagsRead[j]))
|
||||
System.err.println("Tag error [" + j + "]: read = [" + tagsRead[j] + "] want [" + tags[j] + "]");
|
||||
else
|
||||
System.err.println("Tag ok [" + j + "]");
|
||||
}
|
||||
String readText = read2.getEntry().getText();
|
||||
ok = readText.equals(smlOrig.toString());
|
||||
System.err.println("SML text ok? " + ok);
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
}
|
14
apps/syndie/java/src/net/i2p/syndie/data/Entry.java
Normal file
@ -0,0 +1,14 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Entry {
|
||||
private String _text;
|
||||
|
||||
public Entry(String raw) {
|
||||
_text = raw;
|
||||
}
|
||||
|
||||
public String getText() { return _text; }
|
||||
}
|