Compare commits
232 Commits
i2p_0_5_0_
...
i2p_0_6_1_
Author | SHA1 | Date | |
---|---|---|---|
6019a03029 | |||
df5736f571 | |||
9a73c6defe | |||
9dfa87ba47 | |||
934a269753 | |||
1c0dfc242b | |||
3bc3e5d47e | |||
55869af2cc | |||
9f336dd05b | |||
411ca5e6c3 | |||
c528e4db03 | |||
848ead7683 | |||
1b8419b9b5 | |||
900420719e | |||
ef7d1ba964 | |||
ab1654c784 | |||
24bad8e4bb | |||
f6d8200bc8 | |||
aef33548b3 | |||
56ecdcce82 | |||
b9b59ff95f | |||
aa9dd3e5c6 | |||
30bd659149 | |||
3286ca49c8 | |||
7700d12178 | |||
557b7e3f2e | |||
3e1e9146e1 | |||
40d8d1aac1 | |||
1457b8efba | |||
3821e80ac8 | |||
d40bb459ea | |||
edf04f07c9 | |||
2bdea23986 | |||
6be0c4b694 | |||
2a272f465c | |||
a8ecd32b45 | |||
20c42a175d | |||
d6c3ffde87 | |||
177e0ae6a3 | |||
dab1b4d256 | |||
3aba12631b | |||
cfee6430d4 | |||
6b96df1cec | |||
deecfa5047 | |||
6ca3f01038 | |||
d89f589f2b | |||
8c1895e04f | |||
c3d0132a98 | |||
d955279d17 | |||
76266dce0d | |||
5694206b35 | |||
4293a18726 | |||
9865af4174 | |||
c8c109093d | |||
b5784d6025 | |||
31bdb8909a | |||
ee921c22ae | |||
172ffd0434 | |||
d9b4406c09 | |||
8ac0e85df4 | |||
249ccd5e3c | |||
727d76d43e | |||
44770b7c07 | |||
b5d571c75f | |||
da56d83716 | |||
f777e213ce | |||
79906f5a7d | |||
54074e76b5 | |||
c2ea8db683 | |||
744671a518 | |||
7f5b127bbc | |||
89eff0c628 | |||
177aeebb1c | |||
e0e6bde4a5 | |||
e6b145716f | |||
f958342704 | |||
5a1f738505 | |||
8147cdf40c | |||
6afc64ac39 | |||
61b8e3598b | |||
3bb445ff40 | |||
59a8037599 | |||
09cb5fad59 | |||
ee8e45ecf7 | |||
339868838d | |||
c5579fa349 | |||
d4a859547c | |||
779aa240d2 | |||
9aaad00383 | |||
6422f7ef78 | |||
3e51584b3c | |||
4ff8a53084 | |||
ccb73437c4 | |||
b43114f61b | |||
9bd87ab511 | |||
b6ea55f7ef | |||
5f18cec97d | |||
3ba921ec0e | |||
e313da254c | |||
8660cf0d74 | |||
e0bfdff152 | |||
c27aed3603 | |||
cdc6002f0e | |||
4cf3d9c1a2 | |||
0473e08e21 | |||
346faa3de2 | |||
5ec6dca64d | |||
1a6b49cfb8 | |||
c7b75df390 | |||
f97c09291b | |||
8f2a5b403c | |||
ea41a90eae | |||
b1dd29e64d | |||
46e47c47ac | |||
b7bf431f0d | |||
7f432122d9 | |||
e7be8c6097 | |||
adf56a16e1 | |||
11204b8a2b | |||
cade27dceb | |||
5597d28e59 | |||
0502fec432 | |||
a6714fc2de | |||
1219dadbd5 | |||
77b995f5ed | |||
2f53b9ff68 | |||
d84d045849 | |||
d8e72dfe48 | |||
88b9f7a74c | |||
6a19501214 | |||
ba30b56c5f | |||
a375e4b2ce | |||
44fd71e17f | |||
b41c378de9 | |||
4ce6b308b3 | |||
72c6e7d1c5 | |||
7ca3f22e77 | |||
59790dafef | |||
7227cae6ef | |||
03bba51c1e | |||
0637050cbc | |||
7f58a68c5a | |||
8120b0397c | |||
fbe42b7dce | |||
def24e34ad | |||
593253e6a3 | |||
56dd4cb8b5 | |||
10c6f67500 | |||
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 |
@ -6,8 +6,7 @@
|
||||
<property name="dist" location="dist"/>
|
||||
<property name="jar" value="addressbook.jar"/>
|
||||
<property name="war" value="addressbook.war"/>
|
||||
<property name="servlet" value="../jetty/jettylib/javax.servlet.jar"/>
|
||||
|
||||
|
||||
<target name="init">
|
||||
<mkdir dir="${build}"/>
|
||||
<mkdir dir="${dist}"/>
|
||||
@ -22,7 +21,12 @@
|
||||
|
||||
<target name="compile" depends="init">
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
srcdir="${src}" destdir="${build}" classpath="${servlet}"/>
|
||||
srcdir="${src}" destdir="${build}">
|
||||
<classpath>
|
||||
<pathelement location="../../core/java/build/i2p.jar" />
|
||||
<pathelement location="../jetty/jettylib/javax.servlet.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
|
@ -24,11 +24,12 @@ package addressbook;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.EepGet;
|
||||
|
||||
/**
|
||||
* An address book for storing human readable names mapped to base64 i2p
|
||||
* destinations. AddressBooks can be created from local and remote files, merged
|
||||
@ -65,14 +66,18 @@ public class AddressBook {
|
||||
* where key is a human readable name, and value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
public AddressBook(URL url) {
|
||||
this.location = url.getHost();
|
||||
|
||||
public AddressBook(String url, String proxyHost, int proxyPort) {
|
||||
this.location = url;
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp", url, true,
|
||||
null);
|
||||
get.fetch();
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(url);
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,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.
|
||||
|
@ -36,6 +36,7 @@ import java.io.File;
|
||||
*/
|
||||
public class Daemon {
|
||||
public static final String VERSION = "2.0.3";
|
||||
private static final Daemon _instance = new Daemon();
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
@ -56,17 +57,16 @@ public class Daemon {
|
||||
* @param log
|
||||
* The log to write changes and conflicts to.
|
||||
*/
|
||||
public static void update(AddressBook master, AddressBook router,
|
||||
public void update(AddressBook master, AddressBook router,
|
||||
File published, SubscriptionList subscriptions, Log log) {
|
||||
String routerLocation = router.getLocation();
|
||||
master.merge(router);
|
||||
router.merge(master, true, null);
|
||||
Iterator iter = subscriptions.iterator();
|
||||
while (iter.hasNext()) {
|
||||
master.merge((AddressBook) iter.next(), log);
|
||||
router.merge((AddressBook) iter.next(), false, log);
|
||||
}
|
||||
master.write(new File(routerLocation));
|
||||
router.write();
|
||||
if (published != null)
|
||||
master.write(published);
|
||||
router.write(published);
|
||||
subscriptions.write();
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ public class Daemon {
|
||||
* @param home
|
||||
* The directory containing addressbook's configuration files.
|
||||
*/
|
||||
public static void update(Map settings, String home) {
|
||||
public void update(Map settings, String home) {
|
||||
File masterFile = new File(home, (String) settings
|
||||
.get("master_addressbook"));
|
||||
File routerFile = new File(home, (String) settings
|
||||
@ -101,10 +101,11 @@ public class Daemon {
|
||||
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, defaultSubs);
|
||||
etagsFile, lastModifiedFile, defaultSubs, (String) settings
|
||||
.get("proxy_host"), Integer.parseInt((String) settings.get("proxy_port")));
|
||||
Log log = new Log(logFile);
|
||||
|
||||
Daemon.update(master, router, published, subscriptions, log);
|
||||
update(master, router, published, subscriptions, log);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,6 +119,10 @@ public class Daemon {
|
||||
* others are ignored.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
_instance.run(args);
|
||||
}
|
||||
|
||||
public void run(String[] args) {
|
||||
String settingsLocation = "config.txt";
|
||||
Map settings = new HashMap();
|
||||
String home;
|
||||
@ -151,24 +156,36 @@ public class Daemon {
|
||||
|
||||
File settingsFile = new File(homeFile, settingsLocation);
|
||||
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
// wait
|
||||
try {
|
||||
Thread.currentThread().sleep(5*60*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
while (true) {
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
|
||||
System.setProperty("proxySet", "true");
|
||||
System.setProperty("http.proxyHost", (String) settings
|
||||
.get("proxy_host"));
|
||||
System.setProperty("http.proxyPort", (String) settings
|
||||
.get("proxy_port"));
|
||||
long delay = Long.parseLong((String) settings.get("update_delay"));
|
||||
if (delay < 1) {
|
||||
delay = 1;
|
||||
}
|
||||
|
||||
Daemon.update(settings, home);
|
||||
update(settings, home);
|
||||
try {
|
||||
Thread.sleep(delay * 60 * 60 * 1000);
|
||||
synchronized (this) {
|
||||
wait(delay * 60 * 60 * 1000);
|
||||
}
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to get the addressbook to reread its config and
|
||||
* refetch its subscriptions.
|
||||
*/
|
||||
public static void wakeup() {
|
||||
synchronized (_instance) {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
@ -44,10 +44,10 @@ public class DaemonThread extends Thread {
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(5 * 60 * 1000);
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
//try {
|
||||
// Thread.sleep(5 * 60 * 1000);
|
||||
//} catch (InterruptedException exp) {
|
||||
//}
|
||||
Daemon.main(this.args);
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@ import java.util.List;
|
||||
public class SubscriptionIterator implements Iterator {
|
||||
|
||||
private Iterator subIterator;
|
||||
private String proxyHost;
|
||||
private int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
|
||||
@ -40,8 +42,10 @@ public class SubscriptionIterator implements Iterator {
|
||||
* @param subscriptions
|
||||
* List of Subscription objects that represent address books.
|
||||
*/
|
||||
public SubscriptionIterator(List subscriptions) {
|
||||
public SubscriptionIterator(List subscriptions, String proxyHost, int proxyPort) {
|
||||
this.subIterator = subscriptions.iterator();
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
}
|
||||
|
||||
|
||||
@ -49,15 +53,15 @@ public class SubscriptionIterator implements Iterator {
|
||||
* @see java.util.Iterator#hasNext()
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return subIterator.hasNext();
|
||||
return this.subIterator.hasNext();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#next()
|
||||
*/
|
||||
public Object next() {
|
||||
Subscription sub = (Subscription) subIterator.next();
|
||||
return new AddressBook(sub);
|
||||
Subscription sub = (Subscription) this.subIterator.next();
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
@ -42,6 +42,10 @@ public class SubscriptionList {
|
||||
private File etagsFile;
|
||||
|
||||
private File lastModifiedFile;
|
||||
|
||||
private String proxyHost;
|
||||
|
||||
private int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionList using the urls from locationsFile and, if
|
||||
@ -58,15 +62,18 @@ public class SubscriptionList {
|
||||
* GET. The file is in the format "url=leastmodified".
|
||||
*/
|
||||
public SubscriptionList(File locationsFile, File etagsFile,
|
||||
File lastModifiedFile, List defaultSubs) {
|
||||
File lastModifiedFile, List defaultSubs, String proxyHost,
|
||||
int proxyPort) {
|
||||
this.subscriptions = new LinkedList();
|
||||
this.etagsFile = etagsFile;
|
||||
this.lastModifiedFile = lastModifiedFile;
|
||||
List locations;
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
Map etags;
|
||||
Map lastModified;
|
||||
String location;
|
||||
locations = ConfigParser.parseSubscriptions(locationsFile, defaultSubs);
|
||||
List locations = ConfigParser.parseSubscriptions(locationsFile,
|
||||
defaultSubs);
|
||||
try {
|
||||
etags = ConfigParser.parse(etagsFile);
|
||||
} catch (IOException exp) {
|
||||
@ -80,11 +87,9 @@ public class SubscriptionList {
|
||||
Iterator iter = locations.iterator();
|
||||
while (iter.hasNext()) {
|
||||
location = (String) iter.next();
|
||||
subscriptions.add(new Subscription(location, (String) etags
|
||||
this.subscriptions.add(new Subscription(location, (String) etags
|
||||
.get(location), (String) lastModified.get(location)));
|
||||
}
|
||||
|
||||
iter = this.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +99,8 @@ public class SubscriptionList {
|
||||
* @return A SubscriptionIterator.
|
||||
*/
|
||||
public SubscriptionIterator iterator() {
|
||||
return new SubscriptionIterator(this.subscriptions);
|
||||
return new SubscriptionIterator(this.subscriptions, this.proxyHost,
|
||||
this.proxyPort);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,12 +111,12 @@ public class Bogobot extends PircBot {
|
||||
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
|
||||
|
||||
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
|
||||
_ircServer = config.getProperty("ircServer", "irc.duck.i2p");
|
||||
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
|
||||
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
|
||||
|
||||
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
|
||||
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.duck.i2p.i2p-chat");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
|
||||
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
|
||||
|
||||
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();
|
||||
|
@ -8,36 +8,50 @@ package net.i2p.i2ptunnel;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple stream for delivering an HTTP response to
|
||||
* the client, trivially filtered to make sure "Connection: close"
|
||||
* is always in the response.
|
||||
* is always in the response. Perhaps add transparent handling of the
|
||||
* Content-encoding: x-i2p-gzip, adjusting the headers to say Content-encoding: identity?
|
||||
* Content-encoding: gzip is trivial as well, but Transfer-encoding: chunked makes it
|
||||
* more work than is worthwhile at the moment.
|
||||
*
|
||||
*/
|
||||
class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
private static final Log _log = new Log(HTTPResponseOutputStream.class);
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private ByteCache _cache;
|
||||
protected ByteArray _headerBuffer;
|
||||
private boolean _headerWritten;
|
||||
private byte _buf1[];
|
||||
protected boolean _gzip;
|
||||
private long _dataWritten;
|
||||
private InternalGZIPInputStream _in;
|
||||
private static final int CACHE_SIZE = 8*1024;
|
||||
|
||||
public HTTPResponseOutputStream(OutputStream raw) {
|
||||
super(raw);
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
_cache = ByteCache.getInstance(8, CACHE_SIZE);
|
||||
_headerBuffer = _cache.acquire();
|
||||
_headerWritten = false;
|
||||
_gzip = false;
|
||||
_dataWritten = 0;
|
||||
_buf1 = new byte[1];
|
||||
}
|
||||
|
||||
@ -51,6 +65,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
public void write(byte buf[], int off, int len) throws IOException {
|
||||
if (_headerWritten) {
|
||||
out.write(buf, off, len);
|
||||
_dataWritten += len;
|
||||
out.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -62,8 +78,12 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
if (headerReceived()) {
|
||||
writeHeader();
|
||||
_headerWritten = true;
|
||||
if (i + 1 < len) // write out the remaining
|
||||
if (i + 1 < len) {
|
||||
// write out the remaining
|
||||
out.write(buf, off+i+1, len-i-1);
|
||||
_dataWritten += len-i-1;
|
||||
out.flush();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -128,7 +148,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
if ( (keyLen <= 0) || (valLen <= 0) )
|
||||
throw new IOException("Invalid header @ " + j);
|
||||
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
|
||||
String val = new String(_headerBuffer.getData(), j+2, valLen);
|
||||
String val = new String(_headerBuffer.getData(), j+2, valLen).trim();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Response header [" + key + "] = [" + val + "]");
|
||||
|
||||
if ("Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Connection: close\n".getBytes());
|
||||
@ -136,6 +159,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
proxyConnectionSent = true;
|
||||
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
|
||||
_gzip = true;
|
||||
} else {
|
||||
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
|
||||
}
|
||||
@ -152,13 +177,103 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
if (!proxyConnectionSent)
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
finishHeaders();
|
||||
|
||||
boolean shouldCompress = shouldCompress();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After headers: gzip? " + _gzip + " compress? " + shouldCompress);
|
||||
|
||||
// done, shove off
|
||||
if (_headerBuffer.getData().length == CACHE_SIZE)
|
||||
_cache.release(_headerBuffer);
|
||||
else
|
||||
_headerBuffer = null;
|
||||
if (shouldCompress) {
|
||||
beginProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean shouldCompress() { return _gzip; }
|
||||
|
||||
protected void finishHeaders() throws IOException {
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
}
|
||||
|
||||
protected void beginProcessing() throws IOException {
|
||||
//out.flush();
|
||||
PipedInputStream pi = new PipedInputStream();
|
||||
PipedOutputStream po = new PipedOutputStream(pi);
|
||||
new I2PThread(new Pusher(pi, out), "HTTP decompresser").start();
|
||||
out = po;
|
||||
}
|
||||
|
||||
private class Pusher implements Runnable {
|
||||
private InputStream _inRaw;
|
||||
private OutputStream _out;
|
||||
public Pusher(InputStream in, OutputStream out) {
|
||||
_inRaw = in;
|
||||
_out = out;
|
||||
}
|
||||
public void run() {
|
||||
OutputStream to = null;
|
||||
_in = null;
|
||||
long start = System.currentTimeMillis();
|
||||
long written = 0;
|
||||
try {
|
||||
_in = new InternalGZIPInputStream(_inRaw);
|
||||
byte buf[] = new byte[8192];
|
||||
int read = -1;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read " + read + " and writing it to the browser/streams");
|
||||
_out.write(buf, 0, read);
|
||||
_out.flush();
|
||||
written += read;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Decompressed: " + written + ", " + _in.getTotalRead() + "/" + _in.getTotalExpanded());
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error decompressing: " + written + ", " + (_in != null ? _in.getTotalRead() + "/" + _in.getTotalExpanded() : ""), ioe);
|
||||
} finally {
|
||||
if (_log.shouldLog(Log.WARN) && (_in != null))
|
||||
_log.warn("After decompression, written=" + written +
|
||||
(_in != null ?
|
||||
" read=" + _in.getTotalRead()
|
||||
+ ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining()
|
||||
+ ", finished=" + _in.getFinished()
|
||||
: ""));
|
||||
if (_out != null) try {
|
||||
_out.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
double compressed = (_in != null ? _in.getTotalRead() : 0);
|
||||
double expanded = (_in != null ? _in.getTotalExpanded() : 0);
|
||||
double ratio = 0;
|
||||
if (expanded > 0)
|
||||
ratio = compressed/expanded;
|
||||
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
|
||||
}
|
||||
}
|
||||
private class InternalGZIPInputStream extends GZIPInputStream {
|
||||
public InternalGZIPInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
public long getTotalRead() { return super.inf.getTotalIn(); }
|
||||
public long getTotalExpanded() { return super.inf.getTotalOut(); }
|
||||
public long getRemaining() { return super.inf.getRemaining(); }
|
||||
public boolean getFinished() { return super.inf.finished(); }
|
||||
public String toString() {
|
||||
return "Read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString() + ": " + _in;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
|
@ -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
|
||||
|
@ -101,6 +101,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
this.l = l;
|
||||
this.handlerName = handlerName + _clientId;
|
||||
|
||||
// no need to load the netDb with leaseSets for destinations that will never
|
||||
// be looked up
|
||||
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
|
||||
|
||||
while (sockMgr == null) {
|
||||
synchronized (sockLock) {
|
||||
if (ownDest) {
|
||||
@ -110,7 +114,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
if (sockMgr == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager");
|
||||
_log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -70,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 =
|
||||
@ -95,6 +96,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_AHELPER_CONFLICT =
|
||||
("HTTP/1.1 409 Conflict\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: Destination key conflict</H1>"+
|
||||
"The addresshelper link you followed specifies a different destination key "+
|
||||
"than a host entry in your host database. "+
|
||||
"Someone could be trying to impersonate another eepsite, "+
|
||||
"or people have given two eepsites identical names.<P/>"+
|
||||
"You can resolve the conflict by considering which key you trust, "+
|
||||
"and either discarding the addresshelper link, "+
|
||||
"discarding the host entry from your host database, "+
|
||||
"or naming one of them differently.<P/>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
@ -178,6 +195,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static final boolean DEFAULT_GZIP = true;
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
OutputStream out = null;
|
||||
@ -190,6 +209,7 @@ 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 + "]");
|
||||
@ -241,49 +261,102 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
// Quick hack for foo.bar.i2p
|
||||
if (host.toLowerCase().endsWith(".i2p")) {
|
||||
// Destination gets the host name
|
||||
destination = host;
|
||||
// Host becomes the destination key
|
||||
host = getHostName(destination);
|
||||
if ( (host != null) && ("i2p".equals(host)) ) {
|
||||
int pos2;
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
if (fragment.startsWith("i2paddresshelper")) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
if (pos2 >= 0) {
|
||||
addressHelpers.put(destination,fragment.substring(pos2 + 1));
|
||||
}
|
||||
} else {
|
||||
// append each fragment unless it's the address helper
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
}
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
int pos2;
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
String ahelperKey = null;
|
||||
boolean ahelperConflict = false;
|
||||
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
String initialFragments = fragments;
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
|
||||
// Fragment looks like addresshelper key
|
||||
if (fragment.startsWith("i2paddresshelper=")) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
ahelperKey = fragment.substring(pos2 + 1);
|
||||
|
||||
// Key contains data, lets not ignore it
|
||||
if (ahelperKey != null) {
|
||||
|
||||
// Host resolvable only with addresshelper
|
||||
if ( (host == null) || ("i2p".equals(host)) )
|
||||
{
|
||||
// Cannot check, use addresshelper key
|
||||
addressHelpers.put(destination,ahelperKey);
|
||||
} else {
|
||||
// Host resolvable from database, verify addresshelper key
|
||||
// Silently bypass correct keys, otherwise alert
|
||||
if (!host.equals(ahelperKey))
|
||||
{
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + host + "], specified key [" + ahelperKey + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other fragments, just pass along
|
||||
// Append each fragment to urlEncoding
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
|
||||
// Did addresshelper key conflict?
|
||||
if (ahelperConflict)
|
||||
{
|
||||
String str;
|
||||
byte[] header;
|
||||
str = FileUtil.readTextFile("docs/ahelper-conflict-header.ht", 100, true);
|
||||
if (str != null) header = str.getBytes();
|
||||
else header = ERR_AHELPER_CONFLICT;
|
||||
|
||||
if (out != null) {
|
||||
long alias = I2PAppContext.getGlobalContext().random().nextLong();
|
||||
String trustedURL = protocol + uriPath + urlEncoding;
|
||||
String conflictURL = protocol + alias + ".i2p/?" + initialFragments;
|
||||
out.write(header);
|
||||
out.write(("To visit the destination in your host database, click <a href=\"" + trustedURL + "\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"" + conflictURL + "\">here</a>.<P/>").getBytes());
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
ahelper = 1;
|
||||
}
|
||||
|
||||
line = method + " " + request.substring(pos);
|
||||
} else if (host.indexOf(".") != -1) {
|
||||
// The request must be forwarded to a WWW proxy
|
||||
@ -366,6 +439,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
|
||||
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
|
||||
boolean gzip = DEFAULT_GZIP;
|
||||
if (ok != null)
|
||||
gzip = Boolean.valueOf(ok).booleanValue();
|
||||
if (gzip)
|
||||
newRequest.append("Accept-Encoding: x-i2p-gzip\r\n");
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
@ -403,7 +483,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;
|
||||
}
|
||||
@ -476,10 +568,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();
|
||||
@ -493,7 +591,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);
|
||||
}
|
||||
|
@ -3,16 +3,13 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -30,12 +27,38 @@ import net.i2p.util.Log;
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
|
||||
private Log _log;
|
||||
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
|
||||
super(s, i2ps, slock, initialI2PData, sockList, onTimeout);
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class);
|
||||
}
|
||||
|
||||
protected OutputStream getSocketOut() throws IOException {
|
||||
OutputStream raw = super.getSocketOut();
|
||||
return new HTTPResponseOutputStream(raw);
|
||||
}
|
||||
|
||||
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
|
||||
try {
|
||||
i2pin.close();
|
||||
i2pout.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unable to close the i2p socket output stream: " + i2pout, ioe);
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unable to close the browser output stream: " + out, ioe);
|
||||
}
|
||||
i2ps.close();
|
||||
s.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,14 +3,13 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@ -24,7 +23,9 @@ import net.i2p.util.Log;
|
||||
/**
|
||||
* Simple extension to the I2PTunnelServer that filters the HTTP
|
||||
* headers sent from the client to the server, replacing the Host
|
||||
* header with whatever this instance has been configured with.
|
||||
* header with whatever this instance has been configured with, and
|
||||
* if the browser set Accept-encoding: x-i2p-gzip, gzip the http
|
||||
* message body and set Content-encoding: x-i2p-gzip.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
@ -35,87 +36,204 @@ 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.
|
||||
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) {
|
||||
_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();
|
||||
|
||||
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);
|
||||
|
||||
InputStream in = socket.getInputStream();
|
||||
|
||||
StringBuffer command = new StringBuffer(128);
|
||||
Properties headers = readHeaders(in, command);
|
||||
headers.setProperty("Host", _spoofHost);
|
||||
if ( (_spoofHost != null) && (_spoofHost.trim().length() > 0) )
|
||||
headers.setProperty("Host", _spoofHost);
|
||||
headers.setProperty("Connection", "close");
|
||||
return formatHeaders(headers, command);
|
||||
// we keep the enc sent by the browser before clobbering it, since it may have
|
||||
// been x-i2p-gzip
|
||||
String enc = headers.getProperty("Accept-encoding");
|
||||
headers.setProperty("Accept-encoding", "identity;q=1, *;q=0");
|
||||
String modifiedHeader = formatHeaders(headers, command);
|
||||
|
||||
//String modifiedHeader = getModifiedHeader(socket);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Modified header: [" + modifiedHeader + "]");
|
||||
|
||||
socket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = getTunnel().getContext().clock().now();
|
||||
// instead of i2ptunnelrunner, use something that reads the HTTP
|
||||
// request from the socket, modifies the headers, sends the request to the
|
||||
// server, reads the response headers, rewriting to include Content-encoding: x-i2p-gzip
|
||||
// if it was one of the Accept-encoding: values, and gzip the payload
|
||||
Properties opts = getTunnel().getClientOptions();
|
||||
boolean allowGZIP = true;
|
||||
if (opts != null) {
|
||||
String val = opts.getProperty("i2ptunnel.gzip");
|
||||
if ( (val != null) && (!Boolean.valueOf(val).booleanValue()) )
|
||||
allowGZIP = false;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("HTTP server encoding header: " + enc);
|
||||
if ( allowGZIP && (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) ) {
|
||||
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
|
||||
req.start();
|
||||
} else {
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} 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 class CompressedRequestor implements Runnable {
|
||||
private Socket _webserver;
|
||||
private I2PSocket _browser;
|
||||
private String _headers;
|
||||
public CompressedRequestor(Socket webserver, I2PSocket browser, String headers) {
|
||||
_webserver = webserver;
|
||||
_browser = browser;
|
||||
_headers = headers;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Compressed requestor running");
|
||||
OutputStream serverout = null;
|
||||
OutputStream browserout = null;
|
||||
InputStream browserin = null;
|
||||
InputStream serverin = null;
|
||||
try {
|
||||
serverout = _webserver.getOutputStream();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("request headers: " + _headers);
|
||||
serverout.write(_headers.getBytes());
|
||||
browserin = _browser.getInputStream();
|
||||
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), "http compressed sender");
|
||||
sender.start();
|
||||
|
||||
browserout = _browser.getOutputStream();
|
||||
serverin = _webserver.getInputStream();
|
||||
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
|
||||
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Before pumping the compressed response");
|
||||
s.run(); // same thread
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After pumping the compressed response: " + compressedOut.getTotalRead() + "/" + compressedOut.getTotalCompressed());
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("error compressing", ioe);
|
||||
} finally {
|
||||
if (browserout != null) try { browserout.close(); } catch (IOException ioe) {}
|
||||
if (serverout != null) try { serverout.close(); } catch (IOException ioe) {}
|
||||
if (browserin != null) try { browserin.close(); } catch (IOException ioe) {}
|
||||
if (serverin != null) try { serverin.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
private class Sender implements Runnable {
|
||||
private OutputStream _out;
|
||||
private InputStream _in;
|
||||
private String _name;
|
||||
public Sender(OutputStream out, InputStream in, String name) {
|
||||
_out = out;
|
||||
_in = in;
|
||||
_name = name;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": Begin sending");
|
||||
try {
|
||||
byte buf[] = new byte[16*1024];
|
||||
int read = 0;
|
||||
int total = 0;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": read " + read + " and sending through the stream");
|
||||
_out.write(buf, 0, read);
|
||||
total += read;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": Done sending: " + total);
|
||||
_out.flush();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error sending", ioe);
|
||||
} finally {
|
||||
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
|
||||
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
private class CompressedResponseOutputStream extends HTTPResponseOutputStream {
|
||||
private InternalGZIPOutputStream _gzipOut;
|
||||
public CompressedResponseOutputStream(OutputStream o) {
|
||||
super(o);
|
||||
}
|
||||
|
||||
protected boolean shouldCompress() { return true; }
|
||||
protected void finishHeaders() throws IOException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Including x-i2p-gzip as the content encoding in the response");
|
||||
out.write("Content-encoding: x-i2p-gzip\n".getBytes());
|
||||
super.finishHeaders();
|
||||
}
|
||||
|
||||
protected void beginProcessing() throws IOException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Beginning compression processing");
|
||||
out.flush();
|
||||
_gzipOut = new InternalGZIPOutputStream(out);
|
||||
out = _gzipOut;
|
||||
}
|
||||
public long getTotalRead() { return _gzipOut.getTotalRead(); }
|
||||
public long getTotalCompressed() { return _gzipOut.getTotalCompressed(); }
|
||||
}
|
||||
private class InternalGZIPOutputStream extends GZIPOutputStream {
|
||||
public InternalGZIPOutputStream(OutputStream target) throws IOException {
|
||||
super(target);
|
||||
}
|
||||
public long getTotalRead() { return super.def.getTotalIn(); }
|
||||
public long getTotalCompressed() { return super.def.getTotalOut(); }
|
||||
}
|
||||
|
||||
private String formatHeaders(Properties headers, StringBuffer command) {
|
||||
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
|
||||
buf.append(command.toString()).append('\n');
|
||||
@ -150,6 +268,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
|
||||
String name = buf.substring(0, split);
|
||||
String value = buf.substring(split+2); // ": "
|
||||
if ("Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "Accept-encoding";
|
||||
headers.setProperty(name, value);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the header [" + name + "] = [" + value + "]");
|
||||
|
@ -153,11 +153,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
onTimeout.run();
|
||||
}
|
||||
|
||||
// now one connection is dead - kill the other as well.
|
||||
s.close();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
// now one connection is dead - kill the other as well, after making sure we flush
|
||||
close(out, in, i2pout, i2pin, s, i2ps, t1, t2);
|
||||
} catch (InterruptedException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Interrupted", ex);
|
||||
@ -188,6 +185,27 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
}
|
||||
|
||||
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
i2pout.flush();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
in.close();
|
||||
i2pin.close();
|
||||
// ok, yeah, there's a race here in theory, if data comes in after flushing and before
|
||||
// closing, but its better than before...
|
||||
s.close();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
}
|
||||
|
||||
public void errorOccurred() {
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
|
@ -11,6 +11,7 @@ import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.ConnectException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
@ -39,6 +40,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
protected InetAddress remoteHost;
|
||||
protected int remotePort;
|
||||
private boolean _usePool;
|
||||
|
||||
private Logging l;
|
||||
|
||||
@ -46,15 +48,27 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
/** default timeout to 3 minutes - override if desired */
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
private static final boolean DEFAULT_USE_POOL = false;
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
init(host, port, bais, privData, l);
|
||||
}
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, File privkey, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
try {
|
||||
init(host, port, new FileInputStream(privkey), privkeyname, l);
|
||||
} catch (IOException ioe) {
|
||||
@ -65,6 +79,11 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
init(host, port, privData, privkeyname, l);
|
||||
}
|
||||
|
||||
@ -159,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;
|
||||
}
|
||||
@ -331,6 +333,7 @@ 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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -73,6 +73,14 @@ public class EditBean extends IndexBean {
|
||||
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) {
|
||||
|
@ -52,6 +52,7 @@ public class IndexBean {
|
||||
private String _privKeyFile;
|
||||
private String _profile;
|
||||
private boolean _startOnLoad;
|
||||
private boolean _sharedClient;
|
||||
private boolean _privKeyGenerate;
|
||||
private boolean _removeConfirmed;
|
||||
|
||||
@ -204,7 +205,8 @@ public class IndexBean {
|
||||
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())) {
|
||||
//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);
|
||||
@ -343,6 +345,14 @@ public class IndexBean {
|
||||
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 "";
|
||||
@ -469,6 +479,12 @@ public class IndexBean {
|
||||
public void setStartOnLoad(String moo) {
|
||||
_startOnLoad = true;
|
||||
}
|
||||
public void setShared(String moo) {
|
||||
_sharedClient=true;
|
||||
}
|
||||
public void setShared(boolean val) {
|
||||
_sharedClient=val;
|
||||
}
|
||||
public void setConnectDelay(String moo) {
|
||||
_connectDelay = true;
|
||||
}
|
||||
@ -496,8 +512,14 @@ public class IndexBean {
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
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);
|
||||
@ -510,6 +532,11 @@ public class IndexBean {
|
||||
|
||||
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);
|
||||
@ -567,7 +594,7 @@ public class IndexBean {
|
||||
}
|
||||
|
||||
config.setProperty("startOnLoad", _startOnLoad + "");
|
||||
|
||||
|
||||
if (_tunnelCount != null) {
|
||||
config.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
config.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
@ -581,7 +608,7 @@ public class IndexBean {
|
||||
else
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if (_name != null) {
|
||||
if ( (!"client".equals(_type)) && (!"httpclient".equals(_type)) ) {
|
||||
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))) || (!_sharedClient) ) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
} else {
|
||||
|
@ -93,7 +93,7 @@ if (curTunnel >= 0) {
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachablyByOther" size="20" />
|
||||
<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>
|
||||
@ -102,7 +102,7 @@ if (curTunnel >= 0) {
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachablyByOther" size="20" />
|
||||
<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>
|
||||
@ -111,7 +111,7 @@ if (curTunnel >= 0) {
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachablyByOther" size="20" value="<%=clientInterface%>" />
|
||||
<input type="text" name="reachableByOther" size="20" value="<%=clientInterface%>" />
|
||||
<% } %>
|
||||
|
||||
</td>
|
||||
@ -161,10 +161,23 @@ if (curTunnel >= 0) {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Shared Client</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.isSharedClient(curTunnel)) { %>
|
||||
<input type="checkbox" value="true" name="shared" checked="true" />
|
||||
<% } else { %>
|
||||
<input type="checkbox" value="true" name="shared" />
|
||||
<% } %>
|
||||
<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;">(Those are shared between ALL your Client proxies!)</span></b>
|
||||
<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>
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,27 +94,44 @@ 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;
|
||||
@ -125,47 +139,55 @@ public class StreamSinkServer {
|
||||
//_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());
|
||||
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", 7654);
|
||||
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();
|
||||
|
@ -149,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) {
|
||||
@ -167,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);
|
||||
@ -188,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) {
|
||||
@ -203,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
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
146
apps/q/java/src/net/i2p/aum/I2PTunnelXMLObject.java
Normal file
@ -0,0 +1,146 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.i2ptunnel.I2PTunnelXMLWrapper;
|
||||
|
||||
/**
|
||||
* Defines the I2P tunnel management methods which will be
|
||||
* exposed to XML-RPC clients
|
||||
* Methods in this class are forwarded to an I2PTunnelXMLWrapper object
|
||||
*/
|
||||
public class I2PTunnelXMLObject
|
||||
{
|
||||
protected I2PTunnelXMLWrapper tunmgr;
|
||||
|
||||
/**
|
||||
* Builds the interface object. You normally shouldn't have to
|
||||
* instantiate this directly - leave it to I2PTunnelXMLServer
|
||||
*/
|
||||
public I2PTunnelXMLObject()
|
||||
{
|
||||
tunmgr = new I2PTunnelXMLWrapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an I2P keypair, returning a dict with keys 'result' (usually 'ok'),
|
||||
* priv' (private key as base64) and 'dest' (destination as base64)
|
||||
*/
|
||||
public Hashtable genkeys()
|
||||
{
|
||||
return tunmgr.xmlrpcGenkeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of active TCP tunnels currently being managed by this
|
||||
* tunnel manager.
|
||||
* @return a dict with keys 'status' (usually 'ok'),
|
||||
* 'jobs' (a list of dicts representing each job, each with keys 'job' (int, job
|
||||
* number), 'type' (string, 'server' or 'client'), port' (int, the port number).
|
||||
* Also for server, keys 'host' (hostname, string) and 'ip' (IP address, string).
|
||||
* For clients, key 'dest' (string, remote destination as base64).
|
||||
*/
|
||||
public Hashtable list()
|
||||
{
|
||||
return tunmgr.xmlrpcList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find I2P hostname in hosts.txt.
|
||||
* @param hostname string, I2P hostname
|
||||
* @return dict with keys 'status' ('ok' or 'fail'),
|
||||
* and if successful lookup, 'dest' (base64 destination).
|
||||
*/
|
||||
public Hashtable lookup(String hostname)
|
||||
{
|
||||
return tunmgr.xmlrpcLookup(hostname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to open client tunnel
|
||||
* @param port local port to listen on, int
|
||||
* @param dest remote dest to tunnel to, base64 string
|
||||
* @return dict with keys 'status' (string - 'ok' or 'fail').
|
||||
* If 'ok', also key 'result' with text output from tunnelmgr
|
||||
*/
|
||||
public Hashtable client(int port, String dest)
|
||||
{
|
||||
return tunmgr.xmlrpcClient(port, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open server tunnel
|
||||
* @param host TCP hostname of TCP server to tunnel to
|
||||
* @param port number of TCP server
|
||||
* @param key - base64 private key to receive I2P connections on
|
||||
* @return dict with keys 'status' (string, 'ok' or 'fail').
|
||||
* if 'fail', also a key 'error' with explanatory text.
|
||||
*/
|
||||
public Hashtable server(String host, int port, String key)
|
||||
{
|
||||
return tunmgr.xmlrpcServer(host, port, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close an existing tunnel
|
||||
* @param jobnum (int) job number of connection to close
|
||||
* @return dict with keys 'status' (string, 'ok' or 'fail')
|
||||
*/
|
||||
public Hashtable close(int jobnum)
|
||||
{
|
||||
return tunmgr.xmlrpcClose(jobnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close an existing tunnel
|
||||
* @param jobnum (string) job number of connection to close as string,
|
||||
* 'all' to close all jobs.
|
||||
* @return dict with keys 'status' (string, 'ok' or 'fail')
|
||||
*/
|
||||
public Hashtable close(String job)
|
||||
{
|
||||
return tunmgr.xmlrpcClose(job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close zero or more tunnels matching given criteria
|
||||
* @param criteria A dict containing zero or more of the keys:
|
||||
* 'job' (job number), 'type' (string, 'server' or 'client'),
|
||||
* 'host' (hostname), 'port' (port number),
|
||||
* 'ip' (IP address), 'dest' (string, remote dest)
|
||||
*/
|
||||
public Hashtable close(Hashtable criteria)
|
||||
{
|
||||
return tunmgr.xmlrpcClose(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* simple method to help with debugging your client prog
|
||||
* @param x an int
|
||||
* @return x + 1
|
||||
*/
|
||||
public int bar(int x)
|
||||
{
|
||||
System.out.println("foo invoked");
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* as for bar(int), but returns zero if no arg given
|
||||
*/
|
||||
public int bar()
|
||||
{
|
||||
return bar(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
71
apps/q/java/src/net/i2p/aum/I2PTunnelXMLServer.java
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* Provides a means for programs in any language to dynamically manage
|
||||
* their own I2P <-> TCP tunnels, via simple TCP XML-RPC function calls.
|
||||
* This server is presently hardwired to listen on port 22322.
|
||||
*/
|
||||
|
||||
public class I2PTunnelXMLServer
|
||||
{
|
||||
protected WebServer ws;
|
||||
protected I2PTunnelXMLObject tunobj;
|
||||
|
||||
public int port = 22322;
|
||||
|
||||
// constructor
|
||||
|
||||
public void _init()
|
||||
{
|
||||
ws = new WebServer(port);
|
||||
tunobj = new I2PTunnelXMLObject();
|
||||
ws.addHandler("i2p.tunnel", tunobj);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// default constructor
|
||||
public I2PTunnelXMLServer()
|
||||
{
|
||||
super();
|
||||
_init();
|
||||
}
|
||||
|
||||
// constructor which takes shell args
|
||||
public I2PTunnelXMLServer(String args[])
|
||||
{
|
||||
super();
|
||||
_init();
|
||||
}
|
||||
|
||||
// run the server
|
||||
public void run()
|
||||
{
|
||||
ws.start();
|
||||
System.out.println("I2PTunnel XML-RPC server listening on port "+port);
|
||||
ws.run();
|
||||
|
||||
}
|
||||
|
||||
public static void main(String args[])
|
||||
{
|
||||
I2PTunnelXMLServer tun;
|
||||
|
||||
tun = new I2PTunnelXMLServer();
|
||||
tun.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
71
apps/q/java/src/net/i2p/aum/I2PXmlRpcClient.java
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
|
||||
/**
|
||||
* an object which is used to invoke methods on remote I2P XML-RPC
|
||||
* servers. You should not instantiate these objects directly, but
|
||||
* create them through
|
||||
* {@link net.i2p.aum.I2PXmlRpcClientFactory#newClient(Destination) I2PXmlRpcClientFactory.newClient()}
|
||||
* Note that this is really just a thin wrapper around XmlRpcClient, mostly for reasons
|
||||
* of consistency with I2PXmlRpcServer[Factory].
|
||||
*/
|
||||
|
||||
public class I2PXmlRpcClient extends XmlRpcClient
|
||||
{
|
||||
public static boolean debug = false;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
/**
|
||||
* Construct an I2P XML-RPC client with this URL.
|
||||
* Note that you should not
|
||||
* use this constructor directly - use I2PXmlRpcClientFactory.newClient() instead
|
||||
*/
|
||||
public I2PXmlRpcClient(URL url)
|
||||
{
|
||||
super(url);
|
||||
_log = new Log("I2PXmlRpcClient");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a XML-RPC client for the URL represented by this String.
|
||||
* Note that you should not
|
||||
* use this constructor directly - use I2PXmlRpcClientFactory.newClient() instead
|
||||
*/
|
||||
public I2PXmlRpcClient(String url) throws MalformedURLException
|
||||
{
|
||||
super(url);
|
||||
_log = new Log("I2PXmlRpcClientFactory");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a XML-RPC client for the specified hostname and port.
|
||||
* Note that you should not
|
||||
* use this constructor directly - use I2PXmlRpcClientFactory.newClient() instead
|
||||
*/
|
||||
public I2PXmlRpcClient(String hostname, int port) throws MalformedURLException
|
||||
{
|
||||
super(hostname, port);
|
||||
_log = new Log("I2PXmlRpcClient");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
229
apps/q/java/src/net/i2p/aum/I2PXmlRpcClientFactory.java
Normal file
@ -0,0 +1,229 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
|
||||
/**
|
||||
* Creates I2P XML-RPC client objects, which you can use
|
||||
* to issue XML-RPC function calls over I2P.
|
||||
* Instantiating this class causes the vm-wide http proxy system
|
||||
* properties to be set to the address of the I2P eepProxy host/port.
|
||||
* I2PXmlRpcClient objects need to communicate with the I2P
|
||||
* eepProxy. If your eepProxy is at the standard localhost:4444 address,
|
||||
* you can use the default constructor. Otherwise, you can set this
|
||||
* eepProxy address by either (1) passing eepProxy hostname/port to the
|
||||
* constructor, or (2) running the jvm with 'eepproxy.tcp.host' and
|
||||
* 'eepproxy.tcp.port' system properties set. Note that (1) takes precedence.
|
||||
* Failure to set up EepProxy host/port correctly will result in an IOException
|
||||
* when you invoke .execute() on your client objects.
|
||||
* Invoke this class from your shell to see a demo
|
||||
*/
|
||||
|
||||
public class I2PXmlRpcClientFactory
|
||||
{
|
||||
public static boolean debug = false;
|
||||
|
||||
public static String _defaultEepHost = "127.0.0.1";
|
||||
public static int _defaultEepPort = 4444;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC client factory, and set it to create
|
||||
* clients of a given class.
|
||||
* @param clientClass a class to use when creating new clients
|
||||
*/
|
||||
public I2PXmlRpcClientFactory()
|
||||
{
|
||||
this(null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC client factory, and set it to create
|
||||
* clients of a given class, and dispatch calls through a non-standard
|
||||
* eepProxy.
|
||||
* @param eepHost the eepProxy TCP hostname
|
||||
* @param eepPort the eepProxy TCP port number
|
||||
*/
|
||||
public I2PXmlRpcClientFactory(String eepHost, int eepPort)
|
||||
{
|
||||
String eepPortStr;
|
||||
|
||||
_log = new Log("I2PXmlRpcClientFactory");
|
||||
_log.shouldLog(Log.DEBUG);
|
||||
|
||||
Properties p = System.getProperties();
|
||||
|
||||
// determine what actual eepproxy host/port we're using
|
||||
if (eepHost == null) {
|
||||
eepHost = p.getProperty("eepproxy.tcp.host", _defaultEepHost);
|
||||
}
|
||||
if (eepPort > 0) {
|
||||
eepPortStr = String.valueOf(eepPort);
|
||||
}
|
||||
else {
|
||||
eepPortStr = p.getProperty("eepproxy.tcp.port");
|
||||
if (eepPortStr == null) {
|
||||
eepPortStr = String.valueOf(_defaultEepPort);
|
||||
}
|
||||
}
|
||||
|
||||
p.put("proxySet", "true");
|
||||
p.put("http.proxyHost", eepHost);
|
||||
p.put("http.proxyPort", eepPortStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC client object, which is subsequently used for
|
||||
* dispatching XML-RPC requests.
|
||||
* @param dest - an I2P destination object, comprising the
|
||||
* destination of the remote
|
||||
* I2P XML-RPC server.
|
||||
* @return a new XmlRpcClient object (refer org.apache.xmlrpc.XmlRpcClient).
|
||||
*/
|
||||
public I2PXmlRpcClient newClient(Destination dest) throws MalformedURLException {
|
||||
|
||||
return newClient(new URL("http", "i2p/"+dest.toBase64(), "/"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC client object, which is subsequently used for
|
||||
* dispatching XML-RPC requests.
|
||||
* @param hostOrDest - an I2P hostname (listed in hosts.txt) or a
|
||||
* destination base64 string, for the remote I2P XML-RPC server
|
||||
* @return a new XmlRpcClient object (refer org.apache.xmlrpc.XmlRpcClient).
|
||||
*/
|
||||
public I2PXmlRpcClient newClient(String hostOrDest)
|
||||
throws DataFormatException, MalformedURLException
|
||||
{
|
||||
String hostname;
|
||||
URL u;
|
||||
|
||||
try {
|
||||
// try to make a dest out of the string
|
||||
Destination dest = new Destination();
|
||||
dest.fromBase64(hostOrDest);
|
||||
|
||||
// converted ok, treat as valid dest, form i2p/blahblah url from it
|
||||
I2PXmlRpcClient client = newClient(new URL("http", "i2p/"+hostOrDest, "/"));
|
||||
client.debug = debug;
|
||||
return client;
|
||||
|
||||
} catch (DataFormatException e) {
|
||||
|
||||
if (debug) {
|
||||
e.printStackTrace();
|
||||
print("hostOrDest length="+hostOrDest.length());
|
||||
}
|
||||
|
||||
// failed to load up a dest, test length
|
||||
if (hostOrDest.length() < 255) {
|
||||
// short-ish, assume a hostname
|
||||
u = new URL("http", hostOrDest, "/");
|
||||
I2PXmlRpcClient client = newClient(u);
|
||||
client.debug = debug;
|
||||
return client;
|
||||
}
|
||||
else {
|
||||
// too long for a host, barf
|
||||
throw new DataFormatException("Bad I2P hostname/dest:\n"+hostOrDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC client object, which is subsequently used for
|
||||
* dispatching XML-RPC requests. This method is not recommended.
|
||||
* @param u - a URL object, containing the URL of the remote
|
||||
* I2P XML-RPC server, for example, "http://xmlrpc.aum.i2p" (assuming
|
||||
* there's a hosts.txt entry for 'xmlrpc.aum.i2p'), or
|
||||
* "http://i2p/base64destblahblah...". Note that if you use this method
|
||||
* directly, the created XML-RPC client object will ONLY work if you
|
||||
* instantiate the URL object as 'new URL("http", "i2p/"+host-or-dest, "/")'.
|
||||
*/
|
||||
protected I2PXmlRpcClient newClient(URL u)
|
||||
{
|
||||
Object [] args = { u };
|
||||
//return new I2PXmlRpcClient(u);
|
||||
|
||||
// construct and return a client object of required class
|
||||
return new I2PXmlRpcClient(u);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a demo of an I2P XML-RPC client. Assumes you have already
|
||||
* launched an I2PXmlRpcServerFactory demo, because it gets its
|
||||
* dest from the file 'demo.dest64' created by I2PXmlRpcServerFactory demo.
|
||||
*
|
||||
* Ensure you have first launched net.i2p.aum.I2PXmlRpcServerFactory
|
||||
* from your command line.
|
||||
*/
|
||||
public static void main(String [] args) {
|
||||
|
||||
String destStr;
|
||||
|
||||
debug = true;
|
||||
|
||||
try {
|
||||
print("Creating client factory...");
|
||||
|
||||
I2PXmlRpcClientFactory f = new I2PXmlRpcClientFactory();
|
||||
|
||||
print("Creating new client...");
|
||||
|
||||
if (args.length == 0) {
|
||||
print("Reading dest from demo.dest64");
|
||||
destStr = new SimpleFile("demo.dest64", "r").read();
|
||||
}
|
||||
else {
|
||||
destStr = args[0];
|
||||
}
|
||||
|
||||
XmlRpcClient c = f.newClient(destStr);
|
||||
|
||||
print("Invoking foo...");
|
||||
|
||||
Vector v = new Vector();
|
||||
v.add("one");
|
||||
v.add("two");
|
||||
|
||||
Object res = c.execute("foo.bar", v);
|
||||
|
||||
print("Got back object: " + res);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Used for internal debugging
|
||||
*/
|
||||
protected static void print(String msg)
|
||||
{
|
||||
if (debug) {
|
||||
System.out.println("I2PXmlRpcClient: " + msg);
|
||||
|
||||
if (_log != null) {
|
||||
System.out.println("LOGGING SOME SHIT");
|
||||
_log.debug(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
34
apps/q/java/src/net/i2p/aum/I2PXmlRpcDemoClass.java
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* A simple class providing callable xmlrpc server methods, gets linked in to
|
||||
* the server demo.
|
||||
*/
|
||||
public class I2PXmlRpcDemoClass
|
||||
{
|
||||
public int add1(int n) {
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
public String bar(String arg1, String arg2) {
|
||||
System.out.println("Demo: got hit to bar: arg1='"+arg1+"', arg2='"+arg2+"'");
|
||||
return "I2P demo xmlrpc server(foo.bar): arg1='"+arg1+"', arg2='"+arg2+"'";
|
||||
}
|
||||
|
||||
}
|
||||
|
428
apps/q/java/src/net/i2p/aum/I2PXmlRpcServer.java
Normal file
@ -0,0 +1,428 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
|
||||
/**
|
||||
* An XML-RPC server which works completely within I2P, listening
|
||||
* on a dest for requests.
|
||||
* You should not instantiate this class directly, but instead create
|
||||
* an I2PXmlRpcServerFactory object, and use its .newServer() method
|
||||
* to create a server object.
|
||||
*/
|
||||
public class I2PXmlRpcServer extends XmlRpcServer implements Runnable
|
||||
{
|
||||
public class I2PXmlRpcServerWorkerThread extends Thread {
|
||||
|
||||
I2PSocket _sock;
|
||||
|
||||
public I2PXmlRpcServerWorkerThread(I2PSocket sock) {
|
||||
_sock = sock;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
try {
|
||||
System.out.println("I2PXmlRpcServer.run: got inbound XML-RPC I2P conn");
|
||||
|
||||
log.info("run: Got client connection, creating streams");
|
||||
|
||||
InputStream socketIn = _sock.getInputStream();
|
||||
OutputStreamWriter socketOut = new OutputStreamWriter(_sock.getOutputStream());
|
||||
|
||||
log.info("run: reading http headers");
|
||||
|
||||
// read headers, determine size of req
|
||||
int size = readHttpHeaders(socketIn);
|
||||
|
||||
if (size <= 0) {
|
||||
// bad news
|
||||
log.info("read req failed, terminating session");
|
||||
_sock.close();
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("run: reading request body of "+size+" bytes");
|
||||
|
||||
// get raw request body
|
||||
byte [] reqBody = new byte[size];
|
||||
for (int i=0; i<size; i++) {
|
||||
int b = socketIn.read();
|
||||
reqBody[i] = (byte)b;
|
||||
}
|
||||
//socketIn.read(reqBody);
|
||||
|
||||
//log.info("reqBody='" + (new String(reqBody)) + "'");
|
||||
//System.out.println("reqBody='" + (new String(reqBody)) + "'");
|
||||
//System.out.println("reqBody:");
|
||||
//for (int ii=0; ii<reqBody.length; ii++) {
|
||||
// System.out.println("i=" + ii + " ch="+reqBody[ii]);
|
||||
//}
|
||||
|
||||
ByteArrayInputStream reqBodyStream = new ByteArrayInputStream(reqBody);
|
||||
|
||||
log.info("run: executing request");
|
||||
|
||||
System.out.println("run: executing request");
|
||||
|
||||
// read and execute full request
|
||||
byte [] result;
|
||||
try {
|
||||
result = execute(reqBodyStream);
|
||||
} catch (Exception e) {
|
||||
System.out.println("run: execute failed, closing socket");
|
||||
_sock.close();
|
||||
System.out.println("run: closed socket");
|
||||
throw e;
|
||||
}
|
||||
|
||||
log.info("run: sending response");
|
||||
|
||||
|
||||
// fudge - manual header and response generation
|
||||
socketOut.write(
|
||||
"HTTP/1.0 200 OK\r\n" +
|
||||
"Server: I2P XML-RPC server by aum\r\n" +
|
||||
"Date: " + (new Date().toString()) + "\r\n" +
|
||||
"Content-type: text/xml\r\n" +
|
||||
"Content-length: " + String.valueOf(result.length) + "\r\n" +
|
||||
"\r\n");
|
||||
socketOut.write(new String(result));
|
||||
//socketOut.write(result);
|
||||
socketOut.flush();
|
||||
|
||||
log.info("closing socket");
|
||||
System.out.println("closing socket");
|
||||
|
||||
//response.setContentType ("text/xml");
|
||||
//response.setContentLength (result.length());
|
||||
//OutputStream out = response.getOutputStream();
|
||||
//out.write (result);
|
||||
//out.flush ();
|
||||
|
||||
_sock.close();
|
||||
|
||||
log.info("session complete");
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
e.printStackTrace();
|
||||
_sock.close();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convenience - dest this server is listening on
|
||||
public Destination dest;
|
||||
|
||||
// server's socket manager object
|
||||
public I2PSocketManager socketMgr;
|
||||
|
||||
// server's socket
|
||||
public I2PServerSocket serverSocket;
|
||||
|
||||
/** socket of latest incoming connection */
|
||||
public I2PSocket sessSocket;
|
||||
|
||||
// set to enable debugging msgs
|
||||
public static boolean debug = false;
|
||||
|
||||
// stream-proented xmlrpc server
|
||||
|
||||
protected net.i2p.util.Log log;
|
||||
protected I2PAppContext i2p;
|
||||
|
||||
public Thread serverThread;
|
||||
|
||||
/**
|
||||
* (do not use this constructor directly)
|
||||
*/
|
||||
|
||||
public I2PXmlRpcServer(String keyStr, Properties props, I2PAppContext i2p)
|
||||
throws DataFormatException, I2PException, IOException
|
||||
{
|
||||
this(PrivDestination.fromBase64String(keyStr), props, i2p);
|
||||
}
|
||||
|
||||
/**
|
||||
* (do not use this constructor directly)
|
||||
*/
|
||||
|
||||
public I2PXmlRpcServer(PrivDestination privKey, Properties props, I2PAppContext i2p)
|
||||
throws DataFormatException, I2PException
|
||||
{
|
||||
super();
|
||||
|
||||
log = i2p.logManager().getLog(this.getClass());
|
||||
|
||||
log.info("creating socket manager for key dest "
|
||||
+ privKey.getDestinationBase64().substring(0, 16)
|
||||
+ "...");
|
||||
|
||||
// start by getting a socket manager
|
||||
socketMgr = I2PSocketManagerFactory.createManager(privKey.getInputStream(), props);
|
||||
if (socketMgr == null) {
|
||||
throw new I2PException("Failed to create socketManager, maybe can't reach i2cp port");
|
||||
}
|
||||
|
||||
log.info("getting server socket, socketMgr="+socketMgr);
|
||||
|
||||
// get a server socket
|
||||
serverSocket = socketMgr.getServerSocket();
|
||||
|
||||
log.info("got server socket, ready to run");
|
||||
|
||||
dest = socketMgr.getSession().getMyDestination();
|
||||
|
||||
log.info("full dest="+dest.toBase64());
|
||||
System.out.println("full dest="+dest.toBase64());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this server within the current thread of execution.
|
||||
* This function never returns. If you want to run the server
|
||||
* in a background thread, use the .start() method instead.
|
||||
*/
|
||||
|
||||
public void run()
|
||||
{
|
||||
log.info("run: listening for inbound XML-RPC requests...");
|
||||
|
||||
while (true)
|
||||
{
|
||||
System.out.println("I2PXmlRpcServer.run: waiting for inbound XML-RPC I2P conn...");
|
||||
|
||||
try {
|
||||
sessSocket = serverSocket.accept();
|
||||
|
||||
I2PXmlRpcServerWorkerThread sessThread = new I2PXmlRpcServerWorkerThread(sessSocket);
|
||||
sessThread.start();
|
||||
|
||||
/**
|
||||
System.out.println("I2PXmlRpcServer.run: got inbound XML-RPC I2P conn");
|
||||
|
||||
log.info("run: Got client connection, creating streams");
|
||||
|
||||
InputStream socketIn = sessSocket.getInputStream();
|
||||
OutputStreamWriter socketOut = new OutputStreamWriter(sessSocket.getOutputStream());
|
||||
|
||||
log.info("run: reading http headers");
|
||||
|
||||
// read headers, determine size of req
|
||||
int size = readHttpHeaders(socketIn);
|
||||
|
||||
if (size <= 0) {
|
||||
// bad news
|
||||
log.info("read req failed, terminating session");
|
||||
sessSocket.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info("run: reading request body of "+size+" bytes");
|
||||
|
||||
// get raw request body
|
||||
byte [] reqBody = new byte[size];
|
||||
for (int i=0; i<size; i++) {
|
||||
int b = socketIn.read();
|
||||
reqBody[i] = (byte)b;
|
||||
}
|
||||
//socketIn.read(reqBody);
|
||||
|
||||
//log.info("reqBody='" + (new String(reqBody)) + "'");
|
||||
//System.out.println("reqBody='" + (new String(reqBody)) + "'");
|
||||
//System.out.println("reqBody:");
|
||||
//for (int ii=0; ii<reqBody.length; ii++) {
|
||||
// System.out.println("i=" + ii + " ch="+reqBody[ii]);
|
||||
//}
|
||||
|
||||
ByteArrayInputStream reqBodyStream = new ByteArrayInputStream(reqBody);
|
||||
|
||||
log.info("run: executing request");
|
||||
|
||||
System.out.println("run: executing request");
|
||||
|
||||
// read and execute full request
|
||||
byte [] result;
|
||||
try {
|
||||
result = execute(reqBodyStream);
|
||||
} catch (Exception e) {
|
||||
System.out.println("run: execute failed, closing socket");
|
||||
sessSocket.close();
|
||||
System.out.println("run: closed socket");
|
||||
throw e;
|
||||
}
|
||||
|
||||
log.info("run: sending response");
|
||||
|
||||
|
||||
// fudge - manual header and response generation
|
||||
socketOut.write(
|
||||
"HTTP/1.0 200 OK\r\n" +
|
||||
"Server: I2P XML-RPC server by aum\r\n" +
|
||||
"Date: " + (new Date().toString()) + "\r\n" +
|
||||
"Content-type: text/xml\r\n" +
|
||||
"Content-length: " + String.valueOf(result.length) + "\r\n" +
|
||||
"\r\n");
|
||||
socketOut.write(new String(result));
|
||||
socketOut.flush();
|
||||
|
||||
log.info("closing socket");
|
||||
System.out.println("closing socket");
|
||||
|
||||
//response.setContentType ("text/xml");
|
||||
//response.setContentLength (result.length());
|
||||
//OutputStream out = response.getOutputStream();
|
||||
//out.write (result);
|
||||
//out.flush ();
|
||||
|
||||
sessSocket.close();
|
||||
|
||||
log.info("session complete");
|
||||
**/
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called as part of an incoming XML-RPC request,
|
||||
* reads and parses http headers from input stream.
|
||||
* @param in the InputStream of the socket connection from the
|
||||
* currently connected client
|
||||
* @return value of 'Content-Length' field, as the number of bytes
|
||||
* expected in the body of the request, or -1 if the request headers
|
||||
* are invalid
|
||||
* @throws IOException
|
||||
*/
|
||||
|
||||
protected int readHttpHeaders(InputStream in) throws IOException
|
||||
{
|
||||
int contentLength = -1;
|
||||
|
||||
while (true) {
|
||||
// read/parse one line
|
||||
String line = readline(in);
|
||||
|
||||
String [] flds = line.split(":\\s+", 2);
|
||||
log.debug("after split: flds='"+flds+"'");
|
||||
|
||||
String hdrKey = flds[0];
|
||||
|
||||
if (flds.length == 1) {
|
||||
// not an HTTP header
|
||||
log.info("skipping non-header, hdrKey='"+hdrKey+"'");
|
||||
continue;
|
||||
}
|
||||
|
||||
System.out.println("I2PXmlRpcServer: '"+flds[0]+"'='"+flds[1]+"'");
|
||||
|
||||
String hdrVal = flds[1];
|
||||
|
||||
log.info("hdrKey='"+hdrKey+"', hdrVal='"+hdrVal+"'");
|
||||
|
||||
if (hdrKey.equals("Content-Type")) {
|
||||
if (!hdrVal.equals("text/xml")) {
|
||||
// barf - not text/xml content type
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (hdrKey.equals("Content-Length")) {
|
||||
// got our length now - done with headers
|
||||
contentLength = new Integer(hdrVal).intValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Got content-length, now read remaining headers");
|
||||
|
||||
// read and discard any remaining headers
|
||||
while (true) {
|
||||
String line = readline(in);
|
||||
int lineLen = line.length();
|
||||
log.info("line("+lineLen+")='"+line+"'");
|
||||
System.out.println("Disccarding superflous header: '"+line+"'");
|
||||
if (lineLen == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Content length is "+contentLength);
|
||||
|
||||
return contentLength;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called as part of an incoming XML-RPC request,
|
||||
* reads and parses http headers from input stream.
|
||||
* @param in the InputStream of the socket connection from the
|
||||
* currently connected client
|
||||
* @return the line read, as a string
|
||||
* @throws IOException
|
||||
*/
|
||||
|
||||
protected String readline(InputStream in) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
while (true) {
|
||||
int ch = in.read();
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
case -1:
|
||||
String s = os.toString();
|
||||
log.debug("Got line '"+s+"'");
|
||||
return os.toString();
|
||||
case '\r':
|
||||
break;
|
||||
default:
|
||||
os.write(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the server as a background thread.
|
||||
* (To run within the calling thread, use the .run() method instead).
|
||||
*/
|
||||
|
||||
public void start()
|
||||
{
|
||||
log.debug("Starting server as a thread");
|
||||
serverThread = new Thread(this);
|
||||
serverThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
public void stop()
|
||||
{
|
||||
if (serverThread != null) {
|
||||
serverThread.stop();
|
||||
}
|
||||
}
|
||||
**/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
162
apps/q/java/src/net/i2p/aum/I2PXmlRpcServerFactory.java
Normal file
@ -0,0 +1,162 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
|
||||
/**
|
||||
* Generates I2P-compatible XML-RPC server objects
|
||||
* (of class I2PXmlRpcServer). If you instead want to create
|
||||
* instances of your own
|
||||
*
|
||||
* Invoke this class from your shell to see a demo
|
||||
* @author aum
|
||||
*/
|
||||
public class I2PXmlRpcServerFactory
|
||||
{
|
||||
public I2PSocketManager socketManager;
|
||||
|
||||
public Properties props;
|
||||
|
||||
public static int defaultTunnelLength = 2;
|
||||
|
||||
// set to enable debugging msgs
|
||||
public static boolean debug = false;
|
||||
|
||||
public static Log log;
|
||||
protected I2PAppContext i2p;
|
||||
|
||||
// hostname/port of I2P router we're using
|
||||
//public static String i2cpHost = "127.0.0.1";
|
||||
//public static int i2cpPort = 7654;
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC server factory using default
|
||||
* tunnel settings
|
||||
*/
|
||||
public I2PXmlRpcServerFactory(I2PAppContext i2p)
|
||||
{
|
||||
// get a socket manager
|
||||
this(defaultTunnelLength, defaultTunnelLength,
|
||||
defaultTunnelLength, defaultTunnelLength, i2p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC server factory, using settings provided
|
||||
* by arguments
|
||||
* @param lengthIn The value of 'inbound.length' property
|
||||
* @param lengthOut The value of 'outbound.length' property
|
||||
* @param lengthVarianceIn Value of 'inbound.lengthVariance' property
|
||||
* @param lengthVarianceOut Value of 'outbound.lengthVariance' property
|
||||
* @param log an I2P logger
|
||||
*/
|
||||
public I2PXmlRpcServerFactory(int lengthIn, int lengthOut,
|
||||
int lengthVarianceIn, int lengthVarianceOut,
|
||||
I2PAppContext i2p)
|
||||
{
|
||||
this.i2p = i2p;
|
||||
log = i2p.logManager().getLog(this.getClass());
|
||||
|
||||
// set up tunnel properties for server objects
|
||||
props = new Properties();
|
||||
props.setProperty("inbound.length", String.valueOf(lengthIn));
|
||||
props.setProperty("outbound.length", String.valueOf(lengthOut));
|
||||
props.setProperty("inbound.lengthVariance", String.valueOf(lengthVarianceIn));
|
||||
props.setProperty("outbound.lengthVariance", String.valueOf(lengthVarianceIn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new I2PXmlRpcServer listening on a new randomly created destination
|
||||
* @return a new I2PXmlRpcServer object, whose '.addHandler()' method you should
|
||||
* invoke to add a handler object.
|
||||
* @throws I2PException, IOException, DataFormatException
|
||||
*/
|
||||
public I2PXmlRpcServer newServer() throws I2PException, IOException, DataFormatException
|
||||
{
|
||||
return newServer(PrivDestination.newKey());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new I2PXmlRpcServer listening on a given dest, from key provided
|
||||
* as a base64 string
|
||||
* @param keyStr base64 representation of full private key for the destination
|
||||
* the server is to listen on
|
||||
* @return a new I2PXmlRpcServer object
|
||||
* @throws DataFormatException
|
||||
*/
|
||||
public I2PXmlRpcServer newServer(String keyStr)
|
||||
throws DataFormatException, I2PException, IOException
|
||||
{
|
||||
return newServer(PrivDestination.fromBase64String(keyStr));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new I2PXmlRpcServer listening on a given dest, from key provided
|
||||
* as a PrivDestination object
|
||||
* @param key a PrivDestination object representing the private destination
|
||||
* the server is to listen on
|
||||
* @return a new I2PXmlRpcServer object
|
||||
* @throws DataFormatException
|
||||
*/
|
||||
public I2PXmlRpcServer newServer(PrivDestination key) throws DataFormatException, I2PException
|
||||
{
|
||||
// main newServer
|
||||
I2PXmlRpcServer server = new I2PXmlRpcServer(key, props, i2p);
|
||||
server.debug = debug;
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstration of I2P XML-RPC server.
|
||||
* Creates a server listening on a random destination, and writes the base64
|
||||
* destination into a file called "demo.dest64".
|
||||
*
|
||||
* After launching this program from a command shell, you should
|
||||
* launch I2PXmlRpcClientFactory from another command shell
|
||||
* to execute the client side of the demo.
|
||||
*
|
||||
* This program accepts no arguments.
|
||||
*/
|
||||
public static void main(String [] args)
|
||||
{
|
||||
debug = true;
|
||||
I2PXmlRpcServer.debug = true;
|
||||
|
||||
I2PAppContext i2p = new I2PAppContext();
|
||||
|
||||
I2PXmlRpcServerFactory f = new I2PXmlRpcServerFactory(0,0,0,0, i2p);
|
||||
|
||||
try {
|
||||
|
||||
f.log.info("Creating new server on a new key");
|
||||
I2PXmlRpcServer s = f.newServer();
|
||||
|
||||
f.log.info("Creating and adding handler object");
|
||||
I2PXmlRpcDemoClass demo = new I2PXmlRpcDemoClass();
|
||||
s.addHandler("foo", demo);
|
||||
|
||||
f.log.info("Saving dest for this server in file 'demo.dest64'");
|
||||
new SimpleFile("demo.dest64", "rws").write(s.dest.toBase64());
|
||||
|
||||
f.log.info("Running server (Press Ctrl-C to kill)");
|
||||
s.run();
|
||||
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
|
||||
}
|
||||
}
|
||||
|
392
apps/q/java/src/net/i2p/aum/Mimetypes.java
Normal file
@ -0,0 +1,392 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
/**
|
||||
* creates a convenient map of file extensions <-> mimetypes
|
||||
*/
|
||||
|
||||
public class Mimetypes
|
||||
{
|
||||
public static String [][] _map = {
|
||||
|
||||
{ ".bz2", "application/x-bzip2" },
|
||||
{ ".csm", "application/cu-seeme" },
|
||||
{ ".cu", "application/cu-seeme" },
|
||||
{ ".tsp", "application/dsptype" },
|
||||
{ ".xls", "application/excel" },
|
||||
{ ".spl", "application/futuresplash" },
|
||||
{ ".hqx", "application/mac-binhex40" },
|
||||
{ ".doc", "application/msword" },
|
||||
{ ".dot", "application/msword" },
|
||||
{ ".bin", "application/octet-stream" },
|
||||
{ ".oda", "application/oda" },
|
||||
{ ".pdf", "application/pdf" },
|
||||
{ ".asc", "application/pgp-keys" },
|
||||
{ ".pgp", "application/pgp-signature" },
|
||||
{ ".ps", "application/postscript" },
|
||||
{ ".ai", "application/postscript" },
|
||||
{ ".eps", "application/postscript" },
|
||||
{ ".ppt", "application/powerpoint" },
|
||||
{ ".rtf", "application/rtf" },
|
||||
{ ".wp5", "application/wordperfect5.1" },
|
||||
{ ".zip", "application/zip" },
|
||||
{ ".wk", "application/x-123" },
|
||||
{ ".bcpio", "application/x-bcpio" },
|
||||
{ ".pgn", "application/x-chess-pgn" },
|
||||
{ ".cpio", "application/x-cpio" },
|
||||
{ ".deb", "application/x-debian-package" },
|
||||
{ ".dcr", "application/x-director" },
|
||||
{ ".dir", "application/x-director" },
|
||||
{ ".dxr", "application/x-director" },
|
||||
{ ".dvi", "application/x-dvi" },
|
||||
{ ".pfa", "application/x-font" },
|
||||
{ ".pfb", "application/x-font" },
|
||||
{ ".gsf", "application/x-font" },
|
||||
{ ".pcf", "application/x-font" },
|
||||
{ ".pcf.Z", "application/x-font" },
|
||||
{ ".gtar", "application/x-gtar" },
|
||||
{ ".tgz", "application/x-gtar" },
|
||||
{ ".hdf", "application/x-hdf" },
|
||||
{ ".phtml", "application/x-httpd-php" },
|
||||
{ ".pht", "application/x-httpd-php" },
|
||||
{ ".php", "application/x-httpd-php" },
|
||||
{ ".php3", "application/x-httpd-php3" },
|
||||
{ ".phps", "application/x-httpd-php3-source" },
|
||||
{ ".php3p", "application/x-httpd-php3-preprocessed" },
|
||||
{ ".class", "application/x-java" },
|
||||
{ ".latex", "application/x-latex" },
|
||||
{ ".frm", "application/x-maker" },
|
||||
{ ".maker", "application/x-maker" },
|
||||
{ ".frame", "application/x-maker" },
|
||||
{ ".fm", "application/x-maker" },
|
||||
{ ".fb", "application/x-maker" },
|
||||
{ ".book", "application/x-maker" },
|
||||
{ ".fbdoc", "application/x-maker" },
|
||||
{ ".mif", "application/x-mif" },
|
||||
{ ".nc", "application/x-netcdf" },
|
||||
{ ".cdf", "application/x-netcdf" },
|
||||
{ ".pac", "application/x-ns-proxy-autoconfig" },
|
||||
{ ".o", "application/x-object" },
|
||||
{ ".pl", "application/x-perl" },
|
||||
{ ".pm", "application/x-perl" },
|
||||
{ ".shar", "application/x-shar" },
|
||||
{ ".swf", "application/x-shockwave-flash" },
|
||||
{ ".swfl", "application/x-shockwave-flash" },
|
||||
{ ".sit", "application/x-stuffit" },
|
||||
{ ".sv4cpio", "application/x-sv4cpio" },
|
||||
{ ".sv4crc", "application/x-sv4crc" },
|
||||
{ ".tar", "application/x-tar" },
|
||||
{ ".gf", "application/x-tex-gf" },
|
||||
{ ".pk", "application/x-tex-pk" },
|
||||
{ ".PK", "application/x-tex-pk" },
|
||||
{ ".texinfo", "application/x-texinfo" },
|
||||
{ ".texi", "application/x-texinfo" },
|
||||
{ ".~", "application/x-trash" },
|
||||
{ ".%", "application/x-trash" },
|
||||
{ ".bak", "application/x-trash" },
|
||||
{ ".old", "application/x-trash" },
|
||||
{ ".sik", "application/x-trash" },
|
||||
{ ".t", "application/x-troff" },
|
||||
{ ".tr", "application/x-troff" },
|
||||
{ ".roff", "application/x-troff" },
|
||||
{ ".man", "application/x-troff-man" },
|
||||
{ ".me", "application/x-troff-me" },
|
||||
{ ".ms", "application/x-troff-ms" },
|
||||
{ ".ustar", "application/x-ustar" },
|
||||
{ ".src", "application/x-wais-source" },
|
||||
{ ".wz", "application/x-wingz" },
|
||||
{ ".au", "audio/basic" },
|
||||
{ ".snd", "audio/basic" },
|
||||
{ ".mid", "audio/midi" },
|
||||
{ ".midi", "audio/midi" },
|
||||
{ ".mpga", "audio/mpeg" },
|
||||
{ ".mpega", "audio/mpeg" },
|
||||
{ ".mp2", "audio/mpeg" },
|
||||
{ ".mp3", "audio/mpeg" },
|
||||
{ ".m3u", "audio/mpegurl" },
|
||||
{ ".aif", "audio/x-aiff" },
|
||||
{ ".aiff", "audio/x-aiff" },
|
||||
{ ".aifc", "audio/x-aiff" },
|
||||
{ ".gsm", "audio/x-gsm" },
|
||||
{ ".ra", "audio/x-pn-realaudio" },
|
||||
{ ".rm", "audio/x-pn-realaudio" },
|
||||
{ ".ram", "audio/x-pn-realaudio" },
|
||||
{ ".rpm", "audio/x-pn-realaudio-plugin" },
|
||||
{ ".wav", "audio/x-wav" },
|
||||
{ ".gif", "image/gif" },
|
||||
{ ".ief", "image/ief" },
|
||||
{ ".jpeg", "image/jpeg" },
|
||||
{ ".jpg", "image/jpeg" },
|
||||
{ ".jpe", "image/jpeg" },
|
||||
{ ".png", "image/png" },
|
||||
{ ".tiff", "image/tiff" },
|
||||
{ ".tif", "image/tiff" },
|
||||
{ ".ras", "image/x-cmu-raster" },
|
||||
{ ".bmp", "image/x-ms-bmp" },
|
||||
{ ".pnm", "image/x-portable-anymap" },
|
||||
{ ".pbm", "image/x-portable-bitmap" },
|
||||
{ ".pgm", "image/x-portable-graymap" },
|
||||
{ ".ppm", "image/x-portable-pixmap" },
|
||||
{ ".rgb", "image/x-rgb" },
|
||||
{ ".xbm", "image/x-xbitmap" },
|
||||
{ ".xpm", "image/x-xpixmap" },
|
||||
{ ".xwd", "image/x-xwindowdump" },
|
||||
{ ".csv", "text/comma-separated-values" },
|
||||
{ ".html", "text/html" },
|
||||
{ ".htm", "text/html" },
|
||||
{ ".mml", "text/mathml" },
|
||||
{ ".txt", "text/plain" },
|
||||
{ ".rtx", "text/richtext" },
|
||||
{ ".tsv", "text/tab-separated-values" },
|
||||
{ ".h++", "text/x-c++hdr" },
|
||||
{ ".hpp", "text/x-c++hdr" },
|
||||
{ ".hxx", "text/x-c++hdr" },
|
||||
{ ".hh", "text/x-c++hdr" },
|
||||
{ ".c++", "text/x-c++src" },
|
||||
{ ".cpp", "text/x-c++src" },
|
||||
{ ".cxx", "text/x-c++src" },
|
||||
{ ".cc", "text/x-c++src" },
|
||||
{ ".h", "text/x-chdr" },
|
||||
{ ".csh", "text/x-csh" },
|
||||
{ ".c", "text/x-csrc" },
|
||||
{ ".java", "text/x-java" },
|
||||
{ ".moc", "text/x-moc" },
|
||||
{ ".p", "text/x-pascal" },
|
||||
{ ".pas", "text/x-pascal" },
|
||||
{ ".etx", "text/x-setext" },
|
||||
{ ".sh", "text/x-sh" },
|
||||
{ ".tcl", "text/x-tcl" },
|
||||
{ ".tk", "text/x-tcl" },
|
||||
{ ".tex", "text/x-tex" },
|
||||
{ ".ltx", "text/x-tex" },
|
||||
{ ".sty", "text/x-tex" },
|
||||
{ ".cls", "text/x-tex" },
|
||||
{ ".vcs", "text/x-vCalendar" },
|
||||
{ ".vcf", "text/x-vCard" },
|
||||
{ ".dl", "video/dl" },
|
||||
{ ".fli", "video/fli" },
|
||||
{ ".gl", "video/gl" },
|
||||
{ ".mpeg", "video/mpeg" },
|
||||
{ ".mpg", "video/mpeg" },
|
||||
{ ".mpe", "video/mpeg" },
|
||||
{ ".qt", "video/quicktime" },
|
||||
{ ".mov", "video/quicktime" },
|
||||
{ ".asf", "video/x-ms-asf" },
|
||||
{ ".asx", "video/x-ms-asf" },
|
||||
{ ".avi", "video/x-msvideo" },
|
||||
{ ".movie", "video/x-sgi-movie" },
|
||||
{ ".vrm", "x-world/x-vrml" },
|
||||
{ ".vrml", "x-world/x-vrml" },
|
||||
{ ".wrl", "x-world/x-vrml" },
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempts to determine a mimetype
|
||||
* @param path - either a file extension string (containing the
|
||||
* leading '.') or a full file pathname (in which case, the extension
|
||||
* will be extracted).
|
||||
* @return the mimetype that corresponds to the file extension, if the
|
||||
* file extension is known, or "application/octet-stream" if the
|
||||
* file extension is not known.
|
||||
*/
|
||||
public static String guessType(String path) {
|
||||
// rip the file extension from the path
|
||||
// first - split 'directories', and get last part
|
||||
String [] dirs = path.split("/");
|
||||
String filename = dirs[dirs.length-1];
|
||||
String [] bits = filename.split("\\.");
|
||||
String extension = "." + bits[bits.length-1];
|
||||
|
||||
// default mimetype applied to unknown file extensions
|
||||
String type = "application/octet-stream";
|
||||
|
||||
for (int i=0; i<_map.length; i++) {
|
||||
String [] rec = _map[i];
|
||||
if (rec[0].equals(extension)) {
|
||||
type = rec[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to guess the file extension corresponding to a given
|
||||
* mimetype.
|
||||
* @param type a mimetype string
|
||||
* @return a file extension commonly used for storing files of this type,
|
||||
* or defaults to ".bin" if mimetype not known
|
||||
*/
|
||||
public static String guessExtension(String type) {
|
||||
// default extension applied to unknown mimetype
|
||||
String extension = ".bin";
|
||||
for (int i=0; i<_map.length; i++) {
|
||||
String [] rec = _map[i];
|
||||
if (rec[1].equals(type)) {
|
||||
extension = rec[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
suffix_map = {
|
||||
'.tgz': '.tar.gz',
|
||||
'.taz': '.tar.gz',
|
||||
'.tz': '.tar.gz',
|
||||
}
|
||||
|
||||
encodings_map = {
|
||||
'.gz': 'gzip',
|
||||
'.Z': 'compress',
|
||||
}
|
||||
|
||||
# Before adding new types, make sure they are either registered with IANA, at
|
||||
# http://www.isi.edu/in-notes/iana/assignments/media-types
|
||||
# or extensions, i.e. using the x- prefix
|
||||
|
||||
# If you add to these, please keep them sorted!
|
||||
types_map = {
|
||||
'.a' : 'application/octet-stream',
|
||||
'.ai' : 'application/postscript',
|
||||
'.aif' : 'audio/x-aiff',
|
||||
'.aifc' : 'audio/x-aiff',
|
||||
'.aiff' : 'audio/x-aiff',
|
||||
'.au' : 'audio/basic',
|
||||
'.avi' : 'video/x-msvideo',
|
||||
'.bat' : 'text/plain',
|
||||
'.bcpio' : 'application/x-bcpio',
|
||||
'.bin' : 'application/octet-stream',
|
||||
'.bmp' : 'image/x-ms-bmp',
|
||||
'.c' : 'text/plain',
|
||||
# Duplicates :(
|
||||
'.cdf' : 'application/x-cdf',
|
||||
'.cdf' : 'application/x-netcdf',
|
||||
'.cpio' : 'application/x-cpio',
|
||||
'.csh' : 'application/x-csh',
|
||||
'.css' : 'text/css',
|
||||
'.dll' : 'application/octet-stream',
|
||||
'.doc' : 'application/msword',
|
||||
'.dot' : 'application/msword',
|
||||
'.dvi' : 'application/x-dvi',
|
||||
'.eml' : 'message/rfc822',
|
||||
'.eps' : 'application/postscript',
|
||||
'.etx' : 'text/x-setext',
|
||||
'.exe' : 'application/octet-stream',
|
||||
'.gif' : 'image/gif',
|
||||
'.gtar' : 'application/x-gtar',
|
||||
'.h' : 'text/plain',
|
||||
'.hdf' : 'application/x-hdf',
|
||||
'.htm' : 'text/html',
|
||||
'.html' : 'text/html',
|
||||
'.ief' : 'image/ief',
|
||||
'.jpe' : 'image/jpeg',
|
||||
'.jpeg' : 'image/jpeg',
|
||||
'.jpg' : 'image/jpeg',
|
||||
'.js' : 'application/x-javascript',
|
||||
'.ksh' : 'text/plain',
|
||||
'.latex' : 'application/x-latex',
|
||||
'.m1v' : 'video/mpeg',
|
||||
'.man' : 'application/x-troff-man',
|
||||
'.me' : 'application/x-troff-me',
|
||||
'.mht' : 'message/rfc822',
|
||||
'.mhtml' : 'message/rfc822',
|
||||
'.mif' : 'application/x-mif',
|
||||
'.mov' : 'video/quicktime',
|
||||
'.movie' : 'video/x-sgi-movie',
|
||||
'.mp2' : 'audio/mpeg',
|
||||
'.mp3' : 'audio/mpeg',
|
||||
'.mpa' : 'video/mpeg',
|
||||
'.mpe' : 'video/mpeg',
|
||||
'.mpeg' : 'video/mpeg',
|
||||
'.mpg' : 'video/mpeg',
|
||||
'.ms' : 'application/x-troff-ms',
|
||||
'.nc' : 'application/x-netcdf',
|
||||
'.nws' : 'message/rfc822',
|
||||
'.o' : 'application/octet-stream',
|
||||
'.obj' : 'application/octet-stream',
|
||||
'.oda' : 'application/oda',
|
||||
'.p12' : 'application/x-pkcs12',
|
||||
'.p7c' : 'application/pkcs7-mime',
|
||||
'.pbm' : 'image/x-portable-bitmap',
|
||||
'.pdf' : 'application/pdf',
|
||||
'.pfx' : 'application/x-pkcs12',
|
||||
'.pgm' : 'image/x-portable-graymap',
|
||||
'.pl' : 'text/plain',
|
||||
'.png' : 'image/png',
|
||||
'.pnm' : 'image/x-portable-anymap',
|
||||
'.pot' : 'application/vnd.ms-powerpoint',
|
||||
'.ppa' : 'application/vnd.ms-powerpoint',
|
||||
'.ppm' : 'image/x-portable-pixmap',
|
||||
'.pps' : 'application/vnd.ms-powerpoint',
|
||||
'.ppt' : 'application/vnd.ms-powerpoint',
|
||||
'.ps' : 'application/postscript',
|
||||
'.pwz' : 'application/vnd.ms-powerpoint',
|
||||
'.py' : 'text/x-python',
|
||||
'.pyc' : 'application/x-python-code',
|
||||
'.pyo' : 'application/x-python-code',
|
||||
'.qt' : 'video/quicktime',
|
||||
'.ra' : 'audio/x-pn-realaudio',
|
||||
'.ram' : 'application/x-pn-realaudio',
|
||||
'.ras' : 'image/x-cmu-raster',
|
||||
'.rdf' : 'application/xml',
|
||||
'.rgb' : 'image/x-rgb',
|
||||
'.roff' : 'application/x-troff',
|
||||
'.rtx' : 'text/richtext',
|
||||
'.sgm' : 'text/x-sgml',
|
||||
'.sgml' : 'text/x-sgml',
|
||||
'.sh' : 'application/x-sh',
|
||||
'.shar' : 'application/x-shar',
|
||||
'.snd' : 'audio/basic',
|
||||
'.so' : 'application/octet-stream',
|
||||
'.src' : 'application/x-wais-source',
|
||||
'.sv4cpio': 'application/x-sv4cpio',
|
||||
'.sv4crc' : 'application/x-sv4crc',
|
||||
'.swf' : 'application/x-shockwave-flash',
|
||||
'.t' : 'application/x-troff',
|
||||
'.tar' : 'application/x-tar',
|
||||
'.tcl' : 'application/x-tcl',
|
||||
'.tex' : 'application/x-tex',
|
||||
'.texi' : 'application/x-texinfo',
|
||||
'.texinfo': 'application/x-texinfo',
|
||||
'.tif' : 'image/tiff',
|
||||
'.tiff' : 'image/tiff',
|
||||
'.tr' : 'application/x-troff',
|
||||
'.tsv' : 'text/tab-separated-values',
|
||||
'.txt' : 'text/plain',
|
||||
'.ustar' : 'application/x-ustar',
|
||||
'.vcf' : 'text/x-vcard',
|
||||
'.wav' : 'audio/x-wav',
|
||||
'.wiz' : 'application/msword',
|
||||
'.xbm' : 'image/x-xbitmap',
|
||||
'.xlb' : 'application/vnd.ms-excel',
|
||||
# Duplicates :(
|
||||
'.xls' : 'application/excel',
|
||||
'.xls' : 'application/vnd.ms-excel',
|
||||
'.xml' : 'text/xml',
|
||||
'.xpm' : 'image/x-xpixmap',
|
||||
'.xsl' : 'application/xml',
|
||||
'.xwd' : 'image/x-xwindowdump',
|
||||
'.zip' : 'application/zip',
|
||||
}
|
||||
|
||||
# These are non-standard types, commonly found in the wild. They will only
|
||||
# match if strict=0 flag is given to the API methods.
|
||||
|
||||
# Please sort these too
|
||||
common_types = {
|
||||
'.jpg' : 'image/jpg',
|
||||
'.mid' : 'audio/midi',
|
||||
'.midi': 'audio/midi',
|
||||
'.pct' : 'image/pict',
|
||||
'.pic' : 'image/pict',
|
||||
'.pict': 'image/pict',
|
||||
'.rtf' : 'application/rtf',
|
||||
'.xul' : 'text/xul'
|
||||
}
|
||||
**/
|
||||
|
18
apps/q/java/src/net/i2p/aum/OOTest.java
Normal file
@ -0,0 +1,18 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
|
||||
public class OOTest
|
||||
{
|
||||
public int add(int a, int b)
|
||||
{
|
||||
return (a + b);
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
OOTest mytest = new OOTest();
|
||||
System.out.println(mytest.add(3,3));
|
||||
}
|
||||
}
|
||||
|
||||
|
232
apps/q/java/src/net/i2p/aum/PrivDestination.java
Normal file
@ -0,0 +1,232 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* A convenience class for encapsulating and manipulating I2P private keys
|
||||
*/
|
||||
|
||||
public class PrivDestination
|
||||
//extends ByteArrayInputStream
|
||||
extends DataStructureImpl
|
||||
{
|
||||
protected byte [] _bytes;
|
||||
|
||||
protected Destination _dest;
|
||||
protected PrivateKey _privKey;
|
||||
protected SigningPrivateKey _signingPrivKey;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
/**
|
||||
* Create a PrivDestination object.
|
||||
* In most cases, you'll probably want to skip this constructor,
|
||||
* and create PrivDestination objects by invoking the desired static methods
|
||||
* of this class.
|
||||
* @param raw an array of bytes containing the raw binary private key
|
||||
*/
|
||||
public PrivDestination(byte [] raw) throws DataFormatException, IOException
|
||||
{
|
||||
//super(raw);
|
||||
_log = new Log("PrivDestination");
|
||||
|
||||
_bytes = raw;
|
||||
readBytes(getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* reconstitutes a PrivDestination from previously exported Base64
|
||||
*/
|
||||
public PrivDestination(String b64) throws DataFormatException, IOException {
|
||||
this(Base64.decode(b64));
|
||||
}
|
||||
|
||||
/**
|
||||
* generates a new PrivDestination with random keys
|
||||
*/
|
||||
public PrivDestination() throws I2PException, IOException
|
||||
{
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
|
||||
ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
|
||||
|
||||
// create a dest
|
||||
client.createDestination(streamOut);
|
||||
|
||||
_bytes = streamOut.toByteArray();
|
||||
readBytes(getInputStream());
|
||||
|
||||
// construct from the stream
|
||||
//return new PrivDestination(streamOut.toByteArray());
|
||||
}
|
||||
|
||||
/** return the public Destination object for this private dest */
|
||||
public Destination getDestination() {
|
||||
return _dest;
|
||||
}
|
||||
|
||||
/** return a PublicKey (encryption public key) object for this priv dest */
|
||||
public PublicKey getPublicKey() {
|
||||
return getDestination().getPublicKey();
|
||||
}
|
||||
|
||||
/** return a PrivateKey (encryption private key) object for this priv dest */
|
||||
public PrivateKey getPrivateKey() {
|
||||
return _privKey;
|
||||
}
|
||||
|
||||
/** return a SigningPublicKey object for this priv dest */
|
||||
public SigningPublicKey getSigningPublicKey() {
|
||||
return getDestination().getSigningPublicKey();
|
||||
}
|
||||
|
||||
/** return a SigningPrivateKey object for this priv dest */
|
||||
public SigningPrivateKey getSigningPrivateKey() {
|
||||
return _signingPrivKey;
|
||||
}
|
||||
|
||||
// static methods returning an instance
|
||||
|
||||
/**
|
||||
* Creates a PrivDestination object
|
||||
* @param base64 a string containing the base64 private key data
|
||||
* @return a PrivDestination object encapsulating that key
|
||||
*/
|
||||
public static PrivDestination fromBase64String(String base64)
|
||||
throws DataFormatException, IOException
|
||||
{
|
||||
return new PrivDestination(Base64.decode(base64));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PrivDestination object, from the base64 key data
|
||||
* stored in a file.
|
||||
* @param path the pathname of the file from which to read the base64 private key data
|
||||
* @return a PrivDestination object encapsulating that key
|
||||
*/
|
||||
public static PrivDestination fromBase64File(String path)
|
||||
throws FileNotFoundException, IOException, DataFormatException
|
||||
{
|
||||
return fromBase64String(new SimpleFile(path, "r").read());
|
||||
/*
|
||||
File f = new File(path);
|
||||
char [] rawchars = new char[(int)(f.length())];
|
||||
byte [] rawbytes = new byte[(int)(f.length())];
|
||||
FileReader fr = new FileReader(f);
|
||||
fr.read(rawchars);
|
||||
String raw64 = new String(rawchars);
|
||||
return PrivDestination.fromBase64String(raw64);
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PrivDestination object, from the binary key data
|
||||
* stored in a file.
|
||||
* @param path the pathname of the file from which to read the binary private key data
|
||||
* @return a PrivDestination object encapsulating that key
|
||||
*/
|
||||
public static PrivDestination fromBinFile(String path)
|
||||
throws FileNotFoundException, IOException, DataFormatException
|
||||
{
|
||||
byte [] raw = new SimpleFile(path, "r").readBytes();
|
||||
return new PrivDestination(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new random I2P private key
|
||||
* @return a PrivDestination object encapsulating that key
|
||||
*/
|
||||
public static PrivDestination newKey() throws I2PException, IOException
|
||||
{
|
||||
return new PrivDestination();
|
||||
}
|
||||
|
||||
public ByteArrayInputStream getInputStream()
|
||||
{
|
||||
return new ByteArrayInputStream(_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the key's full contents to a string
|
||||
* @return A base64-format string containing the full contents
|
||||
* of this private key. The string can be used in any subsequent
|
||||
* call to the .fromBase64String static constructor method.
|
||||
*/
|
||||
/*
|
||||
public String toBase64()
|
||||
{
|
||||
return Base64.encode(_bytes);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exports the key's full contents to a byte array
|
||||
* @return A byte array containing the full contents
|
||||
* of this private key.
|
||||
*/
|
||||
/*
|
||||
public byte [] toBytes()
|
||||
{
|
||||
return _bytes;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts this key to a public destination.
|
||||
* @return a standard I2P Destination object containing the
|
||||
* public portion of this private key.
|
||||
*/
|
||||
/*
|
||||
public Destination toDestination() throws DataFormatException
|
||||
{
|
||||
Destination dest = new Destination();
|
||||
dest.readBytes(_bytes, 0);
|
||||
return dest;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts this key to a base64 string representing a public destination
|
||||
* @return a string containing a base64 representation of the destination
|
||||
* corresponding to this private key.
|
||||
*/
|
||||
public String getDestinationBase64() throws DataFormatException
|
||||
{
|
||||
return getDestination().toBase64();
|
||||
}
|
||||
|
||||
public void readBytes(java.io.InputStream strm)
|
||||
throws net.i2p.data.DataFormatException, java.io.IOException
|
||||
{
|
||||
_dest = new Destination();
|
||||
_privKey = new PrivateKey();
|
||||
_signingPrivKey = new SigningPrivateKey();
|
||||
|
||||
_dest.readBytes(strm);
|
||||
_privKey.readBytes(strm);
|
||||
_signingPrivKey.readBytes(strm);
|
||||
}
|
||||
|
||||
public void writeBytes(java.io.OutputStream outputStream)
|
||||
throws net.i2p.data.DataFormatException, java.io.IOException
|
||||
{
|
||||
_dest.writeBytes(outputStream);
|
||||
_privKey.writeBytes(outputStream);
|
||||
_signingPrivKey.writeBytes(outputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
209
apps/q/java/src/net/i2p/aum/PropertiesFile.java
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* PropertiesFile.java
|
||||
*
|
||||
* Created on 20 March 2005, 19:30
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* builds on Properties with methods to load/save directly to/from file
|
||||
*/
|
||||
public class PropertiesFile extends Properties {
|
||||
|
||||
public String _path;
|
||||
public File _file;
|
||||
public boolean _fileExists;
|
||||
|
||||
/**
|
||||
* Creates a new instance of PropertiesFile
|
||||
* @param path Absolute pathname of file where properties are to be stored
|
||||
*/
|
||||
public PropertiesFile(String path) throws IOException {
|
||||
super();
|
||||
_path = path;
|
||||
_file = new File(path);
|
||||
_fileExists = _file.isFile();
|
||||
|
||||
if (_file.canRead()) {
|
||||
loadFromFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new PropertiesFile, updating its content with the
|
||||
* keys/values in given hashtable
|
||||
* @param path absolute pathname where properties file is located in filesystem
|
||||
* @param h instance of Hashtable (or subclass). its content
|
||||
* will be written to this object (note that string representations of keys/vals
|
||||
* will be used)
|
||||
*/
|
||||
public PropertiesFile(String path, Hashtable h) throws IOException
|
||||
{
|
||||
this(path);
|
||||
Enumeration keys = h.keys();
|
||||
Object key;
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
key = keys.nextElement();
|
||||
} catch (NoSuchElementException e) {
|
||||
break;
|
||||
}
|
||||
setProperty(key.toString(), h.get(key).toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads this object from the file
|
||||
*/
|
||||
public void loadFromFile() throws IOException, FileNotFoundException {
|
||||
if (_file.canRead()) {
|
||||
InputStream fis = new FileInputStream(_file);
|
||||
load(fis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this object to the file
|
||||
*/
|
||||
public void saveToFile() throws IOException, FileNotFoundException {
|
||||
|
||||
if (!_fileExists) {
|
||||
_file.createNewFile();
|
||||
_fileExists = true;
|
||||
}
|
||||
OutputStream fos = new FileOutputStream(_file);
|
||||
store(fos, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores attribute
|
||||
*/
|
||||
public Object setProperty(String key, String value) {
|
||||
Object o = super.setProperty(key, value);
|
||||
try {
|
||||
saveToFile();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as an int, fall back on default if not found or invalid
|
||||
*/
|
||||
public int getIntProperty(String key, int dflt) {
|
||||
try {
|
||||
return new Integer((String)getProperty(key)).intValue();
|
||||
} catch (Exception e) {
|
||||
setIntProperty(key, dflt);
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as an int
|
||||
*/
|
||||
public int getIntProperty(String key) {
|
||||
return new Integer((String)getProperty(key)).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* set a property as an int
|
||||
*/
|
||||
public void setIntProperty(String key, int value) {
|
||||
setProperty(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a long, fall back on default if not found or invalid
|
||||
*/
|
||||
public long getIntProperty(String key, long dflt) {
|
||||
try {
|
||||
return new Long((String)getProperty(key)).longValue();
|
||||
} catch (Exception e) {
|
||||
setLongProperty(key, dflt);
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as an int
|
||||
*/
|
||||
public long getLongProperty(String key) {
|
||||
return new Long((String)getProperty(key)).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* set a property as an int
|
||||
*/
|
||||
public void setLongProperty(String key, long value) {
|
||||
setProperty(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a float
|
||||
*/
|
||||
public double getFloatProperty(String key) {
|
||||
return new Float((String)getProperty(key)).floatValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a float, fall back on default if not found or invalid
|
||||
*/
|
||||
public double getFloatProperty(String key, float dflt) {
|
||||
try {
|
||||
return new Float((String)getProperty(key)).floatValue();
|
||||
} catch (Exception e) {
|
||||
setFloatProperty(key, dflt);
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set a property as a float
|
||||
*/
|
||||
public void setFloatProperty(String key, float value) {
|
||||
setProperty(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a double
|
||||
*/
|
||||
public double getDoubleProperty(String key) {
|
||||
return new Double((String)getProperty(key)).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a double, fall back on default if not found
|
||||
*/
|
||||
public double getDoubleProperty(String key, double dflt) {
|
||||
try {
|
||||
return new Double((String)getProperty(key)).doubleValue();
|
||||
} catch (Exception e) {
|
||||
setDoubleProperty(key, dflt);
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set a property as a double
|
||||
*/
|
||||
public void setDoubleProperty(String key, double value) {
|
||||
setProperty(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* increment an integer property value
|
||||
*/
|
||||
public void incrementIntProperty(String key) {
|
||||
setIntProperty(key, getIntProperty(key)+1);
|
||||
}
|
||||
|
||||
}
|
||||
|
120
apps/q/java/src/net/i2p/aum/SimpleFile.java
Normal file
@ -0,0 +1,120 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* SimpleFile - subclass of File which adds some python-like
|
||||
* methods. Cuts out a lot of the red tape involved with reading
|
||||
* from and writing to files
|
||||
*/
|
||||
public class SimpleFile {
|
||||
|
||||
public RandomAccessFile _file;
|
||||
public String _path;
|
||||
|
||||
public SimpleFile(String path, String mode) throws FileNotFoundException {
|
||||
|
||||
_path = path;
|
||||
_file = new RandomAccessFile(path, mode);
|
||||
}
|
||||
|
||||
public byte [] readBytes() throws IOException {
|
||||
return readBytes((int)_file.length());
|
||||
}
|
||||
|
||||
public byte[] readBytes(int n) throws IOException {
|
||||
byte [] buf = new byte[n];
|
||||
_file.readFully(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
public char [] readChars() throws IOException {
|
||||
return readChars((int)_file.length());
|
||||
}
|
||||
|
||||
public char[] readChars(int n) throws IOException {
|
||||
char [] buf = new char[n];
|
||||
//_file.readFully(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all remaining content from the file
|
||||
* @return the content as a String
|
||||
* @throws IOException
|
||||
*/
|
||||
public String read() throws IOException {
|
||||
|
||||
return read((int)_file.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one or more bytes of data from the file
|
||||
* @return the content as a String
|
||||
* @throws IOException
|
||||
*/
|
||||
public String read(int nbytes) throws IOException {
|
||||
|
||||
return new String(readBytes(nbytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes one or more bytes of data to a file
|
||||
* @param buf a String containing the data to write
|
||||
* @return the number of bytes written, as an int
|
||||
* @throws IOException
|
||||
*/
|
||||
public int write(String buf) throws IOException {
|
||||
|
||||
return write(buf.getBytes());
|
||||
}
|
||||
|
||||
public int write(byte [] buf) throws IOException {
|
||||
|
||||
_file.write(buf);
|
||||
return buf.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* convenient one-hit write
|
||||
* @param path pathname of file to write to
|
||||
* @param buf data to write
|
||||
*/
|
||||
public static int write(String path, String buf) throws IOException {
|
||||
return new SimpleFile(path, "rws").write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if argument refers to an actual file
|
||||
* @param path pathname to test
|
||||
* @return true if a file, false if not
|
||||
*/
|
||||
public boolean isFile() {
|
||||
return new File(_path).isFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if argument refers to a directory
|
||||
* @param path pathname to test
|
||||
* @return true if a directory, false if not
|
||||
*/
|
||||
public boolean isDir() {
|
||||
return new File(_path).isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if a file or directory exists
|
||||
* @param path pathname to test
|
||||
* @return true if exists, or false
|
||||
*/
|
||||
public boolean exists() {
|
||||
return new File(_path).exists();
|
||||
}
|
||||
|
||||
}
|
||||
|
123
apps/q/java/src/net/i2p/aum/SimpleFile_old.java
Normal file
@ -0,0 +1,123 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* SimpleFile - subclass of File which adds some python-like
|
||||
* methods. Cuts out a lot of the red tape involved with reading
|
||||
* from and writing to files
|
||||
*/
|
||||
public class SimpleFile_old extends File {
|
||||
|
||||
public FileReader _reader;
|
||||
public FileWriter _writer;
|
||||
|
||||
public SimpleFile_old(String path) {
|
||||
|
||||
super(path);
|
||||
|
||||
_reader = null;
|
||||
_writer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all remaining content from the file
|
||||
* @return the content as a String
|
||||
* @throws IOException
|
||||
*/
|
||||
public String read() throws IOException {
|
||||
|
||||
return read((int)length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one or more bytes of data from the file
|
||||
* @return the content as a String
|
||||
* @throws IOException
|
||||
*/
|
||||
public String read(int nbytes) throws IOException {
|
||||
|
||||
// get a reader, if we don't already have one
|
||||
if (_reader == null) {
|
||||
_reader = new FileReader(this);
|
||||
}
|
||||
|
||||
char [] cbuf = new char[nbytes];
|
||||
|
||||
int nread = _reader.read(cbuf);
|
||||
|
||||
if (nread == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return new String(cbuf, 0, nread);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes one or more bytes of data to a file
|
||||
* @param buf a String containing the data to write
|
||||
* @return the number of bytes written, as an int
|
||||
* @throws IOException
|
||||
*/
|
||||
public int write(String buf) throws IOException {
|
||||
|
||||
// get a reader, if we don't already have one
|
||||
if (_writer == null) {
|
||||
_writer = new FileWriter(this);
|
||||
}
|
||||
|
||||
_writer.write(buf);
|
||||
_writer.flush();
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
public int write(byte [] buf) throws IOException {
|
||||
|
||||
return write(new String(buf));
|
||||
}
|
||||
|
||||
/**
|
||||
* convenient one-hit write
|
||||
* @param path pathname of file to write to
|
||||
* @param buf data to write
|
||||
*/
|
||||
public static int write(String path, String buf) throws IOException {
|
||||
SimpleFile_old f = new SimpleFile_old(path);
|
||||
return f.write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if argument refers to an actual file
|
||||
* @param path pathname to test
|
||||
* @return true if a file, false if not
|
||||
*/
|
||||
public static boolean isFile(String path) {
|
||||
return new File(path).isFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if argument refers to a directory
|
||||
* @param path pathname to test
|
||||
* @return true if a directory, false if not
|
||||
*/
|
||||
public static boolean isDir(String path) {
|
||||
return new File(path).isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if a file or directory exists
|
||||
* @param path pathname to test
|
||||
* @return true if exists, or false
|
||||
*/
|
||||
public static boolean exists(String path) {
|
||||
return new File(path).exists();
|
||||
}
|
||||
|
||||
}
|
||||
|
138
apps/q/java/src/net/i2p/aum/SimpleQueue.java
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* SimpleQueue.java
|
||||
*
|
||||
* Created on March 24, 2005, 11:14 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Implements simething similar to python's 'Queue' class
|
||||
*/
|
||||
public class SimpleQueue {
|
||||
|
||||
public Vector items;
|
||||
|
||||
/** Creates a new instance of SimpleQueue */
|
||||
public SimpleQueue() {
|
||||
items = new Vector();
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches the item at head of queue, blocking if queue is empty
|
||||
*/
|
||||
public synchronized Object get()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
if (items.size() == 0)
|
||||
wait();
|
||||
|
||||
// someone has added
|
||||
Object item = items.get(0);
|
||||
items.remove(0);
|
||||
return item;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new object to the queue
|
||||
*/
|
||||
public synchronized void put(Object item)
|
||||
{
|
||||
items.addElement(item);
|
||||
notify();
|
||||
}
|
||||
|
||||
private static class TestThread extends Thread {
|
||||
|
||||
String id;
|
||||
|
||||
SimpleQueue q;
|
||||
|
||||
public TestThread(String id, SimpleQueue 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];
|
||||
|
||||
SimpleQueue q = new SimpleQueue();
|
||||
|
||||
// populate the queue with some stuff
|
||||
q.put("red");
|
||||
q.put("orange");
|
||||
q.put("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();
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
// wait a bit and see what happens
|
||||
String [] items = {"green", "blue", "indigo", "violet", "black", "white", "brown"};
|
||||
for (i = 0; i < items.length; i++) {
|
||||
String item = items[i];
|
||||
System.out.println("main: adding '"+item+"'...");
|
||||
q.put(item);
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("main: terminating");
|
||||
|
||||
}
|
||||
|
||||
}
|
108
apps/q/java/src/net/i2p/aum/SimpleSemaphore.java
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* SimpleSemaphore.java
|
||||
*
|
||||
* Created on March 24, 2005, 11:51 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
/**
|
||||
* Simple implementation of semaphores
|
||||
*/
|
||||
public class SimpleSemaphore {
|
||||
|
||||
protected int count;
|
||||
|
||||
/** Creates a new instance of SimpleSemaphore */
|
||||
public SimpleSemaphore(int size) {
|
||||
count = size;
|
||||
}
|
||||
|
||||
public synchronized void acquire() throws InterruptedException
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
wait();
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
public synchronized void release()
|
||||
{
|
||||
count += 1;
|
||||
notify();
|
||||
}
|
||||
|
||||
private static class TestThread extends Thread
|
||||
{
|
||||
String id;
|
||||
SimpleSemaphore sem;
|
||||
|
||||
public TestThread(String id, SimpleSemaphore sem)
|
||||
{
|
||||
this.id = id;
|
||||
this.sem = sem;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
try {
|
||||
print("waiting for semaphore");
|
||||
sem.acquire();
|
||||
|
||||
print("got semaphore");
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
print("releasing semaphore");
|
||||
|
||||
sem.release();
|
||||
|
||||
print("terminating");
|
||||
|
||||
} 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;
|
||||
|
||||
Thread [] threads = new Thread[10];
|
||||
|
||||
SimpleSemaphore sem = new SimpleSemaphore(3);
|
||||
|
||||
// populate threads array
|
||||
for (i = 0; i < 10; i++) {
|
||||
threads[i] = new TestThread("thread"+i, sem);
|
||||
}
|
||||
|
||||
// and launch the threads
|
||||
for (i = 0; i < 10; i++) {
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
// wait a bit and see what happens
|
||||
System.out.println("main: threads launched, waiting 20 secs");
|
||||
|
||||
try {
|
||||
Thread.sleep(20000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("main: terminating");
|
||||
|
||||
}
|
||||
|
||||
}
|