Compare commits

..

49 Commits

Author SHA1 Message Date
jrandom
df926fb60d * 2005-04-20 0.5.0.7 released 2005-04-20 20:14:17 +00:00
jrandom
a2c7c5a516 2005-04-20 jrandom
* In the SDK, we don't actually need to block when we're sending a message
      as BestEffort (and these days, we're always sending BestEffort).
    * Pass out client messages in fewer (larger) steps.
    * Have the InNetMessagePool short circuit dispatch requests.
    * Have the message validator take into account expiration to cut down on
      false positives at high transfer rates.
    * Allow configuration of the probabalistic window size growth rate in the
      streaming lib's slow start and congestion avoidance phases, and default
      them to a more conservative value (2), rather than the previous value
      (1).
    * Reduce the ack delay in the streaming lib to 500ms
    * Honor choke requests in the streaming lib (only affects those getting
      insanely high transfer rates)
    * Let the user specify an interface besides 127.0.0.1 or 0.0.0.0 on the
      I2PTunnel client page (thanks maestro^!)
(plus minor udp tweaks)
2005-04-20 19:15:25 +00:00
aum
1861379d43 needed for QConsole 2005-04-20 18:54:39 +00:00
aum
408a344aae added QConsole 2005-04-20 18:53:08 +00:00
jrandom
e9c1ed70d0 added sirup.i2p 2005-04-18 23:27:31 +00:00
jrandom
916dcca2b0 * build with reference to the i2p.jar/mstreaming.jar/i2ptunnel.jar inline (building as necessary)
* removed unnecessary references to i2ptunnel (though i2ptunnelxmlobject still references i2ptunnelxmlwrapper)
2005-04-18 18:47:22 +00:00
aum
31e81bab17 fixed build failures 2005-04-18 18:12:32 +00:00
aum
6a5170c341 oops, forgot to add earlier 2005-04-18 18:05:50 +00:00
aum
42bff8093c removed obsolete ref to MiniHttpRequestHandlerBase, changed to MiniHttpRequestHandler 2005-04-18 18:04:12 +00:00
aum
d1df94f284 added needed html template files 2005-04-18 18:02:07 +00:00
aum
9cf1744291 restored images in binary mode 2005-04-18 17:24:33 +00:00
aum
f0545c8c9a removed images which were not checked in as binary 2005-04-18 17:22:27 +00:00
aum
ef9ed87d30 binary mode this time 2005-04-18 17:20:10 +00:00
aum
58ffd92a34 dammit, forgot binary mode 2005-04-18 17:19:25 +00:00
aum
418facc7e0 Added apps/q - the Q distributed file store framework, by aum 2005-04-18 17:03:21 +00:00
jrandom
7f3c953e14 2005-04-17 sirup
* Added the possibility for i2ptunnel client and httpclient instances to
      have their own i2p session (and hence, destination and tunnels).  By
      default, tunnels are shared, but that can be changed on the web
      interface or with the sharedClient config option in i2ptunnel.config.
2005-04-17  jrandom
    * Marked the net.i2p.i2ptunnel.TunnelManager as deprecated.  Anyone use
      this?  If not, I want to drop it (lots of tiny details with lots of
      duplicated semantics).
2005-04-18 02:07:57 +00:00
jrandom
addab1fa2a 2005-04-17 zzz
* Added new user-editable eepproxy error page templates.
2005-04-17  jrandom
    * Revamp the tunnel building throttles, fixing a situation where the
      rebuild may not recover, and defaulting it to unthrottled (users with
      slow CPUs may want to set "router.tunnel.shouldThrottle=true" in their
      advanced router config)
2005-04-17 23:23:20 +00:00
jrandom
39343ce957 2005-04-16 jrandom
* Migrated to Bouncycastle's SHA256 and HMAC implementations for efficiency
2005-04-17 01:04:06 +00:00
jrandom
7389cec78f 2005-04-16 jrandom
* Migrated to Bouncycastle's SHA256 and HMAC implementations for efficiency
(also lots of udp fixes)
2005-04-17 00:59:48 +00:00
jrandom
9e5fe7d2b6 * fixed some stupid threading issues in the packet handler (duh)
* use the new raw i2np message format (the previous corruptions were due to above)
* add a new test component (UDPFlooder) which floods all peers at the rate desired
* packet munging fix for highly fragmented messages
* include basic slow start code
* fixed the UDP peer rate refilling
* cleaned up some nextSend scheduling
2005-04-16 15:18:09 +00:00
cervantes
a7dfaee5ac added connelly.i2p 2005-04-13 02:29:59 +00:00
jrandom
7beb92b1cc First pass of the UDP transport. No where near ready for use, but it does
the basics (negotiate a session and send I2NP messages back and forth).  Lots,
lots more left.
2005-04-12 16:48:43 +00:00
jrandom
5b56d22da9 2005-04-12 jrandom
* Make sure we don't get cached updates (thanks smeghead!)
    * Clear out the callback for the TestJob after it passes (only affects the
      job timing accounting)
2005-04-12 15:22:11 +00:00
jrandom
e6b343070a removed copy/paste error 2005-04-09 23:15:53 +00:00
smeghead
8496b88518 2005-04-08 smeghead
* Added NativeBigInteger benchmark to scripts/i2pbench.sh.
2005-04-09 03:16:05 +00:00
jrandom
aa542b7876 for implementation simplicity, include fragment size in the SessionConfirmed packets 2005-04-08 23:20:45 +00:00
jrandom
3f7d46378b * specify exactly what gets in the DSA signatures for the connection establishment
* include a new signedOnTime so that we can prepare the packet at a different moment from
  when we encrypt & send it (also allowing us to reuse that signature on resends for the same
  establishment)
2005-04-08 14:21:26 +00:00
smeghead
b36def1f72 2005-04-08 smeghead
* Security improvements to TrustedUpdate: signing and verification of the
      version string along with the data payload for signed update files
      (consequently the positions of the DSA signature and version string fields
      have been swapped in the spec for the update file's header); router will
      no longer perform a trusted update if the signed update's version is lower
      than or equal to the currently running router's version.
    * Added two new CLI commands to TrustedUpdate: showversion, verifyupdate.
    * Extended TrustedUpdate public API for use by third party applications.
2005-04-08 12:39:20 +00:00
smeghead
5a6a3a5e8d oops, forgot to add new eepget script to build 2005-04-08 01:56:02 +00:00
jrandom
c3bd26d9b4 added wspucktracker.i2p 2005-04-07 20:40:30 +00:00
aum
967e106ee7 fixed one last javadoc err 2005-04-07 04:36:06 +00:00
aum
7c73e59482 Fixed more javadoc errors 2005-04-07 04:26:55 +00:00
aum
03dfa913d1 Removed erroneous @author tag from methods 2005-04-07 04:05:13 +00:00
jrandom
348e845793 *cough* thanks cervantes 2005-04-06 16:38:38 +00:00
jrandom
80827c3aad * 2005-04-06 0.5.0.6 released 2005-04-06 15:43:25 +00:00
jrandom
3b4cf0a024 added 55cancri.i2p 2005-04-06 15:14:00 +00:00
jrandom
941252fd80 2005-04-05 jrandom
* Retry I2PTunnel startup if we are unable to build a socketManager for a
      client or httpclient tunnel.
    * Add some basic sanity checking on the I2CP settings (thanks duck!)
2005-04-05 22:24:32 +00:00
jrandom
bc626ece2d 2005-04-05 jrandom
* After a successfull netDb search for a leaseSet, republish it to all of
      the peers we have tried so far who did not give us the key (up to 10),
      rather than the old K closest (which may include peers who had given us
      the key)
    * Don't wait 5 minutes to publish a leaseSet (duh!), and rather than
      republish it every 5 minutes, republish it every 3.  In addition, always
      republish as soon as the leaseSet changes (duh^2).
    * Minor fix for oddball startup race (thanks travis_bickle!)
    * Minor AES update to allow in-place decryption.
2005-04-05 16:06:14 +00:00
jrandom
400feb3ba7 clarify crypto/hmac usage for simpler implementation 2005-04-05 15:28:54 +00:00
jrandom
756a4e3995 added a section for congestion control describing what I hope to implement. what
/actually/ gets implemented will be documented further once its, er, implemented
2005-04-04 17:21:30 +00:00
aum
578301240e Added constructors to PrivateKey, PublicKey, SigningPrivateKey and
SigningPublicKey, which take a single String argument and construct
the object from the Base64 data in that string (where this data is
the product of a .toBase64() call on a prior instance).
2005-04-04 06:13:50 +00:00
aum
9b8f91c7f9 Added 'toPublic()' methods to PrivateKey and SigningPrivateKey, such
that these return PublicKey and SigningPublicKey objects, respectively.
2005-04-04 06:01:13 +00:00
smeghead
c7c389d4fb added eepget wrapper script for *nix 2005-04-03 13:35:52 +00:00
smeghead
68f7adfa0b *** keyword substitution change *** 2005-04-03 13:33:29 +00:00
jrandom
c4ac5170c7 2005-04-03 jrandom
* EepGet fix for open-ended HTTP fetches (such as the news.xml
      feeding the NewsFetcher)
2005-04-03 12:50:11 +00:00
jrandom
32e0c8ac71 updated status blurb 2005-04-03 07:22:28 +00:00
jrandom
c9c1eae32f 2005-04-01 jrandom
* Allow editing I2PTunnel server instances with five digit ports
      (thanks nickless_head!)
    * More NewsFetcher debugging for reported weirdness
2005-04-01 13:29:26 +00:00
jrandom
33366cc291 2005-04-01 jrandom
* Fix to check for missing news file (thanks smeghead!)
    * Added destination display CLI:
      java -cp lib/i2p.jar net.i2p.data.Destination privKeyFilename
    * Added destination display to the web interface (thanks pnspns)
    * Installed CIA backdoor
2005-04-01 11:28:06 +00:00
jrandom
083ac1f125 n3wz0rz 2005-03-31 02:04:18 +00:00
219 changed files with 27383 additions and 1409 deletions

View File

@@ -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;
@@ -288,8 +289,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 +487,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 +507,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 +518,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 +533,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 +549,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 +585,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).");

View File

@@ -209,8 +209,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
props.putAll(System.getProperties());
else
props.putAll(tunnel.getClientOptions());
I2PSocketManager sockManager = I2PSocketManagerFactory.createManager(tunnel.host, Integer.parseInt(tunnel.port), props);
if (sockManager == null) return null;
int portNum = 7654;
if (tunnel.port != null) {
try {
portNum = Integer.parseInt(tunnel.port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + tunnel.port + "], reverting to " + portNum);
}
}
I2PSocketManager sockManager = null;
while (sockManager == null) {
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
if (sockManager == null) {
_log.log(Log.CRIT, "Unable to create socket manager");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
sockManager.setName("Client");
return sockManager;
}

View File

@@ -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 =
@@ -190,6 +191,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 + "]");
@@ -282,6 +284,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (addressHelper != null) {
destination = addressHelper;
host = getHostName(destination);
ahelper = 1;
}
}
line = method + " " + request.substring(pos);
@@ -403,7 +406,19 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
l.log("Could not resolve " + destination + ".");
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, destination);
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else if(ahelper != 0)
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
s.close();
return;
}
@@ -476,10 +491,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (out != null) {
out.write(errMessage);
if (targetRequest != null) {
out.write(targetRequest.getBytes());
int protopos = targetRequest.indexOf(" ");
String uri = targetRequest.substring(0, protopos);
out.write("<a href=\"http://".getBytes());
out.write(uri.getBytes());
out.write("\">http://".getBytes());
out.write(uri.getBytes());
out.write("</a>".getBytes());
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
}
out.write("<p /><i>Generated on: ".getBytes());
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
@@ -493,7 +514,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
if (out != null) {
try {
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, wwwProxy);
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
} catch (IOException ioe) {
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
}

View File

@@ -75,9 +75,18 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
I2PClient client = I2PClientFactory.createClient();
Properties props = new Properties();
props.putAll(getTunnel().getClientOptions());
int portNum = 7654;
if (getTunnel().port != null) {
try {
portNum = Integer.parseInt(getTunnel().port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
}
}
while (sockMgr == null) {
synchronized (slock) {
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, Integer.parseInt(getTunnel().port),
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, portNum,
props);
}

View File

@@ -153,10 +153,11 @@ public class TunnelController implements Logging {
setListenOn();
String listenPort = getListenPort();
String proxyList = getProxyList();
String sharedClient = getSharedClient();
if (proxyList == null)
_tunnel.runHttpClient(new String[] { listenPort }, this);
_tunnel.runHttpClient(new String[] { listenPort, sharedClient }, this);
else
_tunnel.runHttpClient(new String[] { listenPort, proxyList }, this);
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
acquire();
_running = true;
}
@@ -199,7 +200,8 @@ public class TunnelController implements Logging {
setListenOn();
String listenPort = getListenPort();
String dest = getTargetDestination();
_tunnel.runClient(new String[] { listenPort, dest }, this);
String sharedClient = getSharedClient();
_tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
acquire();
_running = true;
}
@@ -258,8 +260,16 @@ public class TunnelController implements Logging {
if ("localhost".equals(_tunnel.host))
_tunnel.host = "127.0.0.1";
String port = getI2CPPort();
if ( (port != null) && (port.length() > 0) )
_tunnel.port = port;
if ( (port != null) && (port.length() > 0) ) {
try {
int portNum = Integer.parseInt(port);
_tunnel.port = String.valueOf(portNum);
} catch (NumberFormatException nfe) {
_tunnel.port = "7654";
}
} else {
_tunnel.port = "7654";
}
}
public void stopTunnel() {
@@ -323,7 +333,20 @@ public class TunnelController implements Logging {
public String getListenPort() { return _config.getProperty("listenPort"); }
public String getTargetDestination() { return _config.getProperty("targetDestination"); }
public String getProxyList() { return _config.getProperty("proxyList"); }
public String getSharedClient() { return _config.getProperty("sharedClient", "true"); }
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
public String getMyDestination() {
if (_tunnel != null) {
List sessions = _tunnel.getSessions();
for (int i = 0; i < sessions.size(); i++) {
I2PSession session = (I2PSession)sessions.get(i);
Destination dest = session.getMyDestination();
if (dest != null)
return dest.toBase64();
}
}
return null;
}
public boolean getIsRunning() { return _running; }
public boolean getIsStarting() { return _starting; }

View File

@@ -1,6 +1,7 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.IOException;
@@ -55,7 +56,7 @@ import net.i2p.util.Log;
* Sets the ip address clients will listen on. By default this is the
* localhost (127.0.0.1)
* -------------------------------------------------
* openclient &lt;listenPort&gt; &lt;peer&gt;\n
* openclient &lt;listenPort&gt; &lt;peer&gt;[ &lt;sharedClient&gt;]\n
* --
* ok [&lt;jobId&gt;]\n
* or
@@ -70,8 +71,10 @@ import net.i2p.util.Log;
* specified as 'file:&lt;filename&gt;' or the name of a destination listed in
* hosts.txt. The &lt;jobId&gt; returned together with "ok" and &lt;listenport&gt; can
* later be used as argument for the "close" command.
* &lt;sharedClient&gt; indicates if this httpclient shares tunnels with other
* clients or not (just use 'true' and 'false'
* -------------------------------------------------
* openhttpclient &lt;listenPort&gt; [&lt;proxy&gt;]\n
* openhttpclient &lt;listenPort&gt; [&lt;sharedClient&gt;] [&lt;proxy&gt;]\n
* --
* ok [&lt;jobId&gt;]\n
* or
@@ -90,6 +93,8 @@ import net.i2p.util.Log;
* hosts.txt. The &lt;jobId&gt; returned together with "ok" and
* &lt;listenport&gt; can later be used as argument for the "close"
* command.
* &lt;sharedClient&gt; indicates if this httpclient shares tunnels with other
* clients or not (just use 'true' and 'false'
* -------------------------------------------------
* opensockstunnel &lt;listenPort&gt;\n
* --
@@ -145,6 +150,11 @@ import net.i2p.util.Log;
* description depends on the type of job.
* -------------------------------------------------
* </pre>
*
*
* @deprecated this isn't run by default, and no one seems to use it, and has
* lots of things to maintain. so, at some point this may dissapear
* unless someone pipes up ;)
*/
public class TunnelManager implements Runnable {
private final static Log _log = new Log(TunnelManager.class);
@@ -322,9 +332,9 @@ public class TunnelManager implements Runnable {
buf.ignoreFurtherActions();
}
public void processOpenClient(int listenPort, String peer, OutputStream out) throws IOException {
public void processOpenClient(int listenPort, String peer, String sharedClient, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("client " + listenPort + " " + peer, buf);
_tunnel.runCommand("client " + listenPort + " " + peer + " " + sharedClient, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("clientTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
@@ -348,9 +358,9 @@ public class TunnelManager implements Runnable {
buf.ignoreFurtherActions();
}
public void processOpenHTTPClient(int listenPort, String proxy, OutputStream out) throws IOException {
public void processOpenHTTPClient(int listenPort, String sharedClient, String proxy, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("httpclient " + listenPort + " " + proxy, buf);
_tunnel.runCommand("httpclient " + listenPort + " " + sharedClient + " " + proxy, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("httpclientTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
@@ -432,4 +442,4 @@ public class TunnelManager implements Runnable {
out.write(command.getBytes());
out.write("\n".getBytes());
}
}
}

View File

@@ -1,6 +1,7 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
@@ -102,45 +103,53 @@ class TunnelManagerClientRunner implements Runnable {
} else if ("openclient".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String peer = null;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenPort> <peer>", out);
String sharedClient = null;
int numTokens = tok.countTokens();
if (numTokens < 2 || numTokens > 3) {
_mgr.error("Usage: openclient <listenPort> <peer> <sharedClient>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
listenPort = Integer.parseInt(tok.nextToken());
peer = tok.nextToken();
if (tok.hasMoreTokens())
sharedClient = tok.nextToken();
else
sharedClient = "true";
_mgr.processOpenClient(listenPort, peer, sharedClient, out);
} 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);
String sharedClient = "true";
int numTokens = tok.countTokens();
if (numTokens < 1 || numTokens > 3) {
_mgr.error("Usage: openhttpclient <listenPort> [<sharedClient>] [<proxy>]", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
listenPort = Integer.parseInt(tok.nextToken());
if (tok.hasMoreTokens()) {
String val = tok.nextToken();
if (tok.hasMoreTokens()) {
sharedClient = val;
proxy = tok.nextToken();
} else {
if ( ("true".equals(val)) || ("false".equals(val)) ) {
sharedClient = val;
} else {
proxy = val;
}
}
}
_mgr.processOpenHTTPClient(listenPort, sharedClient, proxy, out);
} 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()) {
@@ -191,4 +200,4 @@ class TunnelManagerClientRunner implements Runnable {
}
}
}
}
}

View File

@@ -36,14 +36,6 @@ public class EditBean extends IndexBean {
}
}
public String getInternalType(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getType();
else
return "";
}
public String getTargetHost(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
@@ -81,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) {

View File

@@ -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);
@@ -311,6 +313,14 @@ public class IndexBean {
else return internalType;
}
public String getInternalType(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getType();
else
return "";
}
public String getClientInterface(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
@@ -335,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 "";
@@ -350,6 +368,19 @@ public class IndexBean {
return "";
}
public String getDestinationBase64(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
String rv = tun.getMyDestination();
if (rv != null)
return rv;
else
return "";
} else {
return "";
}
}
///
/// bean props for form submission
///
@@ -448,6 +479,9 @@ public class IndexBean {
public void setStartOnLoad(String moo) {
_startOnLoad = true;
}
public void setSharedClient(String moo) {
_sharedClient=true;
}
public void setConnectDelay(String moo) {
_connectDelay = true;
}
@@ -475,8 +509,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);
@@ -489,6 +529,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);
@@ -519,8 +564,10 @@ public class IndexBean {
config.setProperty("description", _description);
if (_i2cpHost != null)
config.setProperty("i2cpHost", _i2cpHost);
if (_i2cpPort != null)
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) )
config.setProperty("i2cpPort", _i2cpPort);
else
config.setProperty("i2cpPort", "7654");
if (_customOptions != null) {
StringTokenizer tok = new StringTokenizer(_customOptions);
@@ -544,7 +591,7 @@ public class IndexBean {
}
config.setProperty("startOnLoad", _startOnLoad + "");
if (_tunnelCount != null) {
config.setProperty("option.inbound.quantity", _tunnelCount);
config.setProperty("option.outbound.quantity", _tunnelCount);
@@ -558,7 +605,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 {

View File

@@ -111,7 +111,7 @@ if (curTunnel >= 0) {
</select>
&nbsp;&nbsp;
<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="sharedClient" checked="true" />
<% } else { %>
<input type="checkbox" value="true" name="sharedClient" />
<% } %>
<i>(Share tunnels with other clients and httpclients? Change requires restart of client proxy)</i>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<b><hr size="1">
Advanced networking options<br />
<span style="color:#dd0000;">(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>

View File

@@ -77,7 +77,7 @@ if (curTunnel >= 0) {
</td>
<td>
Host: <input type="text" size="20" name="targetHost" value="<%=editBean.getTargetHost(curTunnel)%>" />
Port: <input type="text" size="4" maxlength="4" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
Port: <input type="text" size="6" maxlength="5" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
</td>
</tr>
<% String curType = editBean.getInternalType(curTunnel);
@@ -110,6 +110,10 @@ Port: <input type="text" size="4" maxlength="4" name="targetPort" value="<%=edit
</td>
</tr>
<tr>
<td valign="top" align="left"><b>Local destination:</b><br /><i>(if known)</i></td>
<td valign="top" align="left"><input type="text" size="60" value="<%=editBean.getDestinationBase64(curTunnel)%>" /></td>
</tr>
<tr>
<td colspan="2" align="center">
<b><hr size="1">
Advanced networking options<br />

View File

@@ -118,6 +118,9 @@
case IndexBean.RUNNING:
%><b><span style="color:#00dd00">Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
if ("httpserver".equals(indexBean.getInternalType(curServer))) {
%> (<a href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>">preview</a>)<%
}
break;
case IndexBean.NOT_RUNNING:
%><b><span style="color:#dd0000">Not Running</span></b>

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

26
apps/q/doc/diagrams.html Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

80
apps/q/doc/index.html Normal file
View 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>

View 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&nbsp;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 &lt;path&gt;</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&nbsp;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 &lt;keystring&gt; something.txt</pre></code></blockquote>
<i>or</i>:
<blockquote><code><pre>
qmgr get &lt;keystring&gt; &gt; 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 &lt;noderef-file&gt;</pre></code></blockquote>
where &lt;noderef-file&gt; 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 &lt;noderef-file&gt;</pre></code></blockquote>
This will extract the <i>I2P Destination</i> of your running server node, and
write it into &lt;noderef-file&gt;. 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 &lt;noderef-file&gt;</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 &lt;portnum&gt; start</pre></code></blockquote>
You need the <b>-port &lt;portnum&gt;</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 &lt;portnum&gt;</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 &lt;dirpath&gt;</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
View 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
View 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:&lt;basename&gt;[,&lt;cryptoKey&gt;][&lt;path&gt;]</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://&lt;domainname&gt;[:&lt;port&gt;][&lt;path&gt;]</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

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

1
apps/q/doc/qnoderefs.txt Normal file
View File

@@ -0,0 +1 @@
rxvXpHKfWGWsql4PJaHglAERSUYyrdKKAzK6jPHT4QXRf9jgcVd4mInq0j6H4inVOzT9dG4L6c9GrlQwe4ysUm5jSTyZemxiZpQDCAazsoRzNDv6gevA40J6uGl10JtVtOjqXW8Ej0JUKubz88g~ogPb1h4Xibc-RrtqrvsJebg5xYFkLlnr7DxDtiWzIMRSZ9Ri2P~eq0SwZzd81tvASPj5fb3nySHeABAuY8HrNu0gqRLjeayDpd3OK1ogrxf1lMvfutn5pnLrlVcvKHa~6rNWWGSulsuEYWtpUd4Itj9aKqIgF9ES7RF77Z73W1f6NRTHO48ZLyLLaKVLjDIsHQP-0mOevszcPjFWtheqRKvT2D28WEMpVC-mPtfw91BkdgBa3pwWhwG~7KIhvWhGs8bj2NOKkqrwYU7xhNVaHdDDkzv4gsweCutHNiiCF~4yL54WzCIfSKDjcHjQxxVkh2NKeaItzgw9E~mPAKNZD22X~2oAuuL9i~0lldEV1ddUAAAA

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
apps/q/doc/screenshot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
apps/q/doc/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View 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

File diff suppressed because it is too large Load Diff

90
apps/q/java/build.xml Normal file
View 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="${project.name}.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 dir="${classes.dir}" includes="**/QConsole.class"/> -->
<classes file="build/net/i2p/aum/q/QConsole.class"/>
<classes file="build/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>

View 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>

View 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 &copy; 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,6 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Q Tools</td>
</tr>
</table>

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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;
}

View 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>&lt;TMPL_INCLUDE file="&lt;TMPL_VAR name="the_file"&gt;"&gt;</code>:
* <p>
* myTemplate.tmpl:
* <pre>
* &lt;TMPL_INCLUDE file="&lt;%the_file%&gt;"&gt;
* </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 &lt;%the_file%&gt; 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);
}

View 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;
}
}

View 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 = {"&amp;", "&lt;", "&gt;", "&quot;"};
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());
}
}

View 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() {
}
}

View 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();
}
}
}

View 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");
}
}

View 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();
}
}
}

View 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();
}
}

View 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");
}
}

View 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();
}
}
}
}

View 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
{
}

View 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);
}
}

View 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();
}
}

View 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");
}
}

View 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);
}
}
}
}

View 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+"'";
}
}

View 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();
}
}
**/
}

View 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(); }
}
}

View 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'
}
**/

View 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));
}
}

View 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);
}
}

View 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);
}
}

View 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();
}
}

View 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();
}
}

View 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");
}
}

View 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");
}
}

View File

@@ -0,0 +1,17 @@
public class helloworld
{
public static void main(String [] args)
{
helloworld h = new helloworld();
h.greet();
}
public void greet()
{
System.out.println("Hi, this is your greeting");
}
}

View File

@@ -0,0 +1,72 @@
/*
* HtmlPage.java
*
* Created on April 8, 2005, 8:22 PM
*/
package net.i2p.aum.http;
import java.util.*;
import net.i2p.aum.*;
/**
* Framework for building up a page of HTML by method calls alone, breaking
* every design rule by enmeshing content, presentation and logic
*/
public class HtmlPage {
public String dtd = "<!DOCTYPE HTML PUBLIC "
+"\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
+"\"http://www.w3.org/TR/html4/loose.dtd\">";
public Tag page;
public Tag head;
public Tag body;
DupHashtable cssSettings;
/** Creates a new HtmlPage object */
public HtmlPage() {
page = new Tag("html");
head = new Tag(page, "head");
body = new Tag(page, "body");
cssSettings = new DupHashtable();
}
/** renders out the whole page into a single string */
public String toString() {
// embed stylesheet, if non-empty
if (cssSettings.size() > 0) {
Tag t1 = head.nest("style type=\"text/css\"");
t1.raw("<!--\n");
Tag cssTag = t1.nest();
t1.raw("-->\n");
Enumeration elems = cssSettings.keys();
while (elems.hasMoreElements()) {
String name = (String)elems.nextElement();
cssTag.raw(name + " { ");
Enumeration items = cssSettings.get(name).elements();
while (items.hasMoreElements()) {
String item = (String)items.nextElement();
cssTag.raw(item+";");
}
cssTag.raw(" }\n");
}
}
// now render out the whole page
return dtd + "\n" + page;
}
/** adds a setting to the page's embedded stylesheet */
public HtmlPage css(String tag, String item, String val) {
return css(tag, item+":"+val);
}
/** adds a setting to the page's embedded stylesheet */
public HtmlPage css(String tag, String setting) {
cssSettings.put(tag, setting);
return this;
}
}

View File

@@ -0,0 +1,50 @@
/*
* I2PHttpRequestHandler.java
*
* Created on April 8, 2005, 11:57 PM
*/
package net.i2p.aum.http;
import java.lang.*;
import java.lang.reflect.*;
import java.util.*;
import java.io.*;
import java.net.*;
import net.i2p.data.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
/**
*
* @author david
*/
public abstract class I2PHttpRequestHandler extends MiniHttpRequestHandler
{
/** Creates a new instance of I2PHttpRequestHandler */
public I2PHttpRequestHandler(MiniHttpServer server, Object sock, Object arg)
throws Exception
{
super(server, sock, arg);
}
/** Extracts a readable InputStream from own socket */
public InputStream getInputStream() throws IOException {
try {
return ((I2PSocket)socket).getInputStream();
} catch (Exception e) {
return ((Socket)socket).getInputStream();
}
}
/** Extracts a writeable OutputStream from own socket */
public OutputStream getOutputStream() throws IOException {
try {
return ((I2PSocket)socket).getOutputStream();
} catch (Exception e) {
return ((Socket)socket).getOutputStream();
}
}
}

View File

@@ -0,0 +1,119 @@
/*
* I2PHttpServer.java
*
* Created on April 8, 2005, 11:39 PM
*/
package net.i2p.aum.http;
import java.io.*;
import java.util.*;
import net.i2p.*;
import net.i2p.data.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.aum.*;
/**
*
* @author david
*/
public class I2PHttpServer extends MiniHttpServer {
PrivDestination privKey;
I2PSocketManager socketMgr;
public I2PHttpServer(PrivDestination key)
throws DataFormatException, IOException, I2PException
{
this(key, I2PHttpRequestHandler.class, null, null);
}
public I2PHttpServer(PrivDestination key, Class hdlrClass)
throws DataFormatException, IOException, I2PException
{
this(key, hdlrClass, null, null);
}
public I2PHttpServer(PrivDestination key, Class hdlrClass, Properties props)
throws DataFormatException, IOException, I2PException
{
this(key, hdlrClass, null, props);
}
/** Creates a new instance of I2PHttpServer */
public I2PHttpServer(PrivDestination key, Class hdlrClass, Object hdlrArg, Properties props)
throws DataFormatException, IOException, I2PException
{
super(hdlrClass, hdlrArg);
if (key != null) {
privKey = key;
} else {
privKey = new PrivDestination();
}
// get a socket manager
// socketManager = I2PSocketManagerFactory.createManager(key);
if (props == null) {
socketMgr = I2PSocketManagerFactory.createManager(privKey.getInputStream());
} else {
socketMgr = I2PSocketManagerFactory.createManager(privKey.getInputStream(), props);
}
if (socketMgr == null) {
throw new I2PException("I2PHttpServer: Failed to create socketManager");
}
String d = privKey.getDestination().toBase64();
System.out.println("Server: getting server socket for dest "+d);
// get a server socket
//serverSocket = socketManager.getServerSocket();
}
public void getServerSocket() throws IOException {
I2PServerSocket sock;
sock = socketMgr.getServerSocket();
serverSocket = sock;
System.out.println("listening on dest: "+privKey.getDestination().toBase64());
}
/**
* Listens on our 'serverSocket' object for an incoming connection,
* and returns a connected socket object. You should override this
* if you're using non-standard socket objects
*/
public Object acceptConnection() throws IOException {
I2PSocket sock;
try {
sock = ((I2PServerSocket)serverSocket).accept();
} catch (I2PException e) {
throw new IOException(e.toString());
}
System.out.println("Got connection from: "+sock.getPeerDestination().toBase64());
//System.out.println("New connection accepted" +
// sock.getInetAddress() +
// ":" + sock.getPort());
return sock;
}
public static void main(String [] args) {
try {
System.out.println("I2PHttpServer: starting up with new random key");
I2PHttpServer server = new I2PHttpServer((PrivDestination)null);
System.out.println("I2PHttpServer: running server");
server.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,22 @@
/*
* MiniDemoXmlRpcHandler.java
*
* Created on April 13, 2005, 3:20 PM
*/
package net.i2p.aum.http;
public class MiniDemoXmlRpcHandler {
MiniHttpServer server;
public MiniDemoXmlRpcHandler(MiniHttpServer server) {
this.server = server;
}
public String bar(String arg) {
return "bar: got '"+arg+"'";
}
}

View File

@@ -0,0 +1,567 @@
/*
* MiniHttpRequestHandler.java
* Adapted from pont.net's httpRequestHandler (httpServer.java)
*
* Created on April 8, 2005, 3:15 PM
*/
package net.i2p.aum.http;
import java.net.*;
import java.io.*;
import java.util.*;
import java.lang.*;
import net.i2p.aum.*;
public abstract class MiniHttpRequestHandler implements Runnable {
final static String CRLF = "\r\n";
/** server which created this handler */
protected MiniHttpServer server;
/** socket through which client is connected to us */
protected Object socket;
/** stored constructor arg */
protected Object serverArg;
/** input sent from client in request */
protected InputStream input;
/** we use this to read from client */
protected BufferedReader br;
/** output sent to client in reply */
protected OutputStream output;
/** http request type - GET, POST etc */
protected String reqType;
/** the request pathname */
protected String reqFile;
/** the request protocol (eg 'HTTP/1.0') */
protected String reqProto;
/** http headers */
protected DupHashtable headerVars;
/** variable settings from POST data */
public DupHashtable postVars;
/** variable settings from URL (?name1=val1&name2=val2...) */
public DupHashtable urlVars;
/** consolidated variable settings from URL or POST data */
public DupHashtable allVars;
/** first line of response we send back to client, set this
* with 'setStatus'
*/
private String status = "HTTP/1.0 200 OK";
private String contentType = "text/plain";
private String reqContentType = null;
protected String serverName = "aum's MiniHttpServer";
protected byte [] rawContentBytes = null;
/**
* raw data sent by client in post req
*/
protected char [] postData;
/** if a POST, this holds the full POST data as a string */
public String postDataStr;
// Constructors
public MiniHttpRequestHandler(MiniHttpServer server, Object socket) throws Exception {
this(server, socket, null);
}
public MiniHttpRequestHandler(MiniHttpServer server, Object socket, Object arg) throws Exception {
this.server = server;
this.socket = socket;
this.serverArg = arg;
this.input = getInputStream();
this.output = getOutputStream();
this.br = new BufferedReader(new InputStreamReader(input));
}
// -------------------------------------------
// START OF OVERRIDEABLES
// -------------------------------------------
// override these methods in subclass if your socket-type thang is not
// a genuine Socket objct
/** Extracts a readable InputStream from own socket */
public InputStream getInputStream() throws IOException {
return ((Socket)socket).getInputStream();
}
/** Extracts a writeable OutputStream from own socket */
public OutputStream getOutputStream() throws IOException {
return ((Socket)socket).getOutputStream();
}
/** closes the socket (or our socket-ish object) */
public void closeSocket() throws IOException {
((Socket)socket).close();
}
/** method which gets called upon receipt of a GET.
* You should override this
*/
public abstract void on_GET() throws Exception;
/** method which gets called upon receipt of a POST.
* You should override this
*/
public abstract void on_POST() throws Exception;
// -------------------------------------------
// END OF OVERRIDEABLES
// -------------------------------------------
/** Sets the HTTP status line (default 'HTTP/1.0 200 OK') */
public void setStatus(String status) {
this.status = status;
}
/** Sets the Content=Type header (default "text/plain") */
public void setContentType(String contentType) {
this.contentType = contentType;
}
/** Sets the 'Server' header (default "aum's MiniHttpServer") */
public void setServer(String serverType) {
this.serverName = serverType;
}
/** Sets the full body of raw output to be written, replacing
* the generated html tags
*/
public void setRawOutput(String raw) {
setRawOutput(raw.getBytes());
}
/** Sets the full body of raw output to be written, replacing
* the generated html tags
*/
public void setRawOutput(byte [] raw) {
rawContentBytes = raw;
}
/** writes a String to output - normally you shouldn't need to call
* this directly
*/
public void write(String raw) {
write(raw.getBytes());
}
/** writes a byte array to output - normally you shouldn't need to call
* this directly
*/
public void write(byte [] raw) {
try {
output.write(raw);
} catch (Exception e) {
System.out.print(e);
}
}
/** processes the request, sends back response */
public void run() {
try {
processRequest();
}
catch(Exception e) {
e.printStackTrace();
System.out.println(e);
}
}
/** does all the work of processing the request */
protected void processRequest() throws Exception {
headerVars = new DupHashtable();
urlVars = new DupHashtable();
postVars = new DupHashtable();
allVars = new DupHashtable();
String line;
// basic parsing of first req line
String reqLine = br.readLine();
printReq(reqLine);
String [] reqBits = reqLine.split("\\s+", 3);
reqType = reqBits[0];
String [] reqFileBits = reqBits[1].split("[?]", 2);
reqFile = reqFileBits[0];
// check for URL variables
if (reqFileBits.length > 1) {
urlVars = parseVars(reqFileBits[1]);
}
// extract the 'request protocol', default to HTTP/1.0
try {
reqProto = reqBits[2];
} catch (Exception e) {
// workaround eepproxy bug
reqFile = "/";
reqProto = "HTTP/1.0";
}
// suck the headers
while (true) {
line = br.readLine();
//System.out.println("Got header line: "+line);
if (line.equals("")) {
break;
}
String [] lineBits = line.split(":\\s+", 2);
headerVars.put(lineBits[0], lineBits[1]);
}
//br.close();
// GET is simple, all the work is already done
if (reqType.equals("GET")) {
on_GET();
}
// POST is more involved - need to read POST data and
// break it up into fields
else if (reqType.equals("POST")) {
int postLen;
String postLenStr;
try {
reqContentType = headerVars.get("Content-Type", 0, "");
try {
postLenStr = headerVars.get("Content-Length", 0);
} catch (Exception e) {
// damn opera
postLenStr = headerVars.get("Content-length", 0);
}
postLen = new Integer(postLenStr).intValue();
postData = new char[postLen];
//System.out.println("postLen="+postLen);
for (int i=0; i<postLen; i++) {
int n = br.read();
postData[i] = (char)n;
}
//input.read(postData);
postDataStr = new String(postData);
//System.out.println("post data: '"+postDataStr+"'");
// detect RPC
if (reqContentType.equals("text/xml")
&& postDataStr.startsWith("<?xml")
)
{
// yep, it's an rpc, fob off to handler
ByteArrayInputStream in = new ByteArrayInputStream(postDataStr.getBytes());
try {
byte [] resp = server.xmlRpcServer.execute(in);
setRawOutput(resp);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
} else if (reqContentType.startsWith("multipart/form-data")) {
// harder -parse as form
postVars = parsMultipartForm(reqContentType, postDataStr);
on_POST();
} else {
// decode form vars
postVars = parseVars(postDataStr);
//System.out.println("postVars="+postVars);
on_POST();
}
} catch (Exception e) {
e.printStackTrace();
setStatus("HTTP/1.0 400 Missing Content-Length header");
setRawOutput("Missing Content-Length header");
}
}
write(status+"\r\n");
write("Content-Type: "+contentType+"\r\n");
write("Server: "+server+"\r\n");
int contentLength;
if (rawContentBytes == null) {
// render out our html page
String rawPage = toString();
contentLength = rawPage.length();
write("Content-Length: "+rawPage.length()+"\r\n");
write("\r\n");
write(rawPage);
} else {
// sending raw output
write("Content-Length: "+rawContentBytes.length+"\r\n");
write("\r\n");
write(rawContentBytes);
}
output.flush();
try {
input.close();
output.close();
br.close();
closeSocket();
}
catch(Exception e) {}
}
/** helper method which, given a filename, returns a guess at
* a plausible mimetype for it
*/
public String getContentType(String fileName) {
return Mimetypes.guessType(fileName);
}
/** a crude rfc-1867-subset form decoder, for allowing file
* uploads. For binary file upload, only supports
* application/octet-stream and application/x-macbinary at present
*/
public DupHashtable parsMultipartForm(String reqContentType, String postDataStr) {
DupHashtable flds = new DupHashtable();
//System.out.println("contenttype='"+reqContentType+"'");
//System.out.println("raw postDataStr='"+postDataStr+"'");
// determine the 'boundary' string separating the form items
String boundary = reqContentType
.split("multipart/form-data;\\s*")
[1]
.split("boundary\\=")
[1];
// convert it to escaped string
boundary = "\\Q" + boundary + "\\E";
//System.out.println("boundary='"+boundary+"'");
// break up the raw post data into items
String [] postItems = postDataStr.split(boundary);
// try to extract a form variable from each item
for (int i=0; i<postItems.length; i++) {
String item = postItems[i];
// strip the newline at start
String [] items = item.split("^\\s+");
item = items[items.length-1];
// strip the trailing '--'
try {
item = item.substring(0, item.length()-4);
} catch (StringIndexOutOfBoundsException e) {
item = item.substring(0, item.length()-2);
}
postItems[i] = item;
// break up item into headers+content
String [] bits = item.split("\\r\\n\\r\\n", 2);
String [] fldHdrs = bits[0].split("\\r\\n");
String fldContent;
try {
// get item content
fldContent = bits[1];
} catch (ArrayIndexOutOfBoundsException e) {
// no content
fldContent = "";
}
//System.out.println("-----------------------");
// go through the headers in search of 'name='
for (int j=0; j<fldHdrs.length; j++) {
//System.out.println("hdr: '"+fldHdrs[j]+"'");
// break up header into its parts
String [] hdrItems = fldHdrs[j].split(";\\s+");
// go through each part in search of 'name='
for (int k=0; k<hdrItems.length; k++) {
String hdrItem = hdrItems[k];
if (hdrItem.startsWith("name=\"")) {
// got a field name, add to our DupHashtable
String varName = hdrItem.substring(6, hdrItem.length()-1);
flds.put(varName, fldContent);
allVars.put(varName, fldContent);
}
}
}
//System.out.println("data("+fldContent.length()+"): '"+fldContent+"'");
//System.out.println("postItem='"+postItems[i]+"'");
//byte [] b = fldContent.getBytes();
//for (int j=0; j<b.length; j++) {
// System.out.print("" + b[j] + ", ");
//}
//System.out.println("");
}
return flds;
// ----------------------------
// raw sample of a posted form
/*
Content-Type: multipart/form-data; boundary=---------------------------121990404611892642131748622646
Content-Length: 1443
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="cmd"
put
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="type"
other
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="title"
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="path"
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="mimetype"
text/plain
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="keywords"
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="summary"
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="data"; filename="tmpd.lst"
Content-Type: application/octet-stream
/tmp/d
/tmp/d/d1
/tmp/d/d1/f4
/tmp/d/d1/f3
/tmp/d/f2
/tmp/d/f1
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="privkey"
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="submit"
Insert it
-----------------------------121990404611892642131748622646
Content-Disposition: form-data; name="rawdata"
-----------------------------121990404611892642131748622646--
**/
}
public DupHashtable parseVars(String raw) {
DupHashtable h = new DupHashtable();
URLDecoder u = new URLDecoder();
String [] items = raw.split("[&]");
String dec;
for (int i=0; i<items.length; i++) {
try {
dec = u.decode(items[i], "ISO-8859-1");
String [] items1 = dec.split("[=]",2);
//System.out.println("parseVars: "+items1[0]+"="+items1[1]);
h.put(items1[0], items1[1]);
allVars.put(items1[0], items1[1]);
} catch (Exception e) {
e.printStackTrace();
}
}
return h;
}
public void printReq(String r) {
System.out.println(r);
}
public Tag dumpVars() {
Tag t = new Tag("table "
+"width=90% cellspacing=0 cellpadding=4 border=1");
tag(t, "tr")
.nest("td colspan=2")
.nest("big")
.nest("bold")
.raw("Dump of session vars");
dumpVarsFor(t, headerVars, "HTTP header variables");
dumpVarsFor(t, urlVars, "URL variables");
dumpVarsFor(t, postVars, "POST variables");
return t;
}
public void dumpVarsFor(Tag t, DupHashtable h, String heading) {
Tag tr;
Tag td;
//System.out.println("dumpVarsFor: map="+h);
// add the html headers
tr = tag(t, "tr");
t.nest("tr")
.nest("td colspan=2 align=left")
.nest("b")
.raw(heading);
//System.out.println("dumpVarsFor: heading="+heading);
Enumeration en = h.keys();
while (en.hasMoreElements()) {
String key = (String)en.nextElement();
Vector vals = h.get(key);
//System.out.println("dumpVarsFor: key="+key+" val="+vals);
for (int i=0; i<vals.size(); i++) {
tr = tag(t, "tr");
tr.nest("td").raw(i == 0 ? key : "&nbsp");
tr.nest("td").raw((String)vals.get(i));
}
}
}
/** creates an Tag object, and inserts it into
* a parent Tag
*/
public Tag tag(Tag parent, String tagopen) {
return new Tag(parent, tagopen);
}
/** creates an Tag object */
public Tag tag(String tagopen) {
return new Tag(tagopen);
}
}

View File

@@ -0,0 +1,93 @@
/*
* MiniHttpRequestPage.java
*
* Created on April 13, 2005, 11:24 AM
*/
package net.i2p.aum.http;
/**
*
* @author david
*/
public class MiniHttpRequestPage extends MiniHttpRequestHandler {
/** HtmlPage object into which we can write response */
protected HtmlPage page;
/** the 'head' portion of our response page */
protected Tag head;
/** the 'body' portion of our response page */
protected Tag body;
public MiniHttpRequestPage(MiniHttpServer server, Object socket) throws Exception {
super(server, socket, null);
this.page = new HtmlPage();
head = page.head;
body = page.body;
}
/** Creates a new instance of MiniHttpRequestPage */
public MiniHttpRequestPage(MiniHttpServer server, Object socket, Object arg) throws Exception {
super(server, socket, arg);
this.page = new HtmlPage();
head = page.head;
body = page.body;
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
}
public void on_GET() throws Exception {
}
public void on_POST() throws Exception {
}
public void on_RPC() throws Exception {
}
/** adds a string of text, or an Tag object, to the
* body of the output page
*/
public Tag add(String item) {
return body.add(item);
}
public Tag add(Tag item) {
return body.add(item);
}
/** sets up standard page's embedded stylesheet */
public void addCss() {
css("body", "font-family", "helvetica, arial, sans-serif");
css("body", "color", "green");
css("td", "vertical-align", "top");
css("code", "font-family: courier, monospace; font-weight: bold");
css(".border1, .pane1", "border-style: solid; border-width: 1px; border-color: blue");
css(".pane1", "background-color: #f0f0ff");
}
/** adds a single CSS setting */
public HtmlPage css(String tag, String item, String val) {
return css(tag, item+":"+val);
}
/** adds a single CSS setting */
public HtmlPage css(String tag, String setting) {
page.css(tag, setting);
return page;
}
/** renders this page to full html document */
public String toString() {
return page.toString();
}
}

View File

@@ -0,0 +1,225 @@
/*
* MiniHttpServer.java
*
* adapted and expanded from pont.net's httpServer.java
* Created on April 8, 2005, 3:13 PM
*/
package net.i2p.aum.http;
//***************************************
// HTTP Server
// fpont June 2000
// server implements HTTP GET method
//***************************************
import java.net.*;
import java.io.*;
import java.util.*;
import java.lang.*;
import java.lang.reflect.*;
import org.apache.xmlrpc.*;
public class MiniHttpServer extends Thread
{
public Object serverSocket;
public static int defaultPort = 18000;
public int port;
public static Class defaultReqHandlerClass = MiniHttpRequestHandler.class;
public Class reqHandlerClass;
public Object reqHandlerArg = null;
public XmlRpcServer xmlRpcServer;
public MiniHttpServer() {
this(defaultReqHandlerClass, defaultPort);
}
public MiniHttpServer(Class reqHandlerClass) {
this(reqHandlerClass, defaultPort);
}
public MiniHttpServer(Class reqHandlerClass, Object hdlrArg) {
this(reqHandlerClass, defaultPort, hdlrArg);
}
public MiniHttpServer(int port) {
this(defaultReqHandlerClass, port);
}
public MiniHttpServer(Class reqHandlerClass, int port) {
this(reqHandlerClass, port, null);
}
public MiniHttpServer(Class reqHandlerClass, int port, Object hdlrArg) {
super();
this.port = port;
this.reqHandlerClass = reqHandlerClass;
this.reqHandlerArg = hdlrArg;
xmlRpcServer = new XmlRpcServer();
}
// override these following methods if you're using sockets other than ServerSocket
/**
* Gets a server socket object, and assigns it to property 'serverSocket'.
* You should override this if you're using non-socket objects
*/
public void getServerSocket() throws IOException {
serverSocket = new ServerSocket(port);
try {
System.out.println("httpServer running on port "
+ ((ServerSocket)serverSocket).getLocalPort());
} catch (Exception e) {
}
}
/**
* Listens on our 'serverSocket' object for an incoming connection,
* and returns a connected socket object. You should override this
* if you're using non-standard socket objects
*/
public Object acceptConnection() throws IOException {
Socket sock = ((ServerSocket)serverSocket).accept();
//System.out.println("New connection accepted" +
// sock.getInetAddress() +
// ":" + sock.getPort());
return sock;
}
/**
* Invokes constructor on the 'reqHandlerClass' with which
* this server was constructed.
*/
public MiniHttpRequestHandler getRequestHandler(Object sock) throws Exception {
//return new MiniHttpRequestHandlerImpl(sock);
Class [] consArgTypes = {MiniHttpServer.class, Object.class, Object.class};
Object [] consArgs = {this, sock, reqHandlerArg};
Constructor cons = reqHandlerClass.getConstructor(consArgTypes);
try {
MiniHttpRequestHandler reqHdlr = (MiniHttpRequestHandler)(cons.newInstance(consArgs));
return reqHdlr;
} catch (InvocationTargetException e) {
Throwable e1 = e.getTargetException();
e1.printStackTrace();
throw e;
}
}
/**
* Adds an xmlrpc handler.
* Ideally, the handler object should have been constructed
* with 'this' as an argument.
*/
public void addXmlRpcHandler(String name, Object handler) {
xmlRpcServer.addHandler(name, handler);
}
/**
* starts up the server and enters an infinite loop,
* forever servicing requests
*/
public void run() {
Object server_socket;
try {
String clsName = getClass().getName();
// impl-dependent - procure a server socket
System.out.println(clsName + ": calling getServerSocket...");
getServerSocket();
System.out.println(clsName + ": got server socket");
// server infinite loop
while(true) {
System.out.println(clsName + ": awaiting inbound connection...");
// impl-dependent - accept incoming connection
Object sock = acceptConnection();
// Construct handler to process the HTTP request message.
try {
MiniHttpRequestHandler request = getRequestHandler(sock);
// Create a new thread to process the request.
Thread thread = new Thread(request);
// Start the thread.
thread.start();
}
catch(Exception e) {
//System.out.println(e);
e.printStackTrace();
}
}
}
catch (IOException e) {
System.out.println(e);
}
}
public static void main(String [] args) {
/** create a simple request handler */
class DemoHandler extends MiniHttpRequestPage {
public DemoHandler(MiniHttpServer server, Object sock, Object arg)
throws Exception
{
super(server, sock, arg);
}
public void on_GET() {
on_hit();
}
public void on_POST() {
on_hit();
}
public void on_hit() {
setContentType("text/html");
page.head.nest("title").raw("DemoHandler");
page.body
.nest("h3")
.raw("DemoHandler")
.end
.hr()
.raw("reqFile="+reqFile)
.br()
.raw("reqType="+reqType)
.hr()
.add(dumpVars())
;
}
}
MiniHttpServer server;
int port = defaultPort;
if (args.length > 0) {
try {
port = new Integer(args[0]).intValue();
} catch (NumberFormatException e) {
System.out.println("Invalid port: "+args[0]);
System.exit(1);
}
server = new MiniHttpServer(DemoHandler.class, port);
}
else {
server = new MiniHttpServer(DemoHandler.class);
}
MiniDemoXmlRpcHandler hdlr = new MiniDemoXmlRpcHandler(server);
server.addXmlRpcHandler("foo", hdlr);
server.run();
}
}

View File

@@ -0,0 +1,358 @@
/*
* HtmlTag.java
*
* Created on April 8, 2005, 8:22 PM
*/
package net.i2p.aum.http;
import java.lang.*;
import java.util.*;
import java.io.*;
/**
* Base class for building up quick-n-dirty HTML by code alone;
* Breaks every design rule by enmeshing content, presentation and logic together
* into java statements.
*/
public class Tag {
static Vector nlOnOpen = new Vector();
static {
nlOnOpen.addElement("html");
nlOnOpen.addElement("html");
nlOnOpen.addElement("head");
nlOnOpen.addElement("body");
nlOnOpen.addElement("frameset");
nlOnOpen.addElement("frame");
nlOnOpen.addElement("script");
nlOnOpen.addElement("blockquote");
nlOnOpen.addElement("div");
nlOnOpen.addElement("hr");
nlOnOpen.addElement("ul");
nlOnOpen.addElement("ol");
nlOnOpen.addElement("table");
nlOnOpen.addElement("caption");
nlOnOpen.addElement("col");
nlOnOpen.addElement("thead");
nlOnOpen.addElement("tfoot");
nlOnOpen.addElement("tbody");
nlOnOpen.addElement("tr");
nlOnOpen.addElement("form");
nlOnOpen.addElement("applet");
nlOnOpen.addElement("br");
nlOnOpen.addElement("style");
};
static Vector nlOnClose = new Vector();
static {
nlOnClose.addElement("h1");
nlOnClose.addElement("h2");
nlOnClose.addElement("h3");
nlOnClose.addElement("h4");
nlOnClose.addElement("h5");
nlOnClose.addElement("h6");
nlOnClose.addElement("p");
nlOnClose.addElement("pre");
nlOnClose.addElement("li");
nlOnClose.addElement("td");
nlOnClose.addElement("th");
nlOnClose.addElement("button");
nlOnClose.addElement("input");
nlOnClose.addElement("label");
nlOnClose.addElement("select");
nlOnClose.addElement("option");
nlOnClose.addElement("textarea");
nlOnClose.addElement("font");
nlOnClose.addElement("iframe");
nlOnClose.addElement("img");
nlOnClose.addElement("br");
}
String open;
String close;
Vector attribs;
Vector styles;
Vector content;
boolean breakBefore, breakAfter;
public Tag parent = null;
public Tag end = null;
// -----------------------------------------------------
// CONSTRUCTORS
// -----------------------------------------------------
/** Creates a new empty container tag */
public Tag() {
this((String)null);
}
/** Creates a new empty container tag, embedded in a parent tag */
public Tag(Tag parent) {
this(parent, null);
}
/**
* Creates a new HtmlTag instance, adds to a parent
*/
public Tag(Tag parent, String opentag) {
this(opentag);
parent.add(this);
this.end = this.parent = parent;
}
/** Creates a new instance of HtmlTag */
public Tag(String opentag) {
content = new Vector();
attribs = new Vector();
styles = new Vector();
if (opentag == null) {
return;
}
String [] tagBits = opentag.split("\\s+", 2);
open = tagBits[0];
if (open.endsWith("/")) {
open = open.substring(0, open.length()-1);
close = "";
}
else {
close = "</"+open+">";
}
if (tagBits.length > 1) {
attribs.addElement(tagBits[1]);
}
breakBefore = nlOnOpen.contains(open);
breakAfter = breakBefore || nlOnClose.contains(open);
}
// -----------------------------------------------------
// METHODS FOR ADDING SPECIFIC HTML TAGS
// -----------------------------------------------------
/** insert a &lt;br&gt; on the fly */
public Tag br() {
return add("br/");
}
/** insert a &lt;hr&gt; on the fly */
public Tag hr() {
return add("hr/");
}
public Tag center() {
return nest("center");
}
public Tag center(String attr) {
return nest("center "+attr);
}
public Tag big() {
return nest("big");
}
public Tag big(String attr) {
return nest("big "+attr);
}
public Tag small() {
return nest("small");
}
public Tag small(String attr) {
return nest("small "+attr);
}
public Tag i() {
return nest("i");
}
public Tag i(String attr) {
return nest("i "+attr);
}
public Tag strong() {
return nest("strong");
}
public Tag strong(String attr) {
return nest("big "+attr);
}
public Tag table() {
return nest("table");
}
public Tag table(String attr) {
return nest("table "+attr);
}
public Tag tr() {
return nest("tr");
}
public Tag tr(String attr) {
return nest("tr "+attr);
}
public Tag td() {
return nest("td");
}
public Tag td(String attr) {
return nest("td "+attr);
}
public Tag form() {
return nest("form");
}
public Tag form(String attr) {
return nest("form "+attr);
}
// -----------------------------------------------------
// METHODS FOR ADDING GENERAL CONTENT
// -----------------------------------------------------
/** create a new tag, embed it into this one, return this tag */
public Tag add(String s) {
Tag t = new Tag(s);
content.addElement(t);
return this;
}
/** add a tag to this one, returning this tag */
public Tag add(Tag t) {
content.addElement(t);
return this;
}
/** create a new tag, nest it into this one, return the new tag */
public Tag nest(String opentag) {
Tag t = new Tag(this, opentag);
t.parent = this;
return t;
}
public Tag nest() {
Tag t = new Tag(this);
t.parent = this;
return t;
}
/** insert object into this tag, return this tag */
public Tag raw(Object o) {
content.addElement(o);
return this;
}
/** set an attribute of this tag, return this tag */
public Tag set(String name, String val) {
return set(name + "=\"" + val + "\"");
}
/** set an attribute of this tag, return this tag */
public Tag set(String setting) {
attribs.addElement(setting);
return this;
}
public Tag style(String name, String val) {
return style(name+":"+val);
}
public Tag style(String setting) {
styles.addElement(setting);
return this;
}
// -----------------------------------------------------
// METHODS FOR RENDERING
// -----------------------------------------------------
public void render(OutputStream out) throws IOException {
//System.out.print("{render:"+open+"}");
//System.out.flush();
if (open != null) {
out.write("<".getBytes());
out.write(open.getBytes());
// add in attributes, if any
for (int i=0; i<attribs.size(); i++) {
String attr = null;
try {
attr = (String)attribs.get(i);
//buf.append(" "+attr[0]+"="+attr[1]);
out.write((" "+attr).getBytes());
} catch (ClassCastException e) {
e.printStackTrace();
//System.out.println("attr='"+attribs.get(i)+"'");
System.out.println("attribs='"+attribs+"'");
//System.out.println("content='"+content+"'");
}
}
// add in styles, if any
if (styles.size() > 0) {
out.write((" style=\"").getBytes());
Enumeration elems = styles.elements();
while (elems.hasMoreElements()) {
String s = (String)elems.nextElement()+";";
out.write(s.getBytes());
}
out.write("\"".getBytes());
}
if (close.equals("")) {
out.write("/".getBytes());
}
out.write(">".getBytes());
if (breakBefore) {
out.write("\n".getBytes());
}
}
for (int i=0; i < content.size(); i++) {
Object item = content.get(i);
if (item.getClass().isAssignableFrom(Tag.class)) {
((Tag)item).render(out);
} else {
out.write(item.toString().getBytes());
}
}
if (open != null) {
out.write(close.getBytes());
//buf.append(close);
if (breakAfter) {
out.write("\n".getBytes());
}
}
}
public String render() {
ByteArrayOutputStream s = new ByteArrayOutputStream();
try {
render(s);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return s.toString();
}
public String toString() {
return render();
}
}

View File

@@ -0,0 +1,61 @@
package net.i2p.aum.q;
public class Favicon {
public static byte [] image = {
0, 0, 1, 0, 1, 0, 16, 16, 0, 0, 1, 0, 24, 0, 104, 3,
0, 0, 22, 0, 0, 0, 40, 0, 0, 0, 16, 0, 0, 0, 32, 0,
0, 0, 1, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 0,
0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19,
19, 19, -127, -127, -127, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, -120,
-120, -120, -49, -49, -49, 116, 116, 116, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 90, 90, -80, -80, -80,
-18, -18, -18, -55, -55, -55, -122, -122, -122, 68, 68, 68, 107, 107, 107, -62,
-62, -62, -20, -20, -20, -59, -59, -59, 4, 4, 4, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, -109, -109, -109, -30, -30, -30, -8, -8, -8,
-25, -25, -25, -2, -2, -2, -28, -28, -28, -49, -49, -49, -2, -2, -2, -14,
-14, -14, -36, -36, -36, 33, 33, 33, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 72, 72, 72, -1, -1, -1, -1, -1, -1, -28, -28, -28,
-34, -34, -34, 118, 118, 118, -124, -124, -124, -1, -1, -1, -1, -1, -1, -6,
-6, -6, 68, 68, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, -98, -98, -98, -1, -1, -1, -38, -38, -38, -80, -80, -80,
13, 13, 13, 0, 0, 0, 100, 100, 100, -11, -11, -11, -9, -9, -9, -3,
-3, -3, -90, -90, -90, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, -49, -49, -49, -4, -4, -4, -57, -57, -57, 63, 63, 63,
0, 0, 0, 26, 26, 26, -74, -74, -74, -56, -56, -56, -35, -35, -35, -29,
-29, -29, -13, -13, -13, 104, 104, 104, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, -28, -28, -28, -46, -46, -46, -22, -22, -22, 2, 2, 2,
0, 0, 0, 2, 2, 2, 41, 41, 41, 108, 108, 108, 37, 37, 37, -32,
-32, -32, -29, -29, -29, -60, -60, -60, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, -60, -60, -60, -60, -60, -60, -44, -44, -44, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, -56,
-56, -56, -49, -49, -49, -43, -43, -43, 24, 24, 24, 0, 0, 0, 0, 0,
0, 0, 0, 0, -117, -117, -117, -70, -70, -70, -48, -48, -48, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 50, 50, -93,
-93, -93, -12, -12, -12, -47, -47, -47, 32, 32, 32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 54, 54, 54, -42, -42, -42, -79, -79, -79, 28, 28, 28,
0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 110, 110, 110, -70,
-70, -70, -4, -4, -4, -64, -64, -64, 3, 3, 3, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 121, 121, 121, -51, -51, -51, -87, -87, -87,
10, 10, 10, 0, 0, 0, 37, 37, 37, -119, -119, -119, -106, -106, -106, -20,
-20, -20, -33, -33, -33, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, -124, -124, -124, -23, -23, -23,
-33, -33, -33, -107, -107, -107, -75, -75, -75, -68, -68, -68, -15, -15, -15, -16,
-16, -16, -111, -111, -111, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 55, 55,
-97, -97, -97, -26, -26, -26, -29, -29, -29, -31, -31, -31, -61, -61, -61, 121,
121, 121, 11, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
}

View File

@@ -0,0 +1,187 @@
/*
* QClientAPI.java
*
* Created on March 31, 2005, 5:19 PM
*/
package net.i2p.aum.q;
import java.*;
import java.io.*;
import java.util.*;
import java.lang.*;
import java.net.*;
import org.apache.xmlrpc.*;
/**
* <p>The official Java API for client applications wishing to access the Q
* network</p>
* <p>This API is just a thin wrapper that hides the XMLRPC details, and exposes
a simple set of methods.</p>
* <p>Note to app developers - I'm only implementing this API in Java
* and Python at present. If you've got some time and knowledge of other
* languages and their available XML-RPC client libs, we'd really appreciate
* it if you can port this API into other languages - such as Perl, C++,
* Ruby, OCaml, C#, etc. You can take this API implementation as the reference
* code for porting to your own language.</p>
*/
public class QClientAPI {
XmlRpcClient node;
/**
* Creates a new instance of QClientAPI talking on given xmlrpc port
*/
public QClientAPI(int port) throws MalformedURLException {
node = new XmlRpcClient("http://127.0.0.1:"+port);
}
/**
* Creates a new instance of QClientAPI talking on default xmlrpc port
*/
public QClientAPI() throws MalformedURLException {
node = new XmlRpcClient("http://127.0.0.1:"+QClientNode.defaultXmlRpcServerPort);
}
/**
* Pings a Q client node, gets back a bunch of useful stats
*/
public Hashtable ping() throws XmlRpcException, IOException {
return (Hashtable)node.execute("i2p.q.ping", new Vector());
}
/**
* Retrieves an update of content catalog
* @param since a unixtime in seconds. The content list returned will
* be a differential update since this time.
*/
public Hashtable getUpdate(int since)
throws XmlRpcException, IOException
{
Vector args = new Vector();
args.addElement(new Integer(since));
args.addElement(new Integer(1));
args.addElement(new Integer(1));
return (Hashtable)node.execute("i2p.q.getUpdate", args);
}
/**
* Retrieves an item of content from the network, given its key
* @param key the key to retrieve
*/
public Hashtable getItem(String key) throws XmlRpcException, IOException {
Vector args = new Vector();
args.addElement(key);
return (Hashtable)node.execute("i2p.q.getItem", args);
}
/**
* Inserts a single item of data, without metadata. A default metadata set
* will be generated.
* @param data a byte[] of data to insert
* @return a Hashtable containing results, including:
* <ul>
* <li>result - either "ok" or "error"</li>
* <li>error - (only if result != "ok") - terse error label</li>
* <li>key - the key under which this item has been inserted</li>
* </ul>
*/
public Hashtable putItem(byte [] data) throws XmlRpcException, IOException {
Vector args = new Vector();
args.addElement(data);
return (Hashtable)node.execute("i2p.q.putItem", args);
}
/**
* Inserts a single item of data, with metadata
* @param metadata a Hashtable of metadata to insert
* @param data a byte[] of data to insert
* @return a Hashtable containing results, including:
* <ul>
* <li>result - either "ok" or "error"</li>
* <li>error - (only if result != "ok") - terse error label</li>
* <li>key - the key under which this item has been inserted</li>
* </ul>
*/
public Hashtable putItem(Hashtable metadata, byte [] data)
throws XmlRpcException, IOException
{
Vector args = new Vector();
args.addElement(metadata);
args.addElement(data);
return (Hashtable)node.execute("i2p.q.putItem", args);
}
/**
* Generates a new keypair for inserting signed-space items
* @return a struct with the keys:
* <ul>
* <li>status - "ok"</li>
* <li>publicKey - base64-encoded signed space public key</li>
* <li>privateKey - base64-encoded signed space private key</li>
* </ul>
* When inserting an item using the privateKey, the resulting uri
* will be <code>Q:publicKey/path</code>
*/
public Hashtable newKeys() throws XmlRpcException, IOException
{
Vector args = new Vector();
return (Hashtable)node.execute("i2p.q.newKeys", args);
}
/**
* Adds a new noderef to node
* @param dest - the base64 i2p destination for the remote peer
* @return a Hashtable containing results, including:
* <ul>
* <li>result - either "ok" or "error"</li>
* <li>error - (only if result != "ok") - terse error label</li>
* </ul>
*/
public Hashtable hello(String dest) throws XmlRpcException, IOException {
Vector args = new Vector();
args.addElement(dest);
return (Hashtable)node.execute("i2p.q.hello", args);
}
/**
* Shuts down a running node
* If the shutdown succeeds, then this call will fail with an exception. But
* if the call succeeds, then the shutdown has failed (sorry if this is a tad
* counter-intuitive).
* @param privKey - the base64 i2p private key for this node.
* @return a Hashtable containing results, including:
* <ul>
* <li>result - "error"</li>
* <li>error - terse error label</li>
* </ul>
*/
public Hashtable shutdown(String privKey) throws XmlRpcException, IOException {
Vector args = new Vector();
args.addElement(privKey);
return (Hashtable)node.execute("i2p.q.shutdown", args);
}
/**
* Search the node for catalog entries matching a set of criteria
* @param criteria a Hashtable of metadata criteria to match, and whose
* values are regular expressions
* @return a Hashtable containing results, including:
* <ul>
* <li>result - "ok" or "error"</li>
* <li>error - if result != "ok", a terse error label</li>
* <li>items - a Vector of items found which match the given search
* criteria. If no available matching items were found, this vector
* will come back empty.
* </ul>
*/
public Hashtable search(Hashtable criteria) throws XmlRpcException, IOException {
Vector args = new Vector();
args.addElement(criteria);
return (Hashtable)node.execute("i2p.q.search", args);
}
}

View File

@@ -0,0 +1,610 @@
/*
* QClient.java
*
* Created on 20 March 2005, 23:22
*/
package net.i2p.aum.q;
import java.*;
import java.io.*;
import java.util.*;
import java.lang.*;
import org.apache.xmlrpc.*;
import net.i2p.*;
import net.i2p.data.*;
import net.i2p.aum.*;
import net.i2p.aum.http.*;
import HTML.Template;
/**
* Implements Q client nodes.
*/
public class QClientNode extends QNode {
static String defaultStoreDir = ".quartermaster_client";
I2PHttpServer webServer;
MiniHttpServer webServerTcp;
Properties httpProps;
public String nodeType = "Client";
// -------------------------------------------------------
// CONSTRUCTORS
// -------------------------------------------------------
/**
* Creates a new instance of QClient, using default
* datastore location
* @throws IOException, DataFormatException, I2PException
*/
public QClientNode() throws IOException, DataFormatException, I2PException
{
super(System.getProperties().getProperty("user.home") + sep + defaultStoreDir);
log.debug("TEST CLIENT DEBUG MSG1");
}
/**
* Creates a new instance of QClient, using specified
* datastore location
* @param path of node's datastore directory
* @throws IOException, DataFormatException, I2PException
*/
public QClientNode(String dataDir) throws IOException, DataFormatException, I2PException
{
super(dataDir);
log.error("TEST CLIENT DEBUG MSG");
}
// -------------------------------------------------------
// METHODS - XML-RPC PRIMITIVE OVERRIDES
// -------------------------------------------------------
/**
* hello cmds to client nodes are illegal!
*/
/**
public Hashtable localHello(String destBase64)
{
Hashtable h = new Hashtable();
h.put("status", "error");
h.put("error", "unimplemented");
return h;
}
**/
/** perform client-specific setup */
public void setup()
{
updateCatalogFromPeers = 1;
isClient = true;
// allow a port change for xmlrpc client app conns
String xmlPortStr = System.getProperty("q.xmlrpc.tcp.port");
if (xmlPortStr != null) {
xmlRpcServerPort = new Integer(xmlPortStr).intValue();
conf.setIntProperty("xmlRpcServerPort", xmlRpcServerPort);
}
// ditto for listening host
String xmlHostStr = System.getProperty("q.xmlrpc.tcp.host");
if (xmlHostStr != null) {
xmlRpcServerHost = xmlHostStr;
conf.setProperty("xmlRpcServerHost", xmlRpcServerHost);
}
// ---------------------------------------------------
// now fire up the HTTP interface
// listening only within I2P on client node's dest
// set up a properties object for short local tunnel
httpProps = new Properties();
httpProps.setProperty("inbound.length", "0");
httpProps.setProperty("outbound.length", "0");
httpProps.setProperty("inbound.lengthVariance", "0");
httpProps.setProperty("outbound.lengthVariance", "0");
Properties sysProps = System.getProperties();
String i2cpHost = sysProps.getProperty("i2cp.tcp.host", "127.0.0.1");
String i2cpPort = sysProps.getProperty("i2cp.tcp.port", "7654");
httpProps.setProperty("i2cp.tcp.host", i2cpHost);
httpProps.setProperty("i2cp.tcp.port", i2cpPort);
}
public void run() {
// then do all the parent stuff
super.run();
}
/**
* <p>Sets up and launches an http server for servicing requests
* to this node.</p>
* <p>For server nodes, the xml-rpc server listens within I2P on the
* node's destination.</p>
* <p>For client nodes, the xml-rpc server listens on a local TCP
* port (according to attributes xmlRpcServerHost and xmlRpcServerPort)</p>
*/
public void startExternalInterfaces(QServerMethods methods) throws Exception
{
System.out.println("Creating http interface...");
try {
// create tcp http server for xmlrpc and browser access
webServerTcp = new MiniHttpServer(QClientWebInterface.class, xmlRpcServerPort, this);
webServerTcp.addXmlRpcHandler(baseXmlRpcServiceName, methods);
System.out.println("started in-i2p http/xmlrpc server listening on port:" + xmlRpcServerPort);
webServerTcp.start();
// create in-i2p http server for xmlrpc and browser access
webServer
= new I2PHttpServer(privKey,
QClientWebInterface.class,
this,
httpProps
);
webServer.addXmlRpcHandler(baseXmlRpcServiceName, methods);
webServer.start();
System.out.println("Started in-i2p http/xmlrpc server listening on dest:");
String dest = privKey.getDestination().toBase64();
System.out.println(dest);
System.out.println("web interfaces created");
} catch (Exception e) {
e.printStackTrace();
System.out.println("Failed to create client web interfaces");
System.exit(1);
}
/**
WebServer serv = new WebServer(xmlRpcServerPort);
// if host is non-null, add as a listen host
if (xmlRpcServerHost.length() > 0) {
serv.setParanoid(true);
serv.acceptClient(xmlRpcServerHost);
}
serv.addHandler(baseXmlRpcServiceName, methods);
serv.start();
log.info("Client XML-RPC server listening on port "+xmlRpcServerPort+" as service"+baseXmlRpcServiceName);
**/
}
// -----------------------------------------------------
// client-specific customisations of xmlRpc methods
// -----------------------------------------------------
/**
* Insert an item of content, with metadata. Then (since this is the client's
* override) schedules a job to insert this item to a selection of remote peers.
* @param metadata Hashtable of item's metadata
* @param data raw data to insert
*/
public Hashtable putItem(Hashtable metadata, byte [] data) throws QException
{
Hashtable resp = new Hashtable();
QDataItem item;
String uri;
// do the local insert first
try {
item = new QDataItem(metadata, data);
item.processAndValidate(true);
localPutItem(item);
uri = (String)item.get("uri");
} catch (QException e) {
resp.put("status", "error");
resp.put("error", "qexception");
resp.put("summary", e.getLocalizedMessage());
return resp;
}
// now schedule remote insertion
schedulePeerUploadJob(item);
// and return success, rest will happen automatically in background
resp.put("status", "ok");
resp.put("uri", uri);
return resp;
}
/**
* Search datastore and catalog for a given item of content
* @param criteria Hashtable of criteria to match in metadata
*/
public Hashtable search(Hashtable criteria)
{
Hashtable result = new Hashtable();
Vector matchingItems = new Vector();
Iterator items;
Hashtable item;
Hashtable foundUris = new Hashtable();
String uri;
// get an iterator for all catalog items
try {
// test all local content
items = contentIdx.getItemsSince(0);
while (items.hasNext()) {
String uriHash = (String)items.next();
item = getLocalMetadataForHash(uriHash);
uri = (String)item.get("uri");
//System.out.println("search: testing "+metadata+" against "+criteria);
if (metadataMatchesCriteria(item, criteria)) {
matchingItems.addElement(item);
foundUris.put(uri, item);
}
}
// now test remote catalog
items = catalogIdx.getItemsSince(0);
while (items.hasNext()) {
String uriHash = (String)items.next();
item = getLocalCatalogMetadataForHash(uriHash);
uri = (String)item.get("uri");
//System.out.println("search: testing "+metadata+" against "+criteria);
if (metadataMatchesCriteria(item, criteria)) {
if (!foundUris.containsKey("uri")) {
matchingItems.addElement(item);
}
}
}
} catch (Exception e) {
e.printStackTrace();
result.put("status", "error");
result.put("error", e.getMessage());
return result;
}
result.put("status", "ok");
result.put("items", matchingItems);
return result;
}
/**
* retrieves a peers/catalog update - executes on base class, then
* adds in our catalog entries
*/
public Hashtable getUpdate(int since, int includePeers, int includeCatalog)
{
Hashtable h = localGetUpdate(since, includePeers, includeCatalog);
if (includeCatalog != 0) {
// must extend v with remote catalog entries
Vector vCat = (Vector)(h.get("items"));
Iterator items;
// get an iterator for all new catalog items since given unixtime
try {
items = catalogIdx.getItemsSince(since);
// pick through the iterator, and fetch metadata for each item
while (items.hasNext()) {
String key = (String)(items.next());
Hashtable pf = getLocalCatalogMetadata(key);
log.error("getUpdate(client): key="+key+", pf="+pf);
System.out.println("getUpdate(client): key="+key+", pf="+pf);
if (pf != null) {
// clone this metadata, add in the key
Hashtable pf1 = (Hashtable)pf.clone();
pf1.put("key", key);
vCat.addElement(pf1);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
return h;
}
/**
* <p>Retrieve an item of content.</p>
* <p>This client override tries the local datastore first, then
* attempts to get the data from remote servers believed to have the data</p>
*/
public Hashtable getItem(String uri) throws IOException, QException
{
Hashtable res;
log.info("getItem: uri='"+uri+"'");
if (localHasItem(uri)) {
class Fred {
}
Fred xxx = new Fred();
// got it locally, send it back
return localGetItem(uri);
}
// ain't got it locally - try remote sources in turn till we
// either get it or fail
Vector sources = getItemLocation(uri);
// send back an error if not in local catalog
if (sources == null || sources.size() == 0) {
Hashtable dnf = new Hashtable();
dnf.put("status", "error");
dnf.put("error", "notfound");
dnf.put("comment", "uri not known locally or remotely");
return dnf;
}
// ok, got at least one remote source, go through them till
// we get data that checks out
int i;
int npeers = sources.size();
int numCmdFail = 0;
int numDnf = 0;
int numBadData = 0;
for (i=0; i<npeers; i++) {
String peerId = (String)sources.get(i);
try {
res = peerGetItem(peerId, uri);
} catch (Exception e) {
e.printStackTrace();
numCmdFail += 1;
continue;
}
// got some kind of response back from peer
String status = (String)res.get("status");
if (status.equals("ok")) {
// don't trust at face value!
// hash the data, ensure it matches dataHash
Hashtable metadata = (Hashtable)res.get("metadata");
String dataHash = (String)metadata.get("dataHash");
byte [] data = (byte[])res.get("data");
String dataHash1 = sha256Base64(data);
if (dataHash.equals(dataHash1)) {
// at least the data matches, trust in this vers
// TODO - verify metadata hash against main uri
// cache it into local datastore
QDataItem item = new QDataItem(metadata, data);
localPutItem(item);
// and pass back
return res;
}
else {
System.out.println("getItem: bad hash on "+data.length+"-byte uri "+uri);
System.out.println("getItem: expected: "+dataHash);
System.out.println("getItem: received: "+dataHash1);
System.out.println("getItem: metadata="+metadata);
numBadData += 1;
}
}
else {
numDnf += 1;
}
}
// if we get here, then all peers either failed,
// returned dnf, or sent back dodgy data
res = new Hashtable();
res.put("status", "error");
res.put("error", "notfound");
res.put("summary",
"tried "+npeers+" peers, "
+numCmdFail+" cmdfail, "
+numDnf+" notfound, "
+numBadData+" baddata"
);
return res;
}
/**
* Schedules the insertion of a qsite
* @param privKey64 base64 representation of a signed space private key
* @param siteName short text name of the qsite, whose URI will end up
* as 'Q:pubKey64/siteName/'.
* @param rootPath physical absolute pathname of the qsite's root directory
* on the host filesystem.
* Note that this directory must have a file called 'index.html' at its top
* level, which will be used as the qsite's default document.
* @param metadata A set of metadata to associate with the qsite
* @return Hashtable containing results, as the keys:
* <ul>
* <li>status - String - either "ok" or "error"</li>
* <li>error - String - short summary of error, only present if
* status is "error"</li>
* <li>uri - the full Q URI for the top level of the site
* </ul>
*/
public Hashtable insertQSite(String privKey64,
String siteName,
String rootPath,
Hashtable metadata
)
throws Exception
{
// for results
Hashtable result = new Hashtable();
String uri = null; // uri under which this site will be reachable
String pubKey64;
File dir = new File(rootPath);
// barf if no such directory
if (!dir.isDirectory()) {
result.put("status", "error");
result.put("error", "nosuchdir");
result.put("detail", "Path '"+rootPath+"' is not a directory");
return result;
}
// barf if not readable
if (!dir.canRead()) {
result.put("status", "error");
result.put("error", "cantread");
result.put("detail", "Path '"+rootPath+"' is not readable");
return result;
}
// barf if missing or invalid site name
siteName = siteName.trim();
if (!siteName.matches("[a-zA-Z0-9_-]+")) {
result.put("status", "error");
result.put("error", "badsitename");
result.put("detail", "QSite name should be only alphanumerics, '-' and '_'");
return result;
}
String defaultPath = rootPath + sep + "index.html";
File defaultFile = new File(defaultPath);
// barf if index.html not present and readable
if (!(defaultFile.isFile() && defaultFile.canRead())) {
result.put("status", "error");
result.put("error", "noindex");
result.put("detail", "Required file index.html missing or unreadable");
return result;
}
// derive public key and uri for site, barf if bad key
try {
pubKey64 = QUtil.privateToPubHash(privKey64);
} catch (Exception e) {
result.put("status", "error");
result.put("error", "badprivkey");
return result;
}
uri = "Q:" + pubKey64 + "/" + siteName + "/";
// now the fun recursive bit
insertQSiteDir(privKey64, siteName, rootPath, "");
// queue up an insert of default file
metadata.put("type", "qsite");
metadata.put("path", siteName+"/");
metadata.put("mimetype", "text/html");
//System.out.println("insertQSite: privKey='"+privKey64+"'");
//System.out.println("insertQSite: siteName='"+siteName+"'");
//System.out.println("insertQSite: rootDir='"+rootPath+"'");
//System.out.println("insertQSite: metadata="+metadata);
//System.out.println("insertQSite: default="+defaultPath);
insertQSiteFile(privKey64, siteName, defaultPath, "", metadata);
result.put("status", "ok");
result.put("uri", uri);
return result;
}
/**
* recursively queues jobs for the insertion of a directory's contents, for
* a qsite.
* @param privKey64 - private 'signed space' key, base64 format
* @param siteName - short text name for the site
* @param absPath - physical pathname of the subdirectory to insert
* @param relPath - qsite-relative pathname of this item
*/
protected void insertQSiteDir(String privKey64, String siteName, String absPath, String relPath)
throws Exception
{
File dir = new File(absPath);
// fail gracefully if not a readable directory
if (!(dir.isDirectory() && dir.canRead())) {
System.out.println("insertQSiteDir: not a readable directory "+absPath);
return;
}
//System.out.println("insertQSiteDir: entry - abs='"+absPath+"' rel='"+relPath+"'");
// loop through the contents
String [] contents = dir.list();
for (int i=0; i<contents.length; i++) {
String item = contents[i];
String itemAbsPath = absPath + sep + item;
String itemRelPath = relPath + item;
//System.out.println("insertQSiteDir: item='"+item+"' abs='"+itemAbsPath+"' rel='"+itemRelPath+"'");
File itemFile = new File(itemAbsPath);
// what kind of entry is this?
if (itemFile.isDirectory()) {
// directory - recursively insert
insertQSiteDir(privKey64, siteName, itemAbsPath, itemRelPath + "/");
} else {
// file - insert
insertQSiteFile(privKey64, siteName, itemAbsPath, itemRelPath, null);
}
}
}
/**
* queues up the insertion of an individual qsite file
* @param privKey64 - base64 signed space private key
* @param siteName - name of qsite
* @param absPath - absolute location of file on host filesystem
* @param relPath - pathname of file relative to Q uri
*/
protected void insertQSiteFile(String privKey64, String siteName,
String absPath, String relPath, Hashtable metadata)
throws Exception
{
//System.out.println("insertQSiteFile: priv="+privKey64+" name="+siteName+" abs="+absPath+" rel="+relPath+" metadata="+metadata);
if (metadata == null) {
metadata = new Hashtable();
}
metadata.put("privateKey", privKey64);
if (!metadata.containsKey("path")) {
metadata.put("path", siteName + "/" + relPath);
}
if (!metadata.containsKey("mimetype")) {
metadata.put("mimetype", Mimetypes.guessType(relPath));
}
scheduleLocalInsertJob(absPath, metadata);
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
throws IOException, DataFormatException, I2PException, InterruptedException
{
// just for testing
System.setProperty("i2cp.tcp.host", "10.0.0.1");
QClientNode node;
if (args.length > 0) {
node = new QClientNode(args[0]);
}
else {
node = new QClientNode();
}
node.log.info("QClientNode: running node...");
node.run();
}
public void foo1() {
System.out.println("QClientNode.foo: isClient="+isClient);
}
}

View File

@@ -0,0 +1,751 @@
/*
* QClientWebInterface.java
*
* Created on April 9, 2005, 1:10 PM
*/
package net.i2p.aum.q;
import java.lang.*;
import java.lang.reflect.*;
import java.io.*;
import java.net.*;
import java.util.*;
import HTML.Template;
import net.i2p.aum.http.*;
/**
* Request handler for Q Client nodes that listens within I2P
* on the client node's destination. Intended for access via
* eepProxy, and by adding a hosts.txt entry for this dest
* under the hostname 'q'.
*/
public class QClientWebInterface extends I2PHttpRequestHandler {
/** set this to true when debugging html layout */
public static boolean loadTemplateWithEachHit = true;
public QNode node = null;
// refs to main page template, and components of main page
static Template tmplt;
static Vector tabRow;
static Vector pageItems;
/**
* for security - disables direct-uri GETs of content if running directly over TCP;
* we need to coerce users to use their eepproxy browser instead
*/
public boolean isRunningOverTcp = true;
/** Creates a new instance of QClientWebInterface */
public QClientWebInterface(MiniHttpServer server, Object socket, Object node)
throws Exception
{
super(server, socket, node);
this.node = (QNode)node;
isRunningOverTcp = socket.getClass() == Socket.class;
}
static String [] tabNames = {
"home", "search", "insert", "tools", "status", "jobs", "help", "about"
};
/**
* Loads a template of a given name. Invokes method on node
* to resolve this to an absolute pathname, so 'name' -&gt; '/path/to/html/name.html'
*/
public Template loadTemplate(String name) throws Exception {
String fullPath = node.getResourcePath("html"+node.sep+name)+".html";
//System.out.println("fullPath='"+fullPath+"'");
String [] args = new String [] {
"filename", fullPath,
"case_sensitive", "true",
"max_includes", "5"
};
return new Template(args);
}
// ----------------------------------------------------
// FRONT-END METHODS
// ----------------------------------------------------
/** GET and POST both go through .safelyHandleReq() */
public void on_GET() {
safelyHandleReq();
}
/** GET and POST both go through .safelyHandleReq() */
public void on_POST() {
safelyHandleReq();
}
public void on_RPC() {
}
/**
* wrap .handleReq() - on exception, call dump_error() to
* generate a 400 error page with diagnostics
*/
public void safelyHandleReq() {
try {
handleReq();
} catch (Exception e) {
dump_error(e);
}
}
/**
* <p>Forwards hits to either a path handler method, or generic get method.</p>
*
* <p>Detects hits to paths for which we have a handler (ie, methods
* of this class with name 'hdlr_&lt;somepath&gt;', (such as 'hdlr_help'
* for handling hits to '/help').</p>
*
* <p>If we have a handler, forward to it, otherwise forward to standard
* getItem() method</p>
*/
public void handleReq() throws Exception {
Class [] noArgs;
Method hdlrMethod;
// strip useless leading slash from reqFile
reqFile = reqFile.substring(1);
// default to 'home'
if (reqFile.equals("")) {
reqFile = "home";
}
//print("handleReq: reqFile='"+reqFile+"'");
// Set up the main page template
try {
tmplt = loadTemplate("main");
pageItems = new Vector();
tmplt.setParam("items", pageItems);
tmplt.setParam("nodeType", node.nodeType);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
//print("handleReq: loaded template");
// execute if a command
if (allVars.containsKey("cmd")) {
do_cmd();
}
// --------------------------------------------------------
// intercept magic paths for which we have a handler method
noArgs = new Class[0];
try {
// extract top dir of path and make it method-name-safe
String methodName = "hdlr_"+reqFile.split("/")[0].replace('.','_');
hdlrMethod = this.getClass().getMethod(methodName, null);
// now dispatch the method
hdlrMethod.invoke(this, null);
// spit out html, if no raw content inserted
sendPageIfNoContent();
// done
return;
} catch (NoSuchMethodException e) {
// routinely fails if we dont' have handler, so assume it's
// a GET
}
// if we get here, client is requesting a specific uri
allVars.put("uri", reqFile);
if (!cmd_get()) {
hdlr_home();
}
sendPageIfNoContent();
}
/**
* as name implies, generates standard html page
* if setRawOutput hasnt' been called
*/
public void sendPageIfNoContent() {
if (rawContentBytes == null) {
// we're spitting out html
setContentType("text/html");
// set up tab row style vector
setupTabRow();
// finally, render out our filled-out template
setRawOutput(tmplt.output());
}
}
/**
* Inserts an item into main pane
*/
public Object addToMainPane(Object item) {
Hashtable h = new Hashtable();
h.put("item", item);
pageItems.addElement(h);
return item;
}
/**
* Generates a set of tabs and adds these to the page,
* marking as active the tab whose name is in the current URL
*/
public void setupTabRow()
{
Hashtable h;
tabRow = new Vector();
for (int i=0; i< tabNames.length; i++) {
String name = tabNames[i];
h = new Hashtable();
h.put("name", name);
h.put("label", name.substring(0,1).toUpperCase()+name.substring(1));
if (name.equals(reqFile)) {
h.put("active", "1");
}
tabRow.addElement(h);
tmplt.setParam("tabs", tabRow);
}
}
// -----------------------------------------------------
// METHODS FOR HANDLING MAGIC PATHS
// ----------------------------------------------------
/** Display home page */
public void hdlr_home() throws Exception {
// stick in 'getitem' form
addToMainPane(loadTemplate("getform"));
}
/** Display status page */
public void hdlr_status() throws Exception {
// ping the node, extract status items
Vector statusItems = new Vector();
Hashtable h = node.ping();
for (Enumeration e = h.keys(); e.hasMoreElements();) {
String key = (String)e.nextElement();
String val = h.get(key).toString();
if (val.length() > 60) {
// too big for table, stick into a readonly text field
val = "<input type=text size=60 readonly name=\"big_"+key+"\" value=\""+val+"\">";
}
Hashtable rec = new Hashtable();
rec.put("key", key);
rec.put("value", val);
//print("key='"+key+"' val='"+val+"'");
statusItems.addElement(rec);
}
// get status form template insert the items, stick onto main pane
Template tmpltStatus = loadTemplate("status");
tmpltStatus.setParam("items", statusItems);
addToMainPane(tmpltStatus);
}
/** display current node jobs list */
public void hdlr_jobs() throws Exception {
// get jobs list, add to jobs list template, add that to main pane
Template tmpltJobs = loadTemplate("jobs");
tmpltJobs.setParam("items", node.getJobsList());
addToMainPane(tmpltJobs);
}
/** Display search form */
public void hdlr_search() throws Exception {
addToMainPane(loadTemplate("searchform"));
}
/** Display insert page */
public void hdlr_insert() throws Exception {
String formName = allVars.get("mode", 0, "file").equals("site") ? "putsiteform" : "putform";
Template tmpltPut = loadTemplate(formName);
addToMainPane(tmpltPut);
}
/** Display settings screen */
public void hdlr_settings() throws Exception {
addToMainPane(loadTemplate("settings"));
}
/** Display tools screen */
public void hdlr_tools() throws Exception {
addToMainPane(loadTemplate("tools"));
addToMainPane(loadTemplate("genkeysform"));
addToMainPane(loadTemplate("addrefform"));
}
/** Display help screen */
public void hdlr_help() throws Exception {
addToMainPane(loadTemplate("help"));
}
/** Display about screen */
public void hdlr_about() throws Exception {
addToMainPane(loadTemplate("about"));
}
/** handle /favicon.ico hits */
public void hdlr_favicon_ico() {
System.out.println("Sending favicon image");
setContentType("image/x-icon");
setRawOutput(Favicon.image);
}
/** dummy handler, causes an exception (for testing error dump pages */
public void hdlr_shit() throws Exception {
throw new Exception("this method is shit");
}
// ----------------------------------------------------
// METHODS FOR HANDLING COMMANDS
// ----------------------------------------------------
/**
* invoked if GET or POST vars contain 'cmd'.
* attempts to dispatch command handler method 'cmd_xxxx'
*/
public void do_cmd() throws Exception {
// this whole method could be done in python with the statement:
// getattr(self, 'cmd_'+urlVars['cmd'], lambda:None)()
String cmd = allVars.get("cmd", 0);
try {
// extract top dir of path and make it method-name-safe
String methodName = "cmd_"+cmd;
Method hdlrMethod = this.getClass().getMethod(methodName, null);
// now dispatch the method
hdlrMethod.invoke(this, null);
} catch (NoSuchMethodException e) {}
}
/**
* executes a 'get' cmd
*/
public boolean cmd_get() throws Exception {
Hashtable result = null;
String status = null;
Hashtable metadata = null;
String mimetype = null;
// bail if node offline
if (node == null) {
return false;
}
// bail if no 'url' arg
if (!allVars.containsKey("uri")) {
return false;
}
// get uri, prepend 'Q:' if needed
String uri = allVars.get("uri", 0);
if (!uri.startsWith("Q:")) {
uri = "Q:" + uri;
}
// attempt the fetch
result = node.getItem(uri);
status = (String)result.get("status");
// how'd we go?
if (status.equals("ok")) {
// got it - send it back
metadata = (Hashtable)result.get("metadata");
mimetype = (String)metadata.get("mimetype");
// forbid content retrieval via MSIE
boolean isIE = false;
for (Enumeration e = headerVars.get("User-Agent").elements(); e.hasMoreElements();) {
String val = ((String)e.nextElement()).toLowerCase();
if (val.matches(".*(msie|windows|\\.net).*")) {
Template warning = loadTemplate("msiealert");
addToMainPane(warning);
return false;
}
}
// forbid direct delivery of text/* content via direct tcp
if (isRunningOverTcp) {
// security feature - set to application/octet-stream if req arrives via tcp.
// this prevents people surfing the q web interface directly over TCP and
// falling prey to anonymity attacks (eg gif bugs)
// if user is trying to hit an html page, we can send back a warning
if (mimetype.startsWith("text")) {
Template warning = loadTemplate("anonalert");
warning.setParam("dest", node.destStr);
addToMainPane(warning);
return false;
}
setContentType("application/octet-stream");
} else {
// got this conn via I2P and eeproxy - safer to obey the mimetype
setContentType(mimetype);
}
setRawOutput((byte [])result.get("data"));
return true;
} else {
// 404
tmplt.setParam("show_404", "1");
tmplt.setParam("404_uri", uri);
return false;
}
}
/** executes genkeys command */
public void cmd_genkeys() throws Exception {
Hashtable res = node.newKeys();
String pubKey = (String)res.get("publicKey");
String privKey = (String)res.get("privateKey");
Template keysWidget = loadTemplate("genkeysresult");
keysWidget.setParam("publickey", pubKey);
keysWidget.setParam("privatekey", privKey);
addToMainPane(keysWidget);
}
/** adds a noderef */
public void cmd_addref() throws Exception {
String ref = allVars.get("noderef", 0).trim();
node.hello(ref);
}
/** executes 'put' command */
public void cmd_put() throws Exception {
// barf if user posted both data and rawdata
if (allVars.containsKey("data")
&& ((String)allVars.get("data", 0)).length() > 0
&& allVars.containsKey("rawdata")
&& ((String)allVars.get("rawdata", 0)).length() > 0
)
{
Template t = loadTemplate("puterror");
t.setParam("error", "you specified a file as well as 'rawdata'");
addToMainPane(t);
addToMainPane(dumpVars().toString());
return;
}
Hashtable metadata = new Hashtable();
byte [] data = new byte[0];
// stick in some defaults
String [] keys = {
"data", "rawdata",
"mimetype", "keywords", "privkey", "abstract", "type", "title",
"path"
};
//System.out.println("allVars='"+allVars+"'");
// extract all items from form, add to metadata ones that
// have non-zero length. Take 'data' or 'rawdata' and stick their
// bytes into data.
for (int i=0; i<keys.length; i++) {
String key = keys[i];
if (allVars.containsKey(key)) {
//System.out.println("posted item '"+key+"'");
if (key.equals("data")) {
byte [] dataval = allVars.get("data", 0).getBytes();
if (dataval.length > 0) {
data = dataval;
}
} else if (key.equals("rawdata")) {
byte [] dataval = allVars.get("rawdata", 0).getBytes();
if (dataval.length > 0) {
data = dataval;
}
} else if (key.equals("privkey")) {
String k = allVars.get("privkey", 0);
if (k.length() > 0) {
metadata.put("privateKey", k);
}
} else {
String val = allVars.get(key, 0);
//System.out.println("'"+key+"'='"+val+"'");
if (val.length() > 0) {
metadata.put(key, allVars.get(key, 0));
}
}
}
}
//System.out.println("metadata="+metadata);
if (metadata.size() == 0) {
Template err = loadTemplate("puterror");
err.setParam("error", "No metadata!");
addToMainPane(err);
addToMainPane(dumpVars().toString());
return;
}
if (data.length == 0) {
Template err = loadTemplate("puterror");
err.setParam("error", "No data!");
addToMainPane(err);
addToMainPane(dumpVars().toString());
return;
}
// phew! ready to put
System.out.println("WEB:cmd_put: inserting");
Hashtable result = node.putItem(metadata, data);
System.out.println("WEB:cmd_put: got"+result);
String status = (String)result.get("status");
if (!status.equals("ok")) {
String errTxt = (String)result.get("error");
if (result.containsKey("summary")) {
errTxt = errTxt + ":" + result.get("summary").toString();
}
Template err = loadTemplate("puterror");
err.setParam("error", (String)result.get("error"));
addToMainPane(err);
addToMainPane(dumpVars().toString());
return;
}
// success, yay!
Template success = loadTemplate("putok");
success.setParam("uri", (String)result.get("uri"));
addToMainPane(success);
//System.out.println("cmd_put: debug on page??");
//addToMainPane(dumpVars().toString());
}
/** executes 'putsite' command */
public void cmd_putsite() throws Exception {
Hashtable metadata = new Hashtable();
String privKey = allVars.get("privkey", 0, "");
String name = allVars.get("name", 0, "");
String dir = allVars.get("dir", 0, "");
// pick up optional metadata items
String [] keys = {
"title", "keywords", "abstract",
};
// extract all items from form, add to metadata ones that
// have non-zero length.
for (int i=0; i<keys.length; i++) {
String key = keys[i];
if (allVars.containsKey(key)) {
//System.out.println("posted item '"+key+"'");
String val = allVars.get(key, 0);
//System.out.println("'"+key+"'='"+val+"'");
if (val.length() > 0) {
metadata.put(key, allVars.get(key, 0));
}
}
}
//System.out.println("metadata="+metadata);
if (metadata.size() == 0) {
cmd_putsite_error("No metadata!");
return;
}
// phew! ready to put
Hashtable result = node.insertQSite(privKey, name, dir, metadata);
String status = (String)result.get("status");
if (!status.equals("ok")) {
cmd_putsite_error((String)result.get("error"));
return;
}
// success, yay!
Template success = loadTemplate("putok");
success.setParam("is_site", "1");
success.setParam("uri", (String)result.get("uri"));
addToMainPane(success);
//System.out.println("cmd_put: debug on page??");
//addToMainPane(dumpVars().toString());
}
protected void cmd_putsite_error(String msg) throws Exception {
Template err = loadTemplate("puterror");
err.setParam("error", msg);
err.setParam("is_site", "1");
addToMainPane(err);
addToMainPane(dumpVars().toString());
}
/** performs a search */
public void cmd_search() throws Exception {
Hashtable criteria = new Hashtable();
String [] fields = {
"type", "title", "path", "mimetype", "keywords",
"summary", "searchmode"
};
for (int i=0; i<fields.length; i++) {
String fieldName = fields[i];
String fieldVal = allVars.get(fieldName, 0, null);
if (fieldVal == null) {
continue;
}
if (fieldName.equals("type") && fieldVal.equals("any")) {
continue;
}
if (!fieldVal.equals("")) {
if (!(fieldName.equals("searchmode") || allVars.containsKey(fieldName+"_re"))) {
// convert into a regexp which matches exact substring
fieldVal = ".*\\Q" + fieldVal + "\\E.*";
}
criteria.put(fieldName, fieldVal);
}
}
//addToMainPane("Search criteria: "+criteria);
Hashtable result = node.search(criteria);
Vector items = (Vector)result.get("items");
// stick up search results form
Template results = loadTemplate("searchresults");
System.out.println("items="+items);
results.setParam("results", items);
results.setParam("numresults", items.size());
addToMainPane(results);
//addToMainPane(dumpVars().toString());
}
// -----------------------------------------------------
// NODE INTERACTION METHODS
// -----------------------------------------------------
public void do_get(String uri) {
// prefix uri with 'Q:' if needed
if (!uri.startsWith("Q:")) {
uri = "Q:"+uri;
}
print("GET "+uri);
}
// ----------------------------------------------------
// DIAGNOSTICS METHODS
// ----------------------------------------------------
/**
* Sends back a 400 status, together with a disgnostic page
* showing the stack dump and the HTTP url and form variables
*/
public void dump_error(Throwable e) {
// un-wrap InvocationMethodException
if (e instanceof InvocationTargetException) {
Throwable e1 = e.getCause();
if (e1 != null) {
e = e1;
}
}
// set up barf reply
setStatus("HTTP/1.0 400 Error");
setContentType("text/html");
// render the exception into raw string
ByteArrayOutputStream os = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(os);
e.printStackTrace(ps);
String eText = new String(os.toByteArray());
// generate an html page
HtmlPage page = new HtmlPage();
page.head.add("title").raw("Q Error");
// now dump out the trace, and our HTTP vars
page.body
.nest("h3")
.nest("i")
.raw("Dammit!!")
.end
.end
.hr()
.raw("There was an internal error processing your request")
.br().hr()
.nest("code")
.nest("pre")
.raw(eText)
.end
.end
.hr()
.add(dumpVars())
;
setRawOutput(page.toString());
e.printStackTrace();
}
public static void print(String arg) {
System.out.println("QClientWebInterface: "+arg);
}
/**
* run up this web interface on a local port, for testing
*/
public static void main(String [] args) {
// just for testing
System.setProperty("i2cp.tcp.host", "10.0.0.1");
try {
QClientNode node = new QClientNode();
//node.start();
node.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}

Some files were not shown because too many files have changed in this diff Show More