Compare commits
219 Commits
i2p_0_5_0_
...
i2p_0_6_0_
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
5c1f968afa | |||
aaaf437d62 | |||
a8a866b5f6 | |||
aeb8f02269 | |||
45767360ab | |||
3563aa2e4d | |||
843d5b625a | |||
0f8ede85ca | |||
9267d7cae2 | |||
dade5a981b | |||
f873cba27e | |||
108dec53a5 | |||
e9592ed400 | |||
4c230522a2 | |||
16bd19c6dc | |||
b4b6d49d34 | |||
9d5f16a889 | |||
51c492b842 | |||
d3380228ac | |||
ad47bf5da3 | |||
76e8631e31 | |||
f688b9112d | |||
18d3f5d25d | |||
440cf2c983 | |||
adeb09576a | |||
fd52bcf8cd | |||
c2696bba00 | |||
fef9d57483 | |||
c250692ef0 | |||
2a6024e196 | |||
835662b3c9 | |||
6b5b880ab6 | |||
3de23d4206 | |||
ea82f2a8cc | |||
b5ad7642bc | |||
0fbe84e9f0 | |||
8063889d23 | |||
6e1ac8e173 | |||
1b0bb5ea19 | |||
4ce51261f1 | |||
6e34d9b73e | |||
6e01637400 | |||
9a96798f9f | |||
c9db6f87d1 | |||
567ce84e1e | |||
cde7ac7e52 | |||
b2f0d17e94 | |||
dae6be14b7 | |||
20cec857d2 | |||
739f694cfe | |||
84779002fb | |||
df926fb60d | |||
a2c7c5a516 | |||
1861379d43 | |||
408a344aae | |||
e9c1ed70d0 | |||
916dcca2b0 | |||
31e81bab17 | |||
6a5170c341 | |||
42bff8093c | |||
d1df94f284 | |||
9cf1744291 | |||
f0545c8c9a | |||
ef9ed87d30 | |||
58ffd92a34 | |||
418facc7e0 | |||
7f3c953e14 | |||
addab1fa2a | |||
39343ce957 | |||
7389cec78f | |||
9e5fe7d2b6 | |||
a7dfaee5ac | |||
7beb92b1cc | |||
5b56d22da9 | |||
e6b343070a | |||
8496b88518 | |||
aa542b7876 | |||
3f7d46378b | |||
b36def1f72 | |||
5a6a3a5e8d | |||
c3bd26d9b4 | |||
967e106ee7 | |||
7c73e59482 | |||
03dfa913d1 | |||
348e845793 | |||
80827c3aad | |||
3b4cf0a024 | |||
941252fd80 | |||
bc626ece2d | |||
400feb3ba7 | |||
756a4e3995 | |||
578301240e | |||
9b8f91c7f9 | |||
c7c389d4fb | |||
68f7adfa0b | |||
c4ac5170c7 | |||
32e0c8ac71 | |||
c9c1eae32f | |||
33366cc291 | |||
083ac1f125 | |||
80c6290b89 | |||
6492ad165a | |||
f0d1b1a40e | |||
17f044e6cd | |||
63f3a9cd7b | |||
b8ddbf13b4 | |||
be9bdbfe0f | |||
bc74bf1402 | |||
5c2a57f95a | |||
9cd8cc692e | |||
ebac4df2d3 | |||
0626f714c6 | |||
21842291e9 | |||
d461c295f6 | |||
85b3450525 | |||
75d7c81b7c | |||
1433e20f73 | |||
e614a2f726 | |||
32be7f1fd8 | |||
66e1d95a2a | |||
ff03be217e | |||
a52f8b89dc | |||
21c7c043b3 | |||
45e6608ad3 | |||
28978e3680 | |||
904f755c8c | |||
a2c309ddd3 | |||
677eeac8f7 | |||
b232cc0f24 | |||
18bbae1d1e | |||
08ee62b52c | |||
5b83aed719 | |||
b5875ca07b | |||
3f9bf28382 | |||
a2bd71c75b | |||
89509490c5 | |||
a997a46040 | |||
538dd07e7b | |||
046778404e | |||
766f83d653 | |||
b20aee6753 | |||
f9aa3aef18 | |||
d74aa6e53d | |||
ea6fbc7835 | |||
536e604b8e | |||
49d6f5018f | |||
4a830e422a | |||
df6c52fe75 | |||
01979c08b3 | |||
7928ef83cc | |||
10afe0a060 | |||
ef230cfa3d | |||
2d15a42137 | |||
57d6a2f645 | |||
469a0852d7 | |||
7983bb1490 | |||
2e7eac02ed | |||
238389fc7f | |||
4cec9da0a6 | |||
00f27d4400 | |||
f61618e4a4 |
@ -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}"/>
|
||||
@ -21,7 +20,13 @@
|
||||
<target name="distclean" depends="clean" />
|
||||
|
||||
<target name="compile" depends="init">
|
||||
<javac srcdir="${src}" destdir="${build}" classpath="${servlet}"/>
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
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,43 +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 {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,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()) {
|
||||
@ -189,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 "
|
||||
@ -197,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.
|
||||
@ -243,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.
|
||||
|
@ -58,15 +58,14 @@ public class Daemon {
|
||||
*/
|
||||
public static 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();
|
||||
}
|
||||
|
||||
@ -101,7 +100,8 @@ 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);
|
||||
@ -154,11 +154,6 @@ public class Daemon {
|
||||
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;
|
||||
|
@ -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();
|
||||
|
@ -56,7 +56,8 @@
|
||||
<arg value="-webapp" />
|
||||
<arg value="../jsp/" />
|
||||
</java>
|
||||
<javac destdir="../jsp/WEB-INF/classes/" srcdir="../jsp/WEB-INF/classes" includes="**/*.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" />
|
||||
|
@ -0,0 +1,242 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2005 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.ByteCache;
|
||||
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.
|
||||
*
|
||||
*/
|
||||
class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
private static final Log _log = new Log(HTTPResponseOutputStream.class);
|
||||
private ByteCache _cache;
|
||||
protected ByteArray _headerBuffer;
|
||||
private boolean _headerWritten;
|
||||
private byte _buf1[];
|
||||
private static final int CACHE_SIZE = 8*1024;
|
||||
|
||||
public HTTPResponseOutputStream(OutputStream raw) {
|
||||
super(raw);
|
||||
_cache = ByteCache.getInstance(8, CACHE_SIZE);
|
||||
_headerBuffer = _cache.acquire();
|
||||
_headerWritten = false;
|
||||
_buf1 = new byte[1];
|
||||
}
|
||||
|
||||
public void write(int c) throws IOException {
|
||||
_buf1[0] = (byte)c;
|
||||
write(_buf1, 0, 1);
|
||||
}
|
||||
public void write(byte buf[]) throws IOException {
|
||||
write(buf, 0, buf.length);
|
||||
}
|
||||
public void write(byte buf[], int off, int len) throws IOException {
|
||||
if (_headerWritten) {
|
||||
out.write(buf, off, len);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
ensureCapacity();
|
||||
_headerBuffer.getData()[_headerBuffer.getValid()] = buf[off+i];
|
||||
_headerBuffer.setValid(_headerBuffer.getValid()+1);
|
||||
|
||||
if (headerReceived()) {
|
||||
writeHeader();
|
||||
_headerWritten = true;
|
||||
if (i + 1 < len) // write out the remaining
|
||||
out.write(buf, off+i+1, len-i-1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** grow (and free) the buffer as necessary */
|
||||
private void ensureCapacity() {
|
||||
if (_headerBuffer.getValid() + 1 >= _headerBuffer.getData().length) {
|
||||
int newSize = (int)(_headerBuffer.getData().length * 1.5);
|
||||
ByteArray newBuf = new ByteArray(new byte[newSize]);
|
||||
System.arraycopy(_headerBuffer.getData(), 0, newBuf.getData(), 0, _headerBuffer.getValid());
|
||||
newBuf.setValid(_headerBuffer.getValid());
|
||||
newBuf.setOffset(0);
|
||||
if (_headerBuffer.getData().length == CACHE_SIZE)
|
||||
_cache.release(_headerBuffer);
|
||||
_headerBuffer = newBuf;
|
||||
}
|
||||
}
|
||||
|
||||
/** are the headers finished? */
|
||||
private boolean headerReceived() {
|
||||
if (_headerBuffer.getValid() < 3) return false;
|
||||
byte first = _headerBuffer.getData()[_headerBuffer.getValid()-3];
|
||||
byte second = _headerBuffer.getData()[_headerBuffer.getValid()-2];
|
||||
byte third = _headerBuffer.getData()[_headerBuffer.getValid()-1];
|
||||
return (isNL(second) && isNL(third)) || // \n\n
|
||||
(isNL(first) && isNL(third)); // \n\r\n
|
||||
}
|
||||
|
||||
/**
|
||||
* Tweak that first HTTP response line (HTTP 200 OK, etc)
|
||||
*
|
||||
*/
|
||||
protected String filterResponseLine(String line) {
|
||||
return line;
|
||||
}
|
||||
|
||||
/** we ignore any potential \r, since we trim it on write anyway */
|
||||
private static final byte NL = '\n';
|
||||
private boolean isNL(byte b) { return (b == NL); }
|
||||
|
||||
/** ok, received, now munge & write it */
|
||||
private void writeHeader() throws IOException {
|
||||
String responseLine = null;
|
||||
|
||||
boolean connectionSent = false;
|
||||
boolean proxyConnectionSent = false;
|
||||
|
||||
int lastEnd = -1;
|
||||
for (int i = 0; i < _headerBuffer.getValid(); i++) {
|
||||
if (isNL(_headerBuffer.getData()[i])) {
|
||||
if (lastEnd == -1) {
|
||||
responseLine = new String(_headerBuffer.getData(), 0, i+1); // includes NL
|
||||
responseLine = filterResponseLine(responseLine);
|
||||
responseLine = (responseLine.trim() + "\n");
|
||||
out.write(responseLine.getBytes());
|
||||
} else {
|
||||
for (int j = lastEnd+1; j < i; j++) {
|
||||
if (_headerBuffer.getData()[j] == ':') {
|
||||
int keyLen = j-(lastEnd+1);
|
||||
int valLen = i-(j+2);
|
||||
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);
|
||||
|
||||
if ("Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Connection: close\n".getBytes());
|
||||
connectionSent = true;
|
||||
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
proxyConnectionSent = true;
|
||||
} else {
|
||||
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastEnd = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!connectionSent)
|
||||
out.write("Connection: close\n".getBytes());
|
||||
if (!proxyConnectionSent)
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
|
||||
// done, shove off
|
||||
if (_headerBuffer.getData().length == CACHE_SIZE)
|
||||
_cache.release(_headerBuffer);
|
||||
else
|
||||
_headerBuffer = null;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
String simple = "HTTP/1.1 200 OK\n" +
|
||||
"foo: bar\n" +
|
||||
"baz: bat\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
String filtered = "HTTP/1.1 200 OK\n" +
|
||||
"Connection: keep-alive\n" +
|
||||
"foo: bar\n" +
|
||||
"baz: bat\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
String winfilter= "HTTP/1.1 200 OK\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"foo: bar\r\n" +
|
||||
"baz: bat\r\n" +
|
||||
"\r\n" +
|
||||
"hi ho, this is the body";
|
||||
String minimal = "HTTP/1.1 200 OK\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
String winmin = "HTTP/1.1 200 OK\r\n" +
|
||||
"\r\n" +
|
||||
"hi ho, this is the body";
|
||||
String invalid1 = "HTTP/1.1 200 OK\n";
|
||||
String invalid2 = "HTTP/1.1 200 OK";
|
||||
String invalid3 = "HTTP 200 OK\r\n";
|
||||
String invalid4 = "HTTP 200 OK\r";
|
||||
String invalid5 = "HTTP/1.1 200 OK\r\n" +
|
||||
"I am broken, and I smell\r\n" +
|
||||
"\r\n";
|
||||
String invalid6 = "HTTP/1.1 200 OK\r\n" +
|
||||
":I am broken, and I smell\r\n" +
|
||||
"\r\n";
|
||||
String invalid7 = "HTTP/1.1 200 OK\n" +
|
||||
"I am broken, and I smell:\n" +
|
||||
":asdf\n" +
|
||||
":\n" +
|
||||
"\n";
|
||||
String large = "HTTP/1.1 200 OK\n" +
|
||||
"Last-modified: Tue, 25 Nov 2003 12:05:38 GMT\n" +
|
||||
"Expires: Tue, 25 Nov 2003 12:05:38 GMT\n" +
|
||||
"Content-length: 32\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
/* */
|
||||
test("Simple", simple, true);
|
||||
test("Filtered", filtered, true);
|
||||
test("Filtered windows", winfilter, true);
|
||||
test("Minimal", minimal, true);
|
||||
test("Windows", winmin, true);
|
||||
test("Large", large, true);
|
||||
test("Invalid (short headers)", invalid1, true);
|
||||
test("Invalid (no headers)", invalid2, true);
|
||||
test("Invalid (windows with short headers)", invalid3, true);
|
||||
test("Invalid (windows no headers)", invalid4, true);
|
||||
test("Invalid (bad headers)", invalid5, true);
|
||||
test("Invalid (bad headers2)", invalid6, false);
|
||||
test("Invalid (bad headers3)", invalid7, false);
|
||||
/* */
|
||||
}
|
||||
|
||||
private static void test(String name, String orig, boolean shouldPass) {
|
||||
System.out.println("====Testing: " + name + "\n" + orig + "\n------------");
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
|
||||
HTTPResponseOutputStream resp = new HTTPResponseOutputStream(baos);
|
||||
resp.write(orig.getBytes());
|
||||
resp.flush();
|
||||
String received = new String(baos.toByteArray());
|
||||
System.out.println(received);
|
||||
} catch (Exception e) {
|
||||
if (shouldPass)
|
||||
e.printStackTrace();
|
||||
else
|
||||
System.out.println("Properly fails with " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@
|
||||
* not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@ -108,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);
|
||||
@ -288,8 +290,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("textserver <host> <port> <privkey>");
|
||||
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
|
||||
l.log("gentextkeys");
|
||||
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile>");
|
||||
l.log("httpclient <port>");
|
||||
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
|
||||
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
|
||||
l.log("lookup <name>");
|
||||
l.log("quit");
|
||||
l.log("close [forced] <jobnumber>|all");
|
||||
@ -486,12 +488,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
* Also sets the event "openClientResult" = "error" or "ok" (before setting the value to "ok" it also
|
||||
* adds "Ready! Port #" to the logger as well). In addition, it will also set "clientLocalPort" =
|
||||
* Integer port number if the client is listening
|
||||
* sharedClient parameter is a String "true" or "false"
|
||||
*
|
||||
* @param args {portNumber, destinationBase64 or "file:filename"}
|
||||
* @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runClient(String args[], Logging l) {
|
||||
if (args.length == 2) {
|
||||
boolean isShared = true;
|
||||
if (args.length == 3)
|
||||
isShared = Boolean.valueOf(args[2].trim()).booleanValue();
|
||||
if ( (args.length == 2) || (args.length == 3) ) {
|
||||
int portNum = -1;
|
||||
try {
|
||||
portNum = Integer.parseInt(args[0]);
|
||||
@ -502,6 +508,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
return;
|
||||
}
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
@ -512,11 +519,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("clientTaskId", new Integer(-1));
|
||||
}
|
||||
} else {
|
||||
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>");
|
||||
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>]");
|
||||
l.log(" creates a client that forwards port to the pubkey.\n"
|
||||
+ " use 0 as port to get a free port assigned. If you specify\n"
|
||||
+ " a comma delimited list of pubkeys, it will rotate among them\n"
|
||||
+ " randomlyl");
|
||||
+ " randomlyl. sharedClient indicates if this client shares \n"
|
||||
+ " with other clients (true of false)");
|
||||
notifyEvent("clientTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
@ -526,12 +534,13 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
|
||||
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
|
||||
* parameter sharedClient is a String, either "true" or "false"
|
||||
*
|
||||
* @param args {portNumber and (optionally) proxy to be used for the WWW}
|
||||
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runHttpClient(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 2) {
|
||||
if (args.length >= 1 && args.length <= 3) {
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
@ -541,12 +550,32 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("httpclientTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String proxy = "squid.i2p";
|
||||
if (args.length == 2) {
|
||||
proxy = args[1];
|
||||
boolean isShared = true;
|
||||
if (args.length > 1) {
|
||||
if ("true".equalsIgnoreCase(args[1].trim())) {
|
||||
isShared = true;
|
||||
if (args.length == 3)
|
||||
proxy = args[2];
|
||||
} else if ("false".equalsIgnoreCase(args[1].trim())) {
|
||||
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
|
||||
isShared = false;
|
||||
if (args.length == 3)
|
||||
proxy = args[2];
|
||||
} else if (args.length == 3) {
|
||||
isShared = false; // not "true"
|
||||
proxy = args[2];
|
||||
_log.warn("args[1] == [" + args[1] + "] but rejected");
|
||||
} else {
|
||||
// isShared not specified, default to true
|
||||
isShared = true;
|
||||
proxy = args[1];
|
||||
}
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
task = new I2PTunnelHTTPClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
@ -557,8 +586,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("httpclientTaskId", new Integer(-1));
|
||||
}
|
||||
} else {
|
||||
l.log("httpclient <port> [<proxy>]");
|
||||
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
|
||||
l.log(" creates a client that distributes HTTP requests.");
|
||||
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
|
||||
l.log(" <proxy> (optional) indicates a proxy server to be used");
|
||||
l.log(" when trying to access an address out of the .i2p domain");
|
||||
l.log(" (the default proxy is squid.i2p).");
|
||||
@ -1117,6 +1147,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
|
||||
private String getPrefix() { return '[' + _tunnelId + "]: "; }
|
||||
|
||||
public I2PAppContext getContext() { return _context; }
|
||||
|
||||
/**
|
||||
* Call this whenever we lose touch with the router involuntarily (aka the router
|
||||
|
@ -12,15 +12,12 @@ import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
@ -104,11 +101,21 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
this.l = l;
|
||||
this.handlerName = handlerName + _clientId;
|
||||
|
||||
synchronized (sockLock) {
|
||||
if (ownDest) {
|
||||
sockMgr = buildSocketManager();
|
||||
} else {
|
||||
sockMgr = getSocketManager();
|
||||
// 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) {
|
||||
sockMgr = buildSocketManager();
|
||||
} else {
|
||||
sockMgr = getSocketManager();
|
||||
}
|
||||
}
|
||||
if (sockMgr == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
if (sockMgr == null) {
|
||||
@ -206,8 +213,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
props.putAll(System.getProperties());
|
||||
else
|
||||
props.putAll(tunnel.getClientOptions());
|
||||
I2PSocketManager sockManager = I2PSocketManagerFactory.createManager(tunnel.host, Integer.parseInt(tunnel.port), props);
|
||||
if (sockManager == null) return null;
|
||||
int portNum = 7654;
|
||||
if (tunnel.port != null) {
|
||||
try {
|
||||
portNum = Integer.parseInt(tunnel.port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.log(Log.CRIT, "Invalid port specified [" + tunnel.port + "], reverting to " + portNum);
|
||||
}
|
||||
}
|
||||
|
||||
I2PSocketManager sockManager = null;
|
||||
while (sockManager == null) {
|
||||
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
|
||||
|
||||
if (sockManager == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sockManager.setName("Client");
|
||||
return sockManager;
|
||||
}
|
||||
|
@ -22,9 +22,8 @@ import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -72,7 +71,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"wrong BASE64 I2P Destination or the link you are following is "+
|
||||
"bad. The host (or the WWW proxy, if you're using one) could also "+
|
||||
"be temporarily offline. You may want to <b>retry</b>. "+
|
||||
"Could not find the following Destination:<BR><BR>")
|
||||
"Could not find the following Destination:<BR><BR><div>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_TIMEOUT =
|
||||
@ -192,13 +191,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "ISO-8859-1"));
|
||||
String line, method = null, protocol = null, host = null, destination = null;
|
||||
StringBuffer newRequest = new StringBuffer();
|
||||
int ahelper = 0;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
|
||||
|
||||
if (line.startsWith("Connection: ") ||
|
||||
line.startsWith("Keep-Alive: ") ||
|
||||
line.startsWith("Proxy-Connection: "))
|
||||
String lowercaseLine = line.toLowerCase();
|
||||
if (lowercaseLine.startsWith("connection: ") ||
|
||||
lowercaseLine.startsWith("keep-alive: ") ||
|
||||
lowercaseLine.startsWith("proxy-connection: "))
|
||||
continue;
|
||||
|
||||
if (method == null) { // first line (GET /base64/realaddr)
|
||||
@ -283,6 +284,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
ahelper = 1;
|
||||
}
|
||||
}
|
||||
line = method + " " + request.substring(pos);
|
||||
@ -337,29 +339,29 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
} else {
|
||||
if (line.startsWith("Host: ") && !usingWWWProxy) {
|
||||
if (lowercaseLine.startsWith("host: ") && !usingWWWProxy) {
|
||||
line = "Host: " + host;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Setting host = " + host);
|
||||
} else if (line.startsWith("User-Agent: ")) {
|
||||
} else if (lowercaseLine.startsWith("user-agent: ")) {
|
||||
// always stripped, added back at the end
|
||||
line = null;
|
||||
continue;
|
||||
} else if (line.startsWith("Accept")) {
|
||||
} else if (lowercaseLine.startsWith("accept")) {
|
||||
// strip the accept-blah headers, as they vary dramatically from
|
||||
// browser to browser
|
||||
line = null;
|
||||
continue;
|
||||
} else if (line.startsWith("Referer: ")) {
|
||||
} else if (lowercaseLine.startsWith("referer: ")) {
|
||||
// Shouldn't we be more specific, like accepting in-site referers ?
|
||||
//line = "Referer: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (line.startsWith("Via: ")) {
|
||||
} else if (lowercaseLine.startsWith("via: ")) {
|
||||
//line = "Via: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (line.startsWith("From: ")) {
|
||||
} else if (lowercaseLine.startsWith("from: ")) {
|
||||
//line = "From: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
@ -404,7 +406,19 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
l.log("Could not resolve " + destination + ".");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
|
||||
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, destination);
|
||||
String str;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else if(ahelper != 0)
|
||||
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
|
||||
else
|
||||
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
@ -418,7 +432,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions(opts));
|
||||
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
I2PTunnelRunner runner = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
} catch (SocketException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
@ -477,10 +491,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (out != null) {
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
out.write(targetRequest.getBytes());
|
||||
int protopos = targetRequest.indexOf(" ");
|
||||
String uri = targetRequest.substring(0, protopos);
|
||||
out.write("<a href=\"http://".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">http://".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
|
||||
}
|
||||
out.write("<p /><i>Generated on: ".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();
|
||||
@ -494,7 +514,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
|
||||
if (out != null) {
|
||||
try {
|
||||
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
String str;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else
|
||||
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
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.net.Socket;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Override the response with a stream filtering the HTTP headers
|
||||
* received. Specifically, this makes sure we get Connection: close,
|
||||
* so the browser knows they really shouldn't try to use persistent
|
||||
* connections. The HTTP server *should* already be setting this,
|
||||
* since the HTTP headers sent by the browser specify Connection: close,
|
||||
* and the server should echo it. However, both broken and malicious
|
||||
* servers could ignore that, potentially confusing the user.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
|
||||
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
|
||||
super(s, i2ps, slock, initialI2PData, sockList, onTimeout);
|
||||
}
|
||||
|
||||
protected OutputStream getSocketOut() throws IOException {
|
||||
OutputStream raw = super.getSocketOut();
|
||||
return new HTTPResponseOutputStream(raw);
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
@ -17,7 +16,6 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
@ -37,85 +35,68 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
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();
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async handler to keep .accept() from blocking too long.
|
||||
* todo: replace with a thread pool so we dont get overrun by threads if/when
|
||||
* receiving a lot of connection requests concurrently.
|
||||
* Called by the thread pool of I2PSocket handlers
|
||||
*
|
||||
*/
|
||||
private class Handler implements Runnable {
|
||||
private I2PSocket _handleSocket;
|
||||
public Handler(I2PSocket socket) {
|
||||
_handleSocket = socket;
|
||||
}
|
||||
public void run() {
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
long afterAccept = getTunnel().getContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
// give them 5 seconds to send in the HTTP request
|
||||
socket.setReadTimeout(5*1000);
|
||||
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);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
_handleSocket.setReadTimeout(readTimeout);
|
||||
String modifiedHeader = getModifiedHeader();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Modified header: [" + modifiedHeader + "]");
|
||||
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = I2PAppContext.getGlobalContext().clock().now();
|
||||
new I2PTunnelRunner(s, _handleSocket, slock, null, modifiedHeader.getBytes(), null);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
_handleSocket.close();
|
||||
} catch (IOException ioe) {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
|
||||
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
if (timeToHandle > 1000)
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: "
|
||||
+ (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
private String getModifiedHeader() 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);
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new HTTP request", ex);
|
||||
}
|
||||
|
||||
long afterHandle = getTunnel().getContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0);
|
||||
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 String formatHeaders(Properties headers, StringBuffer command) {
|
||||
|
@ -3,7 +3,6 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
@ -31,7 +30,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
* Sun's impl of BufferedOutputStream), but that is the streaming
|
||||
* api's job...
|
||||
*/
|
||||
static int MAX_PACKET_SIZE = 1024 * 32;
|
||||
static int MAX_PACKET_SIZE = 1024 * 4;
|
||||
|
||||
static final int NETWORK_BUFFER_SIZE = MAX_PACKET_SIZE;
|
||||
|
||||
@ -112,10 +111,13 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
return startedOn;
|
||||
}
|
||||
|
||||
protected InputStream getSocketIn() throws IOException { return s.getInputStream(); }
|
||||
protected OutputStream getSocketOut() throws IOException { return s.getOutputStream(); }
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
OutputStream out = s.getOutputStream(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
|
||||
InputStream in = getSocketIn();
|
||||
OutputStream out = getSocketOut(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
|
||||
i2ps.setSocketErrorListener(this);
|
||||
InputStream i2pin = i2ps.getInputStream();
|
||||
OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
@ -216,7 +218,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
this.out = out;
|
||||
_toI2P = toI2P;
|
||||
direction = (toI2P ? "toI2P" : "fromI2P");
|
||||
_cache = ByteCache.getInstance(16, NETWORK_BUFFER_SIZE);
|
||||
_cache = ByteCache.getInstance(32, NETWORK_BUFFER_SIZE);
|
||||
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
|
||||
start();
|
||||
}
|
||||
@ -281,6 +283,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
//else
|
||||
// _log.warn("You may ignore this", ex);
|
||||
} finally {
|
||||
_cache.release(ba);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info(direction + ": done forwarding between "
|
||||
+ from + " and " + to);
|
||||
@ -302,7 +305,6 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
finishLock.notifyAll();
|
||||
// the main thread will close sockets etc. now
|
||||
}
|
||||
_cache.release(ba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
@ -75,10 +94,25 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Properties props = new Properties();
|
||||
props.putAll(getTunnel().getClientOptions());
|
||||
synchronized (slock) {
|
||||
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, Integer.parseInt(getTunnel().port),
|
||||
props);
|
||||
int portNum = 7654;
|
||||
if (getTunnel().port != null) {
|
||||
try {
|
||||
portNum = Integer.parseInt(getTunnel().port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
|
||||
}
|
||||
}
|
||||
|
||||
while (sockMgr == null) {
|
||||
synchronized (slock) {
|
||||
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, portNum,
|
||||
props);
|
||||
|
||||
}
|
||||
if (sockMgr == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sockMgr.setName("Server");
|
||||
getTunnel().addSession(sockMgr.getSession());
|
||||
@ -144,58 +178,103 @@ 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() {
|
||||
try {
|
||||
if (shouldUsePool()) {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
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..
|
||||
}
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldUsePool() { return _usePool; }
|
||||
|
||||
/**
|
||||
* Async handler to keep .accept() from blocking too long.
|
||||
* todo: replace with a thread pool so we dont get overrun by threads if/when
|
||||
* receiving a lot of connection requests concurrently.
|
||||
* minor thread pool to pull off the accept() concurrently. there are still lots
|
||||
* (and lots) of wasted threads within the I2PTunnelRunner, but its a start
|
||||
*
|
||||
*/
|
||||
private class Handler implements Runnable {
|
||||
private I2PSocket _handleSocket;
|
||||
public Handler(I2PSocket socket) {
|
||||
_handleSocket = socket;
|
||||
private I2PServerSocket _serverSocket;
|
||||
public Handler(I2PServerSocket serverSocket) {
|
||||
_serverSocket = serverSocket;
|
||||
}
|
||||
public void run() {
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
_handleSocket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = I2PAppContext.getGlobalContext().clock().now();
|
||||
new I2PTunnelRunner(s, _handleSocket, slock, null, null);
|
||||
} catch (SocketException ex) {
|
||||
while (open) {
|
||||
try {
|
||||
_handleSocket.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
blockingHandle(_serverSocket.accept());
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
return;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
|
||||
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
if (timeToHandle > 1000)
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
socket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = I2PAppContext.getGlobalContext().clock().now();
|
||||
new I2PTunnelRunner(s, socket, slock, null, null);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
|
||||
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
if (timeToHandle > 1000)
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,10 +153,11 @@ public class TunnelController implements Logging {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String proxyList = getProxyList();
|
||||
String sharedClient = getSharedClient();
|
||||
if (proxyList == null)
|
||||
_tunnel.runHttpClient(new String[] { listenPort }, this);
|
||||
_tunnel.runHttpClient(new String[] { listenPort, sharedClient }, this);
|
||||
else
|
||||
_tunnel.runHttpClient(new String[] { listenPort, proxyList }, this);
|
||||
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
@ -199,7 +200,8 @@ public class TunnelController implements Logging {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String dest = getTargetDestination();
|
||||
_tunnel.runClient(new String[] { listenPort, dest }, this);
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
@ -258,8 +260,16 @@ public class TunnelController implements Logging {
|
||||
if ("localhost".equals(_tunnel.host))
|
||||
_tunnel.host = "127.0.0.1";
|
||||
String port = getI2CPPort();
|
||||
if ( (port != null) && (port.length() > 0) )
|
||||
_tunnel.port = port;
|
||||
if ( (port != null) && (port.length() > 0) ) {
|
||||
try {
|
||||
int portNum = Integer.parseInt(port);
|
||||
_tunnel.port = String.valueOf(portNum);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_tunnel.port = "7654";
|
||||
}
|
||||
} else {
|
||||
_tunnel.port = "7654";
|
||||
}
|
||||
}
|
||||
|
||||
public void stopTunnel() {
|
||||
@ -323,7 +333,20 @@ public class TunnelController implements Logging {
|
||||
public String getListenPort() { return _config.getProperty("listenPort"); }
|
||||
public String getTargetDestination() { return _config.getProperty("targetDestination"); }
|
||||
public String getProxyList() { return _config.getProperty("proxyList"); }
|
||||
public String getSharedClient() { return _config.getProperty("sharedClient", "true"); }
|
||||
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
|
||||
public String getMyDestination() {
|
||||
if (_tunnel != null) {
|
||||
List sessions = _tunnel.getSessions();
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
I2PSession session = (I2PSession)sessions.get(i);
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean getIsRunning() { return _running; }
|
||||
public boolean getIsStarting() { return _starting; }
|
||||
|
@ -1,12 +1,8 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -1,435 +0,0 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Quick and dirty socket listener to control an I2PTunnel.
|
||||
* Basically run this class as TunnelManager [listenHost] [listenPort] and
|
||||
* then send it commands on that port. Commands are one shot deals -
|
||||
* Send a command + newline, get a response plus newline, then get disconnected.
|
||||
* <p />
|
||||
* <b>Implemented commands:</b>
|
||||
* <pre>
|
||||
* -------------------------------------------------
|
||||
* lookup <name>\n
|
||||
* --
|
||||
* <base64 of the destination>\n
|
||||
* or
|
||||
* <error message, usually 'Unknown host'>\n
|
||||
*
|
||||
* Lookup the public key of a named destination (i.e. listed in hosts.txt)
|
||||
* -------------------------------------------------
|
||||
* genkey\n
|
||||
* --
|
||||
* <base64 of the destination>\t<base64 of private data>\n
|
||||
*
|
||||
* Generates a new public and private key pair
|
||||
* -------------------------------------------------
|
||||
* convertprivate <base64 of privkey>
|
||||
* --
|
||||
* <base64 of destination>\n
|
||||
* or
|
||||
* <error message>\n
|
||||
*
|
||||
* Returns the destination (pubkey) of a given private key.
|
||||
* -------------------------------------------------
|
||||
* listen_on <ip>\n
|
||||
* --
|
||||
* ok\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Sets the ip address clients will listen on. By default this is the
|
||||
* localhost (127.0.0.1)
|
||||
* -------------------------------------------------
|
||||
* openclient <listenPort> <peer>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open a tunnel on the given <listenport> to the destination specified
|
||||
* by <peer>. If <listenPort> is 0 a free port is picked and returned in
|
||||
* the reply message. Otherwise the short reply message is used.
|
||||
* Peer can be the base64 of the destination, a file with the public key
|
||||
* specified as 'file:<filename>' or the name of a destination listed in
|
||||
* hosts.txt. The <jobId> returned together with "ok" and <listenport> can
|
||||
* later be used as argument for the "close" command.
|
||||
* -------------------------------------------------
|
||||
* openhttpclient <listenPort> [<proxy>]\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open an HTTP proxy through the I2P on the given
|
||||
* <listenport>. <proxy> (optional) specifies a
|
||||
* destination to be used as an outbound proxy, to access normal WWW
|
||||
* sites out of the .i2p domain. If <listenPort> is 0 a free
|
||||
* port is picked and returned in the reply message. Otherwise the
|
||||
* short reply message is used. <proxy> can be the base64 of the
|
||||
* destination, a file with the public key specified as
|
||||
* 'file:<filename>' or the name of a destination listed in
|
||||
* hosts.txt. The <jobId> returned together with "ok" and
|
||||
* <listenport> can later be used as argument for the "close"
|
||||
* command.
|
||||
* -------------------------------------------------
|
||||
* opensockstunnel <listenPort>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open an SOCKS tunnel through the I2P on the given
|
||||
* <listenport>. If <listenPort> is 0 a free port is
|
||||
* picked and returned in the reply message. Otherwise the short
|
||||
* reply message is used. The <jobId> returned together with
|
||||
* "ok" and <listenport> can later be used as argument for the
|
||||
* "close" command.
|
||||
* -------------------------------------------------
|
||||
* openserver <serverHost> <serverPort> <serverKeys>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Starts receiving traffic for the destination specified by <serverKeys>
|
||||
* and forwards it to the <serverPort> of <serverHost>.
|
||||
* <serverKeys> is the base 64 encoded private key set of the local
|
||||
* destination. The <joId> returned together with "ok" can later be used
|
||||
* as argument for the "close" command.
|
||||
* -------------------------------------------------
|
||||
* close [forced] <jobId>\n
|
||||
* or
|
||||
* close [forced] all\n
|
||||
* --
|
||||
* ok\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Closes the job specified by <jobId> or all jobs. Use the list command
|
||||
* for a list of running jobs.
|
||||
* Normally a connection job is not closed when it still has an active
|
||||
* connection. Use the optional 'forced' keyword to close connections
|
||||
* regardless of their use.
|
||||
* -------------------------------------------------
|
||||
* list\n
|
||||
* --
|
||||
* Example output:
|
||||
*
|
||||
* [0] i2p.dnsalias.net/69.55.226.145:5555 <- C:\i2pKeys\squidPriv
|
||||
* [1] 8767 -> HTTPClient
|
||||
* [2] 7575 -> file:C:\i2pKeys\squidPub
|
||||
* [3] 5252 -> sCcSANIO~f4AQtCNI1BvDp3ZBS~9Ag5O0k0Msm7XBWWz5eOnZWL3MQ-2rxlesucb9XnpASGhWzyYNBpWAfaIB3pux1J1xujQLOwscMIhm7T8BP76Ly5jx6BLZCYrrPj0BI0uV90XJyT~4UyQgUlC1jzFQdZ9HDgBPJDf1UI4-YjIwEHuJgdZynYlQ1oUFhgno~HhcDByXO~PDaO~1JDMDbBEfIh~v6MgmHp-Xchod1OfKFrxFrzHgcJbn7E8edTFjZA6JCi~DtFxFelQz1lSBd-QB1qJnA0g-pVL5qngNUojXJCXs4qWcQ7ICLpvIc-Fpfj-0F1gkVlGDSGkb1yLH3~8p4czYgR3W5D7OpwXzezz6clpV8kmbd~x2SotdWsXBPRhqpewO38coU4dJG3OEUbuYmdN~nJMfWbmlcM1lXzz2vBsys4sZzW6dV3hZnbvbfxNTqbdqOh-KXi1iAzXv7CVTun0ubw~CfeGpcAqutC5loRUq7Mq62ngOukyv8Z9AAAA
|
||||
*
|
||||
* Lists descriptions of all running jobs. The exact format of the
|
||||
* description depends on the type of job.
|
||||
* -------------------------------------------------
|
||||
* </pre>
|
||||
*/
|
||||
public class TunnelManager implements Runnable {
|
||||
private final static Log _log = new Log(TunnelManager.class);
|
||||
private I2PTunnel _tunnel;
|
||||
private ServerSocket _socket;
|
||||
private boolean _keepAccepting;
|
||||
|
||||
public TunnelManager(int listenPort) {
|
||||
this(null, listenPort);
|
||||
}
|
||||
|
||||
public TunnelManager(String listenHost, int listenPort) {
|
||||
_tunnel = new I2PTunnel();
|
||||
_keepAccepting = true;
|
||||
try {
|
||||
if (listenHost != null) {
|
||||
_socket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
|
||||
_log.info("Listening for tunnel management clients on " + listenHost + ":" + listenPort);
|
||||
} else {
|
||||
_socket = new ServerSocket(listenPort);
|
||||
_log.info("Listening for tunnel management clients on localhost:" + listenPort);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error starting up tunnel management listener on " + listenPort, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
int port = 7676;
|
||||
String host = null;
|
||||
if (args.length == 1) {
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Usage: TunnelManager [host] [port]");
|
||||
return;
|
||||
}
|
||||
} else if (args.length == 2) {
|
||||
host = args[0];
|
||||
try {
|
||||
port = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Usage: TunnelManager [host] [port]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TunnelManager mgr = new TunnelManager(host, port);
|
||||
Thread t = new I2PThread(mgr, "Listener");
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (_socket == null) {
|
||||
_log.error("Unable to start listening, since the socket was not bound. Already running?");
|
||||
return;
|
||||
}
|
||||
_log.debug("Running");
|
||||
try {
|
||||
while (_keepAccepting) {
|
||||
Socket socket = _socket.accept();
|
||||
_log.debug("Client accepted");
|
||||
if (socket != null) {
|
||||
Thread t = new I2PThread(new TunnelManagerClientRunner(this, socket));
|
||||
t.setName("TunnelManager Client");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error accepting connections", ioe);
|
||||
} catch (Exception e) {
|
||||
_log.error("Other error?!", e);
|
||||
} finally {
|
||||
if (_socket != null) try {
|
||||
_socket.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
|
||||
public void error(String msg, OutputStream out) throws IOException {
|
||||
out.write(msg.getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
|
||||
public void processQuit(OutputStream out) throws IOException {
|
||||
out.write("Nice try".getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
|
||||
public void processList(OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
long startCommand = Clock.getInstance().now();
|
||||
_tunnel.runCommand("list", buf);
|
||||
Object obj = _tunnel.waitEventValue("listDone");
|
||||
long endCommand = Clock.getInstance().now();
|
||||
String str = buf.getBuffer();
|
||||
_log.debug("ListDone complete after " + (endCommand - startCommand) + "ms: [" + str + "]");
|
||||
out.write(str.getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processListenOn(String ip, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("listen_on " + ip, buf);
|
||||
String status = (String) _tunnel.waitEventValue("listen_onResult");
|
||||
out.write((status + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* "lookup <name>" returns with the result in base64, else "Unknown host" [or something like that],
|
||||
* then a newline.
|
||||
*
|
||||
*/
|
||||
public void processLookup(String name, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("lookup " + name, buf);
|
||||
String rv = (String) _tunnel.waitEventValue("lookupResult");
|
||||
out.write(rv.getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processTestDestination(String destKey, OutputStream out) throws IOException {
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
out.write("valid\n".getBytes());
|
||||
} catch (DataFormatException dfe) {
|
||||
out.write("invalid\n".getBytes());
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void processConvertPrivate(String priv, OutputStream out) throws IOException {
|
||||
try {
|
||||
Destination dest = new Destination();
|
||||
dest.fromBase64(priv);
|
||||
String str = dest.toBase64();
|
||||
out.write(str.getBytes());
|
||||
out.write('\n');
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error converting private data", dfe);
|
||||
out.write("Error converting private key\n".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public void processClose(String which, boolean forced, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand((forced ? "close forced " : "close ") + which, buf);
|
||||
String str = (String) _tunnel.waitEventValue("closeResult");
|
||||
out.write((str + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* "genkey" returns with the base64 of the destination, followed by a tab, then the base64 of that
|
||||
* destination's private keys, then a newline.
|
||||
*
|
||||
*/
|
||||
public void processGenKey(OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("gentextkeys", buf);
|
||||
String priv = (String) _tunnel.waitEventValue("privateKey");
|
||||
String pub = (String) _tunnel.waitEventValue("publicDestination");
|
||||
out.write((pub + "\t" + priv).getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenClient(int listenPort, String peer, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("client " + listenPort + " " + peer, buf);
|
||||
Integer taskId = (Integer) _tunnel.waitEventValue("clientTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String) _tunnel.waitEventValue("openClientResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenHTTPClient(int listenPort, String proxy, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("httpclient " + listenPort + " " + proxy, buf);
|
||||
Integer taskId = (Integer) _tunnel.waitEventValue("httpclientTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String) _tunnel.waitEventValue("openHTTPClientResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenSOCKSTunnel(int listenPort, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("sockstunnel " + listenPort, buf);
|
||||
Integer taskId = (Integer) _tunnel.waitEventValue("sockstunnelTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String) _tunnel.waitEventValue("openSOCKSTunnelResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenServer(String serverHost, int serverPort, String privateKeys, OutputStream out)
|
||||
throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("textserver " + serverHost + " " + serverPort + " " + privateKeys, buf);
|
||||
Integer taskId = (Integer) _tunnel.waitEventValue("serverTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
String rv = (String) _tunnel.waitEventValue("openServerResult");
|
||||
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Frisbee.
|
||||
*
|
||||
*/
|
||||
public void unknownCommand(String command, OutputStream out) throws IOException {
|
||||
out.write("Unknown command: ".getBytes());
|
||||
out.write(command.getBytes());
|
||||
out.write("\n".getBytes());
|
||||
}
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Runner thread that reads commands from the socket and fires off commands to
|
||||
* the TunnelManager
|
||||
*
|
||||
*/
|
||||
class TunnelManagerClientRunner implements Runnable {
|
||||
private final static Log _log = new Log(TunnelManagerClientRunner.class);
|
||||
private TunnelManager _mgr;
|
||||
private Socket _clientSocket;
|
||||
|
||||
public TunnelManagerClientRunner(TunnelManager mgr, Socket socket) {
|
||||
_clientSocket = socket;
|
||||
_mgr = mgr;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_log.debug("Client running");
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(_clientSocket.getInputStream()));
|
||||
OutputStream out = _clientSocket.getOutputStream();
|
||||
|
||||
String cmd = reader.readLine();
|
||||
if (cmd != null) processCommand(cmd, out);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error processing client commands", ioe);
|
||||
} finally {
|
||||
if (_clientSocket != null) try {
|
||||
_clientSocket.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
_log.debug("Client closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the command string and fire off the appropriate tunnelManager method,
|
||||
* sending the results to the output stream
|
||||
*/
|
||||
private void processCommand(String command, OutputStream out) throws IOException {
|
||||
_log.debug("Processing [" + command + "]");
|
||||
StringTokenizer tok = new StringTokenizer(command);
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.unknownCommand(command, out);
|
||||
} else {
|
||||
String cmd = tok.nextToken();
|
||||
if ("quit".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processQuit(out);
|
||||
} else if ("lookup".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processLookup(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: lookup <hostname>", out);
|
||||
} else if ("testdestination".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processTestDestination(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: testdestination <publicDestination>", out);
|
||||
} else if ("convertprivate".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processConvertPrivate(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: convertprivate <privateData>", out);
|
||||
} else if ("close".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
String closeArg;
|
||||
if ((closeArg = tok.nextToken()).equals("forced")) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.processClose(tok.nextToken(), true, out);
|
||||
} else {
|
||||
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
|
||||
}
|
||||
} else {
|
||||
_mgr.processClose(closeArg, false, out);
|
||||
}
|
||||
} else {
|
||||
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
|
||||
}
|
||||
} else if ("genkey".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processGenKey(out);
|
||||
} else if ("list".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processList(out);
|
||||
} else if ("listen_on".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.processListenOn(tok.nextToken(), out);
|
||||
} else {
|
||||
_mgr.error("Usage: listen_on <ip>", out);
|
||||
}
|
||||
} else if ("openclient".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String peer = null;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenPort> <peer>", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenport> <peer>", out);
|
||||
return;
|
||||
}
|
||||
peer = tok.nextToken();
|
||||
_mgr.processOpenClient(listenPort, peer, out);
|
||||
} else if ("openhttpclient".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String proxy = "squid.i2p";
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openhttpclient <listenPort> [<proxy>]", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
proxy = tok.nextToken();
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenport> [<proxy>]", out);
|
||||
return;
|
||||
}
|
||||
_mgr.processOpenHTTPClient(listenPort, proxy, out);
|
||||
} else if ("opensockstunnel".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: opensockstunnel <listenPort>", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: opensockstunnel <listenport>", out);
|
||||
return;
|
||||
}
|
||||
_mgr.processOpenSOCKSTunnel(listenPort, out);
|
||||
} else if ("openserver".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String serverHost = null;
|
||||
String serverKeys = null;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
serverHost = tok.nextToken();
|
||||
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
serverKeys = tok.nextToken();
|
||||
_mgr.processOpenServer(serverHost, listenPort, serverKeys, out);
|
||||
} else {
|
||||
_mgr.unknownCommand(command, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,407 +0,0 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Uuuugly code to generate the edit/add forms for the various
|
||||
* I2PTunnel types (httpclient/client/server)
|
||||
*
|
||||
*/
|
||||
class WebEditPageFormGenerator {
|
||||
private static final String SELECT_TYPE_FORM =
|
||||
"<form action=\"edit.jsp\"> Type of tunnel: <select name=\"type\">" +
|
||||
"<option value=\"httpclient\">HTTP proxy</option>" +
|
||||
"<option value=\"client\">Client tunnel</option>" +
|
||||
"<option value=\"server\">Server tunnel</option>" +
|
||||
"<option value=\"httpserver\">HTTP server tunnel</option>" +
|
||||
"</select> <input type=\"submit\" value=\"GO\" />" +
|
||||
"</form>\n";
|
||||
|
||||
/**
|
||||
* Retrieve the form requested
|
||||
*
|
||||
*/
|
||||
public static String getForm(WebEditPageHelper helper) {
|
||||
TunnelController controller = helper.getTunnelController();
|
||||
|
||||
if ( (helper.getType() == null) && (controller == null) )
|
||||
return SELECT_TYPE_FORM;
|
||||
|
||||
String id = helper.getNum();
|
||||
String type = helper.getType();
|
||||
if (controller != null)
|
||||
type = controller.getType();
|
||||
|
||||
if ("httpclient".equals(type))
|
||||
return getEditHttpClientForm(controller, id);
|
||||
else if ("client".equals(type))
|
||||
return getEditClientForm(controller, id);
|
||||
else if ("server".equals(type))
|
||||
return getEditServerForm(controller, id);
|
||||
else if ("httpserver".equals(type))
|
||||
return getEditHttpServerForm(controller, id);
|
||||
else
|
||||
return "WTF, unknown type [" + type + "]";
|
||||
}
|
||||
|
||||
private static String getEditHttpClientForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>HTTP proxy</i><input type=\"hidden\" name=\"type\" value=\"httpclient\" /><br />\n");
|
||||
|
||||
addListeningOn(buf, controller, 4444);
|
||||
|
||||
buf.append("<b>Outproxies:</b> <input type=\"text\" name=\"proxyList\" size=\"20\" ");
|
||||
if ( (controller != null) && (controller.getProxyList() != null) )
|
||||
buf.append("value=\"").append(controller.getProxyList()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"squid.i2p\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
buf.append("<hr />Note: the following options are shared across all client tunnels and");
|
||||
buf.append(" HTTP proxies<br />\n");
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getEditClientForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>Client tunnel</i><input type=\"hidden\" name=\"type\" value=\"client\" /><br />\n");
|
||||
|
||||
addListeningOn(buf, controller, 2025 + new Random().nextInt(1000)); // 2025 since nextInt can be negative
|
||||
|
||||
buf.append("<b>Target:</b> <input type=\"text\" size=\"40\" name=\"targetDestination\" ");
|
||||
if ( (controller != null) && (controller.getTargetDestination() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetDestination()).append("\" ");
|
||||
buf.append(" /> (either the hosts.txt name or the full base64 destination)<br />\n");
|
||||
|
||||
buf.append("<hr />Note: the following options are shared across all client tunnels and");
|
||||
buf.append(" HTTP proxies<br />\n");
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\"><br />\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getEditServerForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>Server tunnel</i><input type=\"hidden\" name=\"type\" value=\"server\" /><br />\n");
|
||||
|
||||
buf.append("<b>Target host:</b> <input type=\"text\" size=\"40\" name=\"targetHost\" ");
|
||||
if ( (controller != null) && (controller.getTargetHost() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"127.0.0.1\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
|
||||
if ( (controller != null) && (controller.getTargetPort() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetPort()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"80\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Private key file:</b> <input type=\"text\" name=\"privKeyFile\" value=\"");
|
||||
if ( (controller != null) && (controller.getPrivKeyFile() != null) ) {
|
||||
buf.append(controller.getPrivKeyFile()).append("\" /><br />");
|
||||
} else {
|
||||
buf.append("myServer.privKey\" /><br />");
|
||||
buf.append("<input type=\"hidden\" name=\"privKeyGenerate\" value=\"true\" />");
|
||||
}
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getEditHttpServerForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>HTTP server tunnel</i><input type=\"hidden\" name=\"type\" value=\"httpserver\" /><br />\n");
|
||||
|
||||
buf.append("<b>Target host:</b> <input type=\"text\" size=\"40\" name=\"targetHost\" ");
|
||||
if ( (controller != null) && (controller.getTargetHost() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"127.0.0.1\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
|
||||
if ( (controller != null) && (controller.getTargetPort() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetPort()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"80\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Website hostname:</b> <input type=\"text\" size=\"16\" name=\"spoofedHost\" ");
|
||||
if ( (controller != null) && (controller.getSpoofedHost() != null) )
|
||||
buf.append("value=\"").append(controller.getSpoofedHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"mysite.i2p\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Private key file:</b> <input type=\"text\" name=\"privKeyFile\" value=\"");
|
||||
if ( (controller != null) && (controller.getPrivKeyFile() != null) ) {
|
||||
buf.append(controller.getPrivKeyFile()).append("\" /><br />");
|
||||
} else {
|
||||
buf.append("myServer.privKey\" /><br />");
|
||||
buf.append("<input type=\"hidden\" name=\"privKeyGenerate\" value=\"true\" />");
|
||||
}
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start off the form and add some common fields (name, num, description)
|
||||
*
|
||||
* @param buf where to shove the form
|
||||
* @param controller tunnel in question, or null if we're creating a new tunnel
|
||||
* @param id index into the current list of tunnelControllerGroup.getControllers() list
|
||||
* (or null if we are generating an 'add' form)
|
||||
*/
|
||||
private static void addGeneral(StringBuffer buf, TunnelController controller, String id) {
|
||||
buf.append("<form action=\"edit.jsp\">");
|
||||
if (id != null)
|
||||
buf.append("<input type=\"hidden\" name=\"num\" value=\"").append(id).append("\" />");
|
||||
long nonce = new Random().nextLong();
|
||||
System.setProperty(WebEditPageHelper.class.getName() + ".nonce", nonce+"");
|
||||
buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(nonce).append("\" />");
|
||||
|
||||
buf.append("<b>Name:</b> <input type=\"text\" name=\"name\" size=\"20\" ");
|
||||
if ( (controller != null) && (controller.getName() != null) )
|
||||
buf.append("value=\"").append(controller.getName()).append("\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
buf.append("<b>Description:</b> <input type=\"text\" name=\"description\" size=\"60\" ");
|
||||
if ( (controller != null) && (controller.getDescription() != null) )
|
||||
buf.append("value=\"").append(controller.getDescription()).append("\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
buf.append("<b>Start automatically?</b> \n");
|
||||
buf.append("<input type=\"checkbox\" name=\"startOnLoad\" value=\"true\" ");
|
||||
if ( (controller != null) && (controller.getStartOnLoad()) )
|
||||
buf.append(" checked=\"true\" />\n<br />\n");
|
||||
else
|
||||
buf.append(" />\n<br />\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the fields asking for what port and interface the tunnel should
|
||||
* listen on.
|
||||
*
|
||||
* @param buf where to shove the form
|
||||
* @param controller tunnel in question, or null if we're creating a new tunnel
|
||||
* @param defaultPort if we are creating a new tunnel, default the form to the given port
|
||||
*/
|
||||
private static void addListeningOn(StringBuffer buf, TunnelController controller, int defaultPort) {
|
||||
buf.append("<b>Listening on port:</b> <input type=\"text\" name=\"port\" size=\"20\" ");
|
||||
if ( (controller != null) && (controller.getListenPort() != null) )
|
||||
buf.append("value=\"").append(controller.getListenPort()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"").append(defaultPort).append("\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
String selectedOn = null;
|
||||
if ( (controller != null) && (controller.getListenOnInterface() != null) )
|
||||
selectedOn = controller.getListenOnInterface();
|
||||
|
||||
buf.append("<b>Reachable by:</b> ");
|
||||
buf.append("<select name=\"reachableBy\">");
|
||||
buf.append("<option value=\"127.0.0.1\" ");
|
||||
if ( (selectedOn != null) && ("127.0.0.1".equals(selectedOn)) )
|
||||
buf.append("selected=\"true\" ");
|
||||
buf.append(">Locally (127.0.0.1)</option>\n");
|
||||
buf.append("<option value=\"0.0.0.0\" ");
|
||||
if ( (selectedOn != null) && ("0.0.0.0".equals(selectedOn)) )
|
||||
buf.append("selected=\"true\" ");
|
||||
buf.append(">Everyone (0.0.0.0)</option>\n");
|
||||
buf.append("</select> ");
|
||||
buf.append("Other: <input type=\"text\" name=\"reachableByOther\" value=\"");
|
||||
if ( (selectedOn != null) && (!"127.0.0.1".equals(selectedOn)) && (!"0.0.0.0".equals(selectedOn)) )
|
||||
buf.append(selectedOn);
|
||||
buf.append("\"><br />\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fields for customizing the I2PSession options, including helpers for
|
||||
* tunnel depth and count, as well as I2CP host and port.
|
||||
*
|
||||
* @param buf where to shove the form
|
||||
* @param controller tunnel in question, or null if we're creating a new tunnel
|
||||
*/
|
||||
private static void addOptions(StringBuffer buf, TunnelController controller) {
|
||||
int tunnelDepth = 2;
|
||||
int numTunnels = 2;
|
||||
int connectDelay = 0;
|
||||
int maxWindowSize = -1;
|
||||
Properties opts = getOptions(controller);
|
||||
if (opts != null) {
|
||||
String depth = opts.getProperty("inbound.length");
|
||||
if (depth != null) {
|
||||
try {
|
||||
tunnelDepth = Integer.parseInt(depth);
|
||||
} catch (NumberFormatException nfe) {
|
||||
tunnelDepth = 2;
|
||||
}
|
||||
}
|
||||
String num = opts.getProperty("inbound.quantity");
|
||||
if (num != null) {
|
||||
try {
|
||||
numTunnels = Integer.parseInt(num);
|
||||
} catch (NumberFormatException nfe) {
|
||||
numTunnels = 2;
|
||||
}
|
||||
}
|
||||
String delay = opts.getProperty("i2p.streaming.connectDelay");
|
||||
if (delay != null) {
|
||||
try {
|
||||
connectDelay = Integer.parseInt(delay);
|
||||
} catch (NumberFormatException nfe) {
|
||||
connectDelay = 0;
|
||||
}
|
||||
}
|
||||
String max = opts.getProperty("i2p.streaming.maxWindowSize");
|
||||
if (max != null) {
|
||||
try {
|
||||
maxWindowSize = Integer.parseInt(max);
|
||||
} catch (NumberFormatException nfe) {
|
||||
maxWindowSize = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.append("<b>Tunnel depth:</b> ");
|
||||
buf.append("<select name=\"tunnelDepth\">");
|
||||
buf.append("<option value=\"0\" ");
|
||||
if (tunnelDepth == 0) buf.append(" selected=\"true\" ");
|
||||
buf.append(">0 hop tunnel (low anonymity, low latency)</option>");
|
||||
buf.append("<option value=\"1\" ");
|
||||
if (tunnelDepth == 1) buf.append(" selected=\"true\" ");
|
||||
buf.append(">1 hop tunnel (medium anonymity, medium latency)</option>");
|
||||
buf.append("<option value=\"2\" ");
|
||||
if (tunnelDepth == 2) buf.append(" selected=\"true\" ");
|
||||
buf.append(">2 hop tunnel (high anonymity, high latency)</option>");
|
||||
if (tunnelDepth > 2) {
|
||||
buf.append("<option value=\"").append(tunnelDepth).append("\" selected=\"true\" >");
|
||||
buf.append(tunnelDepth);
|
||||
buf.append(" hop tunnel (custom)</option>");
|
||||
}
|
||||
buf.append("</select><br />\n");
|
||||
|
||||
buf.append("<b>Tunnel count:</b> ");
|
||||
buf.append("<select name=\"tunnelCount\">");
|
||||
buf.append("<option value=\"1\" ");
|
||||
if (numTunnels == 1) buf.append(" selected=\"true\" ");
|
||||
buf.append(">1 inbound tunnel (low bandwidth usage, less reliability)</option>");
|
||||
buf.append("<option value=\"2\" ");
|
||||
if (numTunnels == 2) buf.append(" selected=\"true\" ");
|
||||
buf.append(">2 inbound tunnels (standard bandwidth usage, standard reliability)</option>");
|
||||
buf.append("<option value=\"3\" ");
|
||||
if (numTunnels == 3) buf.append(" selected=\"true\" ");
|
||||
buf.append(">3 inbound tunnels (higher bandwidth usage, higher reliability)</option>");
|
||||
|
||||
if (numTunnels > 3) {
|
||||
buf.append("<option value=\"").append(numTunnels).append("\" selected=\"true\" >");
|
||||
buf.append(numTunnels);
|
||||
buf.append(" inbound tunnels (custom)</option>");
|
||||
}
|
||||
buf.append("</select><br />\n");
|
||||
|
||||
buf.append("<b>Delay connection briefly? </b> ");
|
||||
buf.append("<input type=\"checkbox\" name=\"connectDelay\" value=\"");
|
||||
buf.append((connectDelay > 0 ? connectDelay : 1000)).append("\" ");
|
||||
if (connectDelay > 0)
|
||||
buf.append("checked=\"true\" ");
|
||||
buf.append("/> (useful for brief request/response connections)<br />\n");
|
||||
|
||||
buf.append("<b>Communication profile:</b>");
|
||||
buf.append("<select name=\"profile\">");
|
||||
if (maxWindowSize <= 0)
|
||||
buf.append("<option value=\"interactive\">Interactive</option><option value=\"bulk\" selected=\"true\">Bulk</option>");
|
||||
else
|
||||
buf.append("<option value=\"interactive\" selected=\"true\">Interactive</option><option value=\"bulk\">Bulk</option>");
|
||||
buf.append("</select><br />\n");
|
||||
|
||||
buf.append("<b>I2CP host:</b> ");
|
||||
buf.append("<input type=\"text\" name=\"clientHost\" size=\"20\" value=\"");
|
||||
if ( (controller != null) && (controller.getI2CPHost() != null) )
|
||||
buf.append(controller.getI2CPHost());
|
||||
else
|
||||
buf.append("127.0.0.1");
|
||||
buf.append("\" /><br />\n");
|
||||
buf.append("<b>I2CP port:</b> ");
|
||||
buf.append("<input type=\"text\" name=\"clientPort\" size=\"20\" value=\"");
|
||||
if ( (controller != null) && (controller.getI2CPPort() != null) )
|
||||
buf.append(controller.getI2CPPort());
|
||||
else
|
||||
buf.append("7654");
|
||||
buf.append("\" /><br />\n");
|
||||
|
||||
buf.append("<b>Other custom options:</b> \n");
|
||||
buf.append("<input type=\"text\" name=\"customOptions\" size=\"60\" value=\"");
|
||||
if (opts != null) {
|
||||
int i = 0;
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = opts.getProperty(key);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
if (i != 0) buf.append(' ');
|
||||
buf.append(key).append('=').append(val);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
buf.append("\" /><br />\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the client options from the tunnel
|
||||
*
|
||||
* @return map of name=val to be used as I2P session options
|
||||
*/
|
||||
private static Properties getOptions(TunnelController controller) {
|
||||
if (controller == null) return null;
|
||||
String opts = controller.getClientOptions();
|
||||
StringTokenizer tok = new StringTokenizer(opts);
|
||||
Properties props = new Properties();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
props.setProperty(key, val);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
@ -1,448 +0,0 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* UUUUuuuuuugly glue code to handle bean interaction from the web, process
|
||||
* that data, and spit out the results (or the form requested). The basic
|
||||
* usage is to set any of the fields with data then query the bean via
|
||||
* getActionResults() which triggers the request processing (taking all the
|
||||
* provided data, doing what needs to be done) and returns the results of those
|
||||
* activites. Then a subsequent call to getEditForm() generates the HTML form
|
||||
* to either edit the currently selected tunnel (if specified) or add a new one.
|
||||
* This functionality is delegated to the WebEditPageFormGenerator.
|
||||
*
|
||||
*/
|
||||
public class WebEditPageHelper {
|
||||
private Log _log;
|
||||
private String _action;
|
||||
private String _type;
|
||||
private String _id;
|
||||
private String _name;
|
||||
private String _description;
|
||||
private String _i2cpHost;
|
||||
private String _i2cpPort;
|
||||
private String _tunnelDepth;
|
||||
private String _tunnelCount;
|
||||
private boolean _connectDelay;
|
||||
private String _customOptions;
|
||||
private String _proxyList;
|
||||
private String _port;
|
||||
private String _reachableBy;
|
||||
private String _reachableByOther;
|
||||
private String _targetDestination;
|
||||
private String _targetHost;
|
||||
private String _targetPort;
|
||||
private String _spoofedHost;
|
||||
private String _privKeyFile;
|
||||
private String _profile;
|
||||
private boolean _startOnLoad;
|
||||
private boolean _privKeyGenerate;
|
||||
private boolean _removeConfirmed;
|
||||
private long _nonce;
|
||||
|
||||
public WebEditPageHelper() {
|
||||
_action = null;
|
||||
_type = null;
|
||||
_id = null;
|
||||
_removeConfirmed = false;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(WebEditPageHelper.class);
|
||||
}
|
||||
|
||||
public void setNonce(String nonce) {
|
||||
if (nonce != null) {
|
||||
try {
|
||||
_nonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for form submit - either "Save" or Remove"
|
||||
*/
|
||||
public void setAction(String action) {
|
||||
_action = (action != null ? action.trim() : null);
|
||||
}
|
||||
/**
|
||||
* What type of tunnel (httpclient, client, or server). This is
|
||||
* required when adding a new tunnel.
|
||||
*
|
||||
*/
|
||||
public void setType(String type) {
|
||||
_type = (type != null ? type.trim() : null);
|
||||
}
|
||||
/**
|
||||
* Which particular tunnel should be edited (index into the current
|
||||
* TunnelControllerGroup's getControllers() list). This is required
|
||||
* when editing a tunnel, but not when adding a new one.
|
||||
*
|
||||
*/
|
||||
public void setNum(String id) {
|
||||
_id = (id != null ? id.trim() : null);
|
||||
}
|
||||
String getType() { return _type; }
|
||||
String getNum() { return _id; }
|
||||
|
||||
/** Short name of the tunnel */
|
||||
public void setName(String name) {
|
||||
_name = (name != null ? name.trim() : null);
|
||||
}
|
||||
/** one line description */
|
||||
public void setDescription(String description) {
|
||||
_description = (description != null ? description.trim() : null);
|
||||
}
|
||||
/** I2CP host the router is on */
|
||||
public void setClientHost(String host) {
|
||||
_i2cpHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** I2CP port the router is on */
|
||||
public void setClientPort(String port) {
|
||||
_i2cpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** how many hops to use for inbound tunnels */
|
||||
public void setTunnelDepth(String tunnelDepth) {
|
||||
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
|
||||
}
|
||||
/** how many parallel inbound tunnels to use */
|
||||
public void setTunnelCount(String tunnelCount) {
|
||||
_tunnelCount = (tunnelCount != null ? tunnelCount.trim() : null);
|
||||
}
|
||||
/** what I2P session overrides should be used */
|
||||
public void setCustomOptions(String customOptions) {
|
||||
_customOptions = (customOptions != null ? customOptions.trim() : null);
|
||||
}
|
||||
/** what HTTP outproxies should be used (httpclient specific) */
|
||||
public void setProxyList(String proxyList) {
|
||||
_proxyList = (proxyList != null ? proxyList.trim() : null);
|
||||
}
|
||||
/** what port should this client/httpclient listen on */
|
||||
public void setPort(String port) {
|
||||
_port = (port != null ? port.trim() : null);
|
||||
}
|
||||
/**
|
||||
* what interface should this client/httpclient listen on (unless
|
||||
* overridden by the setReachableByOther() field)
|
||||
*/
|
||||
public void setReachableBy(String reachableBy) {
|
||||
_reachableBy = (reachableBy != null ? reachableBy.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If specified, defines the exact IP interface to listen for requests
|
||||
* on (in the case of client/httpclient tunnels)
|
||||
*/
|
||||
public void setReachableByOther(String reachableByOther) {
|
||||
_reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
|
||||
}
|
||||
/** What peer does this client tunnel point at */
|
||||
public void setTargetDestination(String dest) {
|
||||
_targetDestination = (dest != null ? dest.trim() : null);
|
||||
}
|
||||
/** What host does this server tunnel point at */
|
||||
public void setTargetHost(String host) {
|
||||
_targetHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What port does this server tunnel point at */
|
||||
public void setTargetPort(String port) {
|
||||
_targetPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** What host does this http server tunnel spoof */
|
||||
public void setSpoofedHost(String host) {
|
||||
_spoofedHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What filename is this server tunnel's private keys stored in */
|
||||
public void setPrivKeyFile(String file) {
|
||||
_privKeyFile = (file != null ? file.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If called with any value, we want to generate a new destination
|
||||
* for this server tunnel. This won't cause any existing private keys
|
||||
* to be overwritten, however.
|
||||
*/
|
||||
public void setPrivKeyGenerate(String moo) {
|
||||
_privKeyGenerate = true;
|
||||
}
|
||||
/**
|
||||
* If called with any value (and the form submitted with action=Remove),
|
||||
* we really do want to stop and remove the tunnel.
|
||||
*/
|
||||
public void setRemoveConfirm(String moo) {
|
||||
_removeConfirmed = true;
|
||||
}
|
||||
/**
|
||||
* If called with any value, we want this tunnel to start whenever it is
|
||||
* loaded (aka right now and whenever the router is started up)
|
||||
*/
|
||||
public void setStartOnLoad(String moo) {
|
||||
_startOnLoad = true;
|
||||
}
|
||||
public void setConnectDelay(String moo) {
|
||||
_connectDelay = true;
|
||||
}
|
||||
public void setProfile(String profile) {
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the form and display any resulting messages
|
||||
*
|
||||
*/
|
||||
public String getActionResults() {
|
||||
try {
|
||||
return processAction();
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Internal error processing request", t);
|
||||
return "Internal error - " + t.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an HTML form to edit / create a tunnel according to the
|
||||
* specified fields
|
||||
*/
|
||||
public String getEditForm() {
|
||||
try {
|
||||
return WebEditPageFormGenerator.getForm(this);
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Internal error retrieving edit form", t);
|
||||
return "Internal error - " + t.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tunnel pointed to by the current id
|
||||
*
|
||||
*/
|
||||
TunnelController getTunnelController() {
|
||||
if (_id == null) return null;
|
||||
int id = -1;
|
||||
try {
|
||||
id = Integer.parseInt(_id);
|
||||
List controllers = TunnelControllerGroup.getInstance().getControllers();
|
||||
if ( (id < 0) || (id >= controllers.size()) )
|
||||
return null;
|
||||
else
|
||||
return (TunnelController)controllers.get(id);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid tunnel id [" + _id + "]", nfe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) )
|
||||
return "";
|
||||
String expected = System.getProperty(getClass().getName() + ".nonce");
|
||||
if ( (expected == null) || (!expected.equals(Long.toString(_nonce))) )
|
||||
return "<b>Invalid nonce, are you being spoofed?</b>";
|
||||
if ("Save".equals(_action))
|
||||
return save();
|
||||
else if ("Remove".equals(_action))
|
||||
return remove();
|
||||
else
|
||||
return "Action <i>" + _action + "</i> unknown";
|
||||
}
|
||||
|
||||
private String remove() {
|
||||
if (!_removeConfirmed)
|
||||
return "Please confirm removal";
|
||||
|
||||
TunnelController cur = getTunnelController();
|
||||
if (cur == null)
|
||||
return "Invalid tunnel number";
|
||||
|
||||
List msgs = TunnelControllerGroup.getInstance().removeController(cur);
|
||||
msgs.addAll(doSave());
|
||||
return getMessages(msgs);
|
||||
}
|
||||
|
||||
private String save() {
|
||||
if (_type == null)
|
||||
return "<b>Invalid form submission (no type?)</b>";
|
||||
Properties config = getConfig();
|
||||
if (config == null)
|
||||
return "<b>Invalid params</b>";
|
||||
|
||||
TunnelController cur = getTunnelController();
|
||||
if (cur == null) {
|
||||
// creating new
|
||||
cur = new TunnelController(config, "", _privKeyGenerate);
|
||||
TunnelControllerGroup.getInstance().addController(cur);
|
||||
if (cur.getStartOnLoad())
|
||||
cur.startTunnelBackground();
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
List controllers = TunnelControllerGroup.getInstance().getControllers();
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
if (c == cur) continue;
|
||||
if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelCount != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
cOpt.setProperty("option.inbound.length", _tunnelDepth);
|
||||
cOpt.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
if (_connectDelay)
|
||||
cOpt.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
else
|
||||
cOpt.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if ("interactive".equals(_profile))
|
||||
cOpt.setProperty("option.i2p.streaming.maxWindowSize", "1");
|
||||
else
|
||||
cOpt.remove("option.i2p.streaming.maxWindowSize");
|
||||
if (_name != null) {
|
||||
cOpt.setProperty("option.inbound.nickname", _name);
|
||||
cOpt.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
c.setConfig(cOpt, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getMessages(doSave());
|
||||
}
|
||||
private List doSave() {
|
||||
TunnelControllerGroup.getInstance().saveConfig();
|
||||
return TunnelControllerGroup.getInstance().clearAllMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on all provided data, create a set of configuration parameters
|
||||
* suitable for use in a TunnelController. This will replace (not add to)
|
||||
* any existing parameters, so this should return a comprehensive mapping.
|
||||
*
|
||||
*/
|
||||
private Properties getConfig() {
|
||||
Properties config = new Properties();
|
||||
updateConfigGeneric(config);
|
||||
|
||||
if ("httpclient".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
} else if ("client".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_targetDestination != null)
|
||||
config.setProperty("targetDestination", _targetDestination);
|
||||
} else if ("server".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
} else if ("httpserver".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
if (_spoofedHost != null)
|
||||
config.setProperty("spoofedHost", _spoofedHost);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private void updateConfigGeneric(Properties config) {
|
||||
config.setProperty("type", _type);
|
||||
if (_name != null)
|
||||
config.setProperty("name", _name);
|
||||
if (_description != null)
|
||||
config.setProperty("description", _description);
|
||||
if (_i2cpHost != null)
|
||||
config.setProperty("i2cpHost", _i2cpHost);
|
||||
if (_i2cpPort != null)
|
||||
config.setProperty("i2cpPort", _i2cpPort);
|
||||
|
||||
if (_customOptions != null) {
|
||||
StringTokenizer tok = new StringTokenizer(_customOptions);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
config.setProperty("option." + key, val);
|
||||
}
|
||||
}
|
||||
|
||||
config.setProperty("startOnLoad", _startOnLoad + "");
|
||||
|
||||
if (_tunnelCount != null) {
|
||||
config.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
config.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
config.setProperty("option.inbound.length", _tunnelDepth);
|
||||
config.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
if (_connectDelay)
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
else
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if (_name != null) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
if ("interactive".equals(_profile))
|
||||
config.setProperty("option.i2p.streaming.maxWindowSize", "1");
|
||||
else
|
||||
config.remove("option.i2p.streaming.maxWindowSize");
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty print the messages provided
|
||||
*
|
||||
*/
|
||||
private String getMessages(List msgs) {
|
||||
if (msgs == null) return "";
|
||||
int num = msgs.size();
|
||||
switch (num) {
|
||||
case 0: return "";
|
||||
case 1: return (String)msgs.get(0);
|
||||
default:
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("<ul>");
|
||||
for (int i = 0; i < num; i++)
|
||||
buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
|
||||
buf.append("</ul>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,213 +0,0 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.util.List;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Ugly hack to let the web interface access the list of known tunnels and
|
||||
* control their operation. Any data submitted by setting properties are
|
||||
* acted upon by calling getActionResults() (which returns any messages
|
||||
* generated). In addition, the getSummaryList() generates the html for
|
||||
* summarizing all of the tunnels known, including both their status and the
|
||||
* links to edit, stop, or start them.
|
||||
*
|
||||
*/
|
||||
public class WebStatusPageHelper {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _action;
|
||||
private int _controllerNum;
|
||||
private long _nonce;
|
||||
|
||||
public WebStatusPageHelper() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_action = null;
|
||||
_controllerNum = -1;
|
||||
_log = _context.logManager().getLog(WebStatusPageHelper.class);
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
_action = action;
|
||||
}
|
||||
public void setNum(String num) {
|
||||
if (num != null) {
|
||||
try {
|
||||
_controllerNum = Integer.parseInt(num);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_controllerNum = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void setNonce(long nonce) { _nonce = nonce; }
|
||||
public void setNonce(String nonce) {
|
||||
if (nonce != null) {
|
||||
try {
|
||||
_nonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public String getActionResults() {
|
||||
try {
|
||||
return processAction();
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Internal error processing web status", t);
|
||||
return "Internal error processing request - " + t.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public String getSummaryList() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
long nonce = _context.random().nextLong();
|
||||
StringBuffer buf = new StringBuffer(4*1024);
|
||||
buf.append("<ul>");
|
||||
List tunnels = group.getControllers();
|
||||
for (int i = 0; i < tunnels.size(); i++) {
|
||||
buf.append("<li>\n");
|
||||
getSummary(buf, i, (TunnelController)tunnels.get(i), nonce);
|
||||
buf.append("</li>\n");
|
||||
}
|
||||
buf.append("</ul>");
|
||||
|
||||
buf.append("<hr /><form action=\"index.jsp\" method=\"GET\">\n");
|
||||
buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(nonce).append("\" />\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Stop all\" />\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Start all\" />\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Restart all\" />\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Reload config\" />\n");
|
||||
buf.append("</form>\n");
|
||||
|
||||
System.setProperty(getClass().getName() + ".nonce", nonce+"");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private void getSummary(StringBuffer buf, int num, TunnelController controller, long nonce) {
|
||||
buf.append("<b>").append(controller.getName()).append("</b>: ");
|
||||
if (controller.getIsRunning()) {
|
||||
buf.append("<i>running</i> ");
|
||||
buf.append("<a href=\"index.jsp?num=").append(num);
|
||||
buf.append("&nonce=").append(nonce);
|
||||
buf.append("&action=stop\">stop</a> ");
|
||||
} else if (controller.getIsStarting()) {
|
||||
buf.append("<i>startup in progress (please be patient)</i>");
|
||||
} else {
|
||||
buf.append("<i>not running</i> ");
|
||||
buf.append("<a href=\"index.jsp?num=").append(num);
|
||||
buf.append("&nonce=").append(nonce);
|
||||
buf.append("&action=start\">start</a> ");
|
||||
}
|
||||
buf.append("<a href=\"edit.jsp?num=").append(num).append("\">edit</a> ");
|
||||
buf.append("<br />\n");
|
||||
controller.getSummary(buf);
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) )
|
||||
return getMessages();
|
||||
String expected = System.getProperty(getClass().getName() + ".nonce");
|
||||
if ( (expected == null) || (!expected.equals(Long.toString(_nonce))) )
|
||||
return "<b>Invalid nonce, are you being spoofed?</b>";
|
||||
if ("Stop all".equals(_action))
|
||||
return stopAll();
|
||||
else if ("Start all".equals(_action))
|
||||
return startAll();
|
||||
else if ("Restart all".equals(_action))
|
||||
return restartAll();
|
||||
else if ("Reload config".equals(_action))
|
||||
return reloadConfig();
|
||||
else if ("stop".equals(_action))
|
||||
return stop();
|
||||
else if ("start".equals(_action))
|
||||
return start();
|
||||
else
|
||||
return "Action <i>" + _action + "</i> unknown";
|
||||
}
|
||||
private String stopAll() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
List msgs = group.stopAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String startAll() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
List msgs = group.startAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String restartAll() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
List msgs = group.restartAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String reloadConfig() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
group.reloadControllers();
|
||||
return "Config reloaded";
|
||||
}
|
||||
private String start() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
if (_controllerNum < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = group.getControllers();
|
||||
if (_controllerNum >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_controllerNum);
|
||||
controller.startTunnelBackground();
|
||||
return getMessages(controller.clearMessages());
|
||||
}
|
||||
|
||||
private String stop() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
if (_controllerNum < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = group.getControllers();
|
||||
if (_controllerNum >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_controllerNum);
|
||||
controller.stopTunnel();
|
||||
return getMessages(controller.clearMessages());
|
||||
}
|
||||
|
||||
private String getMessages() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "";
|
||||
|
||||
return getMessages(group.clearAllMessages());
|
||||
}
|
||||
|
||||
private String getMessages(List msgs) {
|
||||
if (msgs == null) return "";
|
||||
int num = msgs.size();
|
||||
switch (num) {
|
||||
case 0: return "";
|
||||
case 1: return (String)msgs.get(0);
|
||||
default:
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("<ul>");
|
||||
for (int i = 0; i < num; i++)
|
||||
buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
|
||||
buf.append("</ul>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.Log;
|
||||
|
225
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
Normal file
@ -0,0 +1,225 @@
|
||||
package net.i2p.i2ptunnel.web;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2005 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Ugly little accessor for the edit page
|
||||
*/
|
||||
public class EditBean extends IndexBean {
|
||||
public EditBean() { super(); }
|
||||
|
||||
public static boolean staticIsClient(int tunnel) {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
List controllers = group.getControllers();
|
||||
if (controllers.size() > tunnel) {
|
||||
TunnelController cur = (TunnelController)controllers.get(tunnel);
|
||||
if (cur == null) return false;
|
||||
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getTargetHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetHost();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getTargetPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getSpoofedHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getSpoofedHost();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getPrivateKeyFile(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getPrivKeyFile();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean startAutomatically(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getStartOnLoad();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSharedClient(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return "true".equalsIgnoreCase(tun.getSharedClient());
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean shouldDelay(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String delay = opts.getProperty("i2p.streaming.connectDelay");
|
||||
if ( (delay == null) || ("0".equals(delay)) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInteractive(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String wsiz = opts.getProperty("i2p.streaming.maxWindowSize");
|
||||
if ( (wsiz == null) || (!"1".equals(wsiz)) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTunnelDepth(int tunnel, int defaultLength) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.length");
|
||||
if (len == null) return defaultLength;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultLength;
|
||||
}
|
||||
} else {
|
||||
return defaultLength;
|
||||
}
|
||||
} else {
|
||||
return defaultLength;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTunnelCount(int tunnel, int defaultCount) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.quantity");
|
||||
if (len == null) return defaultCount;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultCount;
|
||||
}
|
||||
} else {
|
||||
return defaultCount;
|
||||
}
|
||||
} else {
|
||||
return defaultCount;
|
||||
}
|
||||
}
|
||||
|
||||
public String getI2CPHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getI2CPHost();
|
||||
else
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
public String getI2CPPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getI2CPPort();
|
||||
else
|
||||
return "7654";
|
||||
}
|
||||
|
||||
public String getCustomOptions(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts == null) return "";
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
int i = 0;
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = opts.getProperty(key);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
if (i != 0) buf.append(' ');
|
||||
buf.append(key).append('=').append(val);
|
||||
i++;
|
||||
}
|
||||
return buf.toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the client options from the tunnel
|
||||
*
|
||||
* @return map of name=val to be used as I2P session options
|
||||
*/
|
||||
private static Properties getOptions(TunnelController controller) {
|
||||
if (controller == null) return null;
|
||||
String opts = controller.getClientOptions();
|
||||
StringTokenizer tok = new StringTokenizer(opts);
|
||||
Properties props = new Properties();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
props.setProperty(key, val);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
647
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
Normal file
@ -0,0 +1,647 @@
|
||||
package net.i2p.i2ptunnel.web;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2005 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple accessor for exposing tunnel info, but also an ugly form handler
|
||||
*
|
||||
*/
|
||||
public class IndexBean {
|
||||
protected I2PAppContext _context;
|
||||
protected Log _log;
|
||||
protected TunnelControllerGroup _group;
|
||||
private String _action;
|
||||
private int _tunnel;
|
||||
private long _prevNonce;
|
||||
private long _curNonce;
|
||||
private long _nextNonce;
|
||||
private String _passphrase;
|
||||
|
||||
private String _type;
|
||||
private String _name;
|
||||
private String _description;
|
||||
private String _i2cpHost;
|
||||
private String _i2cpPort;
|
||||
private String _tunnelDepth;
|
||||
private String _tunnelCount;
|
||||
private boolean _connectDelay;
|
||||
private String _customOptions;
|
||||
private String _proxyList;
|
||||
private String _port;
|
||||
private String _reachableBy;
|
||||
private String _reachableByOther;
|
||||
private String _targetDestination;
|
||||
private String _targetHost;
|
||||
private String _targetPort;
|
||||
private String _spoofedHost;
|
||||
private String _privKeyFile;
|
||||
private String _profile;
|
||||
private boolean _startOnLoad;
|
||||
private boolean _sharedClient;
|
||||
private boolean _privKeyGenerate;
|
||||
private boolean _removeConfirmed;
|
||||
|
||||
public static final int RUNNING = 1;
|
||||
public static final int STARTING = 2;
|
||||
public static final int NOT_RUNNING = 3;
|
||||
|
||||
public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
|
||||
static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
|
||||
static final String CLIENT_NICKNAME = "shared clients";
|
||||
|
||||
public IndexBean() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(IndexBean.class);
|
||||
_group = TunnelControllerGroup.getInstance();
|
||||
_action = null;
|
||||
_tunnel = -1;
|
||||
_curNonce = -1;
|
||||
_prevNonce = -1;
|
||||
try {
|
||||
String nonce = System.getProperty(PROP_NONCE);
|
||||
if (nonce != null)
|
||||
_prevNonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
_nextNonce = _context.random().nextLong();
|
||||
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
|
||||
}
|
||||
|
||||
public long getNextNonce() { return _nextNonce; }
|
||||
public void setNonce(String nonce) {
|
||||
if ( (nonce == null) || (nonce.trim().length() <= 0) ) return;
|
||||
try {
|
||||
_curNonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_curNonce = -1;
|
||||
}
|
||||
}
|
||||
public void setPassphrase(String phrase) {
|
||||
_passphrase = phrase;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
if ( (action == null) || (action.trim().length() <= 0) ) return;
|
||||
_action = action;
|
||||
}
|
||||
public void setTunnel(String tunnel) {
|
||||
if ( (tunnel == null) || (tunnel.trim().length() <= 0) ) return;
|
||||
try {
|
||||
_tunnel = Integer.parseInt(tunnel);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_tunnel = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validPassphrase(String proposed) {
|
||||
if (proposed == null) return false;
|
||||
String pass = _context.getProperty(PROP_TUNNEL_PASSPHRASE);
|
||||
if ( (pass != null) && (pass.trim().length() > 0) )
|
||||
return pass.trim().equals(proposed.trim());
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) )
|
||||
return "";
|
||||
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
|
||||
return "Invalid nonce, are you being spoofed?";
|
||||
if ("Stop all tunnels".equals(_action))
|
||||
return stopAll();
|
||||
else if ("Start all tunnels".equals(_action))
|
||||
return startAll();
|
||||
else if ("Restart all".equals(_action))
|
||||
return restartAll();
|
||||
else if ("Reload config".equals(_action))
|
||||
return reloadConfig();
|
||||
else if ("stop".equals(_action))
|
||||
return stop();
|
||||
else if ("start".equals(_action))
|
||||
return start();
|
||||
else if ("Save changes".equals(_action))
|
||||
return saveChanges();
|
||||
else if ("Delete this proxy".equals(_action))
|
||||
return deleteTunnel();
|
||||
else
|
||||
return "Action " + _action + " unknown";
|
||||
}
|
||||
private String stopAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.stopAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String startAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.startAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String restartAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.restartAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String reloadConfig() {
|
||||
if (_group == null) return "";
|
||||
|
||||
_group.reloadControllers();
|
||||
return "Config reloaded";
|
||||
}
|
||||
private String start() {
|
||||
if (_tunnel < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = _group.getControllers();
|
||||
if (_tunnel >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_tunnel);
|
||||
controller.startTunnelBackground();
|
||||
return "";
|
||||
}
|
||||
|
||||
private String stop() {
|
||||
if (_tunnel < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = _group.getControllers();
|
||||
if (_tunnel >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_tunnel);
|
||||
controller.stopTunnel();
|
||||
return "";
|
||||
}
|
||||
|
||||
private String saveChanges() {
|
||||
TunnelController cur = getController(_tunnel);
|
||||
|
||||
Properties config = getConfig();
|
||||
if (config == null)
|
||||
return "Invalid params";
|
||||
|
||||
if (cur == null) {
|
||||
// creating new
|
||||
cur = new TunnelController(config, "", true);
|
||||
_group.addController(cur);
|
||||
if (cur.getStartOnLoad())
|
||||
cur.startTunnelBackground();
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
List controllers = _group.getControllers();
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
if (c == cur) continue;
|
||||
//only change when they really are declared of beeing a sharedClient
|
||||
if (("httpclient".equals(c.getType()) || "client".equals(c.getType())) && "true".equalsIgnoreCase(c.getSharedClient())) {
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelCount != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
cOpt.setProperty("option.inbound.length", _tunnelDepth);
|
||||
cOpt.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
cOpt.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
cOpt.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
|
||||
c.setConfig(cOpt, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List msgs = doSave();
|
||||
msgs.add(0, "Changes saved");
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private List doSave() {
|
||||
_group.saveConfig();
|
||||
return _group.clearAllMessages();
|
||||
}
|
||||
private String deleteTunnel() {
|
||||
if (!_removeConfirmed)
|
||||
return "Please confirm removal";
|
||||
|
||||
TunnelController cur = getController(_tunnel);
|
||||
if (cur == null)
|
||||
return "Invalid tunnel number";
|
||||
|
||||
List msgs = _group.removeController(cur);
|
||||
msgs.addAll(doSave());
|
||||
return getMessages(msgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes any action requested (start/stop/etc) and dump out the
|
||||
* messages.
|
||||
*
|
||||
*/
|
||||
public String getMessages() {
|
||||
if (_group == null)
|
||||
return "";
|
||||
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
if (_action != null) {
|
||||
try {
|
||||
buf.append(processAction()).append("\n");
|
||||
} catch (Exception e) {
|
||||
_log.log(Log.CRIT, "Error processing " + _action, e);
|
||||
}
|
||||
}
|
||||
getMessages(_group.clearAllMessages(), buf);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
////
|
||||
// The remaining methods are simple bean props for the jsp to query
|
||||
////
|
||||
|
||||
public int getTunnelCount() {
|
||||
if (_group == null) return 0;
|
||||
return _group.getControllers().size();
|
||||
}
|
||||
|
||||
public boolean isClient(int tunnelNum) {
|
||||
TunnelController cur = getController(tunnelNum);
|
||||
if (cur == null) return false;
|
||||
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
|
||||
}
|
||||
|
||||
public String getTunnelName(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getName();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClientPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getListenPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTunnelType(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return getTypeName(tun.getType());
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTypeName(String internalType) {
|
||||
if ("client".equals(internalType)) return "Client proxy";
|
||||
else if ("httpclient".equals(internalType)) return "HTTP proxy";
|
||||
else if ("server".equals(internalType)) return "Server";
|
||||
else if ("httpserver".equals(internalType)) return "HTTP server";
|
||||
else return internalType;
|
||||
}
|
||||
|
||||
public String getInternalType(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getType();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClientInterface(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getListenOnInterface();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public int getTunnelStatus(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return NOT_RUNNING;
|
||||
if (tun.getIsRunning()) return RUNNING;
|
||||
else if (tun.getIsStarting()) return STARTING;
|
||||
else return NOT_RUNNING;
|
||||
}
|
||||
|
||||
public String getTunnelDescription(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getDescription();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getSharedClient(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getSharedClient();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClientDestination(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return "";
|
||||
if ("client".equals(tun.getType())) return tun.getTargetDestination();
|
||||
else return tun.getProxyList();
|
||||
}
|
||||
|
||||
public String getServerTarget(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetHost() + ':' + tun.getTargetPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getDestinationBase64(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
String rv = tun.getMyDestination();
|
||||
if (rv != null)
|
||||
return rv;
|
||||
else
|
||||
return "";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// bean props for form submission
|
||||
///
|
||||
|
||||
/**
|
||||
* What type of tunnel (httpclient, client, or server). This is
|
||||
* required when adding a new tunnel.
|
||||
*
|
||||
*/
|
||||
public void setType(String type) {
|
||||
_type = (type != null ? type.trim() : null);
|
||||
}
|
||||
String getType() { return _type; }
|
||||
|
||||
/** Short name of the tunnel */
|
||||
public void setName(String name) {
|
||||
_name = (name != null ? name.trim() : null);
|
||||
}
|
||||
/** one line description */
|
||||
public void setDescription(String description) {
|
||||
_description = (description != null ? description.trim() : null);
|
||||
}
|
||||
/** I2CP host the router is on */
|
||||
public void setClientHost(String host) {
|
||||
_i2cpHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** I2CP port the router is on */
|
||||
public void setClientPort(String port) {
|
||||
_i2cpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** how many hops to use for inbound tunnels */
|
||||
public void setTunnelDepth(String tunnelDepth) {
|
||||
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
|
||||
}
|
||||
/** how many parallel inbound tunnels to use */
|
||||
public void setTunnelCount(String tunnelCount) {
|
||||
_tunnelCount = (tunnelCount != null ? tunnelCount.trim() : null);
|
||||
}
|
||||
/** what I2P session overrides should be used */
|
||||
public void setCustomOptions(String customOptions) {
|
||||
_customOptions = (customOptions != null ? customOptions.trim() : null);
|
||||
}
|
||||
/** what HTTP outproxies should be used (httpclient specific) */
|
||||
public void setProxyList(String proxyList) {
|
||||
_proxyList = (proxyList != null ? proxyList.trim() : null);
|
||||
}
|
||||
/** what port should this client/httpclient listen on */
|
||||
public void setPort(String port) {
|
||||
_port = (port != null ? port.trim() : null);
|
||||
}
|
||||
/**
|
||||
* what interface should this client/httpclient listen on (unless
|
||||
* overridden by the setReachableByOther() field)
|
||||
*/
|
||||
public void setReachableBy(String reachableBy) {
|
||||
_reachableBy = (reachableBy != null ? reachableBy.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If specified, defines the exact IP interface to listen for requests
|
||||
* on (in the case of client/httpclient tunnels)
|
||||
*/
|
||||
public void setReachableByOther(String reachableByOther) {
|
||||
_reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
|
||||
}
|
||||
/** What peer does this client tunnel point at */
|
||||
public void setTargetDestination(String dest) {
|
||||
_targetDestination = (dest != null ? dest.trim() : null);
|
||||
}
|
||||
/** What host does this server tunnel point at */
|
||||
public void setTargetHost(String host) {
|
||||
_targetHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What port does this server tunnel point at */
|
||||
public void setTargetPort(String port) {
|
||||
_targetPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** What host does this http server tunnel spoof */
|
||||
public void setSpoofedHost(String host) {
|
||||
_spoofedHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What filename is this server tunnel's private keys stored in */
|
||||
public void setPrivKeyFile(String file) {
|
||||
_privKeyFile = (file != null ? file.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If called with any value (and the form submitted with action=Remove),
|
||||
* we really do want to stop and remove the tunnel.
|
||||
*/
|
||||
public void setRemoveConfirm(String moo) {
|
||||
_removeConfirmed = true;
|
||||
}
|
||||
/**
|
||||
* If called with any value, we want this tunnel to start whenever it is
|
||||
* loaded (aka right now and whenever the router is started up)
|
||||
*/
|
||||
public void setStartOnLoad(String moo) {
|
||||
_startOnLoad = true;
|
||||
}
|
||||
public void setSharedClient(String moo) {
|
||||
_sharedClient=true;
|
||||
}
|
||||
public void setConnectDelay(String moo) {
|
||||
_connectDelay = true;
|
||||
}
|
||||
public void setProfile(String profile) {
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on all provided data, create a set of configuration parameters
|
||||
* suitable for use in a TunnelController. This will replace (not add to)
|
||||
* any existing parameters, so this should return a comprehensive mapping.
|
||||
*
|
||||
*/
|
||||
private Properties getConfig() {
|
||||
Properties config = new Properties();
|
||||
updateConfigGeneric(config);
|
||||
|
||||
if ("httpclient".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
if (_name != null && !_sharedClient) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
|
||||
config.setProperty("sharedClient", _sharedClient + "");
|
||||
} else if ("client".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_targetDestination != null)
|
||||
config.setProperty("targetDestination", _targetDestination);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
if (_name != null && !_sharedClient) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
config.setProperty("sharedClient", _sharedClient + "");
|
||||
} else if ("server".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
} else if ("httpserver".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
if (_spoofedHost != null)
|
||||
config.setProperty("spoofedHost", _spoofedHost);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private void updateConfigGeneric(Properties config) {
|
||||
config.setProperty("type", _type);
|
||||
if (_name != null)
|
||||
config.setProperty("name", _name);
|
||||
if (_description != null)
|
||||
config.setProperty("description", _description);
|
||||
if (_i2cpHost != null)
|
||||
config.setProperty("i2cpHost", _i2cpHost);
|
||||
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) )
|
||||
config.setProperty("i2cpPort", _i2cpPort);
|
||||
else
|
||||
config.setProperty("i2cpPort", "7654");
|
||||
|
||||
if (_customOptions != null) {
|
||||
StringTokenizer tok = new StringTokenizer(_customOptions);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
config.setProperty("option." + key, val);
|
||||
}
|
||||
}
|
||||
|
||||
config.setProperty("startOnLoad", _startOnLoad + "");
|
||||
|
||||
if (_tunnelCount != null) {
|
||||
config.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
config.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
config.setProperty("option.inbound.length", _tunnelDepth);
|
||||
config.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
if (_connectDelay)
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
else
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if (_name != null) {
|
||||
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))) || (!_sharedClient) ) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
} else {
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
}
|
||||
}
|
||||
if ("interactive".equals(_profile))
|
||||
config.setProperty("option.i2p.streaming.maxWindowSize", "1");
|
||||
else
|
||||
config.remove("option.i2p.streaming.maxWindowSize");
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
|
||||
protected TunnelController getController(int tunnel) {
|
||||
if (tunnel < 0) return null;
|
||||
if (_group == null) return null;
|
||||
List controllers = _group.getControllers();
|
||||
if (controllers.size() > tunnel)
|
||||
return (TunnelController)controllers.get(tunnel);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getMessages(List msgs) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
getMessages(msgs, buf);
|
||||
return buf.toString();
|
||||
}
|
||||
private void getMessages(List msgs, StringBuffer buf) {
|
||||
if (msgs == null) return;
|
||||
for (int i = 0; i < msgs.size(); i++) {
|
||||
buf.append((String)msgs.get(i)).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,26 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean" %>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2PTunnel edit</title>
|
||||
</head><body>
|
||||
|
||||
<a href="index.jsp">Back</a>
|
||||
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.WebEditPageHelper" id="helper" scope="request" />
|
||||
<jsp:setProperty name="helper" property="*" />
|
||||
<b><jsp:getProperty name="helper" property="actionResults" /></b>
|
||||
|
||||
<jsp:getProperty name="helper" property="editForm" />
|
||||
</body>
|
||||
</html>
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
if (tun != null) {
|
||||
try {
|
||||
int curTunnel = Integer.parseInt(tun);
|
||||
if (EditBean.staticIsClient(curTunnel)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
%>Invalid tunnel parameter<%
|
||||
}
|
||||
} else {
|
||||
String type = request.getParameter("type");
|
||||
int curTunnel = -1;
|
||||
if ("client".equals(type) || "httpclient".equals(type)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else if ("server".equals(type) || "httpserver".equals(type)) {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
} else {
|
||||
%>Invalid tunnel type<%
|
||||
}
|
||||
}
|
||||
%>
|
293
apps/i2ptunnel/jsp/editClient.jsp
Normal file
@ -0,0 +1,293 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
int curTunnel = -1;
|
||||
if (tun != null) {
|
||||
try {
|
||||
curTunnel = Integer.parseInt(tun);
|
||||
} catch (NumberFormatException nfe) {
|
||||
curTunnel = -1;
|
||||
}
|
||||
}
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="index.jsp">
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<% if (curTunnel >= 0) { %>
|
||||
<b>Edit proxy settings</b>
|
||||
<% } else { %>
|
||||
<b>New proxy settings</b>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Type: </b>
|
||||
<td><%
|
||||
if (curTunnel >= 0) {
|
||||
%><%=editBean.getTunnelType(curTunnel)%>
|
||||
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
|
||||
<%
|
||||
} else {
|
||||
%><%=editBean.getTypeName(request.getParameter("type"))%>
|
||||
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
|
||||
<%
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Description: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Start automatically?:</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.startAutomatically(curTunnel)) { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
|
||||
<% } else { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" />
|
||||
<% } %>
|
||||
<i>(Check the Box for 'YES')</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <b>Listening Port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="6" maxlength="5" name="port" value="<%=editBean.getClientPort(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b> Accessable by:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="reachableBy">
|
||||
<% String clientInterface = editBean.getClientInterface(curTunnel); %>
|
||||
<% if (("127.0.0.1".equals(clientInterface)) || (clientInterface == null) || (clientInterface.trim().length() <= 0)) { %>
|
||||
<option value="127.0.0.1" selected="true">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
|
||||
<option value="other">LAN Hosts (Please specify your LAN address)</option>
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" />
|
||||
<% } else if ("0.0.0.0".equals(clientInterface)) { %>
|
||||
<option value="127.0.0.1">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0" selected="true">Everyone (0.0.0.0)</option>
|
||||
<option value="other">LAN Hosts (Please specify your LAN address)</option>
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" />
|
||||
<% } else { %>
|
||||
<option value="127.0.0.1">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
|
||||
<option value="other" selected="true">LAN Hosts (Please specify your LAN address)</option>
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" value="<%=clientInterface%>" />
|
||||
<% } %>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
|
||||
<td><b>Outproxies:</b>
|
||||
<% } else { %>
|
||||
<td><b>Target:</b>
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
|
||||
<input type="text" name="proxyList" value="<%=editBean.getClientDestination(curTunnel)%>" />
|
||||
<% } else { %>
|
||||
<input type="text" name="targetDestination" value="<%=editBean.getClientDestination(curTunnel)%>" />
|
||||
<% } %>
|
||||
<i>(name or destination)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Delayed connect?</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.shouldDelay(curTunnel)) { %>
|
||||
<input type="checkbox" value="1000" name="connectDelay" checked="true" />
|
||||
<% } else { %>
|
||||
<input type="checkbox" value="1000" name="connectDelay" />
|
||||
<% } %>
|
||||
<i>(for request/response connections)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Profile:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="profile">
|
||||
<% if (editBean.isInteractive(curTunnel)) { %>
|
||||
<option value="interactive" selected="true">interactive connection </option>
|
||||
<option value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
<% } else { %>
|
||||
<option value="interactive">interactive connection </option>
|
||||
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Shared Client</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.isSharedClient(curTunnel)) { %>
|
||||
<input type="checkbox" value="true" name="sharedClient" checked="true" />
|
||||
<% } else { %>
|
||||
<input type="checkbox" value="true" name="sharedClient" />
|
||||
<% } %>
|
||||
<i>(Share tunnels with other clients and httpclients? Change requires restart of client proxy)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<b><hr size="1">
|
||||
Advanced networking options<br />
|
||||
<span style="color:#dd0000;">(NOTE: when this client proxy is configured to share tunnels, then these options are for all the shared proxy clients!)</span></b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Tunnel depth:</b>
|
||||
</td>
|
||||
<td><select name="tunnelDepth">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
switch (tunnelDepth) {
|
||||
case 0: %>
|
||||
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 1: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Tunnel count:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="tunnelCount">
|
||||
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
|
||||
switch (tunnelCount) {
|
||||
case 1: %>
|
||||
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 3: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
<tr>
|
||||
<td><b>I2CP host:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>I2CP port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Custom options:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<hr size="1">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Save:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Save changes" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Delete?</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Delete this proxy" />
|
||||
<b><span style="color:#dd0000;">confirm delete:</span></b>
|
||||
<input type="checkbox" value="true" name="removeConfirm" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
233
apps/i2ptunnel/jsp/editServer.jsp
Normal file
@ -0,0 +1,233 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
int curTunnel = -1;
|
||||
if (tun != null) {
|
||||
try {
|
||||
curTunnel = Integer.parseInt(tun);
|
||||
} catch (NumberFormatException nfe) {
|
||||
curTunnel = -1;
|
||||
}
|
||||
}
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="index.jsp">
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<% if (curTunnel >= 0) { %>
|
||||
<b>Edit server settings</b>
|
||||
<% } else { %>
|
||||
<b>New server settings</b>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Type: </b>
|
||||
<td><%
|
||||
if (curTunnel >= 0) {
|
||||
%><%=editBean.getTunnelType(curTunnel)%>
|
||||
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
|
||||
<%
|
||||
} else {
|
||||
%><%=editBean.getTypeName(request.getParameter("type"))%>
|
||||
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
|
||||
<%
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Description: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Start automatically?:</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.startAutomatically(curTunnel)) { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
|
||||
<% } else { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" />
|
||||
<% } %>
|
||||
<i>(Check the Box for 'YES')</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <b>Target:</b>
|
||||
</td>
|
||||
<td>
|
||||
Host: <input type="text" size="20" name="targetHost" value="<%=editBean.getTargetHost(curTunnel)%>" />
|
||||
Port: <input type="text" size="6" maxlength="5" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<% String curType = editBean.getInternalType(curTunnel);
|
||||
if ( (curType == null) || (curType.trim().length() <= 0) )
|
||||
curType = request.getParameter("type");
|
||||
if ("httpserver".equals(curType)) { %>
|
||||
<tr>
|
||||
<td><b>Website name:</b></td>
|
||||
<td><input type="text" size="20" name="spoofedHost" value="<%=editBean.getSpoofedHost(curTunnel)%>" />
|
||||
</td></tr>
|
||||
<% } %>
|
||||
<tr>
|
||||
<td><b>Private key file:</b>
|
||||
</td>
|
||||
<td><input type="text" size="30" name="privKeyFile" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Profile:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="profile">
|
||||
<% if (editBean.isInteractive(curTunnel)) { %>
|
||||
<option value="interactive" selected="true">interactive connection </option>
|
||||
<option value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
<% } else { %>
|
||||
<option value="interactive">interactive connection </option>
|
||||
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" align="left"><b>Local destination:</b><br /><i>(if known)</i></td>
|
||||
<td valign="top" align="left"><input type="text" size="60" value="<%=editBean.getDestinationBase64(curTunnel)%>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<b><hr size="1">
|
||||
Advanced networking options<br />
|
||||
</b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Tunnel depth:</b>
|
||||
</td>
|
||||
<td><select name="tunnelDepth">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
switch (tunnelDepth) {
|
||||
case 0: %>
|
||||
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 1: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Tunnel count:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="tunnelCount">
|
||||
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
|
||||
switch (tunnelCount) {
|
||||
case 1: %>
|
||||
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 3: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
<tr>
|
||||
<td><b>I2CP host:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>I2CP port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Custom options:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<hr size="1">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Save:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Save changes" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Delete?</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Delete this proxy" />
|
||||
<b><span style="color:#dd0000;">confirm delete:</span></b>
|
||||
<input type="checkbox" value="true" name="removeConfirm" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
@ -1,26 +1,184 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean" %>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.IndexBean" id="indexBean" scope="request" />
|
||||
<jsp:setProperty name="indexBean" property="*" />
|
||||
|
||||
<html><head>
|
||||
<title>I2PTunnel status</title>
|
||||
</head><body>
|
||||
<html>
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
</head>
|
||||
<body style="font-family: Verdana, Tahoma, Helvetica, sans-serif;font-size:12px;">
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td nowrap="true"><b>New Messages: </b><br />
|
||||
<a href="index.jsp">refresh</a>
|
||||
</td>
|
||||
<td>
|
||||
<textarea rows="3" cols="60" readonly="true"><jsp:getProperty name="indexBean" property="messages" /></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.WebStatusPageHelper" id="helper" scope="request" />
|
||||
<jsp:setProperty name="helper" property="*" />
|
||||
<h2>Messages since last page load:</h2>
|
||||
<b><jsp:getProperty name="helper" property="actionResults" /></b>
|
||||
|
||||
<jsp:getProperty name="helper" property="summaryList" />
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="7" align="center" valign="middle" style="font-size:14px;">
|
||||
<b>Your Client Tunnels:</b><br />
|
||||
<hr size="1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="15%"><b>Name:</b></td>
|
||||
<td><b>Port:</b></td>
|
||||
<td><b>Type:</b></td>
|
||||
<td><b>Interface:</b></td>
|
||||
<td><b>Status:</b></td>
|
||||
</tr>
|
||||
<% for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
|
||||
if (!indexBean.isClient(curClient)) continue; %>
|
||||
<tr>
|
||||
<td valign="top" align="left">
|
||||
<b><a href="edit.jsp?tunnel=<%=curClient%>"><%=indexBean.getTunnelName(curClient) %></a></b></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientPort(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getTunnelType(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientInterface(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%
|
||||
switch (indexBean.getTunnelStatus(curClient)) {
|
||||
case IndexBean.STARTING:
|
||||
%><b><span style="color:#339933">Starting...</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.RUNNING:
|
||||
%><b><span style="color:#00dd00">Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><b><span style="color:#dd0000">Not Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curClient%>">[START]</a><%
|
||||
break;
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr><td align="right" valign="top">Destination:</td>
|
||||
<td colspan="4"><input align="left" size="40" valign="top" style="overflow: hidden" readonly="true" value="<%=indexBean.getClientDestination(curClient) %>" /></td></tr>
|
||||
<tr>
|
||||
<td valign="top" align="right">Description:</td>
|
||||
<td valign="top" align="left" colspan="4"><%=indexBean.getTunnelDescription(curClient) %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="5" align="center" valign="middle" style="font-size:14px;">
|
||||
<b>Your Server Tunnels:</b><br />
|
||||
<hr size="1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="15%"><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Points at:</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Status:</b>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<% for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
|
||||
if (indexBean.isClient(curServer)) continue; %>
|
||||
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<b><a href="edit.jsp?tunnel=<%=curServer%>"><%=indexBean.getTunnelName(curServer)%></a></b>
|
||||
</td>
|
||||
<td valign="top"><%=indexBean.getServerTarget(curServer)%></td>
|
||||
<td valign="top" nowrap="true"><%
|
||||
switch (indexBean.getTunnelStatus(curServer)) {
|
||||
case IndexBean.RUNNING:
|
||||
%><b><span style="color:#00dd00">Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
|
||||
if ("httpserver".equals(indexBean.getInternalType(curServer))) {
|
||||
%> (<a href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>">preview</a>)<%
|
||||
}
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><b><span style="color:#dd0000">Not Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">[START]</a><%
|
||||
break;
|
||||
case IndexBean.STARTING:
|
||||
%>
|
||||
<b><span style="color:#339933">Starting...</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
|
||||
break;
|
||||
}
|
||||
%>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td valign="top" align="right">Description:</td>
|
||||
<td valign="top" align="left" colspan="2"><%=indexBean.getTunnelDescription(curServer)%></td></tr>
|
||||
<% } %>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center" valign="middle">
|
||||
<b>Operations Menu - Please chose from below!</b><br /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<form action="index.jsp" method="GET">
|
||||
<td >
|
||||
<input type="hidden" name="nonce" value="<%=indexBean.getNextNonce()%>" />
|
||||
<input type="submit" name="action" value="Stop all tunnels" />
|
||||
<input type="submit" name="action" value="Start all tunnels" />
|
||||
<input type="submit" name="action" value="Restart all" />
|
||||
<input type="submit" name="action" value="Reload config" />
|
||||
</td>
|
||||
</form>
|
||||
<form action="edit.jsp">
|
||||
<td >
|
||||
<b>Add new:</b>
|
||||
<select name="type">
|
||||
<select name="type">
|
||||
<option value="httpclient">HTTP proxy</option>
|
||||
<option value="client">Client tunnel</option>
|
||||
<option value="server">Server tunnel</option>
|
||||
<option value="httpserver">HTTP server tunnel</option>
|
||||
</select> <input type="submit" value="GO" />
|
||||
<option value="httpserver">HTTP server tunnel</option>
|
||||
</select> <input type="submit" value="Create" />
|
||||
</td>
|
||||
</form>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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>
|
||||
|
@ -62,6 +62,8 @@ public interface I2PSocket {
|
||||
*/
|
||||
public void close() throws IOException;
|
||||
|
||||
public boolean isClosed();
|
||||
|
||||
public void setSocketErrorListener(SocketErrorListener lsnr);
|
||||
/**
|
||||
* Allow notification of underlying errors communicating across I2P without
|
||||
|
@ -233,6 +233,8 @@ class I2PSocketImpl implements I2PSocket {
|
||||
in.notifyClosed();
|
||||
}
|
||||
|
||||
public boolean isClosed() { return _closedOn > 0; }
|
||||
|
||||
/**
|
||||
* Close the socket from the I2P side (by a close packet)
|
||||
*/
|
||||
|
@ -4,27 +4,16 @@
|
||||
*/
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -36,18 +36,27 @@ public class I2PSocketManagerFactory {
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager() {
|
||||
String i2cpHost = System.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
|
||||
int i2cpPort = 7654;
|
||||
String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT);
|
||||
if (i2cpPortStr != null) {
|
||||
try {
|
||||
i2cpPort = Integer.parseInt(i2cpPortStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
return createManager(getHost(), getPort(), System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the local machine on the default port (7654).
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(Properties opts) {
|
||||
return createManager(getHost(), getPort(), opts);
|
||||
}
|
||||
|
||||
return createManager(i2cpHost, i2cpPort, System.getProperties());
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the specified host and port
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(String host, int port) {
|
||||
return createManager(host, port, System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,6 +81,26 @@ public class I2PSocketManagerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the default I2CP host and port.
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream) {
|
||||
return createManager(myPrivateKeyStream, getHost(), getPort(), System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the default I2CP host and port.
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream, Properties opts) {
|
||||
return createManager(myPrivateKeyStream, getHost(), getPort(), opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the I2CP router on the specified machine on the given
|
||||
@ -154,4 +183,20 @@ public class I2PSocketManagerFactory {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getHost() {
|
||||
return System.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
|
||||
}
|
||||
private static int getPort() {
|
||||
int i2cpPort = 7654;
|
||||
String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT);
|
||||
if (i2cpPortStr != null) {
|
||||
try {
|
||||
i2cpPort = Integer.parseInt(i2cpPortStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
return i2cpPort;
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
*
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -74,61 +75,67 @@ public class StreamSinkClient {
|
||||
} finally {
|
||||
if (fis == null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
|
||||
System.out.println("Send " + _sendSize + "KB to " + peer.calculateHash().toBase64());
|
||||
|
||||
try {
|
||||
I2PSocket sock = mgr.connect(peer);
|
||||
byte buf[] = new byte[32*1024];
|
||||
Random rand = new Random();
|
||||
OutputStream out = sock.getOutputStream();
|
||||
long beforeSending = System.currentTimeMillis();
|
||||
for (int i = 0; i < _sendSize; i+= 32) {
|
||||
rand.nextBytes(buf);
|
||||
out.write(buf);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + _sendSize + "KB to " + peer.calculateHash().toBase64());
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
I2PSocket sock = mgr.connect(peer);
|
||||
byte buf[] = new byte[Math.min(32*1024, _sendSize*1024)];
|
||||
Random rand = new Random();
|
||||
OutputStream out = sock.getOutputStream();
|
||||
long beforeSending = System.currentTimeMillis();
|
||||
for (int i = 0; (_sendSize < 0) || (i < _sendSize); i+= buf.length/1024) {
|
||||
rand.nextBytes(buf);
|
||||
out.write(buf);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wrote " + ((1+i*buf.length)/1024) + "/" + _sendSize + "KB");
|
||||
if (_writeDelay > 0) {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sock.close();
|
||||
long afterSending = System.currentTimeMillis();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wrote " + (i+32) + "/" + _sendSize + "KB");
|
||||
if (_writeDelay > 0) {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sock.close();
|
||||
long afterSending = System.currentTimeMillis();
|
||||
System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
return;
|
||||
} catch (NoRouteToHostException nrthe) {
|
||||
_log.error("Unable to connect to the peer", nrthe);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error connecting to the peer", ie);
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("IO error sending", ioe);
|
||||
return;
|
||||
_log.debug("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
//return;
|
||||
} catch (NoRouteToHostException nrthe) {
|
||||
_log.error("Unable to connect to the peer", nrthe);
|
||||
//return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
//return;
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error connecting to the peer", ie);
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("IO error sending", ioe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile</code> <br />
|
||||
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile [concurrentSends]</code> <br />
|
||||
* <ul>
|
||||
* <li><b>sendSizeKB</b>: how many KB to send</li>
|
||||
* <li><b>sendSizeKB</b>: how many KB to send, or -1 for unlimited</li>
|
||||
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
|
||||
* <li><b>serverDestFile</b>: file containing the StreamSinkServer's binary Destination</li>
|
||||
* <li><b>concurrentSends</b>: how many concurrent threads should send to the server at once</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
StreamSinkClient client = null;
|
||||
int sendSizeKB = -1;
|
||||
int writeDelayMs = -1;
|
||||
int concurrent = 1;
|
||||
|
||||
switch (args.length) {
|
||||
case 3:
|
||||
case 3: // fall through
|
||||
case 4:
|
||||
try {
|
||||
sendSizeKB = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
@ -141,9 +148,13 @@ public class StreamSinkClient {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
if (args.length == 4) {
|
||||
try { concurrent = Integer.parseInt(args[3]); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
|
||||
break;
|
||||
case 5:
|
||||
case 5: // fall through
|
||||
case 6:
|
||||
try {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
sendSizeKB = Integer.parseInt(args[2]);
|
||||
@ -152,11 +163,26 @@ public class StreamSinkClient {
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("arg error");
|
||||
}
|
||||
if (args.length == 6) {
|
||||
try { concurrent = Integer.parseInt(args[5]); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile");
|
||||
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile [concurrentSends]");
|
||||
}
|
||||
if (client != null) {
|
||||
for (int i = 0; i < concurrent; i++)
|
||||
new I2PThread(new Runner(client), "Client " + i).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Runner implements Runnable {
|
||||
private StreamSinkClient _client;
|
||||
public Runner(StreamSinkClient client) {
|
||||
_client = client;
|
||||
}
|
||||
public void run() {
|
||||
_client.runClient();
|
||||
}
|
||||
if (client != null)
|
||||
client.runClient();
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
|
@ -6,6 +6,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@ -26,6 +28,7 @@ public class StreamSinkServer {
|
||||
private String _destFile;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
private int _handlers;
|
||||
|
||||
/**
|
||||
* Create but do not start the streaming server.
|
||||
@ -34,13 +37,14 @@ public class StreamSinkServer {
|
||||
* @param ourDestFile filename to write our binary destination to
|
||||
*/
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile) {
|
||||
this(sinkDir, ourDestFile, null, -1);
|
||||
this(sinkDir, ourDestFile, null, -1, 3);
|
||||
}
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort) {
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort, int handlers) {
|
||||
_sinkDir = sinkDir;
|
||||
_destFile = ourDestFile;
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
_handlers = handlers;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class);
|
||||
}
|
||||
|
||||
@ -56,7 +60,8 @@ public class StreamSinkServer {
|
||||
else
|
||||
mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination dest = mgr.getSession().getMyDestination();
|
||||
System.out.println("Listening for connections on: " + dest.calculateHash().toBase64());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Listening for connections on: " + dest.calculateHash().toBase64());
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(_destFile);
|
||||
@ -72,24 +77,16 @@ public class StreamSinkServer {
|
||||
}
|
||||
|
||||
I2PServerSocket sock = mgr.getServerSocket();
|
||||
while (true) {
|
||||
try {
|
||||
I2PSocket curSock = sock.accept();
|
||||
handle(curSock);
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error accepting connection", ie);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
}
|
||||
}
|
||||
startup(sock);
|
||||
}
|
||||
|
||||
private void handle(I2PSocket socket) {
|
||||
I2PThread t = new I2PThread(new ClientRunner(socket));
|
||||
t.setName("Handle " + socket.getPeerDestination().calculateHash().toBase64().substring(0,4));
|
||||
t.start();
|
||||
public void startup(I2PServerSocket sock) {
|
||||
for (int i = 0; i < _handlers; i++) {
|
||||
I2PThread t = new I2PThread(new ClientRunner(sock));
|
||||
t.setName("Handler " + i);
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,74 +94,100 @@ public class StreamSinkServer {
|
||||
*
|
||||
*/
|
||||
private class ClientRunner implements Runnable {
|
||||
private I2PSocket _sock;
|
||||
private FileOutputStream _fos;
|
||||
public ClientRunner(I2PSocket socket) {
|
||||
_sock = socket;
|
||||
private I2PServerSocket _socket;
|
||||
public ClientRunner(I2PServerSocket socket) {
|
||||
_socket = socket;
|
||||
}
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
I2PSocket socket = _socket.accept();
|
||||
if (socket != null)
|
||||
handle(socket);
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error accepting connection", ie);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(I2PSocket sock) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File sink = new File(_sinkDir);
|
||||
if (!sink.exists())
|
||||
sink.mkdirs();
|
||||
File cur = File.createTempFile("clientSink", ".dat", sink);
|
||||
_fos = new FileOutputStream(cur);
|
||||
System.out.println("Writing to " + cur.getAbsolutePath());
|
||||
fos = new FileOutputStream(cur);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Writing to " + cur.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error creating sink", ioe);
|
||||
_fos = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
public void run() {
|
||||
if (_fos == null) return;
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
InputStream in = _sock.getInputStream();
|
||||
InputStream in = sock.getInputStream();
|
||||
byte buf[] = new byte[4096];
|
||||
long written = 0;
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
_fos.write(buf, 0, read);
|
||||
//_fos.write(buf, 0, read);
|
||||
written += read;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read and wrote " + read);
|
||||
_log.debug("read and wrote " + read + " (" + written + ")");
|
||||
}
|
||||
fos.write(("written: [" + written + "]\n").getBytes());
|
||||
long lifetime = System.currentTimeMillis() - start;
|
||||
_log.error("Got EOF from client socket [written=" + written + " lifetime=" + lifetime + "]");
|
||||
_log.info("Got EOF from client socket [written=" + written + " lifetime=" + lifetime + "]");
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing the sink", ioe);
|
||||
} finally {
|
||||
if (_fos != null) try { _fos.close(); } catch (IOException ioe) {}
|
||||
if (_sock != null) try { _sock.close(); } catch (IOException ioe) {}
|
||||
_log.error("Client socket closed");
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
if (sock != null) try { sock.close(); } catch (IOException ioe) {}
|
||||
_log.debug("Client socket closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile</code><br />
|
||||
* Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [numHandlers]</code><br />
|
||||
* <ul>
|
||||
* <li><b>sinkDir</b>: Directory to store received files in</li>
|
||||
* <li><b>ourDestFile</b>: filename to write our binary destination to</li>
|
||||
* <li><b>numHandlers</b>: how many concurrent connections to handle</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
StreamSinkServer server = null;
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
server = new StreamSinkServer("dataDir", "server.key", "localhost", 10001);
|
||||
server = new StreamSinkServer("dataDir", "server.key", "localhost", 7654, 3);
|
||||
break;
|
||||
case 2:
|
||||
server = new StreamSinkServer(args[0], args[1]);
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
int handlers = 3;
|
||||
if (args.length == 5) {
|
||||
try {
|
||||
handlers = Integer.parseInt(args[4]);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
try {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
server = new StreamSinkServer(args[2], args[3], args[0], port);
|
||||
server = new StreamSinkServer(args[2], args[3], args[0], port, handlers);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [handlers]");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [handlers]");
|
||||
}
|
||||
if (server != null)
|
||||
server.runServer();
|
||||
|
@ -1,24 +1,12 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
@ -161,7 +149,8 @@ public class TestSwarm {
|
||||
public void run() {
|
||||
_started = _context.clock().now();
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
|
||||
byte data[] = new byte[32*1024];
|
||||
byte data[] = new byte[4*1024];
|
||||
_context.random().nextBytes(data);
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
if (_socket == null) {
|
||||
@ -179,15 +168,19 @@ public class TestSwarm {
|
||||
try {
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
while (!_closed) {
|
||||
out.write(data);
|
||||
// out.flush();
|
||||
_totalSent += data.length;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
//try { Thread.sleep(100); } catch (InterruptedException ie) {}
|
||||
long now = _context.clock().now();
|
||||
_log.debug("Sending " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
try { Thread.sleep(20); } catch (InterruptedException ie) {}
|
||||
if (shouldSend()) {
|
||||
out.write(data);
|
||||
// out.flush();
|
||||
_totalSent += data.length;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
//try { Thread.sleep(100); } catch (InterruptedException ie) {}
|
||||
long now = _context.clock().now();
|
||||
//_log.debug("Sending " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
//try { Thread.sleep(20); } catch (InterruptedException ie) {}
|
||||
} else {
|
||||
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error sending", e);
|
||||
@ -200,13 +193,13 @@ public class TestSwarm {
|
||||
long now = lastRead;
|
||||
try {
|
||||
InputStream in = _socket.getInputStream();
|
||||
byte buf[] = new byte[32*1024];
|
||||
byte buf[] = new byte[8*1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
now = System.currentTimeMillis();
|
||||
_totalReceived += read;
|
||||
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
|
||||
_log.debug("Receiving " + _connectionId + " with " + read + " after " + (now-lastRead));
|
||||
//_log.debug("Receiving " + _connectionId + " with " + read + " after " + (now-lastRead));
|
||||
lastRead = now;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -215,4 +208,8 @@ public class TestSwarm {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSend() {
|
||||
return Boolean.valueOf(_context.getProperty("shouldSend", "false")).booleanValue();
|
||||
}
|
||||
}
|
BIN
apps/q/doc/client.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
26
apps/q/doc/diagrams.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q System Diagrams</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Diagrams</h1>
|
||||
Informal system diagrams of Q network, hubs and clients.
|
||||
<center>
|
||||
<hr>
|
||||
<img src="overall.jpg">
|
||||
<hr>
|
||||
<img src="client.jpg">
|
||||
<hr>
|
||||
<img src="hub.jpg">
|
||||
</center>
|
||||
<hr>
|
||||
|
||||
<address><a href="mailto:aum@mail.i2p">aum</a></address>
|
||||
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 14:06:02 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
BIN
apps/q/doc/hub.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
80
apps/q/doc/index.html
Normal file
@ -0,0 +1,80 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Quartermaster - I2P Distributed File Store</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<center>
|
||||
<h1>Quartermaster<br>an I2P Distributed File Store</h1>
|
||||
|
||||
<h3>STATUS<h3>
|
||||
<i>Whole new (incompatible) version currently in development;
|
||||
ETA for release approx 4-7 days;
|
||||
view screenshots <a href="screenshots.html">here</a>
|
||||
</i>
|
||||
<br>
|
||||
<hr>
|
||||
|
||||
<small>
|
||||
<a href="manual/index.html">User Manual</a> |
|
||||
<a href="spec/index.html">Protocol Spec</a> |
|
||||
<a href="metadata.html">Metadata Spec</a> |
|
||||
<a href="diagrams.html">Q Pr0n (diagrams)</a> |
|
||||
<a href="api/index.html">API Spec</a> |
|
||||
<a href="qnoderefs.txt">qnoderefs.txt</a> |
|
||||
Full Download |
|
||||
Updated jar
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Intro</h2>
|
||||
|
||||
Quartermaster, or Q for short, is a distributed file storage framework for I2P.
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Now features 'QSites' - the Q equivalent of Freenet freesites,
|
||||
static websites which are retrievable even if author is offline</li>
|
||||
<li>Easy web interface - interact with Q (and view/insert QSites)
|
||||
from your web browser</li>
|
||||
<li>Maximum expectations of content retrievability</li>
|
||||
<li>Content security akin to Freenet CHK and SSK keys</li>
|
||||
<li>Powerful, flexible search engine</li>
|
||||
<li>Comfortably accommodates both permanent and transient
|
||||
nodes without significant network disruption (for instance,
|
||||
no flooding of the I2P network with futile
|
||||
calls to offline nodes)</li>
|
||||
<li>Rapid query resolution, due to distributed catalogue
|
||||
mirroring which eliminates all in-network query traffic</li>
|
||||
<li>Modular, extensible architecture</li>
|
||||
<li>Simple interfaces for 3rd-party app developers</li>
|
||||
<li>Is custom-designed and built around I2P, so no duplication of
|
||||
I2P's encryption/anonymity features</li>
|
||||
<li>Simple XML-RPC interface for all inter-node communication, makes it easy to
|
||||
implement user-level clients in any language; also allows alternative
|
||||
implementations of core server and/or client nodes.</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Status</h2>
|
||||
|
||||
Q is presently under development, and a test release is expected soon.
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Architecture</h2>
|
||||
|
||||
Refer to the <a href="spec/index.html">Protocol Specification</a> for more information.
|
||||
|
||||
<hr>
|
||||
<!-- Created: Sat Mar 26 11:09:12 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 18:55:19 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
805
apps/q/doc/manual/index.html
Normal file
@ -0,0 +1,805 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q User/Programmer Manual</title>
|
||||
<style type="text/css">
|
||||
<!--
|
||||
td { vertical-align: top; }
|
||||
code { font-family: courier, monospace; font-weight: bold }
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="font-family: arial, helvetica, sans-serif">
|
||||
<center>
|
||||
<h1>Q User/Programmer Manual</h1>
|
||||
|
||||
<i>A brief but hopefully easy guide to installing and using the Q distributed file
|
||||
store within the I2P network</i>
|
||||
|
||||
<br><br>
|
||||
<i>(Return to <a href="../index.html">Q Homepage</a>)</i>
|
||||
<br>
|
||||
<br>
|
||||
<small>
|
||||
<a href="#intro">Introduction</a> |
|
||||
<a href="#checklist">Checklist</a> |
|
||||
<a href="#serverorclient">Server?orClient?</a> |
|
||||
<a href="#walkthrough">Walkthrough</a> |
|
||||
<a href="#server">Server Nodes</a> |
|
||||
<a href="#qmgr">About QMgr</a> |
|
||||
<a href="#contact">Contact us</a>
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<a name="intro"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>1. Introduction</h2>
|
||||
|
||||
<blockquote>
|
||||
Q is a distributed Peer2Peer file storage/retrieval network that aims to deliver optimal
|
||||
performance by respecting the properties of the I2P network.<br>
|
||||
<br>
|
||||
This manual serves as a 'walkthrough' guide, to take you through the steps from initial
|
||||
download, to everyday usage. It also provides information for the benefit of higher-level
|
||||
client application authors.
|
||||
</blockquote>
|
||||
|
||||
<a name="checklist"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>2. Preliminary Checklist</h2>
|
||||
|
||||
<blockquote>
|
||||
OK, we assume here that you've already cracked the tarball, and are looking at
|
||||
the distribution files.<br>
|
||||
<br>
|
||||
In order to get Q set up and running, you'll need:
|
||||
<ol>
|
||||
<li>An I2P router installed, set up and (permanently or transiently) running</li>
|
||||
<li>Your system shell set up with at the environment variables:
|
||||
<ul>
|
||||
<li><b>CLASSPATH</b> - this should include:
|
||||
<ul>
|
||||
<li>The regular I2P jar files and 3rd party support jar files (eg <b>i2p.jar</b>,
|
||||
<b>i2ptunnel.jar</b>, <b>streaming.jar</b>,
|
||||
<b>mstreaming.jar</b>, <b>jbigi.jar</b>)</li>
|
||||
<li>Apache's XML-RPC support jarfile - included in this Q distro as
|
||||
<b>xmlrpc.jar</b></li>
|
||||
<li>Aum's jarfile <b>aum.jar</b>, which includes Q and all needed support code</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>PATH</b> - your execution search path <b><i>must</i></b> include the directory
|
||||
in which your main java VM execution program (<b>java</b>, or on windows systems,
|
||||
<b>java.exe</b>) resides.<br>
|
||||
<b>NOTE</b> - if <b>java[.exe]</b> is not on your <b>PATH</b>, then Q <i>will
|
||||
not run</i>.</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</blockquote>
|
||||
|
||||
<a name="serverorclient"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>3. Q Server or Q Client?</h2>
|
||||
|
||||
<blockquote>
|
||||
Nearly everyone will want to run a <b>Q Client Node</b>.<br>
|
||||
<Br>
|
||||
It is only client nodes which provide users with full access to the Q network.<br>
|
||||
<br>
|
||||
However, if you have a (near-) permanently running I2P Router, and you're a kind and
|
||||
generous soul, you might <i>also</i> be willing to run a <b>Q Server Node</b> in addition
|
||||
to your <b>Q Client Node</b>.<br>
|
||||
<br>
|
||||
If you do choose to run a server node, you'll be expected to keep it running as near as
|
||||
possible to 24/7. While transience of client nodes - frequent entering and leaving the
|
||||
Q network - causes little or no disruption, transience of server nodes can significantly
|
||||
impair Q's usability for everyone, particularly if this transience occurs frequently amongst
|
||||
more than the smallest percentage of the server node pool.<br>
|
||||
<br>
|
||||
Until you're feeling well "settled in" with Q, your best approach is to just run a
|
||||
client node for now, and add a server node later when you feel ready.<br>
|
||||
</blockquote>
|
||||
|
||||
<a name="walkthrough"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>4. Q Walkthrough</h2>
|
||||
|
||||
<h3>4.1. Introduction</h3>
|
||||
|
||||
<blockquote>
|
||||
This chapter discusses the deployment and usage of a Q Client Node, and will take you
|
||||
through the steps of:
|
||||
<ol>
|
||||
<li>Double-checking that you've met the installation requirements</li>
|
||||
<li>Launching a Q Client Node</li>
|
||||
<li>Verifying that your Q Client Node is running</li>
|
||||
<li>If your node fails to launch, figuring out why</li>
|
||||
<li>Importing one or more noderefs into your node</li>
|
||||
<li>Observing that your node is discovering other nodes on the network</li>
|
||||
<li>Observing that your node is discovering content on the network</li>
|
||||
<li>Searching for items of content that match chosen criteria</li>
|
||||
<li>Retrieving stuff from the network</li>
|
||||
<li>Inserting stuff to the network</li>
|
||||
<li>Shutting down your client node</li>
|
||||
</ol>
|
||||
Setup and running of Q Server Nodes will be discussed in a later chapter.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.2. Verify Your Q Installation Is Correct</h3>
|
||||
|
||||
<blockquote>
|
||||
Ensure that all the needed I2P jarfiles, as well as <b>xmlrpc.jar</b> and
|
||||
Q's very own <b>aum.jar</b> are correctly listed in your <b>CLASSPATH</b> environment
|
||||
varaible, and your main java launcher is correctly listed in your <b>PATH</b> environment
|
||||
variable.<br>
|
||||
<br>
|
||||
Typically, you will likely copy the jarfiles <b>aum.jar</b> and <b>xmlrpc.jar</b>
|
||||
into the <b>lib/</b> subdirectory of your I2P router installation, along with all
|
||||
the other I2P jar files. Wherever you choose to put these files, make sure they're
|
||||
correctly listed in your <b>CLASSPATH</b>.
|
||||
<br>
|
||||
Also, you'll want to add execute permission to your <b>qmgr</b> (or <b>qmgr.bat</b>)
|
||||
wrapper script, and copy it into one of the directories listed in your <b>PATH</b>
|
||||
environment variable.<br>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.3. Get Familiar With qmgr</h3>
|
||||
|
||||
<blockquote>
|
||||
<b>qmgr</b> (or <b>qmgr.bat</b>) is a convenience wrapper script to save your
|
||||
sore fingers from needless typing. It's just a wrapper which passes arguments
|
||||
to the java command <b><code>java net.i2p.aum.q.QMgr</code></b><br>
|
||||
<br>
|
||||
You can verify you've set up qmgr correctly with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr help</pre></code></blockquote>
|
||||
This displays a brief summary of qmgr commands. On the other hand, the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr help verbose</pre></code></blockquote>
|
||||
floods your terminal window with a detailed explanation of all the qmgr commands
|
||||
and their arguments.<br>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.4. Running A Q Client Node For The First Time</h3>
|
||||
|
||||
<blockquote>
|
||||
Provided you've successfully completed the preliminaries, you can launch your
|
||||
Q Client Node with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr start</pre></code></blockquote>
|
||||
|
||||
All going well, you should have a Q Client Node now running in background.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.5. Verify that your Q Client Node is actually Running</h3>
|
||||
|
||||
<blockquote>
|
||||
After typed the <b>qmgr start</b> command, you will see little or no
|
||||
evidence that Q is actually running.<br>
|
||||
<br>
|
||||
You can test if the node is actually up by typing the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
If your Q Client Node is running, this <b>status</b> command should produce
|
||||
something like:
|
||||
<blockquote><code><pre>
|
||||
Pinging node at '/home/myusername/.quartermaster_client'...
|
||||
Node Ping:
|
||||
status=ok
|
||||
numPeers=0
|
||||
dest=-3LQaE215uIYwl-DsirnzXGQBI31EZQj9u~xx45E823WqjN5i2Umi37GPFTWc8KyislDjF37J7jy5newLUp-qrDpY7BZum3bRyTXo3Udl8a3sUjuu4qR5oBEWFfoghQiqDGYDQyJV9Rtz7DEGaKHGlhtoGsAYRXGXEa8a43T2llqZx2fqaXs~836g8t6sLZjryA5A9fpq98nE5lT0hcTalPieFpluJVairZREXpUiAUmGHG7wAIjF6iszXLEHSZ8Qc622Xgwy0d1yrPojL2yhZ64o05aueYcr~xNCiFxYoHyEJO3XYmkx~q-W-mzS3nn6pRevRda74MnX1~3fFDZ0u~OG6cLZoFkWgnxrwrWGFUUVMR87Yz251xMCKJAX6zErcoGjGFpqGZsWxl4~yq7yfkjPnq3GuTxp2cB75bRAOZRIAieqBOVJDEodFYW5amCinu4AxYE7G1ezz4ghqHFe~0yaAdO74Q1XoUny138YT6P33oNOOlISO1cAAAA
|
||||
uptime=4952
|
||||
load=0.0
|
||||
id=6LVZ9-~GgJJ52WUF1fLHt3UnH50TnXSoPQXy7WZ4GA=
|
||||
numLocalItems=47
|
||||
numRemoteItems=2173</pre></code></blockquote>
|
||||
|
||||
If you see something like this, then smile, because Q is now up on your system.<br>
|
||||
<br>
|
||||
If the node launch failed, you might see something like:
|
||||
<blockquote><code><pre>
|
||||
Pinging node at '/home/myusername/.quartermaster_client'...
|
||||
java.io.IOException: Connection refused
|
||||
at org.apache.xmlrpc.XmlRpcClient$Worker.execute(Unknown Source)
|
||||
at org.apache.xmlrpc.XmlRpcClient.execute(Unknown Source)
|
||||
at net.i2p.aum.q.QMgr.doStatus(QMgr.java:310)
|
||||
at net.i2p.aum.q.QMgr.execute(QMgr.java:813)
|
||||
at net.i2p.aum.q.QMgr.main(QMgr.java:869)
|
||||
Failed to ping node</pre></code></blockquote>
|
||||
This indicates that your Q client node has either crashed, or failed to launch in the
|
||||
first place.<br>
|
||||
<br>
|
||||
If you're having trouble like this, you might like to try running your Q client node
|
||||
in foreground, instead of spawning it off into background.<br>
|
||||
<br>
|
||||
The command to run a Q client node in foreground is:
|
||||
<blockquote><code><pre>
|
||||
qmgr foreground</pre></code></blockquote>
|
||||
You should see some meaningless startup messages, and no return to your shell prompt.<br>
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.6. Diversion - Q Storage Directories</h3>
|
||||
|
||||
<blockquote>
|
||||
By default, when you run a Q Client Node, it creates a datastore directory tree
|
||||
at <b>~/.quartermaster_client</b>. (Windows users note - you'll find this directory
|
||||
wherever your user home directory is - this depends on what version of Windows
|
||||
you have installed).<br>
|
||||
<br>
|
||||
Within this directory tree, you should see a file called <b>node.log</b>, which
|
||||
will contain various debug log messages, and can help you to rectify any problems
|
||||
with your Q installation. If you hit a wall and can't rectify the problems
|
||||
yourself, you should send this file to the Q author (aum).<br>
|
||||
<br>
|
||||
It's possible to run your Q node from another directory, by passing that directory
|
||||
as a <b>-dir <path></b> argument to the
|
||||
<b>qmgr</b> <b>start</b>, <b>foreground</b> and <b>stop</b>
|
||||
commands. See <b>qmgr help verbose</b> for more information.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.7. Importing a Noderef</h3>
|
||||
|
||||
<blockquote>
|
||||
Note from the prior <b>qmgr status</b> command the line:
|
||||
<blockquote><code><pre>
|
||||
numPeers=0</pre></code></blockquote>
|
||||
This means that your Q client node is running standalone, and doesn't have any contact
|
||||
with any Q network. As such, your node is effectively useless. We need to hook up
|
||||
your node with other nodes in the Q network.<br>
|
||||
<br>
|
||||
Q doesn't ship with any means for new client nodes to automatically connect to any Q
|
||||
server nodes. This is deliberate.<br>
|
||||
<br>
|
||||
In all likelihood, there will be one 'main' Q network running within I2P, largely
|
||||
based around the author's own Q server node, and most people will likely want to
|
||||
use this Q network. But the author doesn't want to stop other people running their
|
||||
own private Q networks, for whatever purpose has meaning for them.
|
||||
|
||||
<blockquote><i><small>
|
||||
<hr>
|
||||
This is especially relevant for Q as opposed to Freenet. With Freenet, there's
|
||||
no way for a user to know of the existence of any item of content without
|
||||
first being given its 'key'. However, since Q works with published catalogs,
|
||||
any user can know everything that's available on a Q network, which might
|
||||
not be desirable to those wishing to share content in a private situation.<br>
|
||||
<Br>
|
||||
The Q author anticipates, and warmly supports, people running their own
|
||||
private Q networks within I2P, in addition to accessing the mainstream
|
||||
'official' Q network.<br>
|
||||
<br>
|
||||
The way Q is designed and implemented, there is no way for anyone, including
|
||||
Q's author, to know of the existence of anyone else's private Q network.
|
||||
It is beyond the author's control, (and thus arguably the author's
|
||||
legal responsibility), what private Q networks people set up, and what
|
||||
kind of content is trafficked on these networks. This claim of plausible
|
||||
deniability on the part of Q's author parallels that of a hardware retailer
|
||||
denying responsibility for what people do with tools that they purchase.
|
||||
<hr>
|
||||
</small>
|
||||
</i></blockquote>
|
||||
|
||||
Ok, getting back on topic - your brand new virgin Q client node is useless and lonely,
|
||||
and desperately needs some Q server nodes to talk to. So let's hook up your node to
|
||||
the mainstream Q network.<br>
|
||||
<br>
|
||||
You'll need to get one or more 'noderefs' for Q server nodes.<br>
|
||||
<br>
|
||||
There's nothing fancy about a Q noderef. It's just a regular I2P 'destination', with
|
||||
which your Q Client Node can connect with a Q Server Node.<br>
|
||||
<br>
|
||||
A 'semi-official' list of noderefs for the mainstream Q network can be downloaded
|
||||
from the url: <a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.<br>
|
||||
<br>
|
||||
Download this file, save it as (say) <b>qnoderefs.txt</b>. (Alternatively, if you're
|
||||
wanting to subscribe into a private Q network, then get a noderef for at least one
|
||||
of that network's server nodes from someone on that network who trusts you).<br>
|
||||
<br>
|
||||
Import these noderefs into your Q client node via the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr addref qnoderefs.txt</pre></code></blockquote>
|
||||
If all goes well, you should see no output from this command, or (possibly) a brief
|
||||
line or two suggesting success.<br>
|
||||
<br>
|
||||
Your client node is now subscribed into the Q network of your choice. Verify this
|
||||
with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
In the output from that command, you should see the <b>numPeers=</b> line showing at least
|
||||
1 peer.<br>
|
||||
<br>
|
||||
If there is more than one Q Server Node on the Q network you've just subscribed to,
|
||||
then your local node should sooner or later discover all these server nodes, and
|
||||
the <b>numPeers</b> value should increase over time.<br>
|
||||
<br>
|
||||
<blockquote>
|
||||
<hr>
|
||||
While Q is in its early development and testing stages, the author may abdicate
|
||||
the mainstream Q network, and publish nodrefs for a whole new mainstream Q network.
|
||||
This will especially happen if the author makes any substantial changes to the
|
||||
inter-node protocol, and/or releases incompatible new versions of Q client/server
|
||||
nodes. Remember that
|
||||
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a> will
|
||||
serve as the authoritative source for noderefs for the mainstream Q network within
|
||||
the mainstream I2P network.
|
||||
<hr>
|
||||
</blockquote>
|
||||
|
||||
When your client node gets its noderefs to a Q network, it will periodically,
|
||||
from then on, retrieve differential peer list and catalog updates from servers
|
||||
it knows about.<br>
|
||||
<br>
|
||||
Even if you only feed your client just one ref for a single server node, it will
|
||||
in time discover all other operating server nodes on that Q network, and will
|
||||
build up a full local catalog of everything that's available on that Q network.<br>
|
||||
<br>
|
||||
Provided that your client is running ok, and has been fed with at least one
|
||||
ref for a live Q network that contains content, then over time, successive:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
commands should report increasing values in the fields:
|
||||
<ul>
|
||||
<li><b>numPeers</b> - number of peers this client node knows about</li>
|
||||
<li><b>numLocalItems</b> - number of locally stored content items, ie items
|
||||
which you have either inserted to, or retrieved from, your client node</li>
|
||||
<li><b>numRemoteItems</b> - number of unique data items which are available
|
||||
on remote server nodes in the Q network, and which can be retrieved through
|
||||
your local client node.</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>
|
||||
<hr>
|
||||
|
||||
<h4>4.7.1. One Big Warning</h4>
|
||||
|
||||
If you are participating in more than one distinct Q network, then <b>do not</b>
|
||||
insert noderefs for different networks into the same running instance of a
|
||||
local Q client, unless you don't plan on inserting content via that client.<br>
|
||||
<Br>
|
||||
For instance, let's say you are participating in two different Q networks:
|
||||
<ul>
|
||||
<li>The 'mainstream' Q netowrk</li>
|
||||
<li>A secret Q network - "My friends' teen angst diaries"</li>
|
||||
</ul>
|
||||
If you get a noderef for both these networks, and insert both of these into the
|
||||
same running Q client node, then this local client node will be transparently
|
||||
connected to both networks.<br>
|
||||
<br>
|
||||
If you only ever plan on retrieving content, and never inserting content, this
|
||||
won't be a problem, except that you won't be able to tell which content
|
||||
resides on the mainstream Q network, and which resides in the secret Q network.<br>
|
||||
<Br>
|
||||
The big problem arises from inserting content. Whenever you insert data through this
|
||||
'contaminated'
|
||||
Q client node, this node picks 3 different servers to which upload a copy of this
|
||||
data. You won't have any control over whether the data gets inserted to the mainstream
|
||||
Q network, or your secret Q network. You might insert something sensitive, intending it
|
||||
to go only into the secret Q network, where in fact it also ends up in the mainstream
|
||||
network, with consequences you might not want.
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.8. Content Data and Metadata</h3>
|
||||
|
||||
<blockquote>
|
||||
Whenever content gets stored on Q, it is actually stored as two separate items:
|
||||
<ul>
|
||||
<li>The <b>raw data</b> - whether a text file, or the raw bytes of image files,
|
||||
audio files etc</li>
|
||||
<li>The <b>metadata</b>, which contains human-readable and machine-readable
|
||||
descriptions of the data</li>
|
||||
</ul>
|
||||
Metadata consists of a set of <b>category=value</b> pairs.<br>
|
||||
<br>
|
||||
Confused yet? Don't worry, I'm confused as well. Let's illustrate this with an
|
||||
example of metadata for an MP3 audio recording:
|
||||
<ul>
|
||||
<li>title=Fight_Last_Thursday.mp3</li>
|
||||
<li>type=audio</li>
|
||||
<li>mimetype=audio/mpeg</li>
|
||||
<li>abstract=upcoming single recorded in our garage last April</li>
|
||||
<li>keywords=grunge,country,indie</li>
|
||||
<li>artist=Ring of Fire</li>
|
||||
<li>size=4379443</li>
|
||||
<li>contact=ring-of-fire@mail.i2p</li>
|
||||
<li>key=blah37blah24-yada23hfhyada</li>
|
||||
</ul>
|
||||
All metadata categories are optional. In fact, you can insert content with no metadata
|
||||
at all.<br>
|
||||
<br>
|
||||
If you fail to provide metadata when inserting an item, a blank set of metadata will
|
||||
be created with at least the following categories:
|
||||
<ul>
|
||||
<li><b>key</b> - the derived key, under which the item will later be retrievable
|
||||
by yourself and others</li>
|
||||
<li><b>title</b> - if not provided at insert time, this will be set to the key</li>
|
||||
<li><b>size</b> - size of the item's raw data, in bytes</li>
|
||||
</ul>
|
||||
Within Q, there is a convention to supply a minimal amount of metadata. While this
|
||||
is not expected or enforced, including all these categories is most strongly
|
||||
recommended. These core categories are:
|
||||
<ul>
|
||||
<li><b>title</b> - a meaningful title for the data item, consisting only of characters
|
||||
which are legal in filenames on all platforms, and which ends with a file extension.</li>
|
||||
<li><b>type</b> - one of a superset of eMule classifiers, such as:
|
||||
<ul>
|
||||
<li><b>text</b> - plain text</li>
|
||||
<li><b>html</b> - HTML content</li>
|
||||
<li><b>image</b> - content is in an image format, such as .png, .jpg, .gif etc</li>
|
||||
<li><b>audio</b> - content is an audio sample, such as .ogg, .mp3, .wav etc</li>
|
||||
<li><b>video</b> - due to the sheer size of video files, and Q's present design,
|
||||
it's unlikely people will be inserting video content anytime soon (unless it's
|
||||
very short)</li>
|
||||
<li><b>archive</b> - packed file collections, such as .tar.gz, .zip, .rar etc</li>
|
||||
<li><b>misc</b> - content does not fit into any of the above categories</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>mimetype</b> - not as important as the <b>type</b> category, but providing
|
||||
this category in your metadata is still strongly encouraged. Value for this category
|
||||
should be one of the standard mimetypes, eg <b>text/html</b>, <b>audio/ogg</b> etc.</li>
|
||||
<li><b>abstract</b> - a short description (<255 characters), intended for human reading</li>
|
||||
<li><b>keywords</b> - a comma-separated list of keywords, intended for
|
||||
machine-readability, should be all lowercase, no spaces</li>
|
||||
</ul>
|
||||
Note that you can supply extra metadata categories in addition to the above, and that
|
||||
people searching for content can search on these extra categories if they know about
|
||||
them.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.9. Searching For Content</h3>
|
||||
|
||||
<blockquote>
|
||||
As mentioned earlier - in constrast with Freenet, local Q nodes build up a complete
|
||||
catalog of all available content on whatever Q network they are connected to.<br>
|
||||
<br>
|
||||
This is a design decision, based on the choice to eliminate query traffic.<br>
|
||||
<br>
|
||||
The author hopes that this will result in a distributed storage network with a
|
||||
high retrievability guarantee, in contrast with freenet which offers no such
|
||||
guarantee.<br>
|
||||
<br>
|
||||
With Freenet, you only ever know of the existence of something if someone tells
|
||||
you about it.<br>
|
||||
<br>
|
||||
But with Q, your local client node builds up a global catalog of everything that's
|
||||
available within the whole network.<br>
|
||||
<br>
|
||||
The QMgr client has a command for searching your Q client node:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m category1=pattern1 category2=pattern2 ...</pre></code></blockquote>
|
||||
For example:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m type=audio artist=Mozart keywords=symphony</pre></code></blockquote>
|
||||
or:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m type=text title="bible|biblical|(Nag Hammadi)" keywords="apocrypha|Magdalene"</pre></code></blockquote>
|
||||
As implied in the latter example, search patterns are regular expressions. This example will
|
||||
locate all text items, whose <b>title</b> metadata category contains one of <b>bible</b>, <b>biblical</b> or <b>Nag Hammadi</b>, <i>and</i> whose <b>keywords</b> category contains either
|
||||
or both the words <b>apocrypha</b> or <b>Magdalene</b>.<br>
|
||||
<br>
|
||||
Please use the search function carefully, otherwise (if and when Q usage grows) you
|
||||
could be inundated with thousands or even millions of entries.<br>
|
||||
<br>
|
||||
If a search turns up nothing, qmgr will simply exit. But if it turns up one or more items,
|
||||
it will the items out one at a time, with the key first, then each metadata entry
|
||||
on an indented line following.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.10. Retrieving Content</h3>
|
||||
|
||||
<blockquote>
|
||||
Now, we're actually going to retrieve something.<br>
|
||||
<br>
|
||||
Presumably, after following the previous section, you will have seen one or more search
|
||||
results come up, with the 'keys' under which the items can be accessed.<br>
|
||||
<br>
|
||||
Now, choose one of the keys, preferably for a short text item. Try either of the following
|
||||
commands:
|
||||
<blockquote><code><pre>
|
||||
qmgr get <keystring> something.txt</pre></code></blockquote>
|
||||
<i>or</i>:
|
||||
<blockquote><code><pre>
|
||||
qmgr get <keystring> > something.txt</pre></code></blockquote>
|
||||
(both have the same effect - the first one explicitly writes to the named file, the second
|
||||
one dumps the raw data to stdout, which we shell-redirect into the file.<br>
|
||||
<br>
|
||||
<b><i>Note - redirection of fetched data to a file via shell is not working at present. Use only
|
||||
the first form till we fix the bug.</i></b>
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.11. Inserting Content</h3>
|
||||
|
||||
<blockquote>
|
||||
Our last example in this walkthrough relates to inserting content.<br>
|
||||
<br>
|
||||
Firstly, create a small text file with 2-3 lines of text, and save it as (say)
|
||||
myqinsert.txt.<br>
|
||||
<br>
|
||||
Now, think of some metadata to insert along with the file. Or, you can just use
|
||||
the set:
|
||||
<blockquote><code><pre>
|
||||
type=text
|
||||
keywords=test
|
||||
abstract=My simple test of inserting into Q
|
||||
title=myqinsert.txt</pre></code></blockquote>
|
||||
|
||||
Now, let's insert the file. Ensure your Q client node is running, then type:
|
||||
<blockquote><code><pre>
|
||||
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
|
||||
abstract="My simple test of inserting into Q"</pre></code></blockquote>
|
||||
If all went well, this command should produce half a line of gibberish, followed
|
||||
immediately by your shell prompt, eg:
|
||||
<blockquote><code><pre>
|
||||
aRoFC~9MU~pM2C-uCTDBp5B7j79spFD8gUeu~BNkUf0=<b>$</b>
|
||||
</pre></code></blockquote>
|
||||
The '$' at the end is your shell prompt, and all the characters before it are the 'key'
|
||||
which was derived from the content you just inserted.<br>
|
||||
<br>
|
||||
To avoid the hassle of copying/pasting the key, you could just add output redirection
|
||||
to the above command, eg:
|
||||
<blockquote><code><pre>
|
||||
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
|
||||
abstract="My simple test of inserting into Q" \
|
||||
> myqinsert.key</pre></code></blockquote>
|
||||
This will cause the generated key to be written safe and sound into the file
|
||||
<b>myqinsert.key</b>.<br>
|
||||
<br>
|
||||
You can verify that this insert worked by a 'get' command, as in:
|
||||
<blockquote><code><pre>
|
||||
qmgr get `cat myqinsert.key` somefilename.ext</pre></code></blockquote>
|
||||
(Note that this won't work on windows because the DOS shell is irredeemably brain-damaged. If
|
||||
you're using Windows, you <b>will</b> have to cut/paste the key.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.12. Shutting Down your Node</h3>
|
||||
|
||||
<blockquote>
|
||||
If you've worked through to here, then congratulations! You've got your Q Client Node set up
|
||||
and working, and ready to meet all your distributed file storage and retrieval needs.<br>
|
||||
<br>
|
||||
You can leave your client node running 24/7 if you want. In fact, we recommend you keep your
|
||||
client node running as much of the time as possible, so that you get prompt catalog updates,
|
||||
and can more quickly stay in touch with new content.<br>
|
||||
<br>
|
||||
However, if you need to shut down your node, the command for doing this is:
|
||||
<blockquote><code><pre>
|
||||
qmgr stop</pre></code></blockquote>
|
||||
This command will take a while to complete (since the node has to wait for the I2P
|
||||
java shutdown hooks to complete before it can rest in peace). But once your node is
|
||||
shut down, you can start it up again at any time and pick up where you left off.
|
||||
</blockquote>
|
||||
|
||||
<a name="server"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>5. Running a Q Server Node</h2>
|
||||
|
||||
<h3>5.1. Introduction</h3>
|
||||
<blockquote>
|
||||
This section describes the requirements for, and procedures involved with, running
|
||||
a Q Server Node.<br>
|
||||
<br>
|
||||
We'll use a similar 'walkthrough' style to that which we used in the previous section
|
||||
on client nodes.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.2. Requirements and Choices</h3>
|
||||
<blockquote>
|
||||
Running a Q server is a generous thing to do, and helps substantially with making
|
||||
Q work at its best for everyone. However, please do make sure you can meet some
|
||||
basic requirements:
|
||||
<ul>
|
||||
<li>You are running a permanent (24/7) I2P Router, on a box with at least (say)
|
||||
98% uptime.</li>
|
||||
<li>You have a little bandwidth to spare, and don't mind the extra memory, disk and
|
||||
CPU-usage footprint of running a fulltime Q server node</li>
|
||||
<li>You have already been able to successfully run a Q client node.</li>
|
||||
</ul>
|
||||
Also, please decide whether you want your server node to contribute to the mainstream
|
||||
Q network, or whether you want to create your own private Q network, or join someone
|
||||
else's private network. Your contribution will be most appreciated, though, if you
|
||||
can run a server within the mainstream Q network.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.3. Starting Your Server Node</h3>
|
||||
|
||||
<blockquote>
|
||||
Starting up a Q Server node is very similar to starting up a Q client node, except
|
||||
that with the qmgr command line, you must put the keyword arg <b>server</b> before the
|
||||
command word. So the command is:
|
||||
<blockquote><code><pre>
|
||||
qmgr server start</pre></code></blockquote>
|
||||
Similar to Q client nodes, you can check the status of a running Q server node with
|
||||
the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr server status</pre></code></blockquote>
|
||||
(Note that this command will take longer to complete than with client nodes, because
|
||||
the communication passes through a multi-hop I2P tunnel, rather than just through
|
||||
localhost TCP).<br>
|
||||
<br>
|
||||
If the status command succeeds, then you'll know your new Q Server Node is happily
|
||||
running in background.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.4. Joining A Q Network</h3>
|
||||
|
||||
<blockquote>
|
||||
When a Q Server node starts up for the first time, it is in a private network
|
||||
all by itself.<br>
|
||||
<br>
|
||||
If you want to link your server into an existing Q network, you'll have to add a
|
||||
noderef for at least one other server on that network. The command to do this
|
||||
is similar to that for subscribing a client node to a network:
|
||||
<blockquote><code><pre>
|
||||
qmgr server addref <noderef-file></pre></code></blockquote>
|
||||
where <noderef-file> is a file into which you've saved the noderef for
|
||||
the network you want to join.
|
||||
<blockquote>
|
||||
<hr><i><small>
|
||||
Recall from the section on client nodes that the authoritative noderefs
|
||||
for the mainstream Q network can be downloaded from
|
||||
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.
|
||||
</small></i><hr>
|
||||
</blockquote>
|
||||
After you've added the noderef, subsequent <b>qmgr server status</b> commands
|
||||
should show <b>numPeers</b> having a value of at least 1 (and growing, as more
|
||||
server nodes come online in the mainstream Q network.)
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.5. Private Networks - Exporting Your Server's Noderef</h3>
|
||||
|
||||
<blockquote>
|
||||
If you're planning to start your own private Q network, and want to include other
|
||||
server operators in this network, then you'll have to export your server's noderef
|
||||
and make it available to the others you want to invite into your network.<br>
|
||||
<br>
|
||||
The command to export your Q Server noderef is:
|
||||
<blockquote><code><pre>
|
||||
qmgr server getref <noderef-file></pre></code></blockquote>
|
||||
This will extract the <i>I2P Destination</i> of your running server node, and
|
||||
write it into <noderef-file>. You can then privately share this file with
|
||||
others who you want to invite into your private network. Each recipient of
|
||||
this file will do a <b>qmgr server addref <noderef-file></b> command
|
||||
to import your ref into their servers.<br>
|
||||
<br>
|
||||
Don't forget that if you're running, or participating in, a private Q network, then
|
||||
you'll need to run a separate client for accessing this network, separate from any
|
||||
mainstream Q network client you may already be running.<br>
|
||||
<br>
|
||||
To start this extra client, you'll have to choose a directory where you want this
|
||||
client to reside, a port number you want your client to listen on locally for
|
||||
user commands, and run the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr -dir /path/to/my/new/client -port <portnum> start</pre></code></blockquote>
|
||||
You need the <b>-port <portnum></b> command, because otherwise it'll fail
|
||||
to launch (if you already have a client node running off the mainstream Q network).<br>
|
||||
<br>
|
||||
This will create, and launch, a new instance of a Q client, accessing your private
|
||||
Q network. Don't forget to import your server's noderef into this client. Also,
|
||||
note that you'll have to use this same <b>-port <portnum></b> argument when
|
||||
doing any operation on this client instance, such as get, put, status, search.
|
||||
|
||||
</blockquote>
|
||||
|
||||
<a name="qmgr"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>6. About the qmgr Utility</h2>
|
||||
|
||||
qmgr (or, to people fluent in Java, <b>net.i2p.aum.q.QMgr</b>), is just one simple
|
||||
Q client application, that happens to be bundled in with the Q distro.<br>
|
||||
<br>
|
||||
It is by no means the only, or even main facility for accessing the Q network. We
|
||||
anticipate that folks will write all manner of client apps, including fancy GUI
|
||||
apps.<br>
|
||||
<br>
|
||||
Anyway, qmgr does give you a rudimentary yet workable client for basic access
|
||||
to the Q network. Until fancy apps get written, qmgr will have to do.<br>
|
||||
<br>
|
||||
Don't forget that qmgr has very detailed inbuilt help. Run:
|
||||
<blockquote><code><pre>
|
||||
qmgr help</pre></code></blockquote>
|
||||
for a quick help summary, or:
|
||||
<blockquote><code><pre>
|
||||
qmgr help verbose</pre></code></blockquote>
|
||||
for the 'War and Peace' treatise.<br>
|
||||
<br>
|
||||
<blockquote><hr>
|
||||
One crucial concept to remember with qmgr is that client and server node instances
|
||||
are uniquely identified by the directories at which they reside. If you are running
|
||||
multiple server and/or client instances, you can specify an instance with the
|
||||
<b>-dir <dirpath></b> option - see the help for details.
|
||||
<hr></blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
One last note - we strongly discourage any writing of client apps that spawn a qmgr
|
||||
process, pass it arguments and parse its results. This is most definitely a path to
|
||||
pain, since qmgr's shell interface is subject to radical change at any time without
|
||||
notice.<br>
|
||||
<br>
|
||||
qmgr is for human usage, or at most, inclusion in init/at/cron scripts. Please respect
|
||||
this.<br>
|
||||
<br>
|
||||
If you want to write higher-level clients, your best course of action is to use the
|
||||
official client api library, which we anticipate will have versions available in
|
||||
Java, Python, Perl and C++. If you want to write in another language, such as
|
||||
OCaml, Scheme etc, then the existing api lib implementations should serve as an excellent
|
||||
reference to support you in writing a native port for your own language.
|
||||
|
||||
<a name="contact"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>8. Contacting the Author</h2>
|
||||
|
||||
I am <b>aum</b>, and can be reached as <b>aum</b> on in-I2P IRC networks, and also
|
||||
at the in-I2P email address of <b>aum@mail.i2p</b>.<br>
|
||||
<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<center>
|
||||
Return to <a href="../index.html">Q Homepage</a><br>
|
||||
<br>
|
||||
<small>
|
||||
<a href="#intro">Introduction</a> |
|
||||
<a href="#checklist">Checklist</a> |
|
||||
<a href="#serverorclient">Server?orClient?</a> |
|
||||
<a href="#walkthrough">Walkthrough</a> |
|
||||
<a href="#server">Server Nodes</a> |
|
||||
<a href="#qmgr">About QMgr</a> |
|
||||
<a href="#contact">Contact us</a>
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Created: Fri Apr 1 11:03:27 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Sun Apr 3 20:06:53 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
23
apps/q/doc/manual/notes
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
rise on each hit:
|
||||
|
||||
dy = (1 - y) / kRise
|
||||
|
||||
fall after each time unit:
|
||||
|
||||
dy = y / kFall
|
||||
|
||||
fall after time dt:
|
||||
|
||||
dy = - y ** - (dt / kFall)
|
||||
|
||||
after the next hit:
|
||||
|
||||
y = y - y ** (- dt / kFall) + (1 - y) / kRise
|
||||
|
||||
first attempt at a load measurement algorithm:
|
||||
- kFall is an arbitrary constant which dictates decay rate of load
|
||||
in the absence of hits
|
||||
- kRise is another constant which dictates rise of load with each hit
|
||||
- dt is the time between each hit
|
||||
|
372
apps/q/doc/metadata.html
Normal file
@ -0,0 +1,372 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q Metadata Specification</title>
|
||||
|
||||
<style type="text/css">
|
||||
<!--
|
||||
td { vertical-align: top; }
|
||||
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
|
||||
-->
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Metadata Specification</h1>
|
||||
|
||||
<h2>1. Introduction</h2>
|
||||
|
||||
This document lists the standard metadata keys for Q data items,
|
||||
discussing the rules of metadata insertion, processing and validation.<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>1.1. Definitions</h3>
|
||||
|
||||
To avoid confusions in terminology, this document will strictly abide the following definitions:
|
||||
<br>
|
||||
<br>
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Term</td>
|
||||
<td>Definition</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>key</code></td>
|
||||
<td>A metadata category name, technically a <code>key</code> as the word is used with
|
||||
Java <code>Hashtable</code> and Python <code>dict</code> objects.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>uri</code></td>
|
||||
<td>A Uniform Resource Indicator for an item of content stored within the Q network.<br>
|
||||
Q URIs have the form: <code>Q:<basename>[,<cryptoKey>][<path>]</code>
|
||||
<br>
|
||||
<br>
|
||||
Some examples:
|
||||
<ul>
|
||||
<li><code>Q:fhvnr3HFSK234khsf90sdh42fsh</code> (a plain hash uri, no cryptoKey)</li>
|
||||
<li><code>Q:e54fhjeo39schr2kcy4osEH478D/files/johnny.mp3</code> (a secure space URI,
|
||||
no cryptoKey)</li>
|
||||
<li><code>Q:vhfh4se987WwfkhwWFEwkh3234S,47fhh2dkhseiyu</code> (a plain hash URI, with
|
||||
a cryptoKey)</li>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>basename</code></td>
|
||||
<td>The basic element of a Q uri. This will be a base64-encoded hash - refer below to
|
||||
URI calculation procedures</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>cryptoKey</code></td>
|
||||
<td>An optional session encryption key for the stored data, encoded as base64.
|
||||
This affords some protection to server node operators, and gives them a level
|
||||
of plausible deniability for whatever gets stored in their server's
|
||||
datastore without their direct human awareness.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>Whever an item of content is inserted in <code>secure space</code> mode, this path
|
||||
serves as a pseudo-pathname, and is conceptually similar to the <code>path</code>
|
||||
component in (for example) standard HTTP URLs
|
||||
<code>http://<domainname>[:<port>][<path>]</code>, such as
|
||||
<code>http://slashdot.org/faq/editorial.shtml</code> (whose <code>path</code>
|
||||
is <code>/faq/editorial.shtml</code>).<br>
|
||||
<br>
|
||||
Paths, if not empty, should contain a leading slash ("/").
|
||||
If an application specifies a non-empty <code>path</code> that doesn't begin with a
|
||||
leading '/', a '/' will be automatically prepended by the receiving node.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>plain hash</code></td>
|
||||
<td>A mode of inserting items, whereby the security of the resulting URI comes from
|
||||
computing the URI from a hash of the item's data and metadata (and imposing a
|
||||
mathematical barrier against spoofing content under a given URI). Corresponds to
|
||||
Freenet's <code>CHK@</code> keys.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>secure space</code></td>
|
||||
<td>A mode of inserting items where the security of the URI is based not on a hash of the
|
||||
item's data and metadata (as with <code>plain hash</code> mode),
|
||||
but on the <code>privateKey</code> provided by the
|
||||
application, and a content signature created from that private key.
|
||||
Corresponds to Freenet's <code>SSK@</code> keys. Within a secure space, you
|
||||
can insert any number of items under different pseudo-pathnames (as is the case
|
||||
with Freenet SSK keys).
|
||||
</li>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>2.1. Keys Inserted By Application Before sending <code>putItem</code> RPCs</h3>
|
||||
|
||||
As the heading suggests, this is a list of metadata keys which should be inserted by a
|
||||
Q application prior to invoking a <code>putItem</code> RPC on the local Q client node.<br>
|
||||
<br>
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Key</td>
|
||||
<td>Data Type</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>title</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended. A free-text short description of the item,
|
||||
should be less than 80 characters. The idea is that applications should
|
||||
support a 'view' of catalogue data that shows item titles. (Prior Q convention of
|
||||
titles expressed as valid filename syntax has been abandoned).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended.
|
||||
A virtual 'pathname' for the item, which should be in valid *nix
|
||||
absolute pathname syntax (beginning with '/', containing no '//', consisting
|
||||
only of alphanumerics, '-', '_', '.' and '/'.<br>
|
||||
<br>
|
||||
In Q web interfaces, the <code>filename</code> component of this path will
|
||||
serve as the recommended filename when downloading/saving the item.<br>
|
||||
<br>
|
||||
If the application also provides a
|
||||
<code>privateKey</code> key, the path
|
||||
is used in conjunction with the private key to generate <code>publicKey</code>
|
||||
and <code>signature</code> keys (see below), and ultimately the final <code>uri</code>
|
||||
under which the item can be retrieved by others.<br>
|
||||
<br>
|
||||
Refer also to <code>mimetype</code> below.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>encrypt</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. If this key is present, and has a value "1", "yes" or "true",
|
||||
this indicates that the application wishes the data to be stored on servers in
|
||||
encrypted form.<br>
|
||||
<br>
|
||||
If this key is present and set to a positive value, the Q node, on receiving the
|
||||
<code>putItem</code> RPC, will:
|
||||
<ol>
|
||||
<li>Generate a random symmetric encryption key</li>
|
||||
<li>Encrypt the item's data using this encryption key</li>
|
||||
<li>Delete the <code>encrypt</code> key from the metadata</li>
|
||||
<li>Enclose a base64 representation of this encryption key in the RPC response
|
||||
it sends back to the application (embedded in the <code>uri</code></li>
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>type</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended. A standard ed2k specifier, one of <code>text html image
|
||||
audio video archive other</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mimetype</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but moderately recommended. Mimetype designation of data, eg <code>text/html</code>,
|
||||
<code>image/jpeg</code> etc. If not specified, an attempt will be made to guess
|
||||
a mometype from the value of the <code>path</code> key. If this attempt fails, then
|
||||
this key will be set to <code>application/x-octet-stream</code> by the node receiving
|
||||
the <code>putItem</code> RPC.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>keywords</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but moderately recommended.
|
||||
A set of keywords, under which the inserting app would like this item to be
|
||||
discoverable. Keywords should be entirely lower case and comma-separated. Content
|
||||
inserts should consider keywords carefully, and only use space characters inside
|
||||
keywords when necessary (eg, for flagging a distinctive phrase containing very
|
||||
common words).</td>
|
||||
<tr>
|
||||
<td><code>privateKey</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. A Base64-encoded signing private key, in cases where the application wishes
|
||||
to insert an item in <code>signed space</code> mode. This can be accompanied by another key,
|
||||
<code>path</code>, indicating a 'path' within the signed space. If 'path'
|
||||
is not given, it will default to '/'.<br>
|
||||
<br>
|
||||
Either way, when a node receives a
|
||||
<code>putItem</code> RPC containing a <code>privateKey</code> in its metadata,
|
||||
it removes this key and replaces it with <code>publicKey</code> and
|
||||
<code>signature</code>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. The virtual pathname, within signed space, under which to store the item.
|
||||
This gets ignored and deleted unless the application also provides a
|
||||
<code>privateKey</code> as well. But if the private key is given, the path
|
||||
is used in conjunction with the private key to generate <code>publicKey</code>
|
||||
and <code>signature</code> keys (see below).<br>
|
||||
<code>path</code> should be a 'unix-style pathname', ie, containing only slashes
|
||||
as (pseudo) directory delimiters, and alphanumeric, '-', '_' and '.' characters,
|
||||
and preferably ending in a meaningful file extension such as <code>.html</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>expiry</code></td>
|
||||
<td>int</td>
|
||||
<td>Unixtime at which the inserted item should expire. When this expiry time
|
||||
is reached, the item won't necessarily be deleted straight away, but may
|
||||
be deleted whenever a node's data store is full.<br>
|
||||
<br>
|
||||
If this is not provided, it will default to a given duration according to
|
||||
the client node's configuration.<br>
|
||||
<br>
|
||||
If it is provided, by an application, then the client node will transparently
|
||||
generate the required 'rent payment' before caching the data item and uploading
|
||||
it to servers.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>2.2. Keys Inserted By Node Upon Receipt Of <code>putItem</code> RPC</h3>
|
||||
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Key</td>
|
||||
<td>Data Type</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>size</code></td>
|
||||
<td>Integer</td>
|
||||
<td>Size of the data to be inserted, in bytes.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dataHash</code></td>
|
||||
<td>String</td>
|
||||
<td>base64-encoded SHA256 hash of data.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>uri</code></td>
|
||||
<td>String</td>
|
||||
<td>This depends on whether the item is being inserted in <i>plain</i> or
|
||||
<i>signed space</i> mode.<br>
|
||||
<br>
|
||||
If inserting in <i>plain</i> mode, then the uri is in the form
|
||||
<code>Q:somebase64hash</code>, where the hash is computed according to
|
||||
the <a href="#plainhash">plain hash calculation procedure</a>.<br>
|
||||
<br>
|
||||
If inserting in <i>signed space</i> mode, then the uri will be in the form
|
||||
<code>Q:somebase64hash/path.ext</code>, where the hash is computed as per
|
||||
the <a href="#signedhash">signed space hash calculation procedure</a>, and
|
||||
the <code>/path.ext</code> is the verbatim value of the app-supplied
|
||||
<code>path</code> key.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>publicKey</code></td>
|
||||
<td>String</td>
|
||||
<td>Base64-encoded signing public key. In cases where app provides
|
||||
<code>privateKey</code>,
|
||||
a node will derive the signing public key from the private key,
|
||||
delete the private key from the metadata, and replace it with its corresponding
|
||||
public key
|
||||
key.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>signature</code></td>
|
||||
<td>String</td>
|
||||
<td>Base64-encoded signature of <code>path+dataHash</code>, created using
|
||||
the app-provided <code>privateKey</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rent</code></td>
|
||||
<td>String</td>
|
||||
<td>A rent payment for the data's accommodation on the server.<br>
|
||||
Intention is to support a variety of payment tokens. Initially, the
|
||||
only acceptable form of payment will be a hashcash-like token,
|
||||
in the form <code>hashcash:base64string</code>. The <code>hashcash:</code>
|
||||
prefix indicates that this payment is in hashcash currency, in which case
|
||||
the <code>base64String</code> should decode to a 16-byte string whose
|
||||
SHA256 hash partially collides with <code>dataHash</code>.
|
||||
The greater the number of bits in the collision,
|
||||
the longer the data's accommodation will be 'paid up for'.<br>
|
||||
<br>
|
||||
If this key is already present, a Q node will verify the hashcash,
|
||||
and adjust the <code>expiry</code> key value to the time the item's accommodation
|
||||
is paid up till.<br>
|
||||
<br>
|
||||
If the key is not present:
|
||||
<ul>
|
||||
<li>A client node will generate a value for this key with enough collision bits
|
||||
to pay the accommodation up till the given app-specified <code>expiry</code> date.</li>
|
||||
<li>A server node will grant temporary free accommodation, and adjust the <code>expiry</code>
|
||||
key to the end of the free accommodation period.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<a name="plainhash"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>3. URI Determination Procedures</h2>
|
||||
|
||||
<h3>3.1. Plain Hash URI Calculation Procedure</h3>
|
||||
|
||||
When items are inserted in <code>plain</code> mode, the final URI is determined from
|
||||
a hash of the data and metadata. Security of the item is based on the mathematical difficulty
|
||||
of creating an arbitrary data+metadata set whose hash collides with the target URI.<br>
|
||||
<br>
|
||||
Specifically, the recipe for calculating plain hash URIs is:
|
||||
<ol>
|
||||
<li>If the key <code>size</code> is missing, set this to the size of the data,
|
||||
in bytes</li>
|
||||
<li>If the key <code>dataHash</code> is missing, set this to the base64-encoded
|
||||
SHA256(data)</li>
|
||||
<li>If the key <code>title</code> is missing, set this to the value of <code>dataHash</code></li>
|
||||
<li>From the metadata, create a set of strings, each in the form <code>key=value</code>,
|
||||
where each line contains a metadata <code>key</code> and its <code>value</code>, and
|
||||
is terminated by an ASCII linefeed (\n, 0x10).</li>
|
||||
<li>Ensure that key <code>uri</code> is omitted</li>
|
||||
<li>Sort the strings into ascending ASCII sort order</li>
|
||||
<li>Concatenate the strings together into one big string</li>
|
||||
<li>Calculate the SHA256 hash of this string</li>
|
||||
<li>Encode the hash into Base64</li>
|
||||
<li>Prepend the string <code>Q:</code> to this</li>
|
||||
</ol>
|
||||
|
||||
<a name="signedhash"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>3.2. Signed Space URI Calculation Procedure</h3>
|
||||
|
||||
This is much simpler than determining plain hash URI, since the security of the URI
|
||||
is based not on hashes of data and metadata, but on the cryptographic <code>privateKey</code>
|
||||
given by the application.<br>
|
||||
<br>
|
||||
Calculation recipe for Signed Space URIs is:
|
||||
<ol>
|
||||
<li>Calculate the SHA256 hash of the private key's binary data (not its base64 representation)</li>
|
||||
<li>Encode this hash into base64, dropping any trailing '=' characters</li>
|
||||
<li>Append to this the value of metadata item <code>path</code> (recall that <code>path</code>,
|
||||
if not empty, must begin with a '/')</li>
|
||||
<li>Prepend the string <code>Q:</code> to this</li>
|
||||
</ol>
|
||||
The resulting URI then is in the form <code>Q:pubkeyHash/path.ext</code>
|
||||
|
||||
<hr>
|
||||
<!-- Created: Tue Apr 5 00:56:45 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Wed Apr 6 00:36:37 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
BIN
apps/q/doc/overall.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
1
apps/q/doc/qnoderefs.txt
Normal file
@ -0,0 +1 @@
|
||||
rxvXpHKfWGWsql4PJaHglAERSUYyrdKKAzK6jPHT4QXRf9jgcVd4mInq0j6H4inVOzT9dG4L6c9GrlQwe4ysUm5jSTyZemxiZpQDCAazsoRzNDv6gevA40J6uGl10JtVtOjqXW8Ej0JUKubz88g~ogPb1h4Xibc-RrtqrvsJebg5xYFkLlnr7DxDtiWzIMRSZ9Ri2P~eq0SwZzd81tvASPj5fb3nySHeABAuY8HrNu0gqRLjeayDpd3OK1ogrxf1lMvfutn5pnLrlVcvKHa~6rNWWGSulsuEYWtpUd4Itj9aKqIgF9ES7RF77Z73W1f6NRTHO48ZLyLLaKVLjDIsHQP-0mOevszcPjFWtheqRKvT2D28WEMpVC-mPtfw91BkdgBa3pwWhwG~7KIhvWhGs8bj2NOKkqrwYU7xhNVaHdDDkzv4gsweCutHNiiCF~4yL54WzCIfSKDjcHjQxxVkh2NKeaItzgw9E~mPAKNZD22X~2oAuuL9i~0lldEV1ddUAAAA
|
BIN
apps/q/doc/screenshot-home.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
apps/q/doc/screenshot-iewarn.jpg
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
apps/q/doc/screenshot-qsite.jpg
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
apps/q/doc/screenshot-search.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
apps/q/doc/screenshot.jpg
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
apps/q/doc/screenshot.png
Normal file
After Width: | Height: | Size: 98 KiB |
23
apps/q/doc/screenshots.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q Screenshots</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Screenshots</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="screenshot-search.jpg">Search Screen</li>
|
||||
<li><a href="screenshot-qsite.jpg">QSite Insertion Form</li>
|
||||
<li><a href="screenshot-iewarn.jpg">Q Security Features</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<address><a href="mailto:aum@mail.i2p">aum</a></address>
|
||||
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 14:06:02 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
1460
apps/q/doc/spec/index.html
Normal file
90
apps/q/java/build.xml
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="aum">
|
||||
<!-- Written to assume that classpath is rooted in the current directory. -->
|
||||
<!-- So this should be OK if you make this script in the root of a filesystem. -->
|
||||
<!-- If not, just change src.dir to be the root of your sources' package tree -->
|
||||
<!-- and use e.g. View over a Filesystem to mount that subdirectory with all capabilities. -->
|
||||
<!-- The idea is that both Ant and NetBeans have to know what the package root is -->
|
||||
<!-- for the classes in your application. -->
|
||||
|
||||
<!-- Don't worry if you don't know the Ant syntax completely or need help on some tasks! -->
|
||||
<!-- The standard Ant documentation can be downloaded from AutoUpdate and -->
|
||||
<!-- and then you can access the Ant manual in the online help. -->
|
||||
|
||||
<target name="init">
|
||||
<property location="build" name="classes.dir"/>
|
||||
<property location="src" name="src.dir"/>
|
||||
<property location="doc/q/api" name="javadoc.dir"/>
|
||||
<property name="project.name" value="${ant.project.name}"/>
|
||||
<property location="${project.name}.jar" name="jar"/>
|
||||
<property location="q.war" name="war"/>
|
||||
</target>
|
||||
|
||||
<target name="builddep">
|
||||
<ant dir="../../i2ptunnel/java/" target="build" />
|
||||
<!-- i2ptunnel builds ministreaming and core -->
|
||||
</target>
|
||||
|
||||
<target depends="init,builddep" name="compile">
|
||||
<!-- Both srcdir and destdir should be package roots. -->
|
||||
<mkdir dir="${classes.dir}"/>
|
||||
<javac debug="true"
|
||||
deprecation="true"
|
||||
destdir="${classes.dir}"
|
||||
srcdir="${src.dir}"
|
||||
classpath="xmlrpc.jar:../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar:../../i2ptunnel/java/build/i2ptunnel.jar" >
|
||||
<!-- To add something to the classpath: -->
|
||||
<!-- <classpath><pathelement location="${mylib}"/></classpath> -->
|
||||
<!-- To exclude some files: -->
|
||||
<!-- <exclude name="com/foo/SomeFile.java"/><exclude name="com/foo/somepackage/"/> -->
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target depends="init,compile" name="jar">
|
||||
<!-- To make a standalone app, insert into <jar>: -->
|
||||
<!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> -->
|
||||
<!-- <jar basedir="${classes.dir}" compress="true" jarfile="${jar}"> -->
|
||||
<jar compress="true" jarfile="${jar}">
|
||||
<!-- <jar basedir="." compress="true" jarfile="${jar}" includes="**/*.class,doc/**/*.html"> -->
|
||||
<fileset dir="${classes.dir}"/>
|
||||
<fileset dir="." includes="qresources/**"/>
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.aum.q.QMgr"/>
|
||||
<attribute name="Class-Path" value="i2p.jar xmlrpc.jar mstreaming.jar streaming.jar jbigi.jar"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target depends="init,compile" name="war">
|
||||
<!-- To make a standalone app, insert into <jar>: -->
|
||||
<!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> -->
|
||||
<war compress="true" jarfile="${war}" webxml="web.xml">
|
||||
<!-- <fileset file="build/net/i2p/aum/q/QConsole.class"/> -->
|
||||
<classes dir="build" includes="**/QConsole.class"/>
|
||||
<classes dir="build" includes="**/HTML/**"/>
|
||||
<!-- <fileset includes="**/HTML/*.class"/> -->
|
||||
<lib file="xmlrpc.jar"/>
|
||||
</war>
|
||||
</target>
|
||||
|
||||
<target depends="init,jar,war" description="Build everything." name="all"/>
|
||||
|
||||
<target depends="init" description="Javadoc for my API." name="javadoc">
|
||||
<mkdir dir="${javadoc.dir}"/>
|
||||
<javadoc destdir="${javadoc.dir}" packagenames="*">
|
||||
<sourcepath>
|
||||
<pathelement location="${src.dir}"/>
|
||||
</sourcepath>
|
||||
<sourcepath>
|
||||
<pathelement location="/java/xmlrpc-1.2-b1/src/java"/>
|
||||
</sourcepath>
|
||||
</javadoc>
|
||||
</target>
|
||||
|
||||
<target depends="init" description="Clean all build products." name="clean">
|
||||
<delete dir="${classes.dir}"/>
|
||||
<delete dir="${javadoc.dir}"/>
|
||||
<delete file="${jar}"/>
|
||||
</target>
|
||||
|
||||
</project>
|
10
apps/q/java/qresources/html/404.html
Normal file
@ -0,0 +1,10 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Item Not Found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=center>Failed to retrieve item:<br>
|
||||
<code><tmpl_var 404_uri></code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
27
apps/q/java/qresources/html/about.html
Normal file
@ -0,0 +1,27 @@
|
||||
<TABLE align="center" class="mainpaneitem">
|
||||
<TR>
|
||||
<TD class="formHeading">About Q</TD>
|
||||
</tr>
|
||||
<tr class="formBody">
|
||||
<TD align=center>
|
||||
<p>Version
|
||||
<tmpl_if version>
|
||||
<tmpl_var version></p>
|
||||
<tmpl_else>
|
||||
0.0.1
|
||||
</tmpl_if>
|
||||
<p>Designed and engineered by <a href="mailto:aum@mail.i2p">aum</a></p>
|
||||
|
||||
<p>Copyright © 2005 by aum.
|
||||
<br>Released under the
|
||||
<br>GNU Lesser General Public License</p>
|
||||
|
||||
<p style="font-size: smaller; font-style: italic">
|
||||
Many thanks to jrandom, smeghead and frosk<br>
|
||||
for their patient and knowledgeable support<br>
|
||||
in helping this python programmer<br>
|
||||
get partly proficient in java.</p>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
40
apps/q/java/qresources/html/addrefform.html
Normal file
@ -0,0 +1,40 @@
|
||||
<table align="center" class="mainpaneitem" width=80%>
|
||||
<tr>
|
||||
<td class="formHeading">Add Hub Noderef</td>
|
||||
</tr>
|
||||
|
||||
<form action="/tools" method="POST" class="formBody">
|
||||
|
||||
<input type="hidden" name="cmd" value="addref">
|
||||
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width=70%>
|
||||
<tr>
|
||||
<td>
|
||||
In order for your node to join a Q network, it must have a ref to at
|
||||
least one of the running Q Hubs. You need to get a Q Hub noderef and
|
||||
paste it here.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align=center>
|
||||
<textarea name="noderef" cols=40 rows=6></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center">
|
||||
<input type="submit" name="submit" value="Add Hub Noderef!">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</form>
|
||||
|
||||
</table>
|
||||
|
29
apps/q/java/qresources/html/aiealert.html
Normal file
@ -0,0 +1,29 @@
|
||||
<table align="center" class="mainpaneitem" width=80%>
|
||||
<tr>
|
||||
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing=0 cellpadding=5 align=center border=1>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<p>You are attempting to view a QSite via the Internet Explorer web browser.
|
||||
</p>
|
||||
<p>We have blocked the connection to protect your anonymity.
|
||||
</p>
|
||||
<p>As a matter of policy, Q does not support QSite browsing via Microsoft
|
||||
Internet Explorer (MSIE), because of that browser's abysmal track record with
|
||||
regard to security. If we did allow you to browse Q via MSIE, it would
|
||||
be easy for a malicious QSite author to embed hostile content which
|
||||
undermines your computer's security and compromises your anonymity.
|
||||
</p>
|
||||
<p>If you want to surf I2P QSites, you'll need to use a more secure web
|
||||
browser such as <a href="http://www.mozilla.org">Mozilla or Firefox</a>.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
52
apps/q/java/qresources/html/anonalert.html
Normal file
@ -0,0 +1,52 @@
|
||||
<table align="center" class="mainpaneitem" width=80%>
|
||||
<tr>
|
||||
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing=0 cellpadding=5 align=center border=1>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<p>You are attempting to view a QSite via an unprotected link.
|
||||
</p>
|
||||
<p>We have blocked the connection to protect your anonymity. If we don't
|
||||
do this, then a malicious QSite author can insert content into the QSite
|
||||
which triggers oubound hits to arbitrary servers on the mainstream web,
|
||||
which in turn can easily reveal a lot of personal information about you
|
||||
and the QSite you are accessing.
|
||||
</p>
|
||||
<p>If you want to browse QSites with your web browser with greater safety,
|
||||
you'll have to follow these simple steps:
|
||||
</p>
|
||||
<ol>
|
||||
<li>Edit your I2P <code>hosts.txt</code> file and add the following entry
|
||||
(all on one line):
|
||||
<blockquote><code><textarea rows=5 cols=50>q.i2p=<tmpl_var dest></textarea></code><blockquote>
|
||||
(and make sure you do NOT reveal this entry to anyone else)
|
||||
</li>
|
||||
<li>Configure one of your web browsers to use the proxy server:
|
||||
<blockquote><code>localhost:4444</code></blockquote>
|
||||
(or whatever address you have configured your I2P EEProxy to listen on,
|
||||
if you've changed it)<br><br>
|
||||
</li>
|
||||
<li>Start up the browser you have just configured, and enter the web address:
|
||||
<blockquote><code>http://q.i2p</code></blockquote>
|
||||
</li>
|
||||
</ol>
|
||||
<p>Even if you do this, you still won't have a 100% guarantee of anonymity, since
|
||||
a malicious QSite author can send code to your browser which exploits a vulnerability
|
||||
in the browser to compromise your anonymity (eg, accessing third party cookies, executing
|
||||
arbitrary code, accessing your local filesystem, uploading compromising information
|
||||
about you to hostile I2P EEPsites etc). But you can relax (somewhat) in the knowledge
|
||||
that such attacks are much more difficult, particularly if you use a decent web browser.
|
||||
</p>
|
||||
<p>Thank you for your co-operation. We wish you happy and safe browsing.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
9
apps/q/java/qresources/html/downloads.html
Normal file
@ -0,0 +1,9 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Q Downloads</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Not implemented yet</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
28
apps/q/java/qresources/html/genkeysform.html
Normal file
@ -0,0 +1,28 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Generate New Keypair</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Click the button to generate a new keypair for
|
||||
future inserts of signed space keys.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center">
|
||||
<form action="/tools" method="POST" class="formBody">
|
||||
<input type="hidden" name="cmd" value="genkeys">
|
||||
<input type="submit" name="submit" value="Generate Keys!">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
32
apps/q/java/qresources/html/genkeysresult.html
Normal file
@ -0,0 +1,32 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td colspan=2 class="formHeading">Your New Keys</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" colspan=2>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Please save these keys in a safe place,
|
||||
preferably on an encrypted partition:
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Public Key: </td>
|
||||
<td>
|
||||
<input readonly type="text" size=32 style="font-family: courier, monospace" value="<tmpl_var publickey>">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Private Key: </td>
|
||||
<td><input readonly type="text" size=32 style="font-family: courier, monospace" value="<tmpl_var privatekey>"></td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
15
apps/q/java/qresources/html/getform.html
Normal file
@ -0,0 +1,15 @@
|
||||
<table align="center" class="mainpaneitem" border=0>
|
||||
<TR>
|
||||
<TD class="formHeading" colspan="3">Retrieve Content</TD>
|
||||
</tr>
|
||||
<tr class="formBody">
|
||||
<form action="/home" method="POST">
|
||||
<input type="hidden" name="cmd" value="get">
|
||||
|
||||
<TD>URI:</TD>
|
||||
<td><INPUT type="text" name="uri" value="Q:" size="60" maxlength="128"></td>
|
||||
<td><INPUT type="submit" name="submit" value="Retrieve it"></td>
|
||||
|
||||
</form>
|
||||
</TR>
|
||||
</table>
|
11
apps/q/java/qresources/html/help.html
Normal file
@ -0,0 +1,11 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Q Help</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Not written yet
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
34
apps/q/java/qresources/html/jobs.html
Normal file
@ -0,0 +1,34 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading" colspan=2>Current Background Jobs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
<tmpl_if items>
|
||||
|
||||
<table cellspacing=0 cellpadding=5 align=center border=1>
|
||||
|
||||
<tr>
|
||||
<td><b>Secs From Now</b></td>
|
||||
<td><b>Description</b></td>
|
||||
</tr>
|
||||
|
||||
<tmpl_loop items>
|
||||
<tr>
|
||||
<td><tmpl_var future></td>
|
||||
<td><tmpl_var description></td>
|
||||
</tr>
|
||||
</tmpl_loop>
|
||||
|
||||
</table>
|
||||
|
||||
<tmpl_else>
|
||||
|
||||
<tr><td colspan=2 align=center><i>(No current jobs)</i></td></tr>
|
||||
|
||||
</tmpl_if>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
122
apps/q/java/qresources/html/main.html
Normal file
@ -0,0 +1,122 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<TITLE>Q Web Interface</TITLE>
|
||||
<style type="text/css">
|
||||
<!--
|
||||
body { font-family: helvetica, arial, sans-serif; background-color: #000080 }
|
||||
td { }
|
||||
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
|
||||
.logocell {
|
||||
border-color: #ffe000;
|
||||
border-width: 1px;
|
||||
border-top-style:none;
|
||||
border-bottom-style:solid;
|
||||
border-left-style:none;
|
||||
border-right-style:none;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
color: #f0f0ff;
|
||||
}
|
||||
.tabcell {
|
||||
font-weight: bold;
|
||||
background-color: #fffff0;
|
||||
padding:5px;
|
||||
border-color:#ffe000;
|
||||
border-width: 1px;
|
||||
border-top-style:solid;
|
||||
border-bottom-style:none;
|
||||
border-left-style:solid;
|
||||
border-right-style:solid;
|
||||
}
|
||||
.tabcell_inactive {
|
||||
background-color: #f0f0ff;
|
||||
padding:5px;
|
||||
border-color:#ffe000;
|
||||
border-width: 1px;
|
||||
border-top-style:solid;
|
||||
border-bottom-style:solid;
|
||||
border-left-style:solid;
|
||||
border-right-style:solid;
|
||||
}
|
||||
.tablink {
|
||||
font-size: smaller;
|
||||
text-decoration: none;
|
||||
}
|
||||
.mainpane { border-color:#ffe000;
|
||||
border-width: 1px;
|
||||
border-top-style:none;
|
||||
border-bottom-style:solid;
|
||||
border-left-style:solid;
|
||||
border-right-style:solid;
|
||||
}
|
||||
|
||||
.mainpaneitem1 { border-style:solid; border-color:#0000ff; border-width: thin; }
|
||||
|
||||
.mainpaneitem { border-style:solid;
|
||||
border-color:#0000ff;
|
||||
border-width: thin;
|
||||
background-color: #f0f0ff;
|
||||
}
|
||||
|
||||
.formHeading { font-size:larger; font-weight: bolder; text-align:center; }
|
||||
|
||||
.btn1 { font-weight: bold;
|
||||
}
|
||||
|
||||
input1 { background-color: #fffff0;
|
||||
color: #000080;
|
||||
|
||||
}
|
||||
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
<body bgcolor="#ffffff">
|
||||
<TABLE width="99%" height=99% align="center" cellspacing="0" cellpadding="0" border=0>
|
||||
<TR>
|
||||
<TD>
|
||||
<table width="100%" cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td>
|
||||
<table align=right cellspacing=0 cellpadding=0 border=0>
|
||||
<tr>
|
||||
<tmpl_loop tabs>
|
||||
<td class=<tmpl_if active>"tabcell"<tmpl_else>"tabcell_inactive"</tmpl_if>>
|
||||
<a class="tablink" href="/<tmpl_var name>"><tmpl_var label></a>
|
||||
</td>
|
||||
</tmpl_loop>
|
||||
|
||||
<tmpl_if _ignore>
|
||||
<TD class="tabcell_inactive">
|
||||
<a class="tablink" href="/status">Status</a>
|
||||
</TD>
|
||||
<TD class="tabcell">
|
||||
<a class="tablink" href="/settings">Settings</a>
|
||||
</TD>
|
||||
</tmpl_if>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<TD width=100% class="logocell">
|
||||
Q <tmpl_var nodeType> Node
|
||||
</TD>
|
||||
</TR>
|
||||
</table>
|
||||
</TD> </TR>
|
||||
<tr height=99% bgcolor="#fffff0">
|
||||
<TD class="mainpane">
|
||||
<br>
|
||||
<center>
|
||||
<tmpl_loop items>
|
||||
<br>
|
||||
<tmpl_var item>
|
||||
<br>
|
||||
</tmpl_loop>
|
||||
</center>
|
||||
</TD>
|
||||
</tr>
|
||||
</TABLE>
|
||||
</body>
|
||||
</html>
|
29
apps/q/java/qresources/html/msiealert.html
Normal file
@ -0,0 +1,29 @@
|
||||
<table align="center" class="mainpaneitem" width=80%>
|
||||
<tr>
|
||||
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing=0 cellpadding=5 align=center border=1>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<p>You are attempting to view a QSite via the Internet Explorer web browser.
|
||||
</p>
|
||||
<p>We have blocked the connection to protect your anonymity.
|
||||
</p>
|
||||
<p>As a matter of policy, Q does not support QSite browsing via Microsoft
|
||||
Internet Explorer (MSIE), because of that browser's abysmal track record with
|
||||
regard to security. If we did allow you to browse Q via MSIE, it would
|
||||
be easy for a malicious QSite author to embed hostile content which
|
||||
undermines your computer's security and compromises your anonymity.
|
||||
</p>
|
||||
<p>If you want to surf I2P QSites, you'll need to use a more secure web
|
||||
browser such as <a href="http://www.mozilla.org">Mozilla or Firefox</a>.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
10
apps/q/java/qresources/html/puterror.html
Normal file
@ -0,0 +1,10 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Error Inserting <tmpl_if is_site>QSite<tmpl_else>Item</tmpl_if></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=center>Your insert failed because:<br>
|
||||
<code><tmpl_var error></code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
139
apps/q/java/qresources/html/putform.html
Normal file
@ -0,0 +1,139 @@
|
||||
<TABLE align="center" class="mainpaneitem" width=90% >
|
||||
<TR>
|
||||
<TD class="formHeading" colspan="2">Insert Content</TD>
|
||||
</tr>
|
||||
|
||||
<TR>
|
||||
<TD colspan="2" align=center><a href="/insert?mode=site">Insert A QSite Instead</a></TD>
|
||||
</tr>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<form method="POST" ENCTYPE="multipart/form-data" action="/insert" class="formBody">
|
||||
<!-- <form method="POST" action="/insert" class="formBody"> -->
|
||||
|
||||
<input type=hidden name="cmd" value="put">
|
||||
|
||||
<tr>
|
||||
<td valign=top>Type: </td>
|
||||
<td style="font-size:smaller">
|
||||
<input type="radio" name="type" value="text">Text
|
||||
<input type="radio" name="type" value="html">HTML
|
||||
<input type="radio" name="type" value="image">Image
|
||||
<input type="radio" name="type" value="audio">Audio
|
||||
<input type="radio" name="type" value="video">Video
|
||||
<input type="radio" name="type" value="archive">Archive
|
||||
<input type="radio" name="type" value="other" checked>Other
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Title: </TD>
|
||||
<td>
|
||||
<input type="text" name="title" size="60" maxlength="58">
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(A short descriptive title for this item)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Path: </TD>
|
||||
<td>
|
||||
<input type="text" name="path" size="60" maxlength="128">
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(If you're inserting in plain-hash mode, this should be a suggested
|
||||
filename for people to save the item as when downloading it; If you're
|
||||
inserting in signed-space mode, this can be a longer pathname)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Mimetype: </TD>
|
||||
<td>
|
||||
<input type="text" name="mimetype" size="40" maxlength="40">
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(Leave blank unless you know exactly what this means)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Keywords: </TD>
|
||||
<td>
|
||||
<input type="text" name="keywords" size="60" maxlength="80">
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(Comma-separated list of short descriptive keywords)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Abstract: </TD>
|
||||
<td>
|
||||
<textarea name="abstract" cols="50" rows="2"></textarea>
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(A short descriptive summary for the item, preferably no
|
||||
longer than 256 characters)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>File: </TD>
|
||||
<td>
|
||||
<INPUT type="file" name="data" size="40" maxlength="128">
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(Leave this blank if you are entering the raw data in the text box below)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Private Key: </TD>
|
||||
<td>
|
||||
<INPUT type="text" name="privkey" size="30" maxlength="60">
|
||||
<div style="font-size: smaller; font-style: italic">
|
||||
(Only if inserting in signed space mode)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD colspan=2 align=center>
|
||||
<INPUT class="btn" type="submit" name="submit" value="Insert it">
|
||||
</TD>
|
||||
</tr>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<td valign=top>Raw Data:</td>
|
||||
<td>
|
||||
<div style="font-size: smaller; font-style:italic">
|
||||
(You may enter the raw data here as text instead of selecting a
|
||||
file above)
|
||||
</div>
|
||||
<textarea name="rawdata" cols=60 rows=16></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
</TABLE>
|
10
apps/q/java/qresources/html/putok.html
Normal file
@ -0,0 +1,10 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading"><tmpl_if is_site>QSite<tmpl_else>Item</tmpl_if> Inserted Successfully</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=center>Item URI:<br>
|
||||
<code><input type="text" size=60 readonly value="<tmpl_var uri>"></code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
101
apps/q/java/qresources/html/putsiteform.html
Normal file
@ -0,0 +1,101 @@
|
||||
<table align="center" class="mainpaneitem" width=90% >
|
||||
<TR>
|
||||
<TD class="formHeading" colspan="2">Insert A QSite</TD>
|
||||
</tr>
|
||||
|
||||
<TR>
|
||||
<TD colspan="2" align=center><a href="/insert?mode=file">Insert A Single File Instead</a></TD>
|
||||
</tr>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<form method="POST" action="/insert" class="formBody">
|
||||
<!-- <form method="POST" action="/insert" class="formBody"> -->
|
||||
|
||||
<input type=hidden name="cmd" value="putsite">
|
||||
<input type=hidden name="type" value="qsite">
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Name: </TD>
|
||||
<td>
|
||||
<input type="text" name="name" size="32" maxlength="50">
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(A short name for the QSite - should contain only alphanumerics, '-', and '_';
|
||||
should definitely <i>not</i> contain '/', ':', '\', ' ' etc)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Private Key: </TD>
|
||||
<td>
|
||||
<INPUT type="text" name="privkey" size="30" maxlength="60">
|
||||
<div style="font-size: smaller; font-style: italic">
|
||||
(Mandatory - if you don't have a signed-space keypair, get one by
|
||||
clicking on <b>Tools</b>)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Directory: </TD>
|
||||
<td>
|
||||
<INPUT type="text" name="dir" size="60" maxlength="128">
|
||||
<div style="font-size: smaller; font-style: italic">
|
||||
(Absolute directory path where the QSite's files reside)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Title: </TD>
|
||||
<td>
|
||||
<input type="text" name="title" size="60" maxlength="58">
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(A short descriptive title for this QSite)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Keywords: </TD>
|
||||
<td>
|
||||
<input type="text" name="keywords" size="60" maxlength="80">
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(Comma-separated list of short descriptive keywords)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD valign=top>Abstract: </TD>
|
||||
<td>
|
||||
<textarea name="abstract" cols="50" rows="2"></textarea>
|
||||
<div style="font-style: italic; font-size: smaller">
|
||||
(A short descriptive summary for the QSite, preferably no
|
||||
longer than 256 characters)
|
||||
</div>
|
||||
</td>
|
||||
</TR>
|
||||
|
||||
<tr><td colspan=2><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD colspan=2 align=center>
|
||||
<INPUT class="btn" type="submit" name="submit" value="Insert it">
|
||||
</TD>
|
||||
</tr>
|
||||
|
||||
</form>
|
||||
|
||||
</table>
|
76
apps/q/java/qresources/html/searchform.html
Normal file
@ -0,0 +1,76 @@
|
||||
<TABLE align="center" class="mainpaneitem">
|
||||
<TR>
|
||||
<TD class="formHeading" colspan="3">Search For Content</TD>
|
||||
</tr>
|
||||
|
||||
<form method="GET" action="/search" class="formBody">
|
||||
|
||||
<input type=hidden name=cmd value="search">
|
||||
|
||||
<tr>
|
||||
<td valign=top>Type: </td>
|
||||
<td style="font-size:smaller" colspan=2>
|
||||
<input type="radio" name="type" value="any" checked>ANY
|
||||
<input type="radio" name="type" value="qsite">QSite
|
||||
<input type="radio" name="type" value="text">Text
|
||||
<input type="radio" name="type" value="html">HTML
|
||||
<input type="radio" name="type" value="image">Image
|
||||
<br>
|
||||
<input type="radio" name="type" value="audio">Audio
|
||||
<input type="radio" name="type" value="video">Video
|
||||
<input type="radio" name="type" value="archive">Archive
|
||||
<input type="radio" name="type" value="other">Other
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Title: </td>
|
||||
<td><input type="text" name="title" size="60" value="<tmpl_var title>" maxlength="128"></td>
|
||||
<td><input type="checkbox" name="title_re" <tmpl_if title_re>checked</tmpl_if>>regexp</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<TD>Path: </TD>
|
||||
<td><INPUT type="text" name="path" size="60" value="<tmpl_var title>" maxlength="128"></td>
|
||||
<td><input type="checkbox" name="path_re" <tmpl_if path_re>checked</tmpl_if>>regexp</td>
|
||||
</TR>
|
||||
|
||||
<tr>
|
||||
<TD>Mimetype: </TD>
|
||||
<td><INPUT type="text" name="mimetype" size="40" maxlength="40" value="<tmpl_var mimetype>"></td>
|
||||
<td><input type="checkbox" name="mimetype_re" <tmpl_if mimetype_re>checked</tmpl_if>>regexp</td>
|
||||
</TR>
|
||||
|
||||
<tr>
|
||||
<TD>Keywords: </TD>
|
||||
<td><INPUT type="text" name="keywords" size="60" maxlength="80" value="<tmpl_var keywords>"></td>
|
||||
<td><input type="checkbox" name="keywords_re" <tmpl_if keywords_re>checked</tmpl_if>>regexp</td>
|
||||
</TR>
|
||||
|
||||
<tr>
|
||||
<TD>Summary: </TD>
|
||||
<td><textarea name="summary" cols="50" rows=2><tmpl_var summary></textarea></td>
|
||||
<td><input type="checkbox" name="summary_re" <tmpl_if summary_re>checked</tmpl_if>>regexp</td>
|
||||
</TR>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Search Mode:
|
||||
</td>
|
||||
<td>
|
||||
<input type=radio name="searchmode" value="and" checked>AND
|
||||
<input type=radio name="searchmode" value="or">OR
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr><td colspan=3><hr></td></tr>
|
||||
|
||||
<tr>
|
||||
<TD colspan=3 align=center>
|
||||
<INPUT class="btn" type="submit" name="submit" value="Search!">
|
||||
</TD>
|
||||
</tr>
|
||||
|
||||
</form>
|
||||
|
||||
</TABLE>
|
27
apps/q/java/qresources/html/searchresults.html
Normal file
@ -0,0 +1,27 @@
|
||||
<table align="center" class="mainpaneitem" <tmpl_if results>width="95%"</tmpl_if>>
|
||||
<tr>
|
||||
<td class="formHeading" colspan=2>Search Results</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-style: italic" align="center"><tmpl_var numresults> items found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="90%">
|
||||
<table cellspacing=0 cellpadding=5 align=center border=0 width=95%>
|
||||
<tmpl_loop results>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="font-size: larger"><a href="/<tmpl_var uri>"><tmpl_var title></a></div>
|
||||
<tmpl_if abstract>
|
||||
<div style="font-style: italic"><tmpl_var abstract></div>
|
||||
</tmpl_if>
|
||||
<div style="font-size: smaller; color: #008000;"><tmpl_var uri></div>
|
||||
<div style="font-size:smaller;"><tmpl_var type> (<tmpl_var mimetype>) <tmpl_var size> bytes</div>
|
||||
<div/>
|
||||
</td>
|
||||
</tr>
|
||||
</tmpl_loop>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
9
apps/q/java/qresources/html/settings.html
Normal file
@ -0,0 +1,9 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Q Settings</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Not implemented yet</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
24
apps/q/java/qresources/html/status.html
Normal file
@ -0,0 +1,24 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading" colspan=2>Q Node Status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing=0 cellpadding=5 align=center border=1>
|
||||
|
||||
<tr>
|
||||
<td><b>Item</b></td>
|
||||
<td><b>Value</b></td>
|
||||
</tr>
|
||||
|
||||
<tmpl_loop items>
|
||||
<tr>
|
||||
<td><tmpl_var key></td>
|
||||
<td><tmpl_var value></td>
|
||||
</tr>
|
||||
</tmpl_loop>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
6
apps/q/java/qresources/html/tools.html
Normal file
@ -0,0 +1,6 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Q Tools</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
0
apps/q/java/qresources/html/widgets/itemtype.html
Normal file
1116
apps/q/java/src/HTML/Template.java
Normal file
178
apps/q/java/src/HTML/Tmpl/Element/Conditional.java
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Modified by David McNab (david@rebirthing.co.nz) to allow nesting of
|
||||
* templates (ie, passing a child Template object as a value argument
|
||||
* to a .setParam() invocation on a parent Template object).
|
||||
*
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import HTML.*;
|
||||
|
||||
public class Conditional extends Element
|
||||
{
|
||||
private boolean control_val = false;
|
||||
private Vector [] data;
|
||||
|
||||
public Conditional(String type, String name)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
if(type.equalsIgnoreCase("if"))
|
||||
this.type="if";
|
||||
else if(type.equalsIgnoreCase("unless"))
|
||||
this.type="unless";
|
||||
else
|
||||
throw new IllegalArgumentException(
|
||||
"Unrecognised type: " + type);
|
||||
|
||||
this.name = name;
|
||||
this.data = new Vector[2];
|
||||
this.data[0] = new Vector();
|
||||
}
|
||||
|
||||
public void addBranch() throws IndexOutOfBoundsException
|
||||
{
|
||||
if(data[1] != null)
|
||||
throw new IndexOutOfBoundsException("Already have two branches");
|
||||
|
||||
if(data[0] == null)
|
||||
data[0] = new Vector();
|
||||
else if(data[1] == null)
|
||||
data[1] = new Vector();
|
||||
}
|
||||
|
||||
public void add(String text)
|
||||
{
|
||||
if(data[1] != null)
|
||||
data[1].addElement(text);
|
||||
else
|
||||
data[0].addElement(text);
|
||||
}
|
||||
|
||||
public void add(Element node)
|
||||
{
|
||||
if(data[1] != null)
|
||||
data[1].addElement(node);
|
||||
else
|
||||
data[0].addElement(node);
|
||||
}
|
||||
|
||||
public void setControlValue(Object control_val)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this.control_val = process_var(control_val);
|
||||
}
|
||||
|
||||
public String parse(Hashtable params)
|
||||
{
|
||||
if(!params.containsKey(this.name))
|
||||
this.control_val = false;
|
||||
else
|
||||
setControlValue(params.get(this.name));
|
||||
|
||||
StringBuffer output = new StringBuffer();
|
||||
|
||||
Enumeration de;
|
||||
if(type.equals("if") && control_val ||
|
||||
type.equals("unless") && !control_val)
|
||||
de = data[0].elements();
|
||||
else if(data[1] != null)
|
||||
de = data[1].elements();
|
||||
else
|
||||
return "";
|
||||
|
||||
while(de.hasMoreElements()) {
|
||||
Object e = de.nextElement();
|
||||
String eType = e.getClass().getName();
|
||||
if(eType.endsWith(".String"))
|
||||
output.append((String)e);
|
||||
else if (eType.endsWith(".Template"))
|
||||
output.append(((Template)e).output());
|
||||
else
|
||||
output.append(((Element)e).parse(params));
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
public String typeOfParam(String param)
|
||||
throws NoSuchElementException
|
||||
{
|
||||
for(int i=0; i<data.length; i++)
|
||||
{
|
||||
if(data[i] == null)
|
||||
continue;
|
||||
for(Enumeration e = data[i].elements();
|
||||
e.hasMoreElements();)
|
||||
{
|
||||
Object o = e.nextElement();
|
||||
if(o.getClass().getName().endsWith(".String"))
|
||||
continue;
|
||||
if(((Element)o).Name().equals(param))
|
||||
return ((Element)o).Type();
|
||||
}
|
||||
}
|
||||
throw new NoSuchElementException(param);
|
||||
}
|
||||
|
||||
private boolean process_var(Object control_val)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
String control_class = "";
|
||||
|
||||
if(control_val == null)
|
||||
return false;
|
||||
|
||||
control_class=control_val.getClass().getName();
|
||||
if(control_class.indexOf(".") > 0)
|
||||
control_class = control_class.substring(
|
||||
control_class.lastIndexOf(".")+1);
|
||||
|
||||
if(control_class.equals("String")) {
|
||||
return !(((String)control_val).equals("") ||
|
||||
((String)control_val).equals("0"));
|
||||
} else if(control_class.equals("Vector")) {
|
||||
return !((Vector)control_val).isEmpty();
|
||||
} else if(control_class.equals("Boolean")) {
|
||||
return ((Boolean)control_val).booleanValue();
|
||||
} else if(control_class.equals("Integer")) {
|
||||
return (((Integer)control_val).intValue() != 0);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unrecognised type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
66
apps/q/java/src/HTML/Tmpl/Element/Element.java
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public abstract class Element
|
||||
{
|
||||
protected String type;
|
||||
protected String name="";
|
||||
|
||||
public abstract String parse(Hashtable params);
|
||||
public abstract String typeOfParam(String param)
|
||||
throws NoSuchElementException;
|
||||
|
||||
public void add(String data){}
|
||||
public void add(Element node){}
|
||||
|
||||
public boolean contains(String param)
|
||||
{
|
||||
try {
|
||||
return (typeOfParam(param) != null?true:false);
|
||||
} catch(NoSuchElementException nse) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public final String Type()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public final String Name()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
39
apps/q/java/src/HTML/Tmpl/Element/If.java
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
|
||||
public class If extends Conditional
|
||||
{
|
||||
public If(String control_var) throws IllegalArgumentException
|
||||
{
|
||||
super("if", control_var);
|
||||
}
|
||||
}
|
183
apps/q/java/src/HTML/Tmpl/Element/Loop.java
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
import java.util.Vector;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class Loop extends Element
|
||||
{
|
||||
private boolean loop_context_vars=false;
|
||||
private boolean global_vars=false;
|
||||
|
||||
private Vector control_val = null;
|
||||
private Vector data;
|
||||
|
||||
public Loop(String name)
|
||||
{
|
||||
this.type = "loop";
|
||||
this.name = name;
|
||||
this.data = new Vector();
|
||||
}
|
||||
|
||||
public Loop(String name, boolean loop_context_vars)
|
||||
{
|
||||
this(name);
|
||||
this.loop_context_vars=loop_context_vars;
|
||||
}
|
||||
|
||||
public Loop(String name, boolean loop_context_vars, boolean global_vars)
|
||||
{
|
||||
this(name);
|
||||
this.loop_context_vars=loop_context_vars;
|
||||
this.global_vars=global_vars;
|
||||
}
|
||||
|
||||
public void add(String text)
|
||||
{
|
||||
data.addElement(text);
|
||||
}
|
||||
|
||||
public void add(Element node)
|
||||
{
|
||||
data.addElement(node);
|
||||
}
|
||||
|
||||
public void setControlValue(Vector control_val)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this.control_val = process_var(control_val);
|
||||
}
|
||||
|
||||
public String parse(Hashtable p)
|
||||
{
|
||||
if(!p.containsKey(this.name))
|
||||
this.control_val = null;
|
||||
else {
|
||||
Object o = p.get(this.name);
|
||||
if(!o.getClass().getName().endsWith(".Vector") &&
|
||||
!o.getClass().getName().endsWith(".List"))
|
||||
throw new ClassCastException(
|
||||
"Attempt to set <tmpl_loop> with a non-list. tmpl_loop=" + this.name);
|
||||
setControlValue((Vector)p.get(this.name));
|
||||
}
|
||||
|
||||
if(control_val == null)
|
||||
return "";
|
||||
|
||||
StringBuffer output = new StringBuffer();
|
||||
Enumeration iterator = control_val.elements();
|
||||
|
||||
boolean first=true;
|
||||
boolean last=false;
|
||||
boolean inner=false;
|
||||
boolean odd=true;
|
||||
int counter=1;
|
||||
|
||||
while(iterator.hasMoreElements()) {
|
||||
Hashtable params = (Hashtable)iterator.nextElement();
|
||||
|
||||
if(params==null)
|
||||
params = new Hashtable();
|
||||
|
||||
if(global_vars) {
|
||||
for(Enumeration e = p.keys(); e.hasMoreElements();) {
|
||||
Object key = e.nextElement();
|
||||
if(!params.containsKey(key))
|
||||
params.put(key, p.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
if(loop_context_vars) {
|
||||
if(!iterator.hasMoreElements())
|
||||
last=true;
|
||||
inner = !first && !last;
|
||||
|
||||
params.put("__FIRST__", first?"1":"");
|
||||
params.put("__LAST__", last?"1":"");
|
||||
params.put("__ODD__", odd?"1":"");
|
||||
params.put("__INNER__", inner?"1":"");
|
||||
params.put("__COUNTER__", "" + (counter++));
|
||||
}
|
||||
|
||||
Enumeration de = data.elements();
|
||||
while(de.hasMoreElements()) {
|
||||
|
||||
Object e = de.nextElement();
|
||||
if(e.getClass().getName().indexOf("String")>-1)
|
||||
output.append((String)e);
|
||||
else
|
||||
output.append(((Element)e).parse(params));
|
||||
}
|
||||
first = false;
|
||||
odd = !odd;
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
public String typeOfParam(String param)
|
||||
throws NoSuchElementException
|
||||
{
|
||||
for(Enumeration e = data.elements(); e.hasMoreElements();)
|
||||
{
|
||||
Object o = e.nextElement();
|
||||
if(o.getClass().getName().endsWith(".String"))
|
||||
continue;
|
||||
if(((Element)o).Name().equals(param))
|
||||
return ((Element)o).Type();
|
||||
}
|
||||
throw new NoSuchElementException(param);
|
||||
}
|
||||
|
||||
private Vector process_var(Vector control_val)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
String control_class = "";
|
||||
|
||||
if(control_val == null)
|
||||
return null;
|
||||
|
||||
control_class=control_val.getClass().getName();
|
||||
|
||||
if(control_class.indexOf("Vector") > -1) {
|
||||
if(control_val.isEmpty())
|
||||
return null;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unrecognised type");
|
||||
}
|
||||
|
||||
return control_val;
|
||||
}
|
||||
|
||||
}
|
||||
|
39
apps/q/java/src/HTML/Tmpl/Element/Unless.java
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
|
||||
public class Unless extends Conditional
|
||||
{
|
||||
public Unless(String control_var) throws IllegalArgumentException
|
||||
{
|
||||
super("unless", control_var);
|
||||
}
|
||||
}
|
144
apps/q/java/src/HTML/Tmpl/Element/Var.java
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Modified by David McNab (david@rebirthing.co.nz) to allow nesting of
|
||||
* templates (ie, passing a child Template object as a value argument
|
||||
* to a .setParam() invocation on a parent Template object).
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
import HTML.Tmpl.Util;
|
||||
import HTML.Template;
|
||||
|
||||
public class Var extends Element
|
||||
{
|
||||
public static final int ESCAPE_NONE = 0;
|
||||
public static final int ESCAPE_URL = 1;
|
||||
public static final int ESCAPE_HTML = 2;
|
||||
public static final int ESCAPE_QUOTE = 4;
|
||||
|
||||
public Var(String name, int escape, Object default_value)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this(name, escape);
|
||||
this.default_value = stringify(default_value);
|
||||
}
|
||||
|
||||
public Var(String name, int escape)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
if(name == null)
|
||||
throw new IllegalArgumentException("tmpl_var must have a name");
|
||||
this.type = "var";
|
||||
this.name = name;
|
||||
this.escape = escape;
|
||||
}
|
||||
|
||||
public Var(String name, String escape)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this(name, escape, null);
|
||||
}
|
||||
|
||||
public Var(String name, String escape, Object default_value)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this(name, ESCAPE_NONE, default_value);
|
||||
|
||||
if(escape.equalsIgnoreCase("html"))
|
||||
this.escape = ESCAPE_HTML;
|
||||
else if(escape.equalsIgnoreCase("url"))
|
||||
this.escape = ESCAPE_URL;
|
||||
else if(escape.equalsIgnoreCase("quote"))
|
||||
this.escape = ESCAPE_QUOTE;
|
||||
}
|
||||
|
||||
public Var(String name, boolean escape)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this(name, escape?ESCAPE_HTML:ESCAPE_NONE);
|
||||
}
|
||||
|
||||
public String parse(Hashtable params)
|
||||
{
|
||||
String value = null;
|
||||
|
||||
if(params.containsKey(this.name))
|
||||
value = stringify(params.get(this.name));
|
||||
else
|
||||
value = this.default_value;
|
||||
|
||||
if(value == null)
|
||||
return "";
|
||||
|
||||
if(this.escape == ESCAPE_HTML)
|
||||
return Util.escapeHTML(value);
|
||||
else if(this.escape == ESCAPE_URL)
|
||||
return Util.escapeURL(value);
|
||||
else if(this.escape == ESCAPE_QUOTE)
|
||||
return Util.escapeQuote(value);
|
||||
else
|
||||
return value;
|
||||
}
|
||||
|
||||
public String typeOfParam(String param)
|
||||
throws NoSuchElementException
|
||||
{
|
||||
throw new NoSuchElementException(param);
|
||||
}
|
||||
|
||||
private String stringify(Object o)
|
||||
{
|
||||
if(o == null)
|
||||
return null;
|
||||
|
||||
String cname = o.getClass().getName();
|
||||
if(cname.endsWith(".String"))
|
||||
return (String)o;
|
||||
else if(cname.endsWith(".Integer"))
|
||||
return ((Integer)o).toString();
|
||||
else if(cname.endsWith(".Boolean"))
|
||||
return ((Boolean)o).toString();
|
||||
else if(cname.endsWith(".Date"))
|
||||
return ((java.util.Date)o).toString();
|
||||
else if(cname.endsWith(".Vector"))
|
||||
throw new ClassCastException("Attempt to set <tmpl_var> with a non-scalar. Var name=" + this.name);
|
||||
else if(cname.endsWith(".Template"))
|
||||
return ((Template)o).output();
|
||||
else
|
||||
throw new ClassCastException("Unknown object type: " + cname);
|
||||
}
|
||||
|
||||
// Private data starts here
|
||||
private int escape=ESCAPE_NONE;
|
||||
private String default_value=null;
|
||||
|
||||
}
|
145
apps/q/java/src/HTML/Tmpl/Filter.java
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
package HTML.Tmpl;
|
||||
|
||||
/**
|
||||
* Pre-parse filters for HTML.Template templates.
|
||||
* <p>
|
||||
* The HTML.Tmpl.Filter interface allows you to write Filters
|
||||
* for your templates. The filter is called after the template
|
||||
* is read and before it is parsed.
|
||||
* <p>
|
||||
* You can use a filter to make changes in the template file before
|
||||
* it is parsed by HTML.Template, so for example, use it to replace
|
||||
* constants, or to translate your own tags to HTML.Template tags.
|
||||
* <p>
|
||||
* A common usage would be to do what you think you're doing when you
|
||||
* do <code><TMPL_INCLUDE file="<TMPL_VAR name="the_file">"></code>:
|
||||
* <p>
|
||||
* myTemplate.tmpl:
|
||||
* <pre>
|
||||
* <TMPL_INCLUDE file="<%the_file%>">
|
||||
* </pre>
|
||||
* <p>
|
||||
* myFilter.java:
|
||||
* <pre>
|
||||
* class myFilter implements HTML.Tmpl.Filter
|
||||
* {
|
||||
* private String myFile;
|
||||
* private int type=SCALAR
|
||||
*
|
||||
* public myFilter(String myFile) {
|
||||
* this.myFile = myFile;
|
||||
* }
|
||||
*
|
||||
* public int format() {
|
||||
* return this.type;
|
||||
* }
|
||||
*
|
||||
* public String parse(String t) {
|
||||
* // replace all <%the_file%> with myFile
|
||||
* return t;
|
||||
* }
|
||||
*
|
||||
* public String [] parse(String [] t) {
|
||||
* throw new UnsupportedOperationException();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* myClass.java:
|
||||
* <pre>
|
||||
* Hashtable params = new Hashtable();
|
||||
* params.put("filename", "myTemplate.tmpl");
|
||||
* params.put("filter", new myFilter("myFile.tmpl"));
|
||||
* Template t = new Template(params);
|
||||
* </pre>
|
||||
*
|
||||
* @author Philip S Tellis
|
||||
* @version 0.0.1
|
||||
*/
|
||||
public interface Filter
|
||||
{
|
||||
/**
|
||||
* Tells HTML.Template to call the parse(String) method of this filter.
|
||||
*/
|
||||
public final static int SCALAR=1;
|
||||
|
||||
/**
|
||||
* Tells HTML.Template to call the parse(String []) method of this
|
||||
* filter.
|
||||
*/
|
||||
public final static int ARRAY=2;
|
||||
|
||||
/**
|
||||
* Tells HTML.Template what kind of filter this is.
|
||||
* Should return either SCALAR or ARRAY to indicate which parse method
|
||||
* must be called.
|
||||
*
|
||||
* @return the values SCALAR or ARRAY indicating which parse method
|
||||
* is to be called
|
||||
*/
|
||||
public int format();
|
||||
|
||||
/**
|
||||
* parses the template as a single string, and returns the parsed
|
||||
* template as a single string.
|
||||
* <p>
|
||||
* Should throw an UnsupportedOperationException if it isn't implemented
|
||||
*
|
||||
* @param t a string containing the entire template
|
||||
*
|
||||
* @return a string containing the template after you've parsed it
|
||||
*
|
||||
* @throws UnsupportedOperationException if this method isn't
|
||||
* implemented
|
||||
*/
|
||||
public String parse(String t);
|
||||
|
||||
/**
|
||||
* parses the template as an array of strings, and returns the parsed
|
||||
* template as an array of strings.
|
||||
* <p>
|
||||
* Should throw an UnsupportedOperationException if it isn't implemented
|
||||
*
|
||||
* @param t an array of strings containing the template - one line
|
||||
* at a time
|
||||
*
|
||||
* @return an array of strings containing the parsed template -
|
||||
* one line at a time
|
||||
*
|
||||
* @throws UnsupportedOperationException if this method isn't
|
||||
* implemented
|
||||
*/
|
||||
public String [] parse(String [] t);
|
||||
}
|
||||
|
385
apps/q/java/src/HTML/Tmpl/Parsers/Parser.java
Normal file
@ -0,0 +1,385 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
package HTML.Tmpl.Parsers;
|
||||
import java.util.*;
|
||||
import HTML.Tmpl.Element.*;
|
||||
import HTML.Tmpl.Util;
|
||||
|
||||
public class Parser
|
||||
{
|
||||
private boolean case_sensitive=false;
|
||||
private boolean strict=true;
|
||||
private boolean loop_context_vars=false;
|
||||
private boolean global_vars=false;
|
||||
|
||||
public Parser()
|
||||
{
|
||||
}
|
||||
|
||||
public Parser(String [] args)
|
||||
throws ArrayIndexOutOfBoundsException,
|
||||
IllegalArgumentException
|
||||
{
|
||||
if(args.length%2 != 0)
|
||||
throw new ArrayIndexOutOfBoundsException("odd number of arguments passed");
|
||||
|
||||
for(int i=0; i<args.length; i+=2) {
|
||||
if(args[i].equals("case_sensitive")) {
|
||||
String cs = args[i+1];
|
||||
if(cs.equals("") || cs.equals("0"))
|
||||
this.case_sensitive=false;
|
||||
else
|
||||
this.case_sensitive=true;
|
||||
} else if(args[i].equals("strict")) {
|
||||
String s = args[i+1];
|
||||
if(s.equals("") || s.equals("0"))
|
||||
this.strict=false;
|
||||
else
|
||||
this.strict=true;
|
||||
} else if(args[i].equals("loop_context_vars")) {
|
||||
String s = args[i+1];
|
||||
if(s.equals("") || s.equals("0"))
|
||||
this.loop_context_vars=false;
|
||||
else
|
||||
this.loop_context_vars=true;
|
||||
|
||||
} else if(args[i].equals("global_vars")) {
|
||||
String s = args[i+1];
|
||||
if(s.equals("") || s.equals("0"))
|
||||
this.global_vars=false;
|
||||
else
|
||||
this.global_vars=true;
|
||||
} else {
|
||||
throw new IllegalArgumentException(args[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Element getElement(Properties p)
|
||||
throws NoSuchElementException
|
||||
{
|
||||
String type = p.getProperty("type");
|
||||
|
||||
if(type.equals("if"))
|
||||
return new If(p.getProperty("name"));
|
||||
else if(type.equals("unless"))
|
||||
return new Unless(p.getProperty("name"));
|
||||
else if(type.equals("loop"))
|
||||
return new Loop(p.getProperty("name"),
|
||||
loop_context_vars, global_vars);
|
||||
else
|
||||
throw new NoSuchElementException(type);
|
||||
}
|
||||
|
||||
public Vector parseLine(String line)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
int pos=0, endpos;
|
||||
Vector parts = new Vector();
|
||||
|
||||
char [] c = line.toCharArray();
|
||||
int i=0;
|
||||
|
||||
StringBuffer temp = new StringBuffer();
|
||||
|
||||
for(i=0; i<c.length; i++) {
|
||||
if(c[i] != '<') {
|
||||
temp.append(c[i]);
|
||||
} else {
|
||||
// found a tag
|
||||
Util.debug_print("line so far: " + temp);
|
||||
StringBuffer tag = new StringBuffer();
|
||||
for(; i<c.length && c[i] != '>'; i++) {
|
||||
tag.append(c[i]);
|
||||
}
|
||||
// > is not allowed inside a template tag
|
||||
// so we can be sure that if this is a
|
||||
// template tag, it ends with a >
|
||||
|
||||
// add the closing > as well
|
||||
if(i<c.length)
|
||||
tag.append(c[i]);
|
||||
|
||||
// if this contains more < inside it,
|
||||
// then it could possibly be a template
|
||||
// tag inside a html tag
|
||||
// so remove external tag parts
|
||||
|
||||
while(tag.toString().substring(1).indexOf("<")
|
||||
> -1)
|
||||
{
|
||||
do {
|
||||
temp.append(tag.charAt(0));
|
||||
tag=new StringBuffer(
|
||||
tag.toString().substring(1));
|
||||
} while(tag.charAt(0) != '<');
|
||||
}
|
||||
|
||||
Util.debug_print("tag: " + tag);
|
||||
|
||||
String test_tag = tag.toString().toLowerCase();
|
||||
// if it doesn't contain tmpl_ it is not
|
||||
// a template tag
|
||||
if(test_tag.indexOf("tmpl_") < 0) {
|
||||
temp.append(tag);
|
||||
continue;
|
||||
}
|
||||
|
||||
// may be a template tag
|
||||
// check if it starts with tmpl_
|
||||
|
||||
test_tag = cleanTag(test_tag);
|
||||
|
||||
Util.debug_print("clean: " + test_tag);
|
||||
|
||||
// check if it is a closing tag
|
||||
if(test_tag.startsWith("/"))
|
||||
test_tag = test_tag.substring(1);
|
||||
|
||||
// if it still doesn't start with tmpl_
|
||||
// then it is not a template tag
|
||||
if(!test_tag.startsWith("tmpl_")) {
|
||||
temp.append(tag);
|
||||
continue;
|
||||
}
|
||||
|
||||
// now it must be a template tag
|
||||
String tag_type=getTagType(test_tag);
|
||||
|
||||
if(tag_type == null) {
|
||||
if(strict)
|
||||
throw new
|
||||
IllegalArgumentException(
|
||||
tag.toString());
|
||||
else
|
||||
temp.append(tag);
|
||||
}
|
||||
|
||||
Util.debug_print("type: " + tag_type);
|
||||
|
||||
// if this was an invalid key and we've
|
||||
// reached so far, then next iteration
|
||||
if(tag_type == null)
|
||||
continue;
|
||||
|
||||
// now, push the previous stuff
|
||||
// into the Vector
|
||||
if(temp.length()>0) {
|
||||
parts.addElement(temp.toString());
|
||||
temp = new StringBuffer();
|
||||
}
|
||||
|
||||
// it is a valid template tag
|
||||
// get its properties
|
||||
|
||||
Util.debug_print("Checking: " + tag);
|
||||
Properties tag_props =
|
||||
getTagProps(tag.toString());
|
||||
|
||||
if(tag_props.containsKey("name"))
|
||||
Util.debug_print("name: " +
|
||||
tag_props.getProperty("name"));
|
||||
else
|
||||
Util.debug_print("no name");
|
||||
|
||||
parts.addElement(tag_props);
|
||||
}
|
||||
}
|
||||
|
||||
if(temp.length()>0)
|
||||
parts.addElement(temp.toString());
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
private String cleanTag(String tag)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
String test_tag = new String(tag);
|
||||
// first remove < and >
|
||||
if(test_tag.startsWith("<"))
|
||||
test_tag = test_tag.substring(1);
|
||||
if(test_tag.endsWith(">"))
|
||||
test_tag = test_tag.substring(0, test_tag.length()-1);
|
||||
else
|
||||
throw new IllegalArgumentException("Tags must start " +
|
||||
"and end on the same line");
|
||||
|
||||
// remove any leading !-- and trailing
|
||||
// -- in case of comment style tags
|
||||
if(test_tag.startsWith("!--")) {
|
||||
test_tag=test_tag.substring(3);
|
||||
}
|
||||
if(test_tag.endsWith("--")) {
|
||||
test_tag=test_tag.substring(0, test_tag.length()-2);
|
||||
}
|
||||
// then leading and trailing spaces
|
||||
test_tag = test_tag.trim();
|
||||
|
||||
return test_tag;
|
||||
}
|
||||
|
||||
private String getTagType(String tag)
|
||||
{
|
||||
int sp = tag.indexOf(" ");
|
||||
String tag_type="";
|
||||
if(sp < 0) {
|
||||
tag_type = tag.toLowerCase();
|
||||
} else {
|
||||
tag_type = tag.substring(0, sp).toLowerCase();
|
||||
}
|
||||
if(tag_type.startsWith("tmpl_"))
|
||||
tag_type=tag_type.substring(5);
|
||||
|
||||
Util.debug_print("tag_type: " + tag_type);
|
||||
|
||||
if(tag_type.equals("var") ||
|
||||
tag_type.equals("if") ||
|
||||
tag_type.equals("unless") ||
|
||||
tag_type.equals("loop") ||
|
||||
tag_type.equals("include") ||
|
||||
tag_type.equals("else")) {
|
||||
return tag_type;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Properties getTagProps(String tag)
|
||||
throws IllegalArgumentException,
|
||||
NullPointerException
|
||||
{
|
||||
Properties p = new Properties();
|
||||
|
||||
tag = cleanTag(tag);
|
||||
|
||||
Util.debug_print("clean: " + tag);
|
||||
|
||||
if(tag.startsWith("/")) {
|
||||
p.put("close", "true");
|
||||
tag=tag.substring(1);
|
||||
} else {
|
||||
p.put("close", "");
|
||||
}
|
||||
|
||||
Util.debug_print("close: " + p.getProperty("close"));
|
||||
|
||||
p.put("type", getTagType(tag));
|
||||
|
||||
Util.debug_print("type: " + p.getProperty("type"));
|
||||
|
||||
if(p.getProperty("type").equals("else") ||
|
||||
p.getProperty("close").equals("true"))
|
||||
return p;
|
||||
|
||||
if(p.getProperty("type").equals("var"))
|
||||
p.put("escape", "");
|
||||
|
||||
int sp = tag.indexOf(" ");
|
||||
// if we've got so far, this must succeed
|
||||
|
||||
tag = tag.substring(sp).trim();
|
||||
Util.debug_print("checking params: " + tag);
|
||||
|
||||
// now, we should have either name=value pairs
|
||||
// or name space escape in case of old style vars
|
||||
|
||||
if(tag.indexOf("=") < 0) {
|
||||
// no = means old style
|
||||
// first will be var name
|
||||
// second if any will be escape
|
||||
|
||||
sp = tag.toLowerCase().indexOf(" escape");
|
||||
if(sp < 0) {
|
||||
// no escape
|
||||
p.put("name", tag);
|
||||
p.put("escape", "0");
|
||||
} else {
|
||||
tag = tag.substring(0, sp);
|
||||
p.put("name", tag);
|
||||
p.put("escape", "html");
|
||||
}
|
||||
} else {
|
||||
// = means name=value pairs.
|
||||
// use a StringTokenizer
|
||||
StringTokenizer st = new StringTokenizer(tag, " =");
|
||||
while(st.hasMoreTokens()) {
|
||||
String key, value;
|
||||
key = st.nextToken().toLowerCase();
|
||||
if(st.hasMoreTokens())
|
||||
value = st.nextToken();
|
||||
else if(key.equals("escape"))
|
||||
value = "html";
|
||||
else
|
||||
throw new NullPointerException(
|
||||
"parameter " + key + " has no value");
|
||||
|
||||
if(value.startsWith("\"") &&
|
||||
value.endsWith("\""))
|
||||
value = value.substring(1,
|
||||
value.length()-1);
|
||||
else if(value.startsWith("'") &&
|
||||
value.endsWith("'"))
|
||||
value = value.substring(1,
|
||||
value.length()-1);
|
||||
|
||||
if(value.length()==0)
|
||||
throw new NullPointerException(
|
||||
"parameter " + key + " has no value");
|
||||
|
||||
if(key.equals("escape"))
|
||||
value=value.toLowerCase();
|
||||
|
||||
p.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
String name = p.getProperty("name");
|
||||
// if not case sensitive, and not special variable, flatten case
|
||||
// never flatten case for includes
|
||||
if(!case_sensitive && !p.getProperty("type").equals("include")
|
||||
&& !( name.startsWith("__") && name.endsWith("__") ))
|
||||
{
|
||||
p.put("name", name.toLowerCase());
|
||||
}
|
||||
|
||||
if(!Util.isNameChar(name))
|
||||
throw new IllegalArgumentException(
|
||||
"parameter name may only contain " +
|
||||
"letters, digits, ., /, +, -, _");
|
||||
// __var__ is allowed in the template, but not in the
|
||||
// code. this is so that people can reference __FIRST__,
|
||||
// etc
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
130
apps/q/java/src/HTML/Tmpl/Util.java
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* 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 either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
package HTML.Tmpl;
|
||||
|
||||
public class Util
|
||||
{
|
||||
public static boolean debug=false;
|
||||
|
||||
public static String escapeHTML(String element)
|
||||
{
|
||||
String s = new String(element); // don't change the original
|
||||
String [] metas = {"&", "<", ">", "\""};
|
||||
String [] repls = {"&", "<", ">", """};
|
||||
for(int i = 0; i < metas.length; i++) {
|
||||
int pos=0;
|
||||
do {
|
||||
pos = s.indexOf(metas[i], pos);
|
||||
if(pos<0)
|
||||
break;
|
||||
|
||||
s = s.substring(0, pos) + repls[i] + s.substring(pos+1);
|
||||
pos++;
|
||||
} while(pos >= 0);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String escapeURL(String url)
|
||||
{
|
||||
StringBuffer s = new StringBuffer();
|
||||
String no_escape = "./-_";
|
||||
|
||||
for(int i=0; i<url.length(); i++)
|
||||
{
|
||||
char c = url.charAt(i);
|
||||
if(!Character.isLetterOrDigit(c) &&
|
||||
no_escape.indexOf(c)<0)
|
||||
{
|
||||
String h = Integer.toHexString((int)c);
|
||||
s.append("%");
|
||||
if(h.length()<2)
|
||||
s.append("0");
|
||||
s.append(h);
|
||||
} else {
|
||||
s.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public static String escapeQuote(String element)
|
||||
{
|
||||
String s = new String(element); // don't change the original
|
||||
String [] metas = {"\"", "'"};
|
||||
String [] repls = {"\\\"", "\\'"};
|
||||
for(int i = 0; i < metas.length; i++) {
|
||||
int pos=0;
|
||||
do {
|
||||
pos = s.indexOf(metas[i], pos);
|
||||
if(pos<0)
|
||||
break;
|
||||
|
||||
s = s.substring(0, pos) + repls[i] + s.substring(pos+1);
|
||||
pos++;
|
||||
} while(pos >= 0);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static boolean isNameChar(char c)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isNameChar(String s)
|
||||
{
|
||||
String alt_valid = "./+-_";
|
||||
|
||||
for(int i=0; i<s.length(); i++)
|
||||
if(!Character.isLetterOrDigit(s.charAt(i)) &&
|
||||
alt_valid.indexOf(s.charAt(i))<0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void debug_print(String msg)
|
||||
{
|
||||
if(!debug)
|
||||
return;
|
||||
|
||||
System.err.println(msg);
|
||||
}
|
||||
|
||||
public static void debug_print(Object o)
|
||||
{
|
||||
debug_print(o.toString());
|
||||
}
|
||||
}
|
19
apps/q/java/src/net/i2p/aum/AumUtil.java
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* AumUtil.java
|
||||
*
|
||||
* Created on March 24, 2005, 3:10 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author david
|
||||
*/
|
||||
public class AumUtil {
|
||||
|
||||
/** Creates a new instance of AumUtil */
|
||||
public AumUtil() {
|
||||
}
|
||||
|
||||
}
|
67
apps/q/java/src/net/i2p/aum/DupHashtable.java
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* NonUniqueProperties.java
|
||||
*
|
||||
* Created on April 9, 2005, 10:46 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* similar in some ways to Properties, except that duplicate keys
|
||||
* are allowed
|
||||
*/
|
||||
public class DupHashtable extends Hashtable {
|
||||
|
||||
/** Creates a new instance of NonUniqueProperties */
|
||||
public DupHashtable() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** Adds a value to be stored against key */
|
||||
public void put(String key, String value) {
|
||||
|
||||
if (!containsKey(key)) {
|
||||
put(key, new Vector());
|
||||
}
|
||||
|
||||
((Vector)get(key)).addElement(value);
|
||||
}
|
||||
|
||||
/** retrieves a Vector of values for key, or empty vector if none */
|
||||
public Vector get(String key) {
|
||||
if (!containsKey(key)) {
|
||||
return new Vector();
|
||||
} else {
|
||||
return (Vector)super.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
/** returns the i-th value for given key, or dflt if key not found */
|
||||
public String get(String key, int idx, String dflt) {
|
||||
if (containsKey(key)) {
|
||||
return get(key, idx);
|
||||
} else {
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/** returns the i-th value for given key
|
||||
* @throws ArrayIndexOutOfBoundsException if idx is out of range
|
||||
*/
|
||||
public String get(String key, int idx) {
|
||||
return (String)((Vector)get(key)).get(idx);
|
||||
}
|
||||
|
||||
/** returns the number of values for a given key */
|
||||
public int numValuesFor(String key) {
|
||||
if (!containsKey(key)) {
|
||||
return 0;
|
||||
} else {
|
||||
return ((Vector)get(key)).size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
155
apps/q/java/src/net/i2p/aum/EchoClient.java
Normal file
@ -0,0 +1,155 @@
|
||||
|
||||
// a simple I2P stream client that makes connections to an EchoServer,
|
||||
// sends in stuff, and gets replies
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.util.*;
|
||||
|
||||
/**
|
||||
* a simple program which illustrates the use of I2P stream
|
||||
* sockets from a client point of view
|
||||
*/
|
||||
|
||||
public class EchoClient extends Thread
|
||||
{
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PSocket clientSocket;
|
||||
|
||||
public Destination dest;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
/**
|
||||
* Creates an echoclient, given an I2P Destination object
|
||||
*/
|
||||
public EchoClient(Destination remdest)
|
||||
{
|
||||
_log = new Log("EchoServer");
|
||||
|
||||
_init(remdest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an EchoClient given a destination in base64
|
||||
*/
|
||||
public EchoClient(String destStr) throws DataFormatException
|
||||
{
|
||||
_log = new Log("EchoServer");
|
||||
|
||||
Destination remdest = new Destination();
|
||||
remdest.fromBase64(destStr);
|
||||
_init(remdest);
|
||||
}
|
||||
|
||||
private void _init(Destination remdest)
|
||||
{
|
||||
dest = remdest;
|
||||
|
||||
System.out.println("Client: dest="+dest.toBase64());
|
||||
|
||||
System.out.println("Client: Creating client socketManager");
|
||||
|
||||
// get a socket manager
|
||||
socketManager = I2PSocketManagerFactory.createManager();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* runs the EchoClient demo
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
InputStream socketIn;
|
||||
OutputStreamWriter socketOut;
|
||||
OutputStream socketOutStream;
|
||||
|
||||
System.out.println("Client: Creating connected client socket");
|
||||
System.out.println("dest="+dest.toBase64());
|
||||
|
||||
try {
|
||||
// get a client socket
|
||||
clientSocket = socketManager.connect(dest);
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (ConnectException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (NoRouteToHostException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (InterruptedIOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Client: Successfully connected!");
|
||||
|
||||
try {
|
||||
socketIn = clientSocket.getInputStream();
|
||||
socketOutStream = clientSocket.getOutputStream();
|
||||
socketOut = new OutputStreamWriter(socketOutStream);
|
||||
|
||||
System.out.println("Client: created streams");
|
||||
|
||||
socketOut.write("Hi there server!\n");
|
||||
socketOut.flush();
|
||||
|
||||
System.out.println("Client: sent to server, awaiting reply");
|
||||
|
||||
String line = DataHelper.readLine(socketIn);
|
||||
|
||||
System.out.println("Got reply: '" + line + "'");
|
||||
|
||||
clientSocket.close();
|
||||
|
||||
} catch (NoRouteToHostException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException!!");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* allows the echo client to be run from a command shell
|
||||
*/
|
||||
public static void main(String [] args)
|
||||
{
|
||||
String dest64 = args[0];
|
||||
System.out.println("dest="+dest64);
|
||||
|
||||
Destination d = new Destination();
|
||||
try {
|
||||
d.fromBase64(dest64);
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
EchoClient client = new EchoClient(d);
|
||||
|
||||
System.out.println("client: running");
|
||||
client.run();
|
||||
|
||||
System.out.println("client: done");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
163
apps/q/java/src/net/i2p/aum/EchoServer.java
Normal file
@ -0,0 +1,163 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.util.*;
|
||||
|
||||
/**
|
||||
* a simple program which illustrates the use of I2P stream
|
||||
* sockets from a server point of view
|
||||
*/
|
||||
public class EchoServer extends Thread
|
||||
{
|
||||
//public I2PClient client;
|
||||
//public PrivDestination privDest;
|
||||
//public I2PSession serverSession;
|
||||
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
|
||||
public PrivDestination key;
|
||||
public Destination dest;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
public EchoServer() throws I2PException, IOException
|
||||
{
|
||||
_log = new Log("EchoServer");
|
||||
|
||||
System.out.println("Server: creating new key");
|
||||
|
||||
// key = PrivDestination.newKey();
|
||||
// System.out.println("Server: dest=" + key.toDestinationBase64());
|
||||
|
||||
System.out.println("Server: creating socket manager");
|
||||
|
||||
Properties props = new Properties();
|
||||
props.setProperty("inbound.length", "0");
|
||||
props.setProperty("outbound.length", "0");
|
||||
props.setProperty("inbound.lengthVariance", "0");
|
||||
props.setProperty("outbound.lengthVariance", "0");
|
||||
|
||||
PrivDestination key = PrivDestination.newKey();
|
||||
|
||||
// get a socket manager
|
||||
// socketManager = I2PSocketManagerFactory.createManager(key);
|
||||
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
|
||||
|
||||
System.out.println("Server: getting server socket");
|
||||
|
||||
// get a server socket
|
||||
serverSocket = socketManager.getServerSocket();
|
||||
|
||||
System.out.println("Server: got server socket, ready to run");
|
||||
|
||||
dest = socketManager.getSession().getMyDestination();
|
||||
|
||||
System.out.println("Server: getMyDestination->"+dest.toBase64());
|
||||
|
||||
start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* run this EchoServer
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
System.out.println("Server: listening on dest:");
|
||||
|
||||
/**
|
||||
try {
|
||||
System.out.println(key.toDestinationBase64());
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
*/
|
||||
|
||||
System.out.println(dest.toBase64());
|
||||
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
I2PSocket sessSocket = serverSocket.accept();
|
||||
|
||||
System.out.println("Server: Got connection from client");
|
||||
|
||||
InputStream socketIn = sessSocket.getInputStream();
|
||||
OutputStreamWriter socketOut = new OutputStreamWriter(sessSocket.getOutputStream());
|
||||
|
||||
System.out.println("Server: created streams");
|
||||
|
||||
// read a line from input, and echo it back
|
||||
String line = DataHelper.readLine(socketIn);
|
||||
|
||||
System.out.println("Server: got '" + line + "'");
|
||||
|
||||
String reply = "EchoServer: got '" + line + "'\n";
|
||||
socketOut.write(reply);
|
||||
socketOut.flush();
|
||||
|
||||
System.out.println("Server: sent trply");
|
||||
|
||||
sessSocket.close();
|
||||
|
||||
System.out.println("Server: closed socket");
|
||||
|
||||
} catch (ConnectException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Destination getDest() throws DataFormatException
|
||||
{
|
||||
// return key.toDestination();
|
||||
return dest;
|
||||
}
|
||||
|
||||
public String getDestBase64() throws DataFormatException
|
||||
{
|
||||
// return key.toDestinationBase64();
|
||||
return dest.toBase64();
|
||||
}
|
||||
|
||||
/**
|
||||
* runs EchoServer from the command shell
|
||||
*/
|
||||
public static void main(String [] args)
|
||||
{
|
||||
System.out.println("Constructing an EchoServer");
|
||||
|
||||
try {
|
||||
EchoServer myServer = new EchoServer();
|
||||
System.out.println("Got an EchoServer");
|
||||
System.out.println("Here's the dest:");
|
||||
System.out.println(myServer.getDestBase64());
|
||||
|
||||
myServer.run();
|
||||
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
56
apps/q/java/src/net/i2p/aum/EchoTest.java
Normal file
@ -0,0 +1,56 @@
|
||||
// runs EchoServer and EchoClient as threads
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* A simple program which runs the EchoServer and EchoClient
|
||||
* demos as threads
|
||||
*/
|
||||
|
||||
public class EchoTest
|
||||
{
|
||||
/**
|
||||
* create one instance each of EchoServer and EchoClient,
|
||||
* run the server as a thread, run the client in foreground,
|
||||
* display detailed results
|
||||
*/
|
||||
public static void main(String [] args)
|
||||
{
|
||||
EchoServer server;
|
||||
EchoClient client;
|
||||
|
||||
try {
|
||||
server = new EchoServer();
|
||||
Destination serverDest = server.getDest();
|
||||
|
||||
System.out.println("EchoTest: serverDest=" + serverDest.toBase64());
|
||||
|
||||
client = new EchoClient(serverDest);
|
||||
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace(); return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(); return;
|
||||
}
|
||||
|
||||
System.out.println("Starting server...");
|
||||
//server.start();
|
||||
|
||||
System.out.println("Starting client...");
|
||||
client.run();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
322
apps/q/java/src/net/i2p/aum/EmbargoedQueue.java
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
* SimpleScheduler.java
|
||||
*
|
||||
* Created on March 24, 2005, 11:14 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Implements a queue of objects, where each object is 'embargoed'
|
||||
* against release until a given time. Threads which attempt to .get
|
||||
* items from this queue will block if the queue is empty, or if the
|
||||
* first item of the queue has a 'release time' which has not yet passed.</p>
|
||||
*
|
||||
* <p>Think of it like a news desk which receives media releases which are
|
||||
* 'embargoed' till a certain time. These releases sit in a queue, and when
|
||||
* their embargo expires, they are actioned and go to print or broadcast.
|
||||
* The reporters at this news desk are the 'threads', which get blocked
|
||||
* until the next item's embargo expires.</p>
|
||||
*
|
||||
* <p>Purpose of implementing this is to provide a mechanism for scheduling
|
||||
* background jobs to be executed at precise times</p>.
|
||||
*/
|
||||
public class EmbargoedQueue extends Thread {
|
||||
|
||||
/**
|
||||
* items which are waiting for dispatch - stored as 2-element vectors,
|
||||
* where elem 0 is Integer dispatch time, and elem 1 is the object;
|
||||
* note that this list is kept in strict ascending order of time.
|
||||
* Whenever an object becomes ready, it is removed from this queue
|
||||
* and appended to readyItems
|
||||
*/
|
||||
public Vector waitingItems;
|
||||
|
||||
/**
|
||||
* items which are ready for dispatch (their time has come).
|
||||
*/
|
||||
public SimpleQueue readyItems;
|
||||
|
||||
/** set this true to enable verbose debug messages */
|
||||
public boolean debug = false;
|
||||
|
||||
/** Creates a new embargoed queue */
|
||||
public EmbargoedQueue() {
|
||||
waitingItems = new Vector();
|
||||
readyItems = new SimpleQueue();
|
||||
|
||||
// fire up scheduler thread
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches the item at head of queue, blocking if queue is empty
|
||||
*/
|
||||
public Object get()
|
||||
{
|
||||
return readyItems.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new object to queue without any embargo (or, an embargo that expires
|
||||
* immediately)
|
||||
* @param item the object to be added
|
||||
*/
|
||||
public synchronized void putNow(Object item)
|
||||
{
|
||||
putAfter(0, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new object to queue, embargoed until given number of milliseconds
|
||||
* have elapsed
|
||||
* @param delay number of milliseconds from now when embargo expires
|
||||
* @param item the object to be added
|
||||
*/
|
||||
public synchronized void putAfter(long delay, Object item)
|
||||
{
|
||||
long now = new Date().getTime();
|
||||
putAt(now+delay, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new object to the queue, embargoed until given time
|
||||
* @param time the unixtime in milliseconds when the object's embargo expires,
|
||||
* and the object is to be made available
|
||||
* @param item the object to be added
|
||||
*/
|
||||
public synchronized void putAt(long time, Object item)
|
||||
{
|
||||
Vector elem = new Vector();
|
||||
elem.addElement(new Long(time));
|
||||
elem.addElement(item);
|
||||
|
||||
long now = new Date().getTime();
|
||||
long future = time - now;
|
||||
//System.out.println("putAt: time="+time+" ("+future+"ms from now), job="+item);
|
||||
|
||||
// find where to insert
|
||||
int i;
|
||||
int nitems = waitingItems.size();
|
||||
for (i = 0; i < nitems; i++)
|
||||
{
|
||||
// get item i
|
||||
Vector itemI = (Vector)waitingItems.get(i);
|
||||
long timeI = ((Long)(itemI.get(0))).longValue();
|
||||
if (time < timeI)
|
||||
{
|
||||
// new item earlier than item i, insert here and bust out
|
||||
waitingItems.insertElementAt(elem, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// did we insert?
|
||||
if (i == nitems)
|
||||
{
|
||||
// no - gotta append
|
||||
waitingItems.addElement(elem);
|
||||
}
|
||||
|
||||
// debugging
|
||||
if (debug) {
|
||||
printWaiting();
|
||||
}
|
||||
|
||||
// awaken this scheduler object's thread, so it can
|
||||
// see if any jobs are ready
|
||||
//notify();
|
||||
interrupt();
|
||||
}
|
||||
|
||||
/**
|
||||
* for debugging - prints out a list of waiting items
|
||||
*/
|
||||
public synchronized void printWaiting()
|
||||
{
|
||||
int i;
|
||||
long now = new Date().getTime();
|
||||
|
||||
System.out.println("EmbargoedQueue dump:");
|
||||
|
||||
System.out.println(" Waiting items:");
|
||||
int nwaiting = waitingItems.size();
|
||||
for (i = 0; i < nwaiting; i++)
|
||||
{
|
||||
Vector item = (Vector)waitingItems.get(i);
|
||||
long when = ((Long)item.get(0)).longValue();
|
||||
Object job = item.get(1);
|
||||
int delay = (int)(when - now)/1000;
|
||||
System.out.println(" "+delay+"s, t="+when+", job="+job);
|
||||
}
|
||||
|
||||
System.out.println(" Ready items:");
|
||||
int nready = readyItems.items.size();
|
||||
for (i = 0; i < nready; i++)
|
||||
{
|
||||
//Vector item = (Vector)readyItems.items.get(i);
|
||||
Object item = readyItems.items.get(i);
|
||||
System.out.println(" job="+item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* scheduling thread, which wakes up every time a new job is queued, and
|
||||
* if any jobs are ready, transfers them to the readyQueue and notifies
|
||||
* any waiting client threads
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
// monitor the waiting queue, waiting till one becomes ready
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
if (waitingItems.size() > 0)
|
||||
{
|
||||
// at least 1 waiting item
|
||||
Vector item = (Vector)(waitingItems.get(0));
|
||||
long now = new Date().getTime();
|
||||
long then = ((Long)item.get(0)).longValue();
|
||||
long delay = then - now;
|
||||
|
||||
// ready?
|
||||
if (delay <= 0)
|
||||
{
|
||||
// yep, ready, remove job and stick on waiting queue
|
||||
waitingItems.remove(0); // ditch from waiting
|
||||
Object elem = item.get(1);
|
||||
readyItems.put(elem); // and add to ready
|
||||
|
||||
if (debug)
|
||||
{
|
||||
System.out.println("embargo expired on "+elem);
|
||||
printWaiting();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not ready, hang about till we get woken, or the
|
||||
// job becomes ready
|
||||
if (debug)
|
||||
{
|
||||
System.out.println("waiting for "+delay+"ms");
|
||||
}
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no items yet, hang out for an interrupt
|
||||
if (debug)
|
||||
{
|
||||
System.out.println("queue is empty");
|
||||
}
|
||||
synchronized (this) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//System.out.println("exception");
|
||||
if (debug)
|
||||
{
|
||||
System.out.println("exception ("+e.getClass().getName()+") "+e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestThread extends Thread {
|
||||
|
||||
String id;
|
||||
|
||||
EmbargoedQueue q;
|
||||
|
||||
public TestThread(String id, EmbargoedQueue q) {
|
||||
this.id = id;
|
||||
this.q = q;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
print("waiting for queue");
|
||||
|
||||
Object item = q.get();
|
||||
|
||||
print("got item: '"+item+"'");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void print(String msg) {
|
||||
System.out.println("thread '"+id+"': "+msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
int i;
|
||||
int nthreads = 7;
|
||||
|
||||
Thread [] threads = new Thread[nthreads];
|
||||
|
||||
EmbargoedQueue q = new EmbargoedQueue();
|
||||
SimpleSemaphore threadPool = new SimpleSemaphore(nthreads);
|
||||
|
||||
// populate the queue with some stuff
|
||||
q.putAfter(10000, "red");
|
||||
q.putAfter(3000, "orange");
|
||||
q.putAfter(6000, "yellow");
|
||||
|
||||
// populate threads array
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
threads[i] = new TestThread("thread"+i, q);
|
||||
}
|
||||
|
||||
// and launch the threads
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
// wait, presumably till all these elements are actioned
|
||||
try {
|
||||
Thread.sleep(12000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
// add some more shit to the queue, randomly scheduled
|
||||
Random r = new Random();
|
||||
String [] items = {"green", "blue", "indigo", "violet", "black", "white", "brown"};
|
||||
for (i = 0; i < items.length; i++) {
|
||||
String item = items[i];
|
||||
int delay = 2000 + r.nextInt(8000);
|
||||
System.out.println("main: adding '"+item+"' after "+delay+"ms ...");
|
||||
q.putAfter(delay, item);
|
||||
}
|
||||
|
||||
// wait, presumably for all jobs to finish
|
||||
try {
|
||||
Thread.sleep(12000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("main: terminating");
|
||||
|
||||
}
|
||||
|
||||
}
|
452
apps/q/java/src/net/i2p/aum/I2PCat.java
Normal file
@ -0,0 +1,452 @@
|
||||
|
||||
// I2P equivalent of 'netcat'
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.naming.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.util.*;
|
||||
|
||||
/**
|
||||
* A I2P equivalent of the much-beloved 'netcat' utility.
|
||||
* This command-line utility can either connect to a remote
|
||||
* destination, or listen on a private destination for incoming
|
||||
* connections. Once a connection is established, input on stdin
|
||||
* is sent to the remote peer, and anything received from the
|
||||
* remote peer is printed to stdout
|
||||
*/
|
||||
|
||||
public class I2PCat extends Thread
|
||||
{
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
public I2PSocket sessSocket;
|
||||
|
||||
public PrivDestination key;
|
||||
public Destination dest;
|
||||
|
||||
public InputStream socketIn;
|
||||
public OutputStream socketOutStream;
|
||||
public OutputStreamWriter socketOut;
|
||||
|
||||
public SockInput rxThread;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
public static String defaultHost = "127.0.0.1";
|
||||
public static int defaultPort = 7654;
|
||||
|
||||
/**
|
||||
* a thread for reading from socket and displaying on stdout
|
||||
*/
|
||||
private class SockInput extends Thread {
|
||||
|
||||
InputStream _in;
|
||||
|
||||
protected Log _log;
|
||||
public SockInput(InputStream i) {
|
||||
|
||||
_in = i;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
// the thread portion, receives incoming bytes on
|
||||
// the socket input stream and spits them to stdout
|
||||
|
||||
byte [] ch = new byte[1];
|
||||
|
||||
print("Receiver thread listening...");
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
|
||||
//String line = DataHelper.readLine(socketIn);
|
||||
if (_in.read(ch) != 1) {
|
||||
print("failed to receive from socket");
|
||||
break;
|
||||
}
|
||||
|
||||
//System.out.println(line);
|
||||
System.out.write(ch, 0, 1);
|
||||
System.out.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
print("Receiver thread crashed, terminating!!");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void print(String msg)
|
||||
{
|
||||
System.out.println("-=- I2PCat: "+msg);
|
||||
|
||||
if (_log != null) {
|
||||
_log.debug(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public I2PCat()
|
||||
{
|
||||
_log = new Log("I2PCat");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs I2PCat in server mode, listening on the given destination
|
||||
* for one incoming connection. Once connection is established,
|
||||
* copyies data between the remote peer and
|
||||
* the local terminal console.
|
||||
*/
|
||||
public void runServer(String keyStr) throws IOException, DataFormatException
|
||||
{
|
||||
Properties props = new Properties();
|
||||
props.setProperty("inbound.length", "0");
|
||||
props.setProperty("outbound.length", "0");
|
||||
props.setProperty("inbound.lengthVariance", "0");
|
||||
props.setProperty("outbound.lengthVariance", "0");
|
||||
|
||||
// generate new key if needed
|
||||
if (keyStr.equals("new")) {
|
||||
|
||||
try {
|
||||
key = PrivDestination.newKey();
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
print("Creating new server dest...");
|
||||
|
||||
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
|
||||
|
||||
print("Getting server socket...");
|
||||
|
||||
serverSocket = socketManager.getServerSocket();
|
||||
|
||||
print("Server socket created, ready to run...");
|
||||
|
||||
dest = socketManager.getSession().getMyDestination();
|
||||
|
||||
print("private key follows:");
|
||||
System.out.println(key.toBase64());
|
||||
|
||||
print("dest follows:");
|
||||
System.out.println(dest.toBase64());
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
key = PrivDestination.fromBase64String(keyStr);
|
||||
|
||||
String dest64Abbrev = key.toBase64().substring(0, 16);
|
||||
|
||||
print("Creating server socket manager on dest "+dest64Abbrev+"...");
|
||||
|
||||
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
|
||||
|
||||
serverSocket = socketManager.getServerSocket();
|
||||
|
||||
print("Server socket created, ready to run...");
|
||||
}
|
||||
|
||||
print("Awaiting client connection...");
|
||||
|
||||
I2PSocket sessSocket;
|
||||
|
||||
try {
|
||||
sessSocket = serverSocket.accept();
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (ConnectException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
print("Got connection from client");
|
||||
|
||||
chat(sessSocket);
|
||||
|
||||
}
|
||||
|
||||
public void runClient(String destStr)
|
||||
throws DataFormatException, IOException
|
||||
{
|
||||
runClient(destStr, defaultHost, defaultPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* runs I2PCat in client mode, connecting to a remote
|
||||
* destination then copying data between the remote peer and
|
||||
* the local terminal console
|
||||
*/
|
||||
public void runClient(String destStr, String host, int port)
|
||||
throws DataFormatException, IOException
|
||||
{
|
||||
// accept 'file:' prefix
|
||||
if (destStr.startsWith("file:", 0))
|
||||
{
|
||||
String path = destStr.substring(5);
|
||||
destStr = new SimpleFile(path, "r").read();
|
||||
}
|
||||
|
||||
else if (destStr.length() < 255) {
|
||||
// attempt hosts file lookup
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
HostsTxtNamingService h = new HostsTxtNamingService(ctx);
|
||||
Destination dest1 = h.lookup(destStr);
|
||||
if (dest1 == null) {
|
||||
usage("Cannot resolve hostname: '"+destStr+"'");
|
||||
}
|
||||
|
||||
// successful lookup
|
||||
runClient(dest1, host, port);
|
||||
}
|
||||
|
||||
else {
|
||||
// otherwise, bigger strings are assumed to be base64 dests
|
||||
|
||||
Destination dest = new Destination();
|
||||
dest.fromBase64(destStr);
|
||||
runClient(dest, host, port);
|
||||
}
|
||||
}
|
||||
|
||||
public void runClient(Destination dest) {
|
||||
runClient(dest, "127.0.0.1", 7654);
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative constructor which accepts an I2P Destination object
|
||||
*/
|
||||
public void runClient(Destination dest, String host, int port)
|
||||
{
|
||||
this.dest = dest;
|
||||
|
||||
String destAbbrev = dest.toBase64().substring(0, 16)+"...";
|
||||
|
||||
print("Connecting via i2cp "+host+":"+port+" to destination "+destAbbrev+"...");
|
||||
System.out.flush();
|
||||
|
||||
try {
|
||||
// get a socket manager
|
||||
socketManager = I2PSocketManagerFactory.createManager(host, port);
|
||||
|
||||
// get a client socket
|
||||
print("socketManager="+socketManager);
|
||||
|
||||
sessSocket = socketManager.connect(dest);
|
||||
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (ConnectException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (NoRouteToHostException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (InterruptedIOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
print("Successfully connected!");
|
||||
print("(Press Control-C to quit)");
|
||||
|
||||
// Perform console interaction
|
||||
chat(sessSocket);
|
||||
|
||||
try {
|
||||
sessSocket.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the background thread to copy incoming data to stdout, then
|
||||
* loop in foreground copying lines from stdin and sending them to remote peer
|
||||
*/
|
||||
public void chat(I2PSocket sessSocket) {
|
||||
|
||||
try {
|
||||
socketIn = sessSocket.getInputStream();
|
||||
socketOutStream = sessSocket.getOutputStream();
|
||||
socketOut = new OutputStreamWriter(socketOutStream);
|
||||
|
||||
// launch receiver thread
|
||||
start();
|
||||
//launchRx();
|
||||
|
||||
while (true) {
|
||||
|
||||
String line = DataHelper.readLine(System.in);
|
||||
print("sent: '"+line+"'");
|
||||
|
||||
socketOut.write(line+"\n");
|
||||
socketOut.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* executes in a thread, receiving incoming bytes on
|
||||
* the socket input stream and spitting them to stdout
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
|
||||
byte [] ch = new byte[1];
|
||||
|
||||
print("Receiver thread listening...");
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
|
||||
//String line = DataHelper.readLine(socketIn);
|
||||
if (socketIn.read(ch) != 1) {
|
||||
print("failed to receive from socket");
|
||||
break;
|
||||
}
|
||||
|
||||
//System.out.println(line);
|
||||
System.out.write(ch, 0, 1);
|
||||
System.out.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
print("Receiver thread crashed, terminating!!");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void launchRx() {
|
||||
|
||||
rxThread = new SockInput(socketIn);
|
||||
rxThread.start();
|
||||
|
||||
}
|
||||
|
||||
static void print(String msg)
|
||||
{
|
||||
System.out.println("-=- I2PCat: "+msg);
|
||||
|
||||
if (_log != null) {
|
||||
_log.debug(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void usage(String msg)
|
||||
{
|
||||
usage(msg, 1);
|
||||
}
|
||||
|
||||
public static void usage(String msg, int ret)
|
||||
{
|
||||
System.out.println(msg);
|
||||
usage(ret);
|
||||
}
|
||||
|
||||
public static void usage(int ret)
|
||||
{
|
||||
System.out.print(
|
||||
"This utility is an I2P equivalent of the standard *nix 'netcat' utility\n"+
|
||||
"usage:\n"+
|
||||
" net.i2p.aum.I2PCat [-h]\n"+
|
||||
" - display this help\n"+
|
||||
" net.i2p.aum.I2PCat dest [host [port]]\n"+
|
||||
" - run in client mode, 'dest' should be one of:\n"+
|
||||
" hostname.i2p - an I2P hostname listed in hosts.txt\n"+
|
||||
" (only works with a hosts.txt in current directory)\n"+
|
||||
" base64dest - a full base64 destination string\n"+
|
||||
" file:b64filename - filename of a file containing base64 dest\n"+
|
||||
" net.i2p.aum.I2PCat -l privkey\n"+
|
||||
" - run in server mode, 'key' should be one of:\n"+
|
||||
" base64privkey - a full base64 private key string\n"+
|
||||
" file:b64filename - filename of a file containing base64 privkey\n"+
|
||||
"\n"
|
||||
);
|
||||
System.exit(ret);
|
||||
}
|
||||
|
||||
public static void main(String [] args) throws IOException, DataFormatException
|
||||
{
|
||||
int argc = args.length;
|
||||
|
||||
// barf if no args
|
||||
if (argc == 0) {
|
||||
usage("Missing argument");
|
||||
}
|
||||
|
||||
// show help on request
|
||||
if (args[0].equals("-h") || args[0].equals("--help")) {
|
||||
usage(0);
|
||||
}
|
||||
|
||||
// server or client?
|
||||
if (args[0].equals("-l")) {
|
||||
if (argc != 2) {
|
||||
usage("Bad argument count");
|
||||
}
|
||||
|
||||
new I2PCat().runServer(args[1]);
|
||||
}
|
||||
else {
|
||||
// client mode - barf if not 1-3 args
|
||||
if (argc < 1 || argc > 3) {
|
||||
usage("Bad argument count");
|
||||
}
|
||||
|
||||
try {
|
||||
int port = defaultPort;
|
||||
String host = defaultHost;
|
||||
if (args.length > 1) {
|
||||
host = args[1];
|
||||
if (args.length > 2) {
|
||||
port = new Integer(args[2]).intValue();
|
||||
}
|
||||
}
|
||||
new I2PCat().runClient(args[0], host, port);
|
||||
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
25
apps/q/java/src/net/i2p/aum/I2PSocketHelper.java
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* Class which wraps an I2PSocket object with convenient methods.
|
||||
* Nothing presently implemented here.
|
||||
*/
|
||||
|
||||
public class I2PSocketHelper
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|