Added apps/q - the Q distributed file store framework, by aum
BIN
apps/q/doc/client.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
26
apps/q/doc/diagrams.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q System Diagrams</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Diagrams</h1>
|
||||
Informal system diagrams of Q network, hubs and clients.
|
||||
<center>
|
||||
<hr>
|
||||
<img src="overall.jpg">
|
||||
<hr>
|
||||
<img src="client.jpg">
|
||||
<hr>
|
||||
<img src="hub.jpg">
|
||||
</center>
|
||||
<hr>
|
||||
|
||||
<address><a href="mailto:aum@mail.i2p">aum</a></address>
|
||||
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 14:06:02 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
BIN
apps/q/doc/hub.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
80
apps/q/doc/index.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Quartermaster - I2P Distributed File Store</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<center>
|
||||
<h1>Quartermaster<br>an I2P Distributed File Store</h1>
|
||||
|
||||
<h3>STATUS<h3>
|
||||
<i>Whole new (incompatible) version currently in development;
|
||||
ETA for release approx 4-7 days;
|
||||
view screenshots <a href="screenshots.html">here</a>
|
||||
</i>
|
||||
<br>
|
||||
<hr>
|
||||
|
||||
<small>
|
||||
<a href="manual/index.html">User Manual</a> |
|
||||
<a href="spec/index.html">Protocol Spec</a> |
|
||||
<a href="metadata.html">Metadata Spec</a> |
|
||||
<a href="diagrams.html">Q Pr0n (diagrams)</a> |
|
||||
<a href="api/index.html">API Spec</a> |
|
||||
<a href="qnoderefs.txt">qnoderefs.txt</a> |
|
||||
Full Download |
|
||||
Updated jar
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Intro</h2>
|
||||
|
||||
Quartermaster, or Q for short, is a distributed file storage framework for I2P.
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Now features 'QSites' - the Q equivalent of Freenet freesites,
|
||||
static websites which are retrievable even if author is offline</li>
|
||||
<li>Easy web interface - interact with Q (and view/insert QSites)
|
||||
from your web browser</li>
|
||||
<li>Maximum expectations of content retrievability</li>
|
||||
<li>Content security akin to Freenet CHK and SSK keys</li>
|
||||
<li>Powerful, flexible search engine</li>
|
||||
<li>Comfortably accommodates both permanent and transient
|
||||
nodes without significant network disruption (for instance,
|
||||
no flooding of the I2P network with futile
|
||||
calls to offline nodes)</li>
|
||||
<li>Rapid query resolution, due to distributed catalogue
|
||||
mirroring which eliminates all in-network query traffic</li>
|
||||
<li>Modular, extensible architecture</li>
|
||||
<li>Simple interfaces for 3rd-party app developers</li>
|
||||
<li>Is custom-designed and built around I2P, so no duplication of
|
||||
I2P's encryption/anonymity features</li>
|
||||
<li>Simple XML-RPC interface for all inter-node communication, makes it easy to
|
||||
implement user-level clients in any language; also allows alternative
|
||||
implementations of core server and/or client nodes.</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Status</h2>
|
||||
|
||||
Q is presently under development, and a test release is expected soon.
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Architecture</h2>
|
||||
|
||||
Refer to the <a href="spec/index.html">Protocol Specification</a> for more information.
|
||||
|
||||
<hr>
|
||||
<!-- Created: Sat Mar 26 11:09:12 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 18:55:19 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
805
apps/q/doc/manual/index.html
Normal file
@@ -0,0 +1,805 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q User/Programmer Manual</title>
|
||||
<style type="text/css">
|
||||
<!--
|
||||
td { vertical-align: top; }
|
||||
code { font-family: courier, monospace; font-weight: bold }
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="font-family: arial, helvetica, sans-serif">
|
||||
<center>
|
||||
<h1>Q User/Programmer Manual</h1>
|
||||
|
||||
<i>A brief but hopefully easy guide to installing and using the Q distributed file
|
||||
store within the I2P network</i>
|
||||
|
||||
<br><br>
|
||||
<i>(Return to <a href="../index.html">Q Homepage</a>)</i>
|
||||
<br>
|
||||
<br>
|
||||
<small>
|
||||
<a href="#intro">Introduction</a> |
|
||||
<a href="#checklist">Checklist</a> |
|
||||
<a href="#serverorclient">Server?orClient?</a> |
|
||||
<a href="#walkthrough">Walkthrough</a> |
|
||||
<a href="#server">Server Nodes</a> |
|
||||
<a href="#qmgr">About QMgr</a> |
|
||||
<a href="#contact">Contact us</a>
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<a name="intro"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>1. Introduction</h2>
|
||||
|
||||
<blockquote>
|
||||
Q is a distributed Peer2Peer file storage/retrieval network that aims to deliver optimal
|
||||
performance by respecting the properties of the I2P network.<br>
|
||||
<br>
|
||||
This manual serves as a 'walkthrough' guide, to take you through the steps from initial
|
||||
download, to everyday usage. It also provides information for the benefit of higher-level
|
||||
client application authors.
|
||||
</blockquote>
|
||||
|
||||
<a name="checklist"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>2. Preliminary Checklist</h2>
|
||||
|
||||
<blockquote>
|
||||
OK, we assume here that you've already cracked the tarball, and are looking at
|
||||
the distribution files.<br>
|
||||
<br>
|
||||
In order to get Q set up and running, you'll need:
|
||||
<ol>
|
||||
<li>An I2P router installed, set up and (permanently or transiently) running</li>
|
||||
<li>Your system shell set up with at the environment variables:
|
||||
<ul>
|
||||
<li><b>CLASSPATH</b> - this should include:
|
||||
<ul>
|
||||
<li>The regular I2P jar files and 3rd party support jar files (eg <b>i2p.jar</b>,
|
||||
<b>i2ptunnel.jar</b>, <b>streaming.jar</b>,
|
||||
<b>mstreaming.jar</b>, <b>jbigi.jar</b>)</li>
|
||||
<li>Apache's XML-RPC support jarfile - included in this Q distro as
|
||||
<b>xmlrpc.jar</b></li>
|
||||
<li>Aum's jarfile <b>aum.jar</b>, which includes Q and all needed support code</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>PATH</b> - your execution search path <b><i>must</i></b> include the directory
|
||||
in which your main java VM execution program (<b>java</b>, or on windows systems,
|
||||
<b>java.exe</b>) resides.<br>
|
||||
<b>NOTE</b> - if <b>java[.exe]</b> is not on your <b>PATH</b>, then Q <i>will
|
||||
not run</i>.</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</blockquote>
|
||||
|
||||
<a name="serverorclient"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>3. Q Server or Q Client?</h2>
|
||||
|
||||
<blockquote>
|
||||
Nearly everyone will want to run a <b>Q Client Node</b>.<br>
|
||||
<Br>
|
||||
It is only client nodes which provide users with full access to the Q network.<br>
|
||||
<br>
|
||||
However, if you have a (near-) permanently running I2P Router, and you're a kind and
|
||||
generous soul, you might <i>also</i> be willing to run a <b>Q Server Node</b> in addition
|
||||
to your <b>Q Client Node</b>.<br>
|
||||
<br>
|
||||
If you do choose to run a server node, you'll be expected to keep it running as near as
|
||||
possible to 24/7. While transience of client nodes - frequent entering and leaving the
|
||||
Q network - causes little or no disruption, transience of server nodes can significantly
|
||||
impair Q's usability for everyone, particularly if this transience occurs frequently amongst
|
||||
more than the smallest percentage of the server node pool.<br>
|
||||
<br>
|
||||
Until you're feeling well "settled in" with Q, your best approach is to just run a
|
||||
client node for now, and add a server node later when you feel ready.<br>
|
||||
</blockquote>
|
||||
|
||||
<a name="walkthrough"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>4. Q Walkthrough</h2>
|
||||
|
||||
<h3>4.1. Introduction</h3>
|
||||
|
||||
<blockquote>
|
||||
This chapter discusses the deployment and usage of a Q Client Node, and will take you
|
||||
through the steps of:
|
||||
<ol>
|
||||
<li>Double-checking that you've met the installation requirements</li>
|
||||
<li>Launching a Q Client Node</li>
|
||||
<li>Verifying that your Q Client Node is running</li>
|
||||
<li>If your node fails to launch, figuring out why</li>
|
||||
<li>Importing one or more noderefs into your node</li>
|
||||
<li>Observing that your node is discovering other nodes on the network</li>
|
||||
<li>Observing that your node is discovering content on the network</li>
|
||||
<li>Searching for items of content that match chosen criteria</li>
|
||||
<li>Retrieving stuff from the network</li>
|
||||
<li>Inserting stuff to the network</li>
|
||||
<li>Shutting down your client node</li>
|
||||
</ol>
|
||||
Setup and running of Q Server Nodes will be discussed in a later chapter.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.2. Verify Your Q Installation Is Correct</h3>
|
||||
|
||||
<blockquote>
|
||||
Ensure that all the needed I2P jarfiles, as well as <b>xmlrpc.jar</b> and
|
||||
Q's very own <b>aum.jar</b> are correctly listed in your <b>CLASSPATH</b> environment
|
||||
varaible, and your main java launcher is correctly listed in your <b>PATH</b> environment
|
||||
variable.<br>
|
||||
<br>
|
||||
Typically, you will likely copy the jarfiles <b>aum.jar</b> and <b>xmlrpc.jar</b>
|
||||
into the <b>lib/</b> subdirectory of your I2P router installation, along with all
|
||||
the other I2P jar files. Wherever you choose to put these files, make sure they're
|
||||
correctly listed in your <b>CLASSPATH</b>.
|
||||
<br>
|
||||
Also, you'll want to add execute permission to your <b>qmgr</b> (or <b>qmgr.bat</b>)
|
||||
wrapper script, and copy it into one of the directories listed in your <b>PATH</b>
|
||||
environment variable.<br>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.3. Get Familiar With qmgr</h3>
|
||||
|
||||
<blockquote>
|
||||
<b>qmgr</b> (or <b>qmgr.bat</b>) is a convenience wrapper script to save your
|
||||
sore fingers from needless typing. It's just a wrapper which passes arguments
|
||||
to the java command <b><code>java net.i2p.aum.q.QMgr</code></b><br>
|
||||
<br>
|
||||
You can verify you've set up qmgr correctly with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr help</pre></code></blockquote>
|
||||
This displays a brief summary of qmgr commands. On the other hand, the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr help verbose</pre></code></blockquote>
|
||||
floods your terminal window with a detailed explanation of all the qmgr commands
|
||||
and their arguments.<br>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.4. Running A Q Client Node For The First Time</h3>
|
||||
|
||||
<blockquote>
|
||||
Provided you've successfully completed the preliminaries, you can launch your
|
||||
Q Client Node with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr start</pre></code></blockquote>
|
||||
|
||||
All going well, you should have a Q Client Node now running in background.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.5. Verify that your Q Client Node is actually Running</h3>
|
||||
|
||||
<blockquote>
|
||||
After typed the <b>qmgr start</b> command, you will see little or no
|
||||
evidence that Q is actually running.<br>
|
||||
<br>
|
||||
You can test if the node is actually up by typing the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
If your Q Client Node is running, this <b>status</b> command should produce
|
||||
something like:
|
||||
<blockquote><code><pre>
|
||||
Pinging node at '/home/myusername/.quartermaster_client'...
|
||||
Node Ping:
|
||||
status=ok
|
||||
numPeers=0
|
||||
dest=-3LQaE215uIYwl-DsirnzXGQBI31EZQj9u~xx45E823WqjN5i2Umi37GPFTWc8KyislDjF37J7jy5newLUp-qrDpY7BZum3bRyTXo3Udl8a3sUjuu4qR5oBEWFfoghQiqDGYDQyJV9Rtz7DEGaKHGlhtoGsAYRXGXEa8a43T2llqZx2fqaXs~836g8t6sLZjryA5A9fpq98nE5lT0hcTalPieFpluJVairZREXpUiAUmGHG7wAIjF6iszXLEHSZ8Qc622Xgwy0d1yrPojL2yhZ64o05aueYcr~xNCiFxYoHyEJO3XYmkx~q-W-mzS3nn6pRevRda74MnX1~3fFDZ0u~OG6cLZoFkWgnxrwrWGFUUVMR87Yz251xMCKJAX6zErcoGjGFpqGZsWxl4~yq7yfkjPnq3GuTxp2cB75bRAOZRIAieqBOVJDEodFYW5amCinu4AxYE7G1ezz4ghqHFe~0yaAdO74Q1XoUny138YT6P33oNOOlISO1cAAAA
|
||||
uptime=4952
|
||||
load=0.0
|
||||
id=6LVZ9-~GgJJ52WUF1fLHt3UnH50TnXSoPQXy7WZ4GA=
|
||||
numLocalItems=47
|
||||
numRemoteItems=2173</pre></code></blockquote>
|
||||
|
||||
If you see something like this, then smile, because Q is now up on your system.<br>
|
||||
<br>
|
||||
If the node launch failed, you might see something like:
|
||||
<blockquote><code><pre>
|
||||
Pinging node at '/home/myusername/.quartermaster_client'...
|
||||
java.io.IOException: Connection refused
|
||||
at org.apache.xmlrpc.XmlRpcClient$Worker.execute(Unknown Source)
|
||||
at org.apache.xmlrpc.XmlRpcClient.execute(Unknown Source)
|
||||
at net.i2p.aum.q.QMgr.doStatus(QMgr.java:310)
|
||||
at net.i2p.aum.q.QMgr.execute(QMgr.java:813)
|
||||
at net.i2p.aum.q.QMgr.main(QMgr.java:869)
|
||||
Failed to ping node</pre></code></blockquote>
|
||||
This indicates that your Q client node has either crashed, or failed to launch in the
|
||||
first place.<br>
|
||||
<br>
|
||||
If you're having trouble like this, you might like to try running your Q client node
|
||||
in foreground, instead of spawning it off into background.<br>
|
||||
<br>
|
||||
The command to run a Q client node in foreground is:
|
||||
<blockquote><code><pre>
|
||||
qmgr foreground</pre></code></blockquote>
|
||||
You should see some meaningless startup messages, and no return to your shell prompt.<br>
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.6. Diversion - Q Storage Directories</h3>
|
||||
|
||||
<blockquote>
|
||||
By default, when you run a Q Client Node, it creates a datastore directory tree
|
||||
at <b>~/.quartermaster_client</b>. (Windows users note - you'll find this directory
|
||||
wherever your user home directory is - this depends on what version of Windows
|
||||
you have installed).<br>
|
||||
<br>
|
||||
Within this directory tree, you should see a file called <b>node.log</b>, which
|
||||
will contain various debug log messages, and can help you to rectify any problems
|
||||
with your Q installation. If you hit a wall and can't rectify the problems
|
||||
yourself, you should send this file to the Q author (aum).<br>
|
||||
<br>
|
||||
It's possible to run your Q node from another directory, by passing that directory
|
||||
as a <b>-dir <path></b> argument to the
|
||||
<b>qmgr</b> <b>start</b>, <b>foreground</b> and <b>stop</b>
|
||||
commands. See <b>qmgr help verbose</b> for more information.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.7. Importing a Noderef</h3>
|
||||
|
||||
<blockquote>
|
||||
Note from the prior <b>qmgr status</b> command the line:
|
||||
<blockquote><code><pre>
|
||||
numPeers=0</pre></code></blockquote>
|
||||
This means that your Q client node is running standalone, and doesn't have any contact
|
||||
with any Q network. As such, your node is effectively useless. We need to hook up
|
||||
your node with other nodes in the Q network.<br>
|
||||
<br>
|
||||
Q doesn't ship with any means for new client nodes to automatically connect to any Q
|
||||
server nodes. This is deliberate.<br>
|
||||
<br>
|
||||
In all likelihood, there will be one 'main' Q network running within I2P, largely
|
||||
based around the author's own Q server node, and most people will likely want to
|
||||
use this Q network. But the author doesn't want to stop other people running their
|
||||
own private Q networks, for whatever purpose has meaning for them.
|
||||
|
||||
<blockquote><i><small>
|
||||
<hr>
|
||||
This is especially relevant for Q as opposed to Freenet. With Freenet, there's
|
||||
no way for a user to know of the existence of any item of content without
|
||||
first being given its 'key'. However, since Q works with published catalogs,
|
||||
any user can know everything that's available on a Q network, which might
|
||||
not be desirable to those wishing to share content in a private situation.<br>
|
||||
<Br>
|
||||
The Q author anticipates, and warmly supports, people running their own
|
||||
private Q networks within I2P, in addition to accessing the mainstream
|
||||
'official' Q network.<br>
|
||||
<br>
|
||||
The way Q is designed and implemented, there is no way for anyone, including
|
||||
Q's author, to know of the existence of anyone else's private Q network.
|
||||
It is beyond the author's control, (and thus arguably the author's
|
||||
legal responsibility), what private Q networks people set up, and what
|
||||
kind of content is trafficked on these networks. This claim of plausible
|
||||
deniability on the part of Q's author parallels that of a hardware retailer
|
||||
denying responsibility for what people do with tools that they purchase.
|
||||
<hr>
|
||||
</small>
|
||||
</i></blockquote>
|
||||
|
||||
Ok, getting back on topic - your brand new virgin Q client node is useless and lonely,
|
||||
and desperately needs some Q server nodes to talk to. So let's hook up your node to
|
||||
the mainstream Q network.<br>
|
||||
<br>
|
||||
You'll need to get one or more 'noderefs' for Q server nodes.<br>
|
||||
<br>
|
||||
There's nothing fancy about a Q noderef. It's just a regular I2P 'destination', with
|
||||
which your Q Client Node can connect with a Q Server Node.<br>
|
||||
<br>
|
||||
A 'semi-official' list of noderefs for the mainstream Q network can be downloaded
|
||||
from the url: <a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.<br>
|
||||
<br>
|
||||
Download this file, save it as (say) <b>qnoderefs.txt</b>. (Alternatively, if you're
|
||||
wanting to subscribe into a private Q network, then get a noderef for at least one
|
||||
of that network's server nodes from someone on that network who trusts you).<br>
|
||||
<br>
|
||||
Import these noderefs into your Q client node via the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr addref qnoderefs.txt</pre></code></blockquote>
|
||||
If all goes well, you should see no output from this command, or (possibly) a brief
|
||||
line or two suggesting success.<br>
|
||||
<br>
|
||||
Your client node is now subscribed into the Q network of your choice. Verify this
|
||||
with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
In the output from that command, you should see the <b>numPeers=</b> line showing at least
|
||||
1 peer.<br>
|
||||
<br>
|
||||
If there is more than one Q Server Node on the Q network you've just subscribed to,
|
||||
then your local node should sooner or later discover all these server nodes, and
|
||||
the <b>numPeers</b> value should increase over time.<br>
|
||||
<br>
|
||||
<blockquote>
|
||||
<hr>
|
||||
While Q is in its early development and testing stages, the author may abdicate
|
||||
the mainstream Q network, and publish nodrefs for a whole new mainstream Q network.
|
||||
This will especially happen if the author makes any substantial changes to the
|
||||
inter-node protocol, and/or releases incompatible new versions of Q client/server
|
||||
nodes. Remember that
|
||||
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a> will
|
||||
serve as the authoritative source for noderefs for the mainstream Q network within
|
||||
the mainstream I2P network.
|
||||
<hr>
|
||||
</blockquote>
|
||||
|
||||
When your client node gets its noderefs to a Q network, it will periodically,
|
||||
from then on, retrieve differential peer list and catalog updates from servers
|
||||
it knows about.<br>
|
||||
<br>
|
||||
Even if you only feed your client just one ref for a single server node, it will
|
||||
in time discover all other operating server nodes on that Q network, and will
|
||||
build up a full local catalog of everything that's available on that Q network.<br>
|
||||
<br>
|
||||
Provided that your client is running ok, and has been fed with at least one
|
||||
ref for a live Q network that contains content, then over time, successive:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
commands should report increasing values in the fields:
|
||||
<ul>
|
||||
<li><b>numPeers</b> - number of peers this client node knows about</li>
|
||||
<li><b>numLocalItems</b> - number of locally stored content items, ie items
|
||||
which you have either inserted to, or retrieved from, your client node</li>
|
||||
<li><b>numRemoteItems</b> - number of unique data items which are available
|
||||
on remote server nodes in the Q network, and which can be retrieved through
|
||||
your local client node.</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>
|
||||
<hr>
|
||||
|
||||
<h4>4.7.1. One Big Warning</h4>
|
||||
|
||||
If you are participating in more than one distinct Q network, then <b>do not</b>
|
||||
insert noderefs for different networks into the same running instance of a
|
||||
local Q client, unless you don't plan on inserting content via that client.<br>
|
||||
<Br>
|
||||
For instance, let's say you are participating in two different Q networks:
|
||||
<ul>
|
||||
<li>The 'mainstream' Q netowrk</li>
|
||||
<li>A secret Q network - "My friends' teen angst diaries"</li>
|
||||
</ul>
|
||||
If you get a noderef for both these networks, and insert both of these into the
|
||||
same running Q client node, then this local client node will be transparently
|
||||
connected to both networks.<br>
|
||||
<br>
|
||||
If you only ever plan on retrieving content, and never inserting content, this
|
||||
won't be a problem, except that you won't be able to tell which content
|
||||
resides on the mainstream Q network, and which resides in the secret Q network.<br>
|
||||
<Br>
|
||||
The big problem arises from inserting content. Whenever you insert data through this
|
||||
'contaminated'
|
||||
Q client node, this node picks 3 different servers to which upload a copy of this
|
||||
data. You won't have any control over whether the data gets inserted to the mainstream
|
||||
Q network, or your secret Q network. You might insert something sensitive, intending it
|
||||
to go only into the secret Q network, where in fact it also ends up in the mainstream
|
||||
network, with consequences you might not want.
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.8. Content Data and Metadata</h3>
|
||||
|
||||
<blockquote>
|
||||
Whenever content gets stored on Q, it is actually stored as two separate items:
|
||||
<ul>
|
||||
<li>The <b>raw data</b> - whether a text file, or the raw bytes of image files,
|
||||
audio files etc</li>
|
||||
<li>The <b>metadata</b>, which contains human-readable and machine-readable
|
||||
descriptions of the data</li>
|
||||
</ul>
|
||||
Metadata consists of a set of <b>category=value</b> pairs.<br>
|
||||
<br>
|
||||
Confused yet? Don't worry, I'm confused as well. Let's illustrate this with an
|
||||
example of metadata for an MP3 audio recording:
|
||||
<ul>
|
||||
<li>title=Fight_Last_Thursday.mp3</li>
|
||||
<li>type=audio</li>
|
||||
<li>mimetype=audio/mpeg</li>
|
||||
<li>abstract=upcoming single recorded in our garage last April</li>
|
||||
<li>keywords=grunge,country,indie</li>
|
||||
<li>artist=Ring of Fire</li>
|
||||
<li>size=4379443</li>
|
||||
<li>contact=ring-of-fire@mail.i2p</li>
|
||||
<li>key=blah37blah24-yada23hfhyada</li>
|
||||
</ul>
|
||||
All metadata categories are optional. In fact, you can insert content with no metadata
|
||||
at all.<br>
|
||||
<br>
|
||||
If you fail to provide metadata when inserting an item, a blank set of metadata will
|
||||
be created with at least the following categories:
|
||||
<ul>
|
||||
<li><b>key</b> - the derived key, under which the item will later be retrievable
|
||||
by yourself and others</li>
|
||||
<li><b>title</b> - if not provided at insert time, this will be set to the key</li>
|
||||
<li><b>size</b> - size of the item's raw data, in bytes</li>
|
||||
</ul>
|
||||
Within Q, there is a convention to supply a minimal amount of metadata. While this
|
||||
is not expected or enforced, including all these categories is most strongly
|
||||
recommended. These core categories are:
|
||||
<ul>
|
||||
<li><b>title</b> - a meaningful title for the data item, consisting only of characters
|
||||
which are legal in filenames on all platforms, and which ends with a file extension.</li>
|
||||
<li><b>type</b> - one of a superset of eMule classifiers, such as:
|
||||
<ul>
|
||||
<li><b>text</b> - plain text</li>
|
||||
<li><b>html</b> - HTML content</li>
|
||||
<li><b>image</b> - content is in an image format, such as .png, .jpg, .gif etc</li>
|
||||
<li><b>audio</b> - content is an audio sample, such as .ogg, .mp3, .wav etc</li>
|
||||
<li><b>video</b> - due to the sheer size of video files, and Q's present design,
|
||||
it's unlikely people will be inserting video content anytime soon (unless it's
|
||||
very short)</li>
|
||||
<li><b>archive</b> - packed file collections, such as .tar.gz, .zip, .rar etc</li>
|
||||
<li><b>misc</b> - content does not fit into any of the above categories</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>mimetype</b> - not as important as the <b>type</b> category, but providing
|
||||
this category in your metadata is still strongly encouraged. Value for this category
|
||||
should be one of the standard mimetypes, eg <b>text/html</b>, <b>audio/ogg</b> etc.</li>
|
||||
<li><b>abstract</b> - a short description (<255 characters), intended for human reading</li>
|
||||
<li><b>keywords</b> - a comma-separated list of keywords, intended for
|
||||
machine-readability, should be all lowercase, no spaces</li>
|
||||
</ul>
|
||||
Note that you can supply extra metadata categories in addition to the above, and that
|
||||
people searching for content can search on these extra categories if they know about
|
||||
them.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.9. Searching For Content</h3>
|
||||
|
||||
<blockquote>
|
||||
As mentioned earlier - in constrast with Freenet, local Q nodes build up a complete
|
||||
catalog of all available content on whatever Q network they are connected to.<br>
|
||||
<br>
|
||||
This is a design decision, based on the choice to eliminate query traffic.<br>
|
||||
<br>
|
||||
The author hopes that this will result in a distributed storage network with a
|
||||
high retrievability guarantee, in contrast with freenet which offers no such
|
||||
guarantee.<br>
|
||||
<br>
|
||||
With Freenet, you only ever know of the existence of something if someone tells
|
||||
you about it.<br>
|
||||
<br>
|
||||
But with Q, your local client node builds up a global catalog of everything that's
|
||||
available within the whole network.<br>
|
||||
<br>
|
||||
The QMgr client has a command for searching your Q client node:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m category1=pattern1 category2=pattern2 ...</pre></code></blockquote>
|
||||
For example:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m type=audio artist=Mozart keywords=symphony</pre></code></blockquote>
|
||||
or:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m type=text title="bible|biblical|(Nag Hammadi)" keywords="apocrypha|Magdalene"</pre></code></blockquote>
|
||||
As implied in the latter example, search patterns are regular expressions. This example will
|
||||
locate all text items, whose <b>title</b> metadata category contains one of <b>bible</b>, <b>biblical</b> or <b>Nag Hammadi</b>, <i>and</i> whose <b>keywords</b> category contains either
|
||||
or both the words <b>apocrypha</b> or <b>Magdalene</b>.<br>
|
||||
<br>
|
||||
Please use the search function carefully, otherwise (if and when Q usage grows) you
|
||||
could be inundated with thousands or even millions of entries.<br>
|
||||
<br>
|
||||
If a search turns up nothing, qmgr will simply exit. But if it turns up one or more items,
|
||||
it will the items out one at a time, with the key first, then each metadata entry
|
||||
on an indented line following.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.10. Retrieving Content</h3>
|
||||
|
||||
<blockquote>
|
||||
Now, we're actually going to retrieve something.<br>
|
||||
<br>
|
||||
Presumably, after following the previous section, you will have seen one or more search
|
||||
results come up, with the 'keys' under which the items can be accessed.<br>
|
||||
<br>
|
||||
Now, choose one of the keys, preferably for a short text item. Try either of the following
|
||||
commands:
|
||||
<blockquote><code><pre>
|
||||
qmgr get <keystring> something.txt</pre></code></blockquote>
|
||||
<i>or</i>:
|
||||
<blockquote><code><pre>
|
||||
qmgr get <keystring> > something.txt</pre></code></blockquote>
|
||||
(both have the same effect - the first one explicitly writes to the named file, the second
|
||||
one dumps the raw data to stdout, which we shell-redirect into the file.<br>
|
||||
<br>
|
||||
<b><i>Note - redirection of fetched data to a file via shell is not working at present. Use only
|
||||
the first form till we fix the bug.</i></b>
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.11. Inserting Content</h3>
|
||||
|
||||
<blockquote>
|
||||
Our last example in this walkthrough relates to inserting content.<br>
|
||||
<br>
|
||||
Firstly, create a small text file with 2-3 lines of text, and save it as (say)
|
||||
myqinsert.txt.<br>
|
||||
<br>
|
||||
Now, think of some metadata to insert along with the file. Or, you can just use
|
||||
the set:
|
||||
<blockquote><code><pre>
|
||||
type=text
|
||||
keywords=test
|
||||
abstract=My simple test of inserting into Q
|
||||
title=myqinsert.txt</pre></code></blockquote>
|
||||
|
||||
Now, let's insert the file. Ensure your Q client node is running, then type:
|
||||
<blockquote><code><pre>
|
||||
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
|
||||
abstract="My simple test of inserting into Q"</pre></code></blockquote>
|
||||
If all went well, this command should produce half a line of gibberish, followed
|
||||
immediately by your shell prompt, eg:
|
||||
<blockquote><code><pre>
|
||||
aRoFC~9MU~pM2C-uCTDBp5B7j79spFD8gUeu~BNkUf0=<b>$</b>
|
||||
</pre></code></blockquote>
|
||||
The '$' at the end is your shell prompt, and all the characters before it are the 'key'
|
||||
which was derived from the content you just inserted.<br>
|
||||
<br>
|
||||
To avoid the hassle of copying/pasting the key, you could just add output redirection
|
||||
to the above command, eg:
|
||||
<blockquote><code><pre>
|
||||
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
|
||||
abstract="My simple test of inserting into Q" \
|
||||
> myqinsert.key</pre></code></blockquote>
|
||||
This will cause the generated key to be written safe and sound into the file
|
||||
<b>myqinsert.key</b>.<br>
|
||||
<br>
|
||||
You can verify that this insert worked by a 'get' command, as in:
|
||||
<blockquote><code><pre>
|
||||
qmgr get `cat myqinsert.key` somefilename.ext</pre></code></blockquote>
|
||||
(Note that this won't work on windows because the DOS shell is irredeemably brain-damaged. If
|
||||
you're using Windows, you <b>will</b> have to cut/paste the key.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.12. Shutting Down your Node</h3>
|
||||
|
||||
<blockquote>
|
||||
If you've worked through to here, then congratulations! You've got your Q Client Node set up
|
||||
and working, and ready to meet all your distributed file storage and retrieval needs.<br>
|
||||
<br>
|
||||
You can leave your client node running 24/7 if you want. In fact, we recommend you keep your
|
||||
client node running as much of the time as possible, so that you get prompt catalog updates,
|
||||
and can more quickly stay in touch with new content.<br>
|
||||
<br>
|
||||
However, if you need to shut down your node, the command for doing this is:
|
||||
<blockquote><code><pre>
|
||||
qmgr stop</pre></code></blockquote>
|
||||
This command will take a while to complete (since the node has to wait for the I2P
|
||||
java shutdown hooks to complete before it can rest in peace). But once your node is
|
||||
shut down, you can start it up again at any time and pick up where you left off.
|
||||
</blockquote>
|
||||
|
||||
<a name="server"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>5. Running a Q Server Node</h2>
|
||||
|
||||
<h3>5.1. Introduction</h3>
|
||||
<blockquote>
|
||||
This section describes the requirements for, and procedures involved with, running
|
||||
a Q Server Node.<br>
|
||||
<br>
|
||||
We'll use a similar 'walkthrough' style to that which we used in the previous section
|
||||
on client nodes.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.2. Requirements and Choices</h3>
|
||||
<blockquote>
|
||||
Running a Q server is a generous thing to do, and helps substantially with making
|
||||
Q work at its best for everyone. However, please do make sure you can meet some
|
||||
basic requirements:
|
||||
<ul>
|
||||
<li>You are running a permanent (24/7) I2P Router, on a box with at least (say)
|
||||
98% uptime.</li>
|
||||
<li>You have a little bandwidth to spare, and don't mind the extra memory, disk and
|
||||
CPU-usage footprint of running a fulltime Q server node</li>
|
||||
<li>You have already been able to successfully run a Q client node.</li>
|
||||
</ul>
|
||||
Also, please decide whether you want your server node to contribute to the mainstream
|
||||
Q network, or whether you want to create your own private Q network, or join someone
|
||||
else's private network. Your contribution will be most appreciated, though, if you
|
||||
can run a server within the mainstream Q network.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.3. Starting Your Server Node</h3>
|
||||
|
||||
<blockquote>
|
||||
Starting up a Q Server node is very similar to starting up a Q client node, except
|
||||
that with the qmgr command line, you must put the keyword arg <b>server</b> before the
|
||||
command word. So the command is:
|
||||
<blockquote><code><pre>
|
||||
qmgr server start</pre></code></blockquote>
|
||||
Similar to Q client nodes, you can check the status of a running Q server node with
|
||||
the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr server status</pre></code></blockquote>
|
||||
(Note that this command will take longer to complete than with client nodes, because
|
||||
the communication passes through a multi-hop I2P tunnel, rather than just through
|
||||
localhost TCP).<br>
|
||||
<br>
|
||||
If the status command succeeds, then you'll know your new Q Server Node is happily
|
||||
running in background.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.4. Joining A Q Network</h3>
|
||||
|
||||
<blockquote>
|
||||
When a Q Server node starts up for the first time, it is in a private network
|
||||
all by itself.<br>
|
||||
<br>
|
||||
If you want to link your server into an existing Q network, you'll have to add a
|
||||
noderef for at least one other server on that network. The command to do this
|
||||
is similar to that for subscribing a client node to a network:
|
||||
<blockquote><code><pre>
|
||||
qmgr server addref <noderef-file></pre></code></blockquote>
|
||||
where <noderef-file> is a file into which you've saved the noderef for
|
||||
the network you want to join.
|
||||
<blockquote>
|
||||
<hr><i><small>
|
||||
Recall from the section on client nodes that the authoritative noderefs
|
||||
for the mainstream Q network can be downloaded from
|
||||
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.
|
||||
</small></i><hr>
|
||||
</blockquote>
|
||||
After you've added the noderef, subsequent <b>qmgr server status</b> commands
|
||||
should show <b>numPeers</b> having a value of at least 1 (and growing, as more
|
||||
server nodes come online in the mainstream Q network.)
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.5. Private Networks - Exporting Your Server's Noderef</h3>
|
||||
|
||||
<blockquote>
|
||||
If you're planning to start your own private Q network, and want to include other
|
||||
server operators in this network, then you'll have to export your server's noderef
|
||||
and make it available to the others you want to invite into your network.<br>
|
||||
<br>
|
||||
The command to export your Q Server noderef is:
|
||||
<blockquote><code><pre>
|
||||
qmgr server getref <noderef-file></pre></code></blockquote>
|
||||
This will extract the <i>I2P Destination</i> of your running server node, and
|
||||
write it into <noderef-file>. You can then privately share this file with
|
||||
others who you want to invite into your private network. Each recipient of
|
||||
this file will do a <b>qmgr server addref <noderef-file></b> command
|
||||
to import your ref into their servers.<br>
|
||||
<br>
|
||||
Don't forget that if you're running, or participating in, a private Q network, then
|
||||
you'll need to run a separate client for accessing this network, separate from any
|
||||
mainstream Q network client you may already be running.<br>
|
||||
<br>
|
||||
To start this extra client, you'll have to choose a directory where you want this
|
||||
client to reside, a port number you want your client to listen on locally for
|
||||
user commands, and run the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr -dir /path/to/my/new/client -port <portnum> start</pre></code></blockquote>
|
||||
You need the <b>-port <portnum></b> command, because otherwise it'll fail
|
||||
to launch (if you already have a client node running off the mainstream Q network).<br>
|
||||
<br>
|
||||
This will create, and launch, a new instance of a Q client, accessing your private
|
||||
Q network. Don't forget to import your server's noderef into this client. Also,
|
||||
note that you'll have to use this same <b>-port <portnum></b> argument when
|
||||
doing any operation on this client instance, such as get, put, status, search.
|
||||
|
||||
</blockquote>
|
||||
|
||||
<a name="qmgr"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>6. About the qmgr Utility</h2>
|
||||
|
||||
qmgr (or, to people fluent in Java, <b>net.i2p.aum.q.QMgr</b>), is just one simple
|
||||
Q client application, that happens to be bundled in with the Q distro.<br>
|
||||
<br>
|
||||
It is by no means the only, or even main facility for accessing the Q network. We
|
||||
anticipate that folks will write all manner of client apps, including fancy GUI
|
||||
apps.<br>
|
||||
<br>
|
||||
Anyway, qmgr does give you a rudimentary yet workable client for basic access
|
||||
to the Q network. Until fancy apps get written, qmgr will have to do.<br>
|
||||
<br>
|
||||
Don't forget that qmgr has very detailed inbuilt help. Run:
|
||||
<blockquote><code><pre>
|
||||
qmgr help</pre></code></blockquote>
|
||||
for a quick help summary, or:
|
||||
<blockquote><code><pre>
|
||||
qmgr help verbose</pre></code></blockquote>
|
||||
for the 'War and Peace' treatise.<br>
|
||||
<br>
|
||||
<blockquote><hr>
|
||||
One crucial concept to remember with qmgr is that client and server node instances
|
||||
are uniquely identified by the directories at which they reside. If you are running
|
||||
multiple server and/or client instances, you can specify an instance with the
|
||||
<b>-dir <dirpath></b> option - see the help for details.
|
||||
<hr></blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
One last note - we strongly discourage any writing of client apps that spawn a qmgr
|
||||
process, pass it arguments and parse its results. This is most definitely a path to
|
||||
pain, since qmgr's shell interface is subject to radical change at any time without
|
||||
notice.<br>
|
||||
<br>
|
||||
qmgr is for human usage, or at most, inclusion in init/at/cron scripts. Please respect
|
||||
this.<br>
|
||||
<br>
|
||||
If you want to write higher-level clients, your best course of action is to use the
|
||||
official client api library, which we anticipate will have versions available in
|
||||
Java, Python, Perl and C++. If you want to write in another language, such as
|
||||
OCaml, Scheme etc, then the existing api lib implementations should serve as an excellent
|
||||
reference to support you in writing a native port for your own language.
|
||||
|
||||
<a name="contact"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>8. Contacting the Author</h2>
|
||||
|
||||
I am <b>aum</b>, and can be reached as <b>aum</b> on in-I2P IRC networks, and also
|
||||
at the in-I2P email address of <b>aum@mail.i2p</b>.<br>
|
||||
<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<center>
|
||||
Return to <a href="../index.html">Q Homepage</a><br>
|
||||
<br>
|
||||
<small>
|
||||
<a href="#intro">Introduction</a> |
|
||||
<a href="#checklist">Checklist</a> |
|
||||
<a href="#serverorclient">Server?orClient?</a> |
|
||||
<a href="#walkthrough">Walkthrough</a> |
|
||||
<a href="#server">Server Nodes</a> |
|
||||
<a href="#qmgr">About QMgr</a> |
|
||||
<a href="#contact">Contact us</a>
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Created: Fri Apr 1 11:03:27 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Sun Apr 3 20:06:53 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
23
apps/q/doc/manual/notes
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
rise on each hit:
|
||||
|
||||
dy = (1 - y) / kRise
|
||||
|
||||
fall after each time unit:
|
||||
|
||||
dy = y / kFall
|
||||
|
||||
fall after time dt:
|
||||
|
||||
dy = - y ** - (dt / kFall)
|
||||
|
||||
after the next hit:
|
||||
|
||||
y = y - y ** (- dt / kFall) + (1 - y) / kRise
|
||||
|
||||
first attempt at a load measurement algorithm:
|
||||
- kFall is an arbitrary constant which dictates decay rate of load
|
||||
in the absence of hits
|
||||
- kRise is another constant which dictates rise of load with each hit
|
||||
- dt is the time between each hit
|
||||
|
||||
372
apps/q/doc/metadata.html
Normal file
@@ -0,0 +1,372 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q Metadata Specification</title>
|
||||
|
||||
<style type="text/css">
|
||||
<!--
|
||||
td { vertical-align: top; }
|
||||
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
|
||||
-->
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Metadata Specification</h1>
|
||||
|
||||
<h2>1. Introduction</h2>
|
||||
|
||||
This document lists the standard metadata keys for Q data items,
|
||||
discussing the rules of metadata insertion, processing and validation.<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>1.1. Definitions</h3>
|
||||
|
||||
To avoid confusions in terminology, this document will strictly abide the following definitions:
|
||||
<br>
|
||||
<br>
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Term</td>
|
||||
<td>Definition</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>key</code></td>
|
||||
<td>A metadata category name, technically a <code>key</code> as the word is used with
|
||||
Java <code>Hashtable</code> and Python <code>dict</code> objects.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>uri</code></td>
|
||||
<td>A Uniform Resource Indicator for an item of content stored within the Q network.<br>
|
||||
Q URIs have the form: <code>Q:<basename>[,<cryptoKey>][<path>]</code>
|
||||
<br>
|
||||
<br>
|
||||
Some examples:
|
||||
<ul>
|
||||
<li><code>Q:fhvnr3HFSK234khsf90sdh42fsh</code> (a plain hash uri, no cryptoKey)</li>
|
||||
<li><code>Q:e54fhjeo39schr2kcy4osEH478D/files/johnny.mp3</code> (a secure space URI,
|
||||
no cryptoKey)</li>
|
||||
<li><code>Q:vhfh4se987WwfkhwWFEwkh3234S,47fhh2dkhseiyu</code> (a plain hash URI, with
|
||||
a cryptoKey)</li>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>basename</code></td>
|
||||
<td>The basic element of a Q uri. This will be a base64-encoded hash - refer below to
|
||||
URI calculation procedures</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>cryptoKey</code></td>
|
||||
<td>An optional session encryption key for the stored data, encoded as base64.
|
||||
This affords some protection to server node operators, and gives them a level
|
||||
of plausible deniability for whatever gets stored in their server's
|
||||
datastore without their direct human awareness.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>Whever an item of content is inserted in <code>secure space</code> mode, this path
|
||||
serves as a pseudo-pathname, and is conceptually similar to the <code>path</code>
|
||||
component in (for example) standard HTTP URLs
|
||||
<code>http://<domainname>[:<port>][<path>]</code>, such as
|
||||
<code>http://slashdot.org/faq/editorial.shtml</code> (whose <code>path</code>
|
||||
is <code>/faq/editorial.shtml</code>).<br>
|
||||
<br>
|
||||
Paths, if not empty, should contain a leading slash ("/").
|
||||
If an application specifies a non-empty <code>path</code> that doesn't begin with a
|
||||
leading '/', a '/' will be automatically prepended by the receiving node.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>plain hash</code></td>
|
||||
<td>A mode of inserting items, whereby the security of the resulting URI comes from
|
||||
computing the URI from a hash of the item's data and metadata (and imposing a
|
||||
mathematical barrier against spoofing content under a given URI). Corresponds to
|
||||
Freenet's <code>CHK@</code> keys.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>secure space</code></td>
|
||||
<td>A mode of inserting items where the security of the URI is based not on a hash of the
|
||||
item's data and metadata (as with <code>plain hash</code> mode),
|
||||
but on the <code>privateKey</code> provided by the
|
||||
application, and a content signature created from that private key.
|
||||
Corresponds to Freenet's <code>SSK@</code> keys. Within a secure space, you
|
||||
can insert any number of items under different pseudo-pathnames (as is the case
|
||||
with Freenet SSK keys).
|
||||
</li>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>2.1. Keys Inserted By Application Before sending <code>putItem</code> RPCs</h3>
|
||||
|
||||
As the heading suggests, this is a list of metadata keys which should be inserted by a
|
||||
Q application prior to invoking a <code>putItem</code> RPC on the local Q client node.<br>
|
||||
<br>
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Key</td>
|
||||
<td>Data Type</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>title</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended. A free-text short description of the item,
|
||||
should be less than 80 characters. The idea is that applications should
|
||||
support a 'view' of catalogue data that shows item titles. (Prior Q convention of
|
||||
titles expressed as valid filename syntax has been abandoned).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended.
|
||||
A virtual 'pathname' for the item, which should be in valid *nix
|
||||
absolute pathname syntax (beginning with '/', containing no '//', consisting
|
||||
only of alphanumerics, '-', '_', '.' and '/'.<br>
|
||||
<br>
|
||||
In Q web interfaces, the <code>filename</code> component of this path will
|
||||
serve as the recommended filename when downloading/saving the item.<br>
|
||||
<br>
|
||||
If the application also provides a
|
||||
<code>privateKey</code> key, the path
|
||||
is used in conjunction with the private key to generate <code>publicKey</code>
|
||||
and <code>signature</code> keys (see below), and ultimately the final <code>uri</code>
|
||||
under which the item can be retrieved by others.<br>
|
||||
<br>
|
||||
Refer also to <code>mimetype</code> below.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>encrypt</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. If this key is present, and has a value "1", "yes" or "true",
|
||||
this indicates that the application wishes the data to be stored on servers in
|
||||
encrypted form.<br>
|
||||
<br>
|
||||
If this key is present and set to a positive value, the Q node, on receiving the
|
||||
<code>putItem</code> RPC, will:
|
||||
<ol>
|
||||
<li>Generate a random symmetric encryption key</li>
|
||||
<li>Encrypt the item's data using this encryption key</li>
|
||||
<li>Delete the <code>encrypt</code> key from the metadata</li>
|
||||
<li>Enclose a base64 representation of this encryption key in the RPC response
|
||||
it sends back to the application (embedded in the <code>uri</code></li>
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>type</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended. A standard ed2k specifier, one of <code>text html image
|
||||
audio video archive other</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mimetype</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but moderately recommended. Mimetype designation of data, eg <code>text/html</code>,
|
||||
<code>image/jpeg</code> etc. If not specified, an attempt will be made to guess
|
||||
a mometype from the value of the <code>path</code> key. If this attempt fails, then
|
||||
this key will be set to <code>application/x-octet-stream</code> by the node receiving
|
||||
the <code>putItem</code> RPC.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>keywords</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but moderately recommended.
|
||||
A set of keywords, under which the inserting app would like this item to be
|
||||
discoverable. Keywords should be entirely lower case and comma-separated. Content
|
||||
inserts should consider keywords carefully, and only use space characters inside
|
||||
keywords when necessary (eg, for flagging a distinctive phrase containing very
|
||||
common words).</td>
|
||||
<tr>
|
||||
<td><code>privateKey</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. A Base64-encoded signing private key, in cases where the application wishes
|
||||
to insert an item in <code>signed space</code> mode. This can be accompanied by another key,
|
||||
<code>path</code>, indicating a 'path' within the signed space. If 'path'
|
||||
is not given, it will default to '/'.<br>
|
||||
<br>
|
||||
Either way, when a node receives a
|
||||
<code>putItem</code> RPC containing a <code>privateKey</code> in its metadata,
|
||||
it removes this key and replaces it with <code>publicKey</code> and
|
||||
<code>signature</code>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. The virtual pathname, within signed space, under which to store the item.
|
||||
This gets ignored and deleted unless the application also provides a
|
||||
<code>privateKey</code> as well. But if the private key is given, the path
|
||||
is used in conjunction with the private key to generate <code>publicKey</code>
|
||||
and <code>signature</code> keys (see below).<br>
|
||||
<code>path</code> should be a 'unix-style pathname', ie, containing only slashes
|
||||
as (pseudo) directory delimiters, and alphanumeric, '-', '_' and '.' characters,
|
||||
and preferably ending in a meaningful file extension such as <code>.html</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>expiry</code></td>
|
||||
<td>int</td>
|
||||
<td>Unixtime at which the inserted item should expire. When this expiry time
|
||||
is reached, the item won't necessarily be deleted straight away, but may
|
||||
be deleted whenever a node's data store is full.<br>
|
||||
<br>
|
||||
If this is not provided, it will default to a given duration according to
|
||||
the client node's configuration.<br>
|
||||
<br>
|
||||
If it is provided, by an application, then the client node will transparently
|
||||
generate the required 'rent payment' before caching the data item and uploading
|
||||
it to servers.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>2.2. Keys Inserted By Node Upon Receipt Of <code>putItem</code> RPC</h3>
|
||||
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Key</td>
|
||||
<td>Data Type</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>size</code></td>
|
||||
<td>Integer</td>
|
||||
<td>Size of the data to be inserted, in bytes.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dataHash</code></td>
|
||||
<td>String</td>
|
||||
<td>base64-encoded SHA256 hash of data.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>uri</code></td>
|
||||
<td>String</td>
|
||||
<td>This depends on whether the item is being inserted in <i>plain</i> or
|
||||
<i>signed space</i> mode.<br>
|
||||
<br>
|
||||
If inserting in <i>plain</i> mode, then the uri is in the form
|
||||
<code>Q:somebase64hash</code>, where the hash is computed according to
|
||||
the <a href="#plainhash">plain hash calculation procedure</a>.<br>
|
||||
<br>
|
||||
If inserting in <i>signed space</i> mode, then the uri will be in the form
|
||||
<code>Q:somebase64hash/path.ext</code>, where the hash is computed as per
|
||||
the <a href="#signedhash">signed space hash calculation procedure</a>, and
|
||||
the <code>/path.ext</code> is the verbatim value of the app-supplied
|
||||
<code>path</code> key.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>publicKey</code></td>
|
||||
<td>String</td>
|
||||
<td>Base64-encoded signing public key. In cases where app provides
|
||||
<code>privateKey</code>,
|
||||
a node will derive the signing public key from the private key,
|
||||
delete the private key from the metadata, and replace it with its corresponding
|
||||
public key
|
||||
key.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>signature</code></td>
|
||||
<td>String</td>
|
||||
<td>Base64-encoded signature of <code>path+dataHash</code>, created using
|
||||
the app-provided <code>privateKey</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rent</code></td>
|
||||
<td>String</td>
|
||||
<td>A rent payment for the data's accommodation on the server.<br>
|
||||
Intention is to support a variety of payment tokens. Initially, the
|
||||
only acceptable form of payment will be a hashcash-like token,
|
||||
in the form <code>hashcash:base64string</code>. The <code>hashcash:</code>
|
||||
prefix indicates that this payment is in hashcash currency, in which case
|
||||
the <code>base64String</code> should decode to a 16-byte string whose
|
||||
SHA256 hash partially collides with <code>dataHash</code>.
|
||||
The greater the number of bits in the collision,
|
||||
the longer the data's accommodation will be 'paid up for'.<br>
|
||||
<br>
|
||||
If this key is already present, a Q node will verify the hashcash,
|
||||
and adjust the <code>expiry</code> key value to the time the item's accommodation
|
||||
is paid up till.<br>
|
||||
<br>
|
||||
If the key is not present:
|
||||
<ul>
|
||||
<li>A client node will generate a value for this key with enough collision bits
|
||||
to pay the accommodation up till the given app-specified <code>expiry</code> date.</li>
|
||||
<li>A server node will grant temporary free accommodation, and adjust the <code>expiry</code>
|
||||
key to the end of the free accommodation period.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<a name="plainhash"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>3. URI Determination Procedures</h2>
|
||||
|
||||
<h3>3.1. Plain Hash URI Calculation Procedure</h3>
|
||||
|
||||
When items are inserted in <code>plain</code> mode, the final URI is determined from
|
||||
a hash of the data and metadata. Security of the item is based on the mathematical difficulty
|
||||
of creating an arbitrary data+metadata set whose hash collides with the target URI.<br>
|
||||
<br>
|
||||
Specifically, the recipe for calculating plain hash URIs is:
|
||||
<ol>
|
||||
<li>If the key <code>size</code> is missing, set this to the size of the data,
|
||||
in bytes</li>
|
||||
<li>If the key <code>dataHash</code> is missing, set this to the base64-encoded
|
||||
SHA256(data)</li>
|
||||
<li>If the key <code>title</code> is missing, set this to the value of <code>dataHash</code></li>
|
||||
<li>From the metadata, create a set of strings, each in the form <code>key=value</code>,
|
||||
where each line contains a metadata <code>key</code> and its <code>value</code>, and
|
||||
is terminated by an ASCII linefeed (\n, 0x10).</li>
|
||||
<li>Ensure that key <code>uri</code> is omitted</li>
|
||||
<li>Sort the strings into ascending ASCII sort order</li>
|
||||
<li>Concatenate the strings together into one big string</li>
|
||||
<li>Calculate the SHA256 hash of this string</li>
|
||||
<li>Encode the hash into Base64</li>
|
||||
<li>Prepend the string <code>Q:</code> to this</li>
|
||||
</ol>
|
||||
|
||||
<a name="signedhash"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>3.2. Signed Space URI Calculation Procedure</h3>
|
||||
|
||||
This is much simpler than determining plain hash URI, since the security of the URI
|
||||
is based not on hashes of data and metadata, but on the cryptographic <code>privateKey</code>
|
||||
given by the application.<br>
|
||||
<br>
|
||||
Calculation recipe for Signed Space URIs is:
|
||||
<ol>
|
||||
<li>Calculate the SHA256 hash of the private key's binary data (not its base64 representation)</li>
|
||||
<li>Encode this hash into base64, dropping any trailing '=' characters</li>
|
||||
<li>Append to this the value of metadata item <code>path</code> (recall that <code>path</code>,
|
||||
if not empty, must begin with a '/')</li>
|
||||
<li>Prepend the string <code>Q:</code> to this</li>
|
||||
</ol>
|
||||
The resulting URI then is in the form <code>Q:pubkeyHash/path.ext</code>
|
||||
|
||||
<hr>
|
||||
<!-- Created: Tue Apr 5 00:56:45 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Wed Apr 6 00:36:37 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
BIN
apps/q/doc/overall.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
1
apps/q/doc/qnoderefs.txt
Normal file
@@ -0,0 +1 @@
|
||||
rxvXpHKfWGWsql4PJaHglAERSUYyrdKKAzK6jPHT4QXRf9jgcVd4mInq0j6H4inVOzT9dG4L6c9GrlQwe4ysUm5jSTyZemxiZpQDCAazsoRzNDv6gevA40J6uGl10JtVtOjqXW8Ej0JUKubz88g~ogPb1h4Xibc-RrtqrvsJebg5xYFkLlnr7DxDtiWzIMRSZ9Ri2P~eq0SwZzd81tvASPj5fb3nySHeABAuY8HrNu0gqRLjeayDpd3OK1ogrxf1lMvfutn5pnLrlVcvKHa~6rNWWGSulsuEYWtpUd4Itj9aKqIgF9ES7RF77Z73W1f6NRTHO48ZLyLLaKVLjDIsHQP-0mOevszcPjFWtheqRKvT2D28WEMpVC-mPtfw91BkdgBa3pwWhwG~7KIhvWhGs8bj2NOKkqrwYU7xhNVaHdDDkzv4gsweCutHNiiCF~4yL54WzCIfSKDjcHjQxxVkh2NKeaItzgw9E~mPAKNZD22X~2oAuuL9i~0lldEV1ddUAAAA
|
||||
BIN
apps/q/doc/screenshot-home.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
apps/q/doc/screenshot-iewarn.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
apps/q/doc/screenshot-qsite.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
apps/q/doc/screenshot-search.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
apps/q/doc/screenshot.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
apps/q/doc/screenshot.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
23
apps/q/doc/screenshots.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q Screenshots</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Screenshots</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="screenshot-search.jpg">Search Screen</li>
|
||||
<li><a href="screenshot-qsite.jpg">QSite Insertion Form</li>
|
||||
<li><a href="screenshot-iewarn.jpg">Q Security Features</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<address><a href="mailto:aum@mail.i2p">aum</a></address>
|
||||
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 14:06:02 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
1460
apps/q/doc/spec/index.html
Normal file
68
apps/q/java/build.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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"/>
|
||||
</target>
|
||||
|
||||
<target depends="init" 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}">
|
||||
<!-- 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,jar" 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>
|
||||
1115
apps/q/java/src/HTML/Template.java
Normal file
178
apps/q/java/src/HTML/Tmpl/Element/Conditional.java
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*
|
||||
* Modified by David McNab (david@rebirthing.co.nz) to allow nesting of
|
||||
* templates (ie, passing a child Template object as a value argument
|
||||
* to a .setParam() invocation on a parent Template object).
|
||||
*
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import HTML.*;
|
||||
|
||||
public class Conditional extends Element
|
||||
{
|
||||
private boolean control_val = false;
|
||||
private Vector [] data;
|
||||
|
||||
public Conditional(String type, String name)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
if(type.equalsIgnoreCase("if"))
|
||||
this.type="if";
|
||||
else if(type.equalsIgnoreCase("unless"))
|
||||
this.type="unless";
|
||||
else
|
||||
throw new IllegalArgumentException(
|
||||
"Unrecognised type: " + type);
|
||||
|
||||
this.name = name;
|
||||
this.data = new Vector[2];
|
||||
this.data[0] = new Vector();
|
||||
}
|
||||
|
||||
public void addBranch() throws IndexOutOfBoundsException
|
||||
{
|
||||
if(data[1] != null)
|
||||
throw new IndexOutOfBoundsException("Already have two branches");
|
||||
|
||||
if(data[0] == null)
|
||||
data[0] = new Vector();
|
||||
else if(data[1] == null)
|
||||
data[1] = new Vector();
|
||||
}
|
||||
|
||||
public void add(String text)
|
||||
{
|
||||
if(data[1] != null)
|
||||
data[1].addElement(text);
|
||||
else
|
||||
data[0].addElement(text);
|
||||
}
|
||||
|
||||
public void add(Element node)
|
||||
{
|
||||
if(data[1] != null)
|
||||
data[1].addElement(node);
|
||||
else
|
||||
data[0].addElement(node);
|
||||
}
|
||||
|
||||
public void setControlValue(Object control_val)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this.control_val = process_var(control_val);
|
||||
}
|
||||
|
||||
public String parse(Hashtable params)
|
||||
{
|
||||
if(!params.containsKey(this.name))
|
||||
this.control_val = false;
|
||||
else
|
||||
setControlValue(params.get(this.name));
|
||||
|
||||
StringBuffer output = new StringBuffer();
|
||||
|
||||
Enumeration de;
|
||||
if(type.equals("if") && control_val ||
|
||||
type.equals("unless") && !control_val)
|
||||
de = data[0].elements();
|
||||
else if(data[1] != null)
|
||||
de = data[1].elements();
|
||||
else
|
||||
return "";
|
||||
|
||||
while(de.hasMoreElements()) {
|
||||
Object e = de.nextElement();
|
||||
String eType = e.getClass().getName();
|
||||
if(eType.endsWith(".String"))
|
||||
output.append((String)e);
|
||||
else if (eType.endsWith(".Template"))
|
||||
output.append(((Template)e).output());
|
||||
else
|
||||
output.append(((Element)e).parse(params));
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
public String typeOfParam(String param)
|
||||
throws NoSuchElementException
|
||||
{
|
||||
for(int i=0; i<data.length; i++)
|
||||
{
|
||||
if(data[i] == null)
|
||||
continue;
|
||||
for(Enumeration e = data[i].elements();
|
||||
e.hasMoreElements();)
|
||||
{
|
||||
Object o = e.nextElement();
|
||||
if(o.getClass().getName().endsWith(".String"))
|
||||
continue;
|
||||
if(((Element)o).Name().equals(param))
|
||||
return ((Element)o).Type();
|
||||
}
|
||||
}
|
||||
throw new NoSuchElementException(param);
|
||||
}
|
||||
|
||||
private boolean process_var(Object control_val)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
String control_class = "";
|
||||
|
||||
if(control_val == null)
|
||||
return false;
|
||||
|
||||
control_class=control_val.getClass().getName();
|
||||
if(control_class.indexOf(".") > 0)
|
||||
control_class = control_class.substring(
|
||||
control_class.lastIndexOf(".")+1);
|
||||
|
||||
if(control_class.equals("String")) {
|
||||
return !(((String)control_val).equals("") ||
|
||||
((String)control_val).equals("0"));
|
||||
} else if(control_class.equals("Vector")) {
|
||||
return !((Vector)control_val).isEmpty();
|
||||
} else if(control_class.equals("Boolean")) {
|
||||
return ((Boolean)control_val).booleanValue();
|
||||
} else if(control_class.equals("Integer")) {
|
||||
return (((Integer)control_val).intValue() != 0);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unrecognised type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
66
apps/q/java/src/HTML/Tmpl/Element/Element.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public abstract class Element
|
||||
{
|
||||
protected String type;
|
||||
protected String name="";
|
||||
|
||||
public abstract String parse(Hashtable params);
|
||||
public abstract String typeOfParam(String param)
|
||||
throws NoSuchElementException;
|
||||
|
||||
public void add(String data){}
|
||||
public void add(Element node){}
|
||||
|
||||
public boolean contains(String param)
|
||||
{
|
||||
try {
|
||||
return (typeOfParam(param) != null?true:false);
|
||||
} catch(NoSuchElementException nse) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public final String Type()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public final String Name()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
39
apps/q/java/src/HTML/Tmpl/Element/If.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
|
||||
public class If extends Conditional
|
||||
{
|
||||
public If(String control_var) throws IllegalArgumentException
|
||||
{
|
||||
super("if", control_var);
|
||||
}
|
||||
}
|
||||
183
apps/q/java/src/HTML/Tmpl/Element/Loop.java
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
import java.util.Vector;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class Loop extends Element
|
||||
{
|
||||
private boolean loop_context_vars=false;
|
||||
private boolean global_vars=false;
|
||||
|
||||
private Vector control_val = null;
|
||||
private Vector data;
|
||||
|
||||
public Loop(String name)
|
||||
{
|
||||
this.type = "loop";
|
||||
this.name = name;
|
||||
this.data = new Vector();
|
||||
}
|
||||
|
||||
public Loop(String name, boolean loop_context_vars)
|
||||
{
|
||||
this(name);
|
||||
this.loop_context_vars=loop_context_vars;
|
||||
}
|
||||
|
||||
public Loop(String name, boolean loop_context_vars, boolean global_vars)
|
||||
{
|
||||
this(name);
|
||||
this.loop_context_vars=loop_context_vars;
|
||||
this.global_vars=global_vars;
|
||||
}
|
||||
|
||||
public void add(String text)
|
||||
{
|
||||
data.addElement(text);
|
||||
}
|
||||
|
||||
public void add(Element node)
|
||||
{
|
||||
data.addElement(node);
|
||||
}
|
||||
|
||||
public void setControlValue(Vector control_val)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this.control_val = process_var(control_val);
|
||||
}
|
||||
|
||||
public String parse(Hashtable p)
|
||||
{
|
||||
if(!p.containsKey(this.name))
|
||||
this.control_val = null;
|
||||
else {
|
||||
Object o = p.get(this.name);
|
||||
if(!o.getClass().getName().endsWith(".Vector") &&
|
||||
!o.getClass().getName().endsWith(".List"))
|
||||
throw new ClassCastException(
|
||||
"Attempt to set <tmpl_loop> with a non-list. tmpl_loop=" + this.name);
|
||||
setControlValue((Vector)p.get(this.name));
|
||||
}
|
||||
|
||||
if(control_val == null)
|
||||
return "";
|
||||
|
||||
StringBuffer output = new StringBuffer();
|
||||
Enumeration iterator = control_val.elements();
|
||||
|
||||
boolean first=true;
|
||||
boolean last=false;
|
||||
boolean inner=false;
|
||||
boolean odd=true;
|
||||
int counter=1;
|
||||
|
||||
while(iterator.hasMoreElements()) {
|
||||
Hashtable params = (Hashtable)iterator.nextElement();
|
||||
|
||||
if(params==null)
|
||||
params = new Hashtable();
|
||||
|
||||
if(global_vars) {
|
||||
for(Enumeration e = p.keys(); e.hasMoreElements();) {
|
||||
Object key = e.nextElement();
|
||||
if(!params.containsKey(key))
|
||||
params.put(key, p.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
if(loop_context_vars) {
|
||||
if(!iterator.hasMoreElements())
|
||||
last=true;
|
||||
inner = !first && !last;
|
||||
|
||||
params.put("__FIRST__", first?"1":"");
|
||||
params.put("__LAST__", last?"1":"");
|
||||
params.put("__ODD__", odd?"1":"");
|
||||
params.put("__INNER__", inner?"1":"");
|
||||
params.put("__COUNTER__", "" + (counter++));
|
||||
}
|
||||
|
||||
Enumeration de = data.elements();
|
||||
while(de.hasMoreElements()) {
|
||||
|
||||
Object e = de.nextElement();
|
||||
if(e.getClass().getName().indexOf("String")>-1)
|
||||
output.append((String)e);
|
||||
else
|
||||
output.append(((Element)e).parse(params));
|
||||
}
|
||||
first = false;
|
||||
odd = !odd;
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
public String typeOfParam(String param)
|
||||
throws NoSuchElementException
|
||||
{
|
||||
for(Enumeration e = data.elements(); e.hasMoreElements();)
|
||||
{
|
||||
Object o = e.nextElement();
|
||||
if(o.getClass().getName().endsWith(".String"))
|
||||
continue;
|
||||
if(((Element)o).Name().equals(param))
|
||||
return ((Element)o).Type();
|
||||
}
|
||||
throw new NoSuchElementException(param);
|
||||
}
|
||||
|
||||
private Vector process_var(Vector control_val)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
String control_class = "";
|
||||
|
||||
if(control_val == null)
|
||||
return null;
|
||||
|
||||
control_class=control_val.getClass().getName();
|
||||
|
||||
if(control_class.indexOf("Vector") > -1) {
|
||||
if(control_val.isEmpty())
|
||||
return null;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unrecognised type");
|
||||
}
|
||||
|
||||
return control_val;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
39
apps/q/java/src/HTML/Tmpl/Element/Unless.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
|
||||
public class Unless extends Conditional
|
||||
{
|
||||
public Unless(String control_var) throws IllegalArgumentException
|
||||
{
|
||||
super("unless", control_var);
|
||||
}
|
||||
}
|
||||
144
apps/q/java/src/HTML/Tmpl/Element/Var.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*
|
||||
* Modified by David McNab (david@rebirthing.co.nz) to allow nesting of
|
||||
* templates (ie, passing a child Template object as a value argument
|
||||
* to a .setParam() invocation on a parent Template object).
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
import HTML.Tmpl.Util;
|
||||
import HTML.Template;
|
||||
|
||||
public class Var extends Element
|
||||
{
|
||||
public static final int ESCAPE_NONE = 0;
|
||||
public static final int ESCAPE_URL = 1;
|
||||
public static final int ESCAPE_HTML = 2;
|
||||
public static final int ESCAPE_QUOTE = 4;
|
||||
|
||||
public Var(String name, int escape, Object default_value)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this(name, escape);
|
||||
this.default_value = stringify(default_value);
|
||||
}
|
||||
|
||||
public Var(String name, int escape)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
if(name == null)
|
||||
throw new IllegalArgumentException("tmpl_var must have a name");
|
||||
this.type = "var";
|
||||
this.name = name;
|
||||
this.escape = escape;
|
||||
}
|
||||
|
||||
public Var(String name, String escape)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this(name, escape, null);
|
||||
}
|
||||
|
||||
public Var(String name, String escape, Object default_value)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this(name, ESCAPE_NONE, default_value);
|
||||
|
||||
if(escape.equalsIgnoreCase("html"))
|
||||
this.escape = ESCAPE_HTML;
|
||||
else if(escape.equalsIgnoreCase("url"))
|
||||
this.escape = ESCAPE_URL;
|
||||
else if(escape.equalsIgnoreCase("quote"))
|
||||
this.escape = ESCAPE_QUOTE;
|
||||
}
|
||||
|
||||
public Var(String name, boolean escape)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
this(name, escape?ESCAPE_HTML:ESCAPE_NONE);
|
||||
}
|
||||
|
||||
public String parse(Hashtable params)
|
||||
{
|
||||
String value = null;
|
||||
|
||||
if(params.containsKey(this.name))
|
||||
value = stringify(params.get(this.name));
|
||||
else
|
||||
value = this.default_value;
|
||||
|
||||
if(value == null)
|
||||
return "";
|
||||
|
||||
if(this.escape == ESCAPE_HTML)
|
||||
return Util.escapeHTML(value);
|
||||
else if(this.escape == ESCAPE_URL)
|
||||
return Util.escapeURL(value);
|
||||
else if(this.escape == ESCAPE_QUOTE)
|
||||
return Util.escapeQuote(value);
|
||||
else
|
||||
return value;
|
||||
}
|
||||
|
||||
public String typeOfParam(String param)
|
||||
throws NoSuchElementException
|
||||
{
|
||||
throw new NoSuchElementException(param);
|
||||
}
|
||||
|
||||
private String stringify(Object o)
|
||||
{
|
||||
if(o == null)
|
||||
return null;
|
||||
|
||||
String cname = o.getClass().getName();
|
||||
if(cname.endsWith(".String"))
|
||||
return (String)o;
|
||||
else if(cname.endsWith(".Integer"))
|
||||
return ((Integer)o).toString();
|
||||
else if(cname.endsWith(".Boolean"))
|
||||
return ((Boolean)o).toString();
|
||||
else if(cname.endsWith(".Date"))
|
||||
return ((java.util.Date)o).toString();
|
||||
else if(cname.endsWith(".Vector"))
|
||||
throw new ClassCastException("Attempt to set <tmpl_var> with a non-scalar. Var name=" + this.name);
|
||||
else if(cname.endsWith(".Template"))
|
||||
return ((Template)o).output();
|
||||
else
|
||||
throw new ClassCastException("Unknown object type: " + cname);
|
||||
}
|
||||
|
||||
// Private data starts here
|
||||
private int escape=ESCAPE_NONE;
|
||||
private String default_value=null;
|
||||
|
||||
}
|
||||
145
apps/q/java/src/HTML/Tmpl/Filter.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
|
||||
package HTML.Tmpl;
|
||||
|
||||
/**
|
||||
* Pre-parse filters for HTML.Template templates.
|
||||
* <p>
|
||||
* The HTML.Tmpl.Filter interface allows you to write Filters
|
||||
* for your templates. The filter is called after the template
|
||||
* is read and before it is parsed.
|
||||
* <p>
|
||||
* You can use a filter to make changes in the template file before
|
||||
* it is parsed by HTML.Template, so for example, use it to replace
|
||||
* constants, or to translate your own tags to HTML.Template tags.
|
||||
* <p>
|
||||
* A common usage would be to do what you think you're doing when you
|
||||
* do <code><TMPL_INCLUDE file="<TMPL_VAR name="the_file">"></code>:
|
||||
* <p>
|
||||
* myTemplate.tmpl:
|
||||
* <pre>
|
||||
* <TMPL_INCLUDE file="<%the_file%>">
|
||||
* </pre>
|
||||
* <p>
|
||||
* myFilter.java:
|
||||
* <pre>
|
||||
* class myFilter implements HTML.Tmpl.Filter
|
||||
* {
|
||||
* private String myFile;
|
||||
* private int type=SCALAR
|
||||
*
|
||||
* public myFilter(String myFile) {
|
||||
* this.myFile = myFile;
|
||||
* }
|
||||
*
|
||||
* public int format() {
|
||||
* return this.type;
|
||||
* }
|
||||
*
|
||||
* public String parse(String t) {
|
||||
* // replace all <%the_file%> with myFile
|
||||
* return t;
|
||||
* }
|
||||
*
|
||||
* public String [] parse(String [] t) {
|
||||
* throw new UnsupportedOperationException();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* myClass.java:
|
||||
* <pre>
|
||||
* Hashtable params = new Hashtable();
|
||||
* params.put("filename", "myTemplate.tmpl");
|
||||
* params.put("filter", new myFilter("myFile.tmpl"));
|
||||
* Template t = new Template(params);
|
||||
* </pre>
|
||||
*
|
||||
* @author Philip S Tellis
|
||||
* @version 0.0.1
|
||||
*/
|
||||
public interface Filter
|
||||
{
|
||||
/**
|
||||
* Tells HTML.Template to call the parse(String) method of this filter.
|
||||
*/
|
||||
public final static int SCALAR=1;
|
||||
|
||||
/**
|
||||
* Tells HTML.Template to call the parse(String []) method of this
|
||||
* filter.
|
||||
*/
|
||||
public final static int ARRAY=2;
|
||||
|
||||
/**
|
||||
* Tells HTML.Template what kind of filter this is.
|
||||
* Should return either SCALAR or ARRAY to indicate which parse method
|
||||
* must be called.
|
||||
*
|
||||
* @return the values SCALAR or ARRAY indicating which parse method
|
||||
* is to be called
|
||||
*/
|
||||
public int format();
|
||||
|
||||
/**
|
||||
* parses the template as a single string, and returns the parsed
|
||||
* template as a single string.
|
||||
* <p>
|
||||
* Should throw an UnsupportedOperationException if it isn't implemented
|
||||
*
|
||||
* @param t a string containing the entire template
|
||||
*
|
||||
* @return a string containing the template after you've parsed it
|
||||
*
|
||||
* @throws UnsupportedOperationException if this method isn't
|
||||
* implemented
|
||||
*/
|
||||
public String parse(String t);
|
||||
|
||||
/**
|
||||
* parses the template as an array of strings, and returns the parsed
|
||||
* template as an array of strings.
|
||||
* <p>
|
||||
* Should throw an UnsupportedOperationException if it isn't implemented
|
||||
*
|
||||
* @param t an array of strings containing the template - one line
|
||||
* at a time
|
||||
*
|
||||
* @return an array of strings containing the parsed template -
|
||||
* one line at a time
|
||||
*
|
||||
* @throws UnsupportedOperationException if this method isn't
|
||||
* implemented
|
||||
*/
|
||||
public String [] parse(String [] t);
|
||||
}
|
||||
|
||||
385
apps/q/java/src/HTML/Tmpl/Parsers/Parser.java
Normal file
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
|
||||
package HTML.Tmpl.Parsers;
|
||||
import java.util.*;
|
||||
import HTML.Tmpl.Element.*;
|
||||
import HTML.Tmpl.Util;
|
||||
|
||||
public class Parser
|
||||
{
|
||||
private boolean case_sensitive=false;
|
||||
private boolean strict=true;
|
||||
private boolean loop_context_vars=false;
|
||||
private boolean global_vars=false;
|
||||
|
||||
public Parser()
|
||||
{
|
||||
}
|
||||
|
||||
public Parser(String [] args)
|
||||
throws ArrayIndexOutOfBoundsException,
|
||||
IllegalArgumentException
|
||||
{
|
||||
if(args.length%2 != 0)
|
||||
throw new ArrayIndexOutOfBoundsException("odd number of arguments passed");
|
||||
|
||||
for(int i=0; i<args.length; i+=2) {
|
||||
if(args[i].equals("case_sensitive")) {
|
||||
String cs = args[i+1];
|
||||
if(cs.equals("") || cs.equals("0"))
|
||||
this.case_sensitive=false;
|
||||
else
|
||||
this.case_sensitive=true;
|
||||
} else if(args[i].equals("strict")) {
|
||||
String s = args[i+1];
|
||||
if(s.equals("") || s.equals("0"))
|
||||
this.strict=false;
|
||||
else
|
||||
this.strict=true;
|
||||
} else if(args[i].equals("loop_context_vars")) {
|
||||
String s = args[i+1];
|
||||
if(s.equals("") || s.equals("0"))
|
||||
this.loop_context_vars=false;
|
||||
else
|
||||
this.loop_context_vars=true;
|
||||
|
||||
} else if(args[i].equals("global_vars")) {
|
||||
String s = args[i+1];
|
||||
if(s.equals("") || s.equals("0"))
|
||||
this.global_vars=false;
|
||||
else
|
||||
this.global_vars=true;
|
||||
} else {
|
||||
throw new IllegalArgumentException(args[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Element getElement(Properties p)
|
||||
throws NoSuchElementException
|
||||
{
|
||||
String type = p.getProperty("type");
|
||||
|
||||
if(type.equals("if"))
|
||||
return new If(p.getProperty("name"));
|
||||
else if(type.equals("unless"))
|
||||
return new Unless(p.getProperty("name"));
|
||||
else if(type.equals("loop"))
|
||||
return new Loop(p.getProperty("name"),
|
||||
loop_context_vars, global_vars);
|
||||
else
|
||||
throw new NoSuchElementException(type);
|
||||
}
|
||||
|
||||
public Vector parseLine(String line)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
int pos=0, endpos;
|
||||
Vector parts = new Vector();
|
||||
|
||||
char [] c = line.toCharArray();
|
||||
int i=0;
|
||||
|
||||
StringBuffer temp = new StringBuffer();
|
||||
|
||||
for(i=0; i<c.length; i++) {
|
||||
if(c[i] != '<') {
|
||||
temp.append(c[i]);
|
||||
} else {
|
||||
// found a tag
|
||||
Util.debug_print("line so far: " + temp);
|
||||
StringBuffer tag = new StringBuffer();
|
||||
for(; i<c.length && c[i] != '>'; i++) {
|
||||
tag.append(c[i]);
|
||||
}
|
||||
// > is not allowed inside a template tag
|
||||
// so we can be sure that if this is a
|
||||
// template tag, it ends with a >
|
||||
|
||||
// add the closing > as well
|
||||
if(i<c.length)
|
||||
tag.append(c[i]);
|
||||
|
||||
// if this contains more < inside it,
|
||||
// then it could possibly be a template
|
||||
// tag inside a html tag
|
||||
// so remove external tag parts
|
||||
|
||||
while(tag.toString().substring(1).indexOf("<")
|
||||
> -1)
|
||||
{
|
||||
do {
|
||||
temp.append(tag.charAt(0));
|
||||
tag=new StringBuffer(
|
||||
tag.toString().substring(1));
|
||||
} while(tag.charAt(0) != '<');
|
||||
}
|
||||
|
||||
Util.debug_print("tag: " + tag);
|
||||
|
||||
String test_tag = tag.toString().toLowerCase();
|
||||
// if it doesn't contain tmpl_ it is not
|
||||
// a template tag
|
||||
if(test_tag.indexOf("tmpl_") < 0) {
|
||||
temp.append(tag);
|
||||
continue;
|
||||
}
|
||||
|
||||
// may be a template tag
|
||||
// check if it starts with tmpl_
|
||||
|
||||
test_tag = cleanTag(test_tag);
|
||||
|
||||
Util.debug_print("clean: " + test_tag);
|
||||
|
||||
// check if it is a closing tag
|
||||
if(test_tag.startsWith("/"))
|
||||
test_tag = test_tag.substring(1);
|
||||
|
||||
// if it still doesn't start with tmpl_
|
||||
// then it is not a template tag
|
||||
if(!test_tag.startsWith("tmpl_")) {
|
||||
temp.append(tag);
|
||||
continue;
|
||||
}
|
||||
|
||||
// now it must be a template tag
|
||||
String tag_type=getTagType(test_tag);
|
||||
|
||||
if(tag_type == null) {
|
||||
if(strict)
|
||||
throw new
|
||||
IllegalArgumentException(
|
||||
tag.toString());
|
||||
else
|
||||
temp.append(tag);
|
||||
}
|
||||
|
||||
Util.debug_print("type: " + tag_type);
|
||||
|
||||
// if this was an invalid key and we've
|
||||
// reached so far, then next iteration
|
||||
if(tag_type == null)
|
||||
continue;
|
||||
|
||||
// now, push the previous stuff
|
||||
// into the Vector
|
||||
if(temp.length()>0) {
|
||||
parts.addElement(temp.toString());
|
||||
temp = new StringBuffer();
|
||||
}
|
||||
|
||||
// it is a valid template tag
|
||||
// get its properties
|
||||
|
||||
Util.debug_print("Checking: " + tag);
|
||||
Properties tag_props =
|
||||
getTagProps(tag.toString());
|
||||
|
||||
if(tag_props.containsKey("name"))
|
||||
Util.debug_print("name: " +
|
||||
tag_props.getProperty("name"));
|
||||
else
|
||||
Util.debug_print("no name");
|
||||
|
||||
parts.addElement(tag_props);
|
||||
}
|
||||
}
|
||||
|
||||
if(temp.length()>0)
|
||||
parts.addElement(temp.toString());
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
private String cleanTag(String tag)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
String test_tag = new String(tag);
|
||||
// first remove < and >
|
||||
if(test_tag.startsWith("<"))
|
||||
test_tag = test_tag.substring(1);
|
||||
if(test_tag.endsWith(">"))
|
||||
test_tag = test_tag.substring(0, test_tag.length()-1);
|
||||
else
|
||||
throw new IllegalArgumentException("Tags must start " +
|
||||
"and end on the same line");
|
||||
|
||||
// remove any leading !-- and trailing
|
||||
// -- in case of comment style tags
|
||||
if(test_tag.startsWith("!--")) {
|
||||
test_tag=test_tag.substring(3);
|
||||
}
|
||||
if(test_tag.endsWith("--")) {
|
||||
test_tag=test_tag.substring(0, test_tag.length()-2);
|
||||
}
|
||||
// then leading and trailing spaces
|
||||
test_tag = test_tag.trim();
|
||||
|
||||
return test_tag;
|
||||
}
|
||||
|
||||
private String getTagType(String tag)
|
||||
{
|
||||
int sp = tag.indexOf(" ");
|
||||
String tag_type="";
|
||||
if(sp < 0) {
|
||||
tag_type = tag.toLowerCase();
|
||||
} else {
|
||||
tag_type = tag.substring(0, sp).toLowerCase();
|
||||
}
|
||||
if(tag_type.startsWith("tmpl_"))
|
||||
tag_type=tag_type.substring(5);
|
||||
|
||||
Util.debug_print("tag_type: " + tag_type);
|
||||
|
||||
if(tag_type.equals("var") ||
|
||||
tag_type.equals("if") ||
|
||||
tag_type.equals("unless") ||
|
||||
tag_type.equals("loop") ||
|
||||
tag_type.equals("include") ||
|
||||
tag_type.equals("else")) {
|
||||
return tag_type;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Properties getTagProps(String tag)
|
||||
throws IllegalArgumentException,
|
||||
NullPointerException
|
||||
{
|
||||
Properties p = new Properties();
|
||||
|
||||
tag = cleanTag(tag);
|
||||
|
||||
Util.debug_print("clean: " + tag);
|
||||
|
||||
if(tag.startsWith("/")) {
|
||||
p.put("close", "true");
|
||||
tag=tag.substring(1);
|
||||
} else {
|
||||
p.put("close", "");
|
||||
}
|
||||
|
||||
Util.debug_print("close: " + p.getProperty("close"));
|
||||
|
||||
p.put("type", getTagType(tag));
|
||||
|
||||
Util.debug_print("type: " + p.getProperty("type"));
|
||||
|
||||
if(p.getProperty("type").equals("else") ||
|
||||
p.getProperty("close").equals("true"))
|
||||
return p;
|
||||
|
||||
if(p.getProperty("type").equals("var"))
|
||||
p.put("escape", "");
|
||||
|
||||
int sp = tag.indexOf(" ");
|
||||
// if we've got so far, this must succeed
|
||||
|
||||
tag = tag.substring(sp).trim();
|
||||
Util.debug_print("checking params: " + tag);
|
||||
|
||||
// now, we should have either name=value pairs
|
||||
// or name space escape in case of old style vars
|
||||
|
||||
if(tag.indexOf("=") < 0) {
|
||||
// no = means old style
|
||||
// first will be var name
|
||||
// second if any will be escape
|
||||
|
||||
sp = tag.toLowerCase().indexOf(" escape");
|
||||
if(sp < 0) {
|
||||
// no escape
|
||||
p.put("name", tag);
|
||||
p.put("escape", "0");
|
||||
} else {
|
||||
tag = tag.substring(0, sp);
|
||||
p.put("name", tag);
|
||||
p.put("escape", "html");
|
||||
}
|
||||
} else {
|
||||
// = means name=value pairs.
|
||||
// use a StringTokenizer
|
||||
StringTokenizer st = new StringTokenizer(tag, " =");
|
||||
while(st.hasMoreTokens()) {
|
||||
String key, value;
|
||||
key = st.nextToken().toLowerCase();
|
||||
if(st.hasMoreTokens())
|
||||
value = st.nextToken();
|
||||
else if(key.equals("escape"))
|
||||
value = "html";
|
||||
else
|
||||
throw new NullPointerException(
|
||||
"parameter " + key + " has no value");
|
||||
|
||||
if(value.startsWith("\"") &&
|
||||
value.endsWith("\""))
|
||||
value = value.substring(1,
|
||||
value.length()-1);
|
||||
else if(value.startsWith("'") &&
|
||||
value.endsWith("'"))
|
||||
value = value.substring(1,
|
||||
value.length()-1);
|
||||
|
||||
if(value.length()==0)
|
||||
throw new NullPointerException(
|
||||
"parameter " + key + " has no value");
|
||||
|
||||
if(key.equals("escape"))
|
||||
value=value.toLowerCase();
|
||||
|
||||
p.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
String name = p.getProperty("name");
|
||||
// if not case sensitive, and not special variable, flatten case
|
||||
// never flatten case for includes
|
||||
if(!case_sensitive && !p.getProperty("type").equals("include")
|
||||
&& !( name.startsWith("__") && name.endsWith("__") ))
|
||||
{
|
||||
p.put("name", name.toLowerCase());
|
||||
}
|
||||
|
||||
if(!Util.isNameChar(name))
|
||||
throw new IllegalArgumentException(
|
||||
"parameter name may only contain " +
|
||||
"letters, digits, ., /, +, -, _");
|
||||
// __var__ is allowed in the template, but not in the
|
||||
// code. this is so that people can reference __FIRST__,
|
||||
// etc
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
130
apps/q/java/src/HTML/Tmpl/Util.java
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* HTML.Template: A module for using HTML Templates with java
|
||||
*
|
||||
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
|
||||
*
|
||||
* This module is free software; you can redistribute it
|
||||
* and/or modify it under the terms of either:
|
||||
*
|
||||
* a) the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 1, or (at your option)
|
||||
* any later version, or
|
||||
*
|
||||
* b) the "Artistic License" which comes with this module.
|
||||
*
|
||||
* This program is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See either the GNU General Public License or the
|
||||
* Artistic License for more details.
|
||||
*
|
||||
* You should have received a copy of the Artistic License
|
||||
* with this module, in the file ARTISTIC. If not, I'll be
|
||||
* glad to provide one.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
|
||||
package HTML.Tmpl;
|
||||
|
||||
public class Util
|
||||
{
|
||||
public static boolean debug=false;
|
||||
|
||||
public static String escapeHTML(String element)
|
||||
{
|
||||
String s = new String(element); // don't change the original
|
||||
String [] metas = {"&", "<", ">", "\""};
|
||||
String [] repls = {"&", "<", ">", """};
|
||||
for(int i = 0; i < metas.length; i++) {
|
||||
int pos=0;
|
||||
do {
|
||||
pos = s.indexOf(metas[i], pos);
|
||||
if(pos<0)
|
||||
break;
|
||||
|
||||
s = s.substring(0, pos) + repls[i] + s.substring(pos+1);
|
||||
pos++;
|
||||
} while(pos >= 0);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String escapeURL(String url)
|
||||
{
|
||||
StringBuffer s = new StringBuffer();
|
||||
String no_escape = "./-_";
|
||||
|
||||
for(int i=0; i<url.length(); i++)
|
||||
{
|
||||
char c = url.charAt(i);
|
||||
if(!Character.isLetterOrDigit(c) &&
|
||||
no_escape.indexOf(c)<0)
|
||||
{
|
||||
String h = Integer.toHexString((int)c);
|
||||
s.append("%");
|
||||
if(h.length()<2)
|
||||
s.append("0");
|
||||
s.append(h);
|
||||
} else {
|
||||
s.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public static String escapeQuote(String element)
|
||||
{
|
||||
String s = new String(element); // don't change the original
|
||||
String [] metas = {"\"", "'"};
|
||||
String [] repls = {"\\\"", "\\'"};
|
||||
for(int i = 0; i < metas.length; i++) {
|
||||
int pos=0;
|
||||
do {
|
||||
pos = s.indexOf(metas[i], pos);
|
||||
if(pos<0)
|
||||
break;
|
||||
|
||||
s = s.substring(0, pos) + repls[i] + s.substring(pos+1);
|
||||
pos++;
|
||||
} while(pos >= 0);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static boolean isNameChar(char c)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isNameChar(String s)
|
||||
{
|
||||
String alt_valid = "./+-_";
|
||||
|
||||
for(int i=0; i<s.length(); i++)
|
||||
if(!Character.isLetterOrDigit(s.charAt(i)) &&
|
||||
alt_valid.indexOf(s.charAt(i))<0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void debug_print(String msg)
|
||||
{
|
||||
if(!debug)
|
||||
return;
|
||||
|
||||
System.err.println(msg);
|
||||
}
|
||||
|
||||
public static void debug_print(Object o)
|
||||
{
|
||||
debug_print(o.toString());
|
||||
}
|
||||
}
|
||||
19
apps/q/java/src/net/i2p/aum/AumUtil.java
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* AumUtil.java
|
||||
*
|
||||
* Created on March 24, 2005, 3:10 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author david
|
||||
*/
|
||||
public class AumUtil {
|
||||
|
||||
/** Creates a new instance of AumUtil */
|
||||
public AumUtil() {
|
||||
}
|
||||
|
||||
}
|
||||
67
apps/q/java/src/net/i2p/aum/DupHashtable.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* NonUniqueProperties.java
|
||||
*
|
||||
* Created on April 9, 2005, 10:46 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* similar in some ways to Properties, except that duplicate keys
|
||||
* are allowed
|
||||
*/
|
||||
public class DupHashtable extends Hashtable {
|
||||
|
||||
/** Creates a new instance of NonUniqueProperties */
|
||||
public DupHashtable() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** Adds a value to be stored against key */
|
||||
public void put(String key, String value) {
|
||||
|
||||
if (!containsKey(key)) {
|
||||
put(key, new Vector());
|
||||
}
|
||||
|
||||
((Vector)get(key)).addElement(value);
|
||||
}
|
||||
|
||||
/** retrieves a Vector of values for key, or empty vector if none */
|
||||
public Vector get(String key) {
|
||||
if (!containsKey(key)) {
|
||||
return new Vector();
|
||||
} else {
|
||||
return (Vector)super.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
/** returns the i-th value for given key, or dflt if key not found */
|
||||
public String get(String key, int idx, String dflt) {
|
||||
if (containsKey(key)) {
|
||||
return get(key, idx);
|
||||
} else {
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/** returns the i-th value for given key
|
||||
* @throws ArrayIndexOutOfBoundsException if idx is out of range
|
||||
*/
|
||||
public String get(String key, int idx) {
|
||||
return (String)((Vector)get(key)).get(idx);
|
||||
}
|
||||
|
||||
/** returns the number of values for a given key */
|
||||
public int numValuesFor(String key) {
|
||||
if (!containsKey(key)) {
|
||||
return 0;
|
||||
} else {
|
||||
return ((Vector)get(key)).size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
155
apps/q/java/src/net/i2p/aum/EchoClient.java
Normal file
@@ -0,0 +1,155 @@
|
||||
|
||||
// a simple I2P stream client that makes connections to an EchoServer,
|
||||
// sends in stuff, and gets replies
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.util.*;
|
||||
|
||||
/**
|
||||
* a simple program which illustrates the use of I2P stream
|
||||
* sockets from a client point of view
|
||||
*/
|
||||
|
||||
public class EchoClient extends Thread
|
||||
{
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PSocket clientSocket;
|
||||
|
||||
public Destination dest;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
/**
|
||||
* Creates an echoclient, given an I2P Destination object
|
||||
*/
|
||||
public EchoClient(Destination remdest)
|
||||
{
|
||||
_log = new Log("EchoServer");
|
||||
|
||||
_init(remdest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an EchoClient given a destination in base64
|
||||
*/
|
||||
public EchoClient(String destStr) throws DataFormatException
|
||||
{
|
||||
_log = new Log("EchoServer");
|
||||
|
||||
Destination remdest = new Destination();
|
||||
remdest.fromBase64(destStr);
|
||||
_init(remdest);
|
||||
}
|
||||
|
||||
private void _init(Destination remdest)
|
||||
{
|
||||
dest = remdest;
|
||||
|
||||
System.out.println("Client: dest="+dest.toBase64());
|
||||
|
||||
System.out.println("Client: Creating client socketManager");
|
||||
|
||||
// get a socket manager
|
||||
socketManager = I2PSocketManagerFactory.createManager();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* runs the EchoClient demo
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
InputStream socketIn;
|
||||
OutputStreamWriter socketOut;
|
||||
OutputStream socketOutStream;
|
||||
|
||||
System.out.println("Client: Creating connected client socket");
|
||||
System.out.println("dest="+dest.toBase64());
|
||||
|
||||
try {
|
||||
// get a client socket
|
||||
clientSocket = socketManager.connect(dest);
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (ConnectException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (NoRouteToHostException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (InterruptedIOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Client: Successfully connected!");
|
||||
|
||||
try {
|
||||
socketIn = clientSocket.getInputStream();
|
||||
socketOutStream = clientSocket.getOutputStream();
|
||||
socketOut = new OutputStreamWriter(socketOutStream);
|
||||
|
||||
System.out.println("Client: created streams");
|
||||
|
||||
socketOut.write("Hi there server!\n");
|
||||
socketOut.flush();
|
||||
|
||||
System.out.println("Client: sent to server, awaiting reply");
|
||||
|
||||
String line = DataHelper.readLine(socketIn);
|
||||
|
||||
System.out.println("Got reply: '" + line + "'");
|
||||
|
||||
clientSocket.close();
|
||||
|
||||
} catch (NoRouteToHostException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException!!");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* allows the echo client to be run from a command shell
|
||||
*/
|
||||
public static void main(String [] args)
|
||||
{
|
||||
String dest64 = args[0];
|
||||
System.out.println("dest="+dest64);
|
||||
|
||||
Destination d = new Destination();
|
||||
try {
|
||||
d.fromBase64(dest64);
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
EchoClient client = new EchoClient(d);
|
||||
|
||||
System.out.println("client: running");
|
||||
client.run();
|
||||
|
||||
System.out.println("client: done");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
163
apps/q/java/src/net/i2p/aum/EchoServer.java
Normal file
@@ -0,0 +1,163 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.util.*;
|
||||
|
||||
/**
|
||||
* a simple program which illustrates the use of I2P stream
|
||||
* sockets from a server point of view
|
||||
*/
|
||||
public class EchoServer extends Thread
|
||||
{
|
||||
//public I2PClient client;
|
||||
//public PrivDestination privDest;
|
||||
//public I2PSession serverSession;
|
||||
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
|
||||
public PrivDestination key;
|
||||
public Destination dest;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
public EchoServer() throws I2PException, IOException
|
||||
{
|
||||
_log = new Log("EchoServer");
|
||||
|
||||
System.out.println("Server: creating new key");
|
||||
|
||||
// key = PrivDestination.newKey();
|
||||
// System.out.println("Server: dest=" + key.toDestinationBase64());
|
||||
|
||||
System.out.println("Server: creating socket manager");
|
||||
|
||||
Properties props = new Properties();
|
||||
props.setProperty("inbound.length", "0");
|
||||
props.setProperty("outbound.length", "0");
|
||||
props.setProperty("inbound.lengthVariance", "0");
|
||||
props.setProperty("outbound.lengthVariance", "0");
|
||||
|
||||
PrivDestination key = PrivDestination.newKey();
|
||||
|
||||
// get a socket manager
|
||||
// socketManager = I2PSocketManagerFactory.createManager(key);
|
||||
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
|
||||
|
||||
System.out.println("Server: getting server socket");
|
||||
|
||||
// get a server socket
|
||||
serverSocket = socketManager.getServerSocket();
|
||||
|
||||
System.out.println("Server: got server socket, ready to run");
|
||||
|
||||
dest = socketManager.getSession().getMyDestination();
|
||||
|
||||
System.out.println("Server: getMyDestination->"+dest.toBase64());
|
||||
|
||||
start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* run this EchoServer
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
System.out.println("Server: listening on dest:");
|
||||
|
||||
/**
|
||||
try {
|
||||
System.out.println(key.toDestinationBase64());
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
*/
|
||||
|
||||
System.out.println(dest.toBase64());
|
||||
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
I2PSocket sessSocket = serverSocket.accept();
|
||||
|
||||
System.out.println("Server: Got connection from client");
|
||||
|
||||
InputStream socketIn = sessSocket.getInputStream();
|
||||
OutputStreamWriter socketOut = new OutputStreamWriter(sessSocket.getOutputStream());
|
||||
|
||||
System.out.println("Server: created streams");
|
||||
|
||||
// read a line from input, and echo it back
|
||||
String line = DataHelper.readLine(socketIn);
|
||||
|
||||
System.out.println("Server: got '" + line + "'");
|
||||
|
||||
String reply = "EchoServer: got '" + line + "'\n";
|
||||
socketOut.write(reply);
|
||||
socketOut.flush();
|
||||
|
||||
System.out.println("Server: sent trply");
|
||||
|
||||
sessSocket.close();
|
||||
|
||||
System.out.println("Server: closed socket");
|
||||
|
||||
} catch (ConnectException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Destination getDest() throws DataFormatException
|
||||
{
|
||||
// return key.toDestination();
|
||||
return dest;
|
||||
}
|
||||
|
||||
public String getDestBase64() throws DataFormatException
|
||||
{
|
||||
// return key.toDestinationBase64();
|
||||
return dest.toBase64();
|
||||
}
|
||||
|
||||
/**
|
||||
* runs EchoServer from the command shell
|
||||
*/
|
||||
public static void main(String [] args)
|
||||
{
|
||||
System.out.println("Constructing an EchoServer");
|
||||
|
||||
try {
|
||||
EchoServer myServer = new EchoServer();
|
||||
System.out.println("Got an EchoServer");
|
||||
System.out.println("Here's the dest:");
|
||||
System.out.println(myServer.getDestBase64());
|
||||
|
||||
myServer.run();
|
||||
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
56
apps/q/java/src/net/i2p/aum/EchoTest.java
Normal file
@@ -0,0 +1,56 @@
|
||||
// runs EchoServer and EchoClient as threads
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* A simple program which runs the EchoServer and EchoClient
|
||||
* demos as threads
|
||||
*/
|
||||
|
||||
public class EchoTest
|
||||
{
|
||||
/**
|
||||
* create one instance each of EchoServer and EchoClient,
|
||||
* run the server as a thread, run the client in foreground,
|
||||
* display detailed results
|
||||
*/
|
||||
public static void main(String [] args)
|
||||
{
|
||||
EchoServer server;
|
||||
EchoClient client;
|
||||
|
||||
try {
|
||||
server = new EchoServer();
|
||||
Destination serverDest = server.getDest();
|
||||
|
||||
System.out.println("EchoTest: serverDest=" + serverDest.toBase64());
|
||||
|
||||
client = new EchoClient(serverDest);
|
||||
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace(); return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(); return;
|
||||
}
|
||||
|
||||
System.out.println("Starting server...");
|
||||
//server.start();
|
||||
|
||||
System.out.println("Starting client...");
|
||||
client.run();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
322
apps/q/java/src/net/i2p/aum/EmbargoedQueue.java
Normal file
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* SimpleScheduler.java
|
||||
*
|
||||
* Created on March 24, 2005, 11:14 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Implements a queue of objects, where each object is 'embargoed'
|
||||
* against release until a given time. Threads which attempt to .get
|
||||
* items from this queue will block if the queue is empty, or if the
|
||||
* first item of the queue has a 'release time' which has not yet passed.</p>
|
||||
*
|
||||
* <p>Think of it like a news desk which receives media releases which are
|
||||
* 'embargoed' till a certain time. These releases sit in a queue, and when
|
||||
* their embargo expires, they are actioned and go to print or broadcast.
|
||||
* The reporters at this news desk are the 'threads', which get blocked
|
||||
* until the next item's embargo expires.</p>
|
||||
*
|
||||
* <p>Purpose of implementing this is to provide a mechanism for scheduling
|
||||
* background jobs to be executed at precise times</p>.
|
||||
*/
|
||||
public class EmbargoedQueue extends Thread {
|
||||
|
||||
/**
|
||||
* items which are waiting for dispatch - stored as 2-element vectors,
|
||||
* where elem 0 is Integer dispatch time, and elem 1 is the object;
|
||||
* note that this list is kept in strict ascending order of time.
|
||||
* Whenever an object becomes ready, it is removed from this queue
|
||||
* and appended to readyItems
|
||||
*/
|
||||
public Vector waitingItems;
|
||||
|
||||
/**
|
||||
* items which are ready for dispatch (their time has come).
|
||||
*/
|
||||
public SimpleQueue readyItems;
|
||||
|
||||
/** set this true to enable verbose debug messages */
|
||||
public boolean debug = false;
|
||||
|
||||
/** Creates a new embargoed queue */
|
||||
public EmbargoedQueue() {
|
||||
waitingItems = new Vector();
|
||||
readyItems = new SimpleQueue();
|
||||
|
||||
// fire up scheduler thread
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches the item at head of queue, blocking if queue is empty
|
||||
*/
|
||||
public Object get()
|
||||
{
|
||||
return readyItems.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new object to queue without any embargo (or, an embargo that expires
|
||||
* immediately)
|
||||
* @param item the object to be added
|
||||
*/
|
||||
public synchronized void putNow(Object item)
|
||||
{
|
||||
putAfter(0, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new object to queue, embargoed until given number of milliseconds
|
||||
* have elapsed
|
||||
* @param delay number of milliseconds from now when embargo expires
|
||||
* @param item the object to be added
|
||||
*/
|
||||
public synchronized void putAfter(long delay, Object item)
|
||||
{
|
||||
long now = new Date().getTime();
|
||||
putAt(now+delay, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new object to the queue, embargoed until given time
|
||||
* @param time the unixtime in milliseconds when the object's embargo expires,
|
||||
* and the object is to be made available
|
||||
* @param item the object to be added
|
||||
*/
|
||||
public synchronized void putAt(long time, Object item)
|
||||
{
|
||||
Vector elem = new Vector();
|
||||
elem.addElement(new Long(time));
|
||||
elem.addElement(item);
|
||||
|
||||
long now = new Date().getTime();
|
||||
long future = time - now;
|
||||
//System.out.println("putAt: time="+time+" ("+future+"ms from now), job="+item);
|
||||
|
||||
// find where to insert
|
||||
int i;
|
||||
int nitems = waitingItems.size();
|
||||
for (i = 0; i < nitems; i++)
|
||||
{
|
||||
// get item i
|
||||
Vector itemI = (Vector)waitingItems.get(i);
|
||||
long timeI = ((Long)(itemI.get(0))).longValue();
|
||||
if (time < timeI)
|
||||
{
|
||||
// new item earlier than item i, insert here and bust out
|
||||
waitingItems.insertElementAt(elem, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// did we insert?
|
||||
if (i == nitems)
|
||||
{
|
||||
// no - gotta append
|
||||
waitingItems.addElement(elem);
|
||||
}
|
||||
|
||||
// debugging
|
||||
if (debug) {
|
||||
printWaiting();
|
||||
}
|
||||
|
||||
// awaken this scheduler object's thread, so it can
|
||||
// see if any jobs are ready
|
||||
//notify();
|
||||
interrupt();
|
||||
}
|
||||
|
||||
/**
|
||||
* for debugging - prints out a list of waiting items
|
||||
*/
|
||||
public synchronized void printWaiting()
|
||||
{
|
||||
int i;
|
||||
long now = new Date().getTime();
|
||||
|
||||
System.out.println("EmbargoedQueue dump:");
|
||||
|
||||
System.out.println(" Waiting items:");
|
||||
int nwaiting = waitingItems.size();
|
||||
for (i = 0; i < nwaiting; i++)
|
||||
{
|
||||
Vector item = (Vector)waitingItems.get(i);
|
||||
long when = ((Long)item.get(0)).longValue();
|
||||
Object job = item.get(1);
|
||||
int delay = (int)(when - now)/1000;
|
||||
System.out.println(" "+delay+"s, t="+when+", job="+job);
|
||||
}
|
||||
|
||||
System.out.println(" Ready items:");
|
||||
int nready = readyItems.items.size();
|
||||
for (i = 0; i < nready; i++)
|
||||
{
|
||||
//Vector item = (Vector)readyItems.items.get(i);
|
||||
Object item = readyItems.items.get(i);
|
||||
System.out.println(" job="+item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* scheduling thread, which wakes up every time a new job is queued, and
|
||||
* if any jobs are ready, transfers them to the readyQueue and notifies
|
||||
* any waiting client threads
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
// monitor the waiting queue, waiting till one becomes ready
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
if (waitingItems.size() > 0)
|
||||
{
|
||||
// at least 1 waiting item
|
||||
Vector item = (Vector)(waitingItems.get(0));
|
||||
long now = new Date().getTime();
|
||||
long then = ((Long)item.get(0)).longValue();
|
||||
long delay = then - now;
|
||||
|
||||
// ready?
|
||||
if (delay <= 0)
|
||||
{
|
||||
// yep, ready, remove job and stick on waiting queue
|
||||
waitingItems.remove(0); // ditch from waiting
|
||||
Object elem = item.get(1);
|
||||
readyItems.put(elem); // and add to ready
|
||||
|
||||
if (debug)
|
||||
{
|
||||
System.out.println("embargo expired on "+elem);
|
||||
printWaiting();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not ready, hang about till we get woken, or the
|
||||
// job becomes ready
|
||||
if (debug)
|
||||
{
|
||||
System.out.println("waiting for "+delay+"ms");
|
||||
}
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no items yet, hang out for an interrupt
|
||||
if (debug)
|
||||
{
|
||||
System.out.println("queue is empty");
|
||||
}
|
||||
synchronized (this) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//System.out.println("exception");
|
||||
if (debug)
|
||||
{
|
||||
System.out.println("exception ("+e.getClass().getName()+") "+e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestThread extends Thread {
|
||||
|
||||
String id;
|
||||
|
||||
EmbargoedQueue q;
|
||||
|
||||
public TestThread(String id, EmbargoedQueue q) {
|
||||
this.id = id;
|
||||
this.q = q;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
print("waiting for queue");
|
||||
|
||||
Object item = q.get();
|
||||
|
||||
print("got item: '"+item+"'");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void print(String msg) {
|
||||
System.out.println("thread '"+id+"': "+msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
int i;
|
||||
int nthreads = 7;
|
||||
|
||||
Thread [] threads = new Thread[nthreads];
|
||||
|
||||
EmbargoedQueue q = new EmbargoedQueue();
|
||||
SimpleSemaphore threadPool = new SimpleSemaphore(nthreads);
|
||||
|
||||
// populate the queue with some stuff
|
||||
q.putAfter(10000, "red");
|
||||
q.putAfter(3000, "orange");
|
||||
q.putAfter(6000, "yellow");
|
||||
|
||||
// populate threads array
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
threads[i] = new TestThread("thread"+i, q);
|
||||
}
|
||||
|
||||
// and launch the threads
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
// wait, presumably till all these elements are actioned
|
||||
try {
|
||||
Thread.sleep(12000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
// add some more shit to the queue, randomly scheduled
|
||||
Random r = new Random();
|
||||
String [] items = {"green", "blue", "indigo", "violet", "black", "white", "brown"};
|
||||
for (i = 0; i < items.length; i++) {
|
||||
String item = items[i];
|
||||
int delay = 2000 + r.nextInt(8000);
|
||||
System.out.println("main: adding '"+item+"' after "+delay+"ms ...");
|
||||
q.putAfter(delay, item);
|
||||
}
|
||||
|
||||
// wait, presumably for all jobs to finish
|
||||
try {
|
||||
Thread.sleep(12000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("main: terminating");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
452
apps/q/java/src/net/i2p/aum/I2PCat.java
Normal file
@@ -0,0 +1,452 @@
|
||||
|
||||
// I2P equivalent of 'netcat'
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.naming.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.util.*;
|
||||
|
||||
/**
|
||||
* A I2P equivalent of the much-beloved 'netcat' utility.
|
||||
* This command-line utility can either connect to a remote
|
||||
* destination, or listen on a private destination for incoming
|
||||
* connections. Once a connection is established, input on stdin
|
||||
* is sent to the remote peer, and anything received from the
|
||||
* remote peer is printed to stdout
|
||||
*/
|
||||
|
||||
public class I2PCat extends Thread
|
||||
{
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
public I2PSocket sessSocket;
|
||||
|
||||
public PrivDestination key;
|
||||
public Destination dest;
|
||||
|
||||
public InputStream socketIn;
|
||||
public OutputStream socketOutStream;
|
||||
public OutputStreamWriter socketOut;
|
||||
|
||||
public SockInput rxThread;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
public static String defaultHost = "127.0.0.1";
|
||||
public static int defaultPort = 7654;
|
||||
|
||||
/**
|
||||
* a thread for reading from socket and displaying on stdout
|
||||
*/
|
||||
private class SockInput extends Thread {
|
||||
|
||||
InputStream _in;
|
||||
|
||||
protected Log _log;
|
||||
public SockInput(InputStream i) {
|
||||
|
||||
_in = i;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
// the thread portion, receives incoming bytes on
|
||||
// the socket input stream and spits them to stdout
|
||||
|
||||
byte [] ch = new byte[1];
|
||||
|
||||
print("Receiver thread listening...");
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
|
||||
//String line = DataHelper.readLine(socketIn);
|
||||
if (_in.read(ch) != 1) {
|
||||
print("failed to receive from socket");
|
||||
break;
|
||||
}
|
||||
|
||||
//System.out.println(line);
|
||||
System.out.write(ch, 0, 1);
|
||||
System.out.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
print("Receiver thread crashed, terminating!!");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void print(String msg)
|
||||
{
|
||||
System.out.println("-=- I2PCat: "+msg);
|
||||
|
||||
if (_log != null) {
|
||||
_log.debug(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public I2PCat()
|
||||
{
|
||||
_log = new Log("I2PCat");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs I2PCat in server mode, listening on the given destination
|
||||
* for one incoming connection. Once connection is established,
|
||||
* copyies data between the remote peer and
|
||||
* the local terminal console.
|
||||
*/
|
||||
public void runServer(String keyStr) throws IOException, DataFormatException
|
||||
{
|
||||
Properties props = new Properties();
|
||||
props.setProperty("inbound.length", "0");
|
||||
props.setProperty("outbound.length", "0");
|
||||
props.setProperty("inbound.lengthVariance", "0");
|
||||
props.setProperty("outbound.lengthVariance", "0");
|
||||
|
||||
// generate new key if needed
|
||||
if (keyStr.equals("new")) {
|
||||
|
||||
try {
|
||||
key = PrivDestination.newKey();
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
print("Creating new server dest...");
|
||||
|
||||
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
|
||||
|
||||
print("Getting server socket...");
|
||||
|
||||
serverSocket = socketManager.getServerSocket();
|
||||
|
||||
print("Server socket created, ready to run...");
|
||||
|
||||
dest = socketManager.getSession().getMyDestination();
|
||||
|
||||
print("private key follows:");
|
||||
System.out.println(key.toBase64());
|
||||
|
||||
print("dest follows:");
|
||||
System.out.println(dest.toBase64());
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
key = PrivDestination.fromBase64String(keyStr);
|
||||
|
||||
String dest64Abbrev = key.toBase64().substring(0, 16);
|
||||
|
||||
print("Creating server socket manager on dest "+dest64Abbrev+"...");
|
||||
|
||||
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
|
||||
|
||||
serverSocket = socketManager.getServerSocket();
|
||||
|
||||
print("Server socket created, ready to run...");
|
||||
}
|
||||
|
||||
print("Awaiting client connection...");
|
||||
|
||||
I2PSocket sessSocket;
|
||||
|
||||
try {
|
||||
sessSocket = serverSocket.accept();
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (ConnectException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
print("Got connection from client");
|
||||
|
||||
chat(sessSocket);
|
||||
|
||||
}
|
||||
|
||||
public void runClient(String destStr)
|
||||
throws DataFormatException, IOException
|
||||
{
|
||||
runClient(destStr, defaultHost, defaultPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* runs I2PCat in client mode, connecting to a remote
|
||||
* destination then copying data between the remote peer and
|
||||
* the local terminal console
|
||||
*/
|
||||
public void runClient(String destStr, String host, int port)
|
||||
throws DataFormatException, IOException
|
||||
{
|
||||
// accept 'file:' prefix
|
||||
if (destStr.startsWith("file:", 0))
|
||||
{
|
||||
String path = destStr.substring(5);
|
||||
destStr = new SimpleFile(path, "r").read();
|
||||
}
|
||||
|
||||
else if (destStr.length() < 255) {
|
||||
// attempt hosts file lookup
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
HostsTxtNamingService h = new HostsTxtNamingService(ctx);
|
||||
Destination dest1 = h.lookup(destStr);
|
||||
if (dest1 == null) {
|
||||
usage("Cannot resolve hostname: '"+destStr+"'");
|
||||
}
|
||||
|
||||
// successful lookup
|
||||
runClient(dest1, host, port);
|
||||
}
|
||||
|
||||
else {
|
||||
// otherwise, bigger strings are assumed to be base64 dests
|
||||
|
||||
Destination dest = new Destination();
|
||||
dest.fromBase64(destStr);
|
||||
runClient(dest, host, port);
|
||||
}
|
||||
}
|
||||
|
||||
public void runClient(Destination dest) {
|
||||
runClient(dest, "127.0.0.1", 7654);
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative constructor which accepts an I2P Destination object
|
||||
*/
|
||||
public void runClient(Destination dest, String host, int port)
|
||||
{
|
||||
this.dest = dest;
|
||||
|
||||
String destAbbrev = dest.toBase64().substring(0, 16)+"...";
|
||||
|
||||
print("Connecting via i2cp "+host+":"+port+" to destination "+destAbbrev+"...");
|
||||
System.out.flush();
|
||||
|
||||
try {
|
||||
// get a socket manager
|
||||
socketManager = I2PSocketManagerFactory.createManager(host, port);
|
||||
|
||||
// get a client socket
|
||||
print("socketManager="+socketManager);
|
||||
|
||||
sessSocket = socketManager.connect(dest);
|
||||
|
||||
} catch (I2PException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (ConnectException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (NoRouteToHostException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (InterruptedIOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
print("Successfully connected!");
|
||||
print("(Press Control-C to quit)");
|
||||
|
||||
// Perform console interaction
|
||||
chat(sessSocket);
|
||||
|
||||
try {
|
||||
sessSocket.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the background thread to copy incoming data to stdout, then
|
||||
* loop in foreground copying lines from stdin and sending them to remote peer
|
||||
*/
|
||||
public void chat(I2PSocket sessSocket) {
|
||||
|
||||
try {
|
||||
socketIn = sessSocket.getInputStream();
|
||||
socketOutStream = sessSocket.getOutputStream();
|
||||
socketOut = new OutputStreamWriter(socketOutStream);
|
||||
|
||||
// launch receiver thread
|
||||
start();
|
||||
//launchRx();
|
||||
|
||||
while (true) {
|
||||
|
||||
String line = DataHelper.readLine(System.in);
|
||||
print("sent: '"+line+"'");
|
||||
|
||||
socketOut.write(line+"\n");
|
||||
socketOut.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* executes in a thread, receiving incoming bytes on
|
||||
* the socket input stream and spitting them to stdout
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
|
||||
byte [] ch = new byte[1];
|
||||
|
||||
print("Receiver thread listening...");
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
|
||||
//String line = DataHelper.readLine(socketIn);
|
||||
if (socketIn.read(ch) != 1) {
|
||||
print("failed to receive from socket");
|
||||
break;
|
||||
}
|
||||
|
||||
//System.out.println(line);
|
||||
System.out.write(ch, 0, 1);
|
||||
System.out.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
print("Receiver thread crashed, terminating!!");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void launchRx() {
|
||||
|
||||
rxThread = new SockInput(socketIn);
|
||||
rxThread.start();
|
||||
|
||||
}
|
||||
|
||||
static void print(String msg)
|
||||
{
|
||||
System.out.println("-=- I2PCat: "+msg);
|
||||
|
||||
if (_log != null) {
|
||||
_log.debug(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void usage(String msg)
|
||||
{
|
||||
usage(msg, 1);
|
||||
}
|
||||
|
||||
public static void usage(String msg, int ret)
|
||||
{
|
||||
System.out.println(msg);
|
||||
usage(ret);
|
||||
}
|
||||
|
||||
public static void usage(int ret)
|
||||
{
|
||||
System.out.print(
|
||||
"This utility is an I2P equivalent of the standard *nix 'netcat' utility\n"+
|
||||
"usage:\n"+
|
||||
" net.i2p.aum.I2PCat [-h]\n"+
|
||||
" - display this help\n"+
|
||||
" net.i2p.aum.I2PCat dest [host [port]]\n"+
|
||||
" - run in client mode, 'dest' should be one of:\n"+
|
||||
" hostname.i2p - an I2P hostname listed in hosts.txt\n"+
|
||||
" (only works with a hosts.txt in current directory)\n"+
|
||||
" base64dest - a full base64 destination string\n"+
|
||||
" file:b64filename - filename of a file containing base64 dest\n"+
|
||||
" net.i2p.aum.I2PCat -l privkey\n"+
|
||||
" - run in server mode, 'key' should be one of:\n"+
|
||||
" base64privkey - a full base64 private key string\n"+
|
||||
" file:b64filename - filename of a file containing base64 privkey\n"+
|
||||
"\n"
|
||||
);
|
||||
System.exit(ret);
|
||||
}
|
||||
|
||||
public static void main(String [] args) throws IOException, DataFormatException
|
||||
{
|
||||
int argc = args.length;
|
||||
|
||||
// barf if no args
|
||||
if (argc == 0) {
|
||||
usage("Missing argument");
|
||||
}
|
||||
|
||||
// show help on request
|
||||
if (args[0].equals("-h") || args[0].equals("--help")) {
|
||||
usage(0);
|
||||
}
|
||||
|
||||
// server or client?
|
||||
if (args[0].equals("-l")) {
|
||||
if (argc != 2) {
|
||||
usage("Bad argument count");
|
||||
}
|
||||
|
||||
new I2PCat().runServer(args[1]);
|
||||
}
|
||||
else {
|
||||
// client mode - barf if not 1-3 args
|
||||
if (argc < 1 || argc > 3) {
|
||||
usage("Bad argument count");
|
||||
}
|
||||
|
||||
try {
|
||||
int port = defaultPort;
|
||||
String host = defaultHost;
|
||||
if (args.length > 1) {
|
||||
host = args[1];
|
||||
if (args.length > 2) {
|
||||
port = new Integer(args[2]).intValue();
|
||||
}
|
||||
}
|
||||
new I2PCat().runClient(args[0], host, port);
|
||||
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
26
apps/q/java/src/net/i2p/aum/I2PSocketHelper.java
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
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.*;
|
||||
import net.i2p.i2ptunnel.*;
|
||||
|
||||
/**
|
||||
* Class which wraps an I2PSocket object with convenient methods.
|
||||
* Nothing presently implemented here.
|
||||
*/
|
||||
|
||||
public class I2PSocketHelper
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
147
apps/q/java/src/net/i2p/aum/I2PTunnelXMLObject.java
Normal file
@@ -0,0 +1,147 @@
|
||||
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.*;
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
72
apps/q/java/src/net/i2p/aum/I2PTunnelXMLServer.java
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
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.*;
|
||||
import net.i2p.i2ptunnel.*;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
72
apps/q/java/src/net/i2p/aum/I2PXmlRpcClient.java
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
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.*;
|
||||
import net.i2p.i2ptunnel.*;
|
||||
|
||||
|
||||
/**
|
||||
* 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");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
230
apps/q/java/src/net/i2p/aum/I2PXmlRpcClientFactory.java
Normal file
@@ -0,0 +1,230 @@
|
||||
|
||||
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.*;
|
||||
import net.i2p.i2ptunnel.*;
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
35
apps/q/java/src/net/i2p/aum/I2PXmlRpcDemoClass.java
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
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.*;
|
||||
import net.i2p.i2ptunnel.*;
|
||||
|
||||
/**
|
||||
* 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+"'";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
429
apps/q/java/src/net/i2p/aum/I2PXmlRpcServer.java
Normal file
@@ -0,0 +1,429 @@
|
||||
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.*;
|
||||
import net.i2p.i2ptunnel.*;
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
**/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
163
apps/q/java/src/net/i2p/aum/I2PXmlRpcServerFactory.java
Normal file
@@ -0,0 +1,163 @@
|
||||
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.*;
|
||||
import net.i2p.i2ptunnel.*;
|
||||
|
||||
|
||||
/**
|
||||
* Generates I2P-compatible XML-RPC server objects
|
||||
* (of class I2PXmlRpcServer). If you instead want to create
|
||||
* instances of your own
|
||||
*
|
||||
* Invoke this class from your shell to see a demo
|
||||
* @author aum
|
||||
*/
|
||||
public class I2PXmlRpcServerFactory
|
||||
{
|
||||
public I2PSocketManager socketManager;
|
||||
|
||||
public Properties props;
|
||||
|
||||
public static int defaultTunnelLength = 2;
|
||||
|
||||
// set to enable debugging msgs
|
||||
public static boolean debug = false;
|
||||
|
||||
public static Log log;
|
||||
protected I2PAppContext i2p;
|
||||
|
||||
// hostname/port of I2P router we're using
|
||||
//public static String i2cpHost = "127.0.0.1";
|
||||
//public static int i2cpPort = 7654;
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC server factory using default
|
||||
* tunnel settings
|
||||
*/
|
||||
public I2PXmlRpcServerFactory(I2PAppContext i2p)
|
||||
{
|
||||
// get a socket manager
|
||||
this(defaultTunnelLength, defaultTunnelLength,
|
||||
defaultTunnelLength, defaultTunnelLength, i2p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an I2P XML-RPC server factory, using settings provided
|
||||
* by arguments
|
||||
* @param lengthIn The value of 'inbound.length' property
|
||||
* @param lengthOut The value of 'outbound.length' property
|
||||
* @param lengthVarianceIn Value of 'inbound.lengthVariance' property
|
||||
* @param lengthVarianceOut Value of 'outbound.lengthVariance' property
|
||||
* @param log an I2P logger
|
||||
*/
|
||||
public I2PXmlRpcServerFactory(int lengthIn, int lengthOut,
|
||||
int lengthVarianceIn, int lengthVarianceOut,
|
||||
I2PAppContext i2p)
|
||||
{
|
||||
this.i2p = i2p;
|
||||
log = i2p.logManager().getLog(this.getClass());
|
||||
|
||||
// set up tunnel properties for server objects
|
||||
props = new Properties();
|
||||
props.setProperty("inbound.length", String.valueOf(lengthIn));
|
||||
props.setProperty("outbound.length", String.valueOf(lengthOut));
|
||||
props.setProperty("inbound.lengthVariance", String.valueOf(lengthVarianceIn));
|
||||
props.setProperty("outbound.lengthVariance", String.valueOf(lengthVarianceIn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new I2PXmlRpcServer listening on a new randomly created destination
|
||||
* @return a new I2PXmlRpcServer object, whose '.addHandler()' method you should
|
||||
* invoke to add a handler object.
|
||||
* @throws I2PException, IOException, DataFormatException
|
||||
*/
|
||||
public I2PXmlRpcServer newServer() throws I2PException, IOException, DataFormatException
|
||||
{
|
||||
return newServer(PrivDestination.newKey());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new I2PXmlRpcServer listening on a given dest, from key provided
|
||||
* as a base64 string
|
||||
* @param keyStr base64 representation of full private key for the destination
|
||||
* the server is to listen on
|
||||
* @return a new I2PXmlRpcServer object
|
||||
* @throws DataFormatException
|
||||
*/
|
||||
public I2PXmlRpcServer newServer(String keyStr)
|
||||
throws DataFormatException, I2PException, IOException
|
||||
{
|
||||
return newServer(PrivDestination.fromBase64String(keyStr));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new I2PXmlRpcServer listening on a given dest, from key provided
|
||||
* as a PrivDestination object
|
||||
* @param key a PrivDestination object representing the private destination
|
||||
* the server is to listen on
|
||||
* @return a new I2PXmlRpcServer object
|
||||
* @throws DataFormatException
|
||||
*/
|
||||
public I2PXmlRpcServer newServer(PrivDestination key) throws DataFormatException, I2PException
|
||||
{
|
||||
// main newServer
|
||||
I2PXmlRpcServer server = new I2PXmlRpcServer(key, props, i2p);
|
||||
server.debug = debug;
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstration of I2P XML-RPC server.
|
||||
* Creates a server listening on a random destination, and writes the base64
|
||||
* destination into a file called "demo.dest64".
|
||||
*
|
||||
* After launching this program from a command shell, you should
|
||||
* launch I2PXmlRpcClientFactory from another command shell
|
||||
* to execute the client side of the demo.
|
||||
*
|
||||
* This program accepts no arguments.
|
||||
*/
|
||||
public static void main(String [] args)
|
||||
{
|
||||
debug = true;
|
||||
I2PXmlRpcServer.debug = true;
|
||||
|
||||
I2PAppContext i2p = new I2PAppContext();
|
||||
|
||||
I2PXmlRpcServerFactory f = new I2PXmlRpcServerFactory(0,0,0,0, i2p);
|
||||
|
||||
try {
|
||||
|
||||
f.log.info("Creating new server on a new key");
|
||||
I2PXmlRpcServer s = f.newServer();
|
||||
|
||||
f.log.info("Creating and adding handler object");
|
||||
I2PXmlRpcDemoClass demo = new I2PXmlRpcDemoClass();
|
||||
s.addHandler("foo", demo);
|
||||
|
||||
f.log.info("Saving dest for this server in file 'demo.dest64'");
|
||||
new SimpleFile("demo.dest64", "rws").write(s.dest.toBase64());
|
||||
|
||||
f.log.info("Running server (Press Ctrl-C to kill)");
|
||||
s.run();
|
||||
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
392
apps/q/java/src/net/i2p/aum/Mimetypes.java
Normal file
@@ -0,0 +1,392 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
/**
|
||||
* creates a convenient map of file extensions <-> mimetypes
|
||||
*/
|
||||
|
||||
public class Mimetypes
|
||||
{
|
||||
public static String [][] _map = {
|
||||
|
||||
{ ".bz2", "application/x-bzip2" },
|
||||
{ ".csm", "application/cu-seeme" },
|
||||
{ ".cu", "application/cu-seeme" },
|
||||
{ ".tsp", "application/dsptype" },
|
||||
{ ".xls", "application/excel" },
|
||||
{ ".spl", "application/futuresplash" },
|
||||
{ ".hqx", "application/mac-binhex40" },
|
||||
{ ".doc", "application/msword" },
|
||||
{ ".dot", "application/msword" },
|
||||
{ ".bin", "application/octet-stream" },
|
||||
{ ".oda", "application/oda" },
|
||||
{ ".pdf", "application/pdf" },
|
||||
{ ".asc", "application/pgp-keys" },
|
||||
{ ".pgp", "application/pgp-signature" },
|
||||
{ ".ps", "application/postscript" },
|
||||
{ ".ai", "application/postscript" },
|
||||
{ ".eps", "application/postscript" },
|
||||
{ ".ppt", "application/powerpoint" },
|
||||
{ ".rtf", "application/rtf" },
|
||||
{ ".wp5", "application/wordperfect5.1" },
|
||||
{ ".zip", "application/zip" },
|
||||
{ ".wk", "application/x-123" },
|
||||
{ ".bcpio", "application/x-bcpio" },
|
||||
{ ".pgn", "application/x-chess-pgn" },
|
||||
{ ".cpio", "application/x-cpio" },
|
||||
{ ".deb", "application/x-debian-package" },
|
||||
{ ".dcr", "application/x-director" },
|
||||
{ ".dir", "application/x-director" },
|
||||
{ ".dxr", "application/x-director" },
|
||||
{ ".dvi", "application/x-dvi" },
|
||||
{ ".pfa", "application/x-font" },
|
||||
{ ".pfb", "application/x-font" },
|
||||
{ ".gsf", "application/x-font" },
|
||||
{ ".pcf", "application/x-font" },
|
||||
{ ".pcf.Z", "application/x-font" },
|
||||
{ ".gtar", "application/x-gtar" },
|
||||
{ ".tgz", "application/x-gtar" },
|
||||
{ ".hdf", "application/x-hdf" },
|
||||
{ ".phtml", "application/x-httpd-php" },
|
||||
{ ".pht", "application/x-httpd-php" },
|
||||
{ ".php", "application/x-httpd-php" },
|
||||
{ ".php3", "application/x-httpd-php3" },
|
||||
{ ".phps", "application/x-httpd-php3-source" },
|
||||
{ ".php3p", "application/x-httpd-php3-preprocessed" },
|
||||
{ ".class", "application/x-java" },
|
||||
{ ".latex", "application/x-latex" },
|
||||
{ ".frm", "application/x-maker" },
|
||||
{ ".maker", "application/x-maker" },
|
||||
{ ".frame", "application/x-maker" },
|
||||
{ ".fm", "application/x-maker" },
|
||||
{ ".fb", "application/x-maker" },
|
||||
{ ".book", "application/x-maker" },
|
||||
{ ".fbdoc", "application/x-maker" },
|
||||
{ ".mif", "application/x-mif" },
|
||||
{ ".nc", "application/x-netcdf" },
|
||||
{ ".cdf", "application/x-netcdf" },
|
||||
{ ".pac", "application/x-ns-proxy-autoconfig" },
|
||||
{ ".o", "application/x-object" },
|
||||
{ ".pl", "application/x-perl" },
|
||||
{ ".pm", "application/x-perl" },
|
||||
{ ".shar", "application/x-shar" },
|
||||
{ ".swf", "application/x-shockwave-flash" },
|
||||
{ ".swfl", "application/x-shockwave-flash" },
|
||||
{ ".sit", "application/x-stuffit" },
|
||||
{ ".sv4cpio", "application/x-sv4cpio" },
|
||||
{ ".sv4crc", "application/x-sv4crc" },
|
||||
{ ".tar", "application/x-tar" },
|
||||
{ ".gf", "application/x-tex-gf" },
|
||||
{ ".pk", "application/x-tex-pk" },
|
||||
{ ".PK", "application/x-tex-pk" },
|
||||
{ ".texinfo", "application/x-texinfo" },
|
||||
{ ".texi", "application/x-texinfo" },
|
||||
{ ".~", "application/x-trash" },
|
||||
{ ".%", "application/x-trash" },
|
||||
{ ".bak", "application/x-trash" },
|
||||
{ ".old", "application/x-trash" },
|
||||
{ ".sik", "application/x-trash" },
|
||||
{ ".t", "application/x-troff" },
|
||||
{ ".tr", "application/x-troff" },
|
||||
{ ".roff", "application/x-troff" },
|
||||
{ ".man", "application/x-troff-man" },
|
||||
{ ".me", "application/x-troff-me" },
|
||||
{ ".ms", "application/x-troff-ms" },
|
||||
{ ".ustar", "application/x-ustar" },
|
||||
{ ".src", "application/x-wais-source" },
|
||||
{ ".wz", "application/x-wingz" },
|
||||
{ ".au", "audio/basic" },
|
||||
{ ".snd", "audio/basic" },
|
||||
{ ".mid", "audio/midi" },
|
||||
{ ".midi", "audio/midi" },
|
||||
{ ".mpga", "audio/mpeg" },
|
||||
{ ".mpega", "audio/mpeg" },
|
||||
{ ".mp2", "audio/mpeg" },
|
||||
{ ".mp3", "audio/mpeg" },
|
||||
{ ".m3u", "audio/mpegurl" },
|
||||
{ ".aif", "audio/x-aiff" },
|
||||
{ ".aiff", "audio/x-aiff" },
|
||||
{ ".aifc", "audio/x-aiff" },
|
||||
{ ".gsm", "audio/x-gsm" },
|
||||
{ ".ra", "audio/x-pn-realaudio" },
|
||||
{ ".rm", "audio/x-pn-realaudio" },
|
||||
{ ".ram", "audio/x-pn-realaudio" },
|
||||
{ ".rpm", "audio/x-pn-realaudio-plugin" },
|
||||
{ ".wav", "audio/x-wav" },
|
||||
{ ".gif", "image/gif" },
|
||||
{ ".ief", "image/ief" },
|
||||
{ ".jpeg", "image/jpeg" },
|
||||
{ ".jpg", "image/jpeg" },
|
||||
{ ".jpe", "image/jpeg" },
|
||||
{ ".png", "image/png" },
|
||||
{ ".tiff", "image/tiff" },
|
||||
{ ".tif", "image/tiff" },
|
||||
{ ".ras", "image/x-cmu-raster" },
|
||||
{ ".bmp", "image/x-ms-bmp" },
|
||||
{ ".pnm", "image/x-portable-anymap" },
|
||||
{ ".pbm", "image/x-portable-bitmap" },
|
||||
{ ".pgm", "image/x-portable-graymap" },
|
||||
{ ".ppm", "image/x-portable-pixmap" },
|
||||
{ ".rgb", "image/x-rgb" },
|
||||
{ ".xbm", "image/x-xbitmap" },
|
||||
{ ".xpm", "image/x-xpixmap" },
|
||||
{ ".xwd", "image/x-xwindowdump" },
|
||||
{ ".csv", "text/comma-separated-values" },
|
||||
{ ".html", "text/html" },
|
||||
{ ".htm", "text/html" },
|
||||
{ ".mml", "text/mathml" },
|
||||
{ ".txt", "text/plain" },
|
||||
{ ".rtx", "text/richtext" },
|
||||
{ ".tsv", "text/tab-separated-values" },
|
||||
{ ".h++", "text/x-c++hdr" },
|
||||
{ ".hpp", "text/x-c++hdr" },
|
||||
{ ".hxx", "text/x-c++hdr" },
|
||||
{ ".hh", "text/x-c++hdr" },
|
||||
{ ".c++", "text/x-c++src" },
|
||||
{ ".cpp", "text/x-c++src" },
|
||||
{ ".cxx", "text/x-c++src" },
|
||||
{ ".cc", "text/x-c++src" },
|
||||
{ ".h", "text/x-chdr" },
|
||||
{ ".csh", "text/x-csh" },
|
||||
{ ".c", "text/x-csrc" },
|
||||
{ ".java", "text/x-java" },
|
||||
{ ".moc", "text/x-moc" },
|
||||
{ ".p", "text/x-pascal" },
|
||||
{ ".pas", "text/x-pascal" },
|
||||
{ ".etx", "text/x-setext" },
|
||||
{ ".sh", "text/x-sh" },
|
||||
{ ".tcl", "text/x-tcl" },
|
||||
{ ".tk", "text/x-tcl" },
|
||||
{ ".tex", "text/x-tex" },
|
||||
{ ".ltx", "text/x-tex" },
|
||||
{ ".sty", "text/x-tex" },
|
||||
{ ".cls", "text/x-tex" },
|
||||
{ ".vcs", "text/x-vCalendar" },
|
||||
{ ".vcf", "text/x-vCard" },
|
||||
{ ".dl", "video/dl" },
|
||||
{ ".fli", "video/fli" },
|
||||
{ ".gl", "video/gl" },
|
||||
{ ".mpeg", "video/mpeg" },
|
||||
{ ".mpg", "video/mpeg" },
|
||||
{ ".mpe", "video/mpeg" },
|
||||
{ ".qt", "video/quicktime" },
|
||||
{ ".mov", "video/quicktime" },
|
||||
{ ".asf", "video/x-ms-asf" },
|
||||
{ ".asx", "video/x-ms-asf" },
|
||||
{ ".avi", "video/x-msvideo" },
|
||||
{ ".movie", "video/x-sgi-movie" },
|
||||
{ ".vrm", "x-world/x-vrml" },
|
||||
{ ".vrml", "x-world/x-vrml" },
|
||||
{ ".wrl", "x-world/x-vrml" },
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempts to determine a mimetype
|
||||
* @param path - either a file extension string (containing the
|
||||
* leading '.') or a full file pathname (in which case, the extension
|
||||
* will be extracted).
|
||||
* @return the mimetype that corresponds to the file extension, if the
|
||||
* file extension is known, or "application/octet-stream" if the
|
||||
* file extension is not known.
|
||||
*/
|
||||
public static String guessType(String path) {
|
||||
// rip the file extension from the path
|
||||
// first - split 'directories', and get last part
|
||||
String [] dirs = path.split("/");
|
||||
String filename = dirs[dirs.length-1];
|
||||
String [] bits = filename.split("\\.");
|
||||
String extension = "." + bits[bits.length-1];
|
||||
|
||||
// default mimetype applied to unknown file extensions
|
||||
String type = "application/octet-stream";
|
||||
|
||||
for (int i=0; i<_map.length; i++) {
|
||||
String [] rec = _map[i];
|
||||
if (rec[0].equals(extension)) {
|
||||
type = rec[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to guess the file extension corresponding to a given
|
||||
* mimetype.
|
||||
* @param type a mimetype string
|
||||
* @return a file extension commonly used for storing files of this type,
|
||||
* or defaults to ".bin" if mimetype not known
|
||||
*/
|
||||
public static String guessExtension(String type) {
|
||||
// default extension applied to unknown mimetype
|
||||
String extension = ".bin";
|
||||
for (int i=0; i<_map.length; i++) {
|
||||
String [] rec = _map[i];
|
||||
if (rec[1].equals(type)) {
|
||||
extension = rec[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
suffix_map = {
|
||||
'.tgz': '.tar.gz',
|
||||
'.taz': '.tar.gz',
|
||||
'.tz': '.tar.gz',
|
||||
}
|
||||
|
||||
encodings_map = {
|
||||
'.gz': 'gzip',
|
||||
'.Z': 'compress',
|
||||
}
|
||||
|
||||
# Before adding new types, make sure they are either registered with IANA, at
|
||||
# http://www.isi.edu/in-notes/iana/assignments/media-types
|
||||
# or extensions, i.e. using the x- prefix
|
||||
|
||||
# If you add to these, please keep them sorted!
|
||||
types_map = {
|
||||
'.a' : 'application/octet-stream',
|
||||
'.ai' : 'application/postscript',
|
||||
'.aif' : 'audio/x-aiff',
|
||||
'.aifc' : 'audio/x-aiff',
|
||||
'.aiff' : 'audio/x-aiff',
|
||||
'.au' : 'audio/basic',
|
||||
'.avi' : 'video/x-msvideo',
|
||||
'.bat' : 'text/plain',
|
||||
'.bcpio' : 'application/x-bcpio',
|
||||
'.bin' : 'application/octet-stream',
|
||||
'.bmp' : 'image/x-ms-bmp',
|
||||
'.c' : 'text/plain',
|
||||
# Duplicates :(
|
||||
'.cdf' : 'application/x-cdf',
|
||||
'.cdf' : 'application/x-netcdf',
|
||||
'.cpio' : 'application/x-cpio',
|
||||
'.csh' : 'application/x-csh',
|
||||
'.css' : 'text/css',
|
||||
'.dll' : 'application/octet-stream',
|
||||
'.doc' : 'application/msword',
|
||||
'.dot' : 'application/msword',
|
||||
'.dvi' : 'application/x-dvi',
|
||||
'.eml' : 'message/rfc822',
|
||||
'.eps' : 'application/postscript',
|
||||
'.etx' : 'text/x-setext',
|
||||
'.exe' : 'application/octet-stream',
|
||||
'.gif' : 'image/gif',
|
||||
'.gtar' : 'application/x-gtar',
|
||||
'.h' : 'text/plain',
|
||||
'.hdf' : 'application/x-hdf',
|
||||
'.htm' : 'text/html',
|
||||
'.html' : 'text/html',
|
||||
'.ief' : 'image/ief',
|
||||
'.jpe' : 'image/jpeg',
|
||||
'.jpeg' : 'image/jpeg',
|
||||
'.jpg' : 'image/jpeg',
|
||||
'.js' : 'application/x-javascript',
|
||||
'.ksh' : 'text/plain',
|
||||
'.latex' : 'application/x-latex',
|
||||
'.m1v' : 'video/mpeg',
|
||||
'.man' : 'application/x-troff-man',
|
||||
'.me' : 'application/x-troff-me',
|
||||
'.mht' : 'message/rfc822',
|
||||
'.mhtml' : 'message/rfc822',
|
||||
'.mif' : 'application/x-mif',
|
||||
'.mov' : 'video/quicktime',
|
||||
'.movie' : 'video/x-sgi-movie',
|
||||
'.mp2' : 'audio/mpeg',
|
||||
'.mp3' : 'audio/mpeg',
|
||||
'.mpa' : 'video/mpeg',
|
||||
'.mpe' : 'video/mpeg',
|
||||
'.mpeg' : 'video/mpeg',
|
||||
'.mpg' : 'video/mpeg',
|
||||
'.ms' : 'application/x-troff-ms',
|
||||
'.nc' : 'application/x-netcdf',
|
||||
'.nws' : 'message/rfc822',
|
||||
'.o' : 'application/octet-stream',
|
||||
'.obj' : 'application/octet-stream',
|
||||
'.oda' : 'application/oda',
|
||||
'.p12' : 'application/x-pkcs12',
|
||||
'.p7c' : 'application/pkcs7-mime',
|
||||
'.pbm' : 'image/x-portable-bitmap',
|
||||
'.pdf' : 'application/pdf',
|
||||
'.pfx' : 'application/x-pkcs12',
|
||||
'.pgm' : 'image/x-portable-graymap',
|
||||
'.pl' : 'text/plain',
|
||||
'.png' : 'image/png',
|
||||
'.pnm' : 'image/x-portable-anymap',
|
||||
'.pot' : 'application/vnd.ms-powerpoint',
|
||||
'.ppa' : 'application/vnd.ms-powerpoint',
|
||||
'.ppm' : 'image/x-portable-pixmap',
|
||||
'.pps' : 'application/vnd.ms-powerpoint',
|
||||
'.ppt' : 'application/vnd.ms-powerpoint',
|
||||
'.ps' : 'application/postscript',
|
||||
'.pwz' : 'application/vnd.ms-powerpoint',
|
||||
'.py' : 'text/x-python',
|
||||
'.pyc' : 'application/x-python-code',
|
||||
'.pyo' : 'application/x-python-code',
|
||||
'.qt' : 'video/quicktime',
|
||||
'.ra' : 'audio/x-pn-realaudio',
|
||||
'.ram' : 'application/x-pn-realaudio',
|
||||
'.ras' : 'image/x-cmu-raster',
|
||||
'.rdf' : 'application/xml',
|
||||
'.rgb' : 'image/x-rgb',
|
||||
'.roff' : 'application/x-troff',
|
||||
'.rtx' : 'text/richtext',
|
||||
'.sgm' : 'text/x-sgml',
|
||||
'.sgml' : 'text/x-sgml',
|
||||
'.sh' : 'application/x-sh',
|
||||
'.shar' : 'application/x-shar',
|
||||
'.snd' : 'audio/basic',
|
||||
'.so' : 'application/octet-stream',
|
||||
'.src' : 'application/x-wais-source',
|
||||
'.sv4cpio': 'application/x-sv4cpio',
|
||||
'.sv4crc' : 'application/x-sv4crc',
|
||||
'.swf' : 'application/x-shockwave-flash',
|
||||
'.t' : 'application/x-troff',
|
||||
'.tar' : 'application/x-tar',
|
||||
'.tcl' : 'application/x-tcl',
|
||||
'.tex' : 'application/x-tex',
|
||||
'.texi' : 'application/x-texinfo',
|
||||
'.texinfo': 'application/x-texinfo',
|
||||
'.tif' : 'image/tiff',
|
||||
'.tiff' : 'image/tiff',
|
||||
'.tr' : 'application/x-troff',
|
||||
'.tsv' : 'text/tab-separated-values',
|
||||
'.txt' : 'text/plain',
|
||||
'.ustar' : 'application/x-ustar',
|
||||
'.vcf' : 'text/x-vcard',
|
||||
'.wav' : 'audio/x-wav',
|
||||
'.wiz' : 'application/msword',
|
||||
'.xbm' : 'image/x-xbitmap',
|
||||
'.xlb' : 'application/vnd.ms-excel',
|
||||
# Duplicates :(
|
||||
'.xls' : 'application/excel',
|
||||
'.xls' : 'application/vnd.ms-excel',
|
||||
'.xml' : 'text/xml',
|
||||
'.xpm' : 'image/x-xpixmap',
|
||||
'.xsl' : 'application/xml',
|
||||
'.xwd' : 'image/x-xwindowdump',
|
||||
'.zip' : 'application/zip',
|
||||
}
|
||||
|
||||
# These are non-standard types, commonly found in the wild. They will only
|
||||
# match if strict=0 flag is given to the API methods.
|
||||
|
||||
# Please sort these too
|
||||
common_types = {
|
||||
'.jpg' : 'image/jpg',
|
||||
'.mid' : 'audio/midi',
|
||||
'.midi': 'audio/midi',
|
||||
'.pct' : 'image/pict',
|
||||
'.pic' : 'image/pict',
|
||||
'.pict': 'image/pict',
|
||||
'.rtf' : 'application/rtf',
|
||||
'.xul' : 'text/xul'
|
||||
}
|
||||
**/
|
||||
|
||||
18
apps/q/java/src/net/i2p/aum/OOTest.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
|
||||
public class OOTest
|
||||
{
|
||||
public int add(int a, int b)
|
||||
{
|
||||
return (a + b);
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
OOTest mytest = new OOTest();
|
||||
System.out.println(mytest.add(3,3));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
234
apps/q/java/src/net/i2p/aum/PrivDestination.java
Normal file
@@ -0,0 +1,234 @@
|
||||
|
||||
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.*;
|
||||
import net.i2p.i2ptunnel.*;
|
||||
|
||||
|
||||
/**
|
||||
* A convenience class for encapsulating and manipulating I2P private keys
|
||||
*/
|
||||
|
||||
public class PrivDestination
|
||||
//extends ByteArrayInputStream
|
||||
extends DataStructureImpl
|
||||
{
|
||||
protected byte [] _bytes;
|
||||
|
||||
protected Destination _dest;
|
||||
protected PrivateKey _privKey;
|
||||
protected SigningPrivateKey _signingPrivKey;
|
||||
|
||||
protected static Log _log;
|
||||
|
||||
/**
|
||||
* Create a PrivDestination object.
|
||||
* In most cases, you'll probably want to skip this constructor,
|
||||
* and create PrivDestination objects by invoking the desired static methods
|
||||
* of this class.
|
||||
* @param raw an array of bytes containing the raw binary private key
|
||||
*/
|
||||
public PrivDestination(byte [] raw) throws DataFormatException, IOException
|
||||
{
|
||||
//super(raw);
|
||||
_log = new Log("PrivDestination");
|
||||
|
||||
_bytes = raw;
|
||||
readBytes(getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* reconstitutes a PrivDestination from previously exported Base64
|
||||
*/
|
||||
public PrivDestination(String b64) throws DataFormatException, IOException {
|
||||
this(Base64.decode(b64));
|
||||
}
|
||||
|
||||
/**
|
||||
* generates a new PrivDestination with random keys
|
||||
*/
|
||||
public PrivDestination() throws I2PException, IOException
|
||||
{
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
|
||||
ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
|
||||
|
||||
// create a dest
|
||||
client.createDestination(streamOut);
|
||||
|
||||
_bytes = streamOut.toByteArray();
|
||||
readBytes(getInputStream());
|
||||
|
||||
// construct from the stream
|
||||
//return new PrivDestination(streamOut.toByteArray());
|
||||
}
|
||||
|
||||
/** return the public Destination object for this private dest */
|
||||
public Destination getDestination() {
|
||||
return _dest;
|
||||
}
|
||||
|
||||
/** return a PublicKey (encryption public key) object for this priv dest */
|
||||
public PublicKey getPublicKey() {
|
||||
return getDestination().getPublicKey();
|
||||
}
|
||||
|
||||
/** return a PrivateKey (encryption private key) object for this priv dest */
|
||||
public PrivateKey getPrivateKey() {
|
||||
return _privKey;
|
||||
}
|
||||
|
||||
/** return a SigningPublicKey object for this priv dest */
|
||||
public SigningPublicKey getSigningPublicKey() {
|
||||
return getDestination().getSigningPublicKey();
|
||||
}
|
||||
|
||||
/** return a SigningPrivateKey object for this priv dest */
|
||||
public SigningPrivateKey getSigningPrivateKey() {
|
||||
return _signingPrivKey;
|
||||
}
|
||||
|
||||
// static methods returning an instance
|
||||
|
||||
/**
|
||||
* Creates a PrivDestination object
|
||||
* @param base64 a string containing the base64 private key data
|
||||
* @return a PrivDestination object encapsulating that key
|
||||
*/
|
||||
public static PrivDestination fromBase64String(String base64)
|
||||
throws DataFormatException, IOException
|
||||
{
|
||||
return new PrivDestination(Base64.decode(base64));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PrivDestination object, from the base64 key data
|
||||
* stored in a file.
|
||||
* @param path the pathname of the file from which to read the base64 private key data
|
||||
* @return a PrivDestination object encapsulating that key
|
||||
*/
|
||||
public static PrivDestination fromBase64File(String path)
|
||||
throws FileNotFoundException, IOException, DataFormatException
|
||||
{
|
||||
return fromBase64String(new SimpleFile(path, "r").read());
|
||||
/*
|
||||
File f = new File(path);
|
||||
char [] rawchars = new char[(int)(f.length())];
|
||||
byte [] rawbytes = new byte[(int)(f.length())];
|
||||
FileReader fr = new FileReader(f);
|
||||
fr.read(rawchars);
|
||||
String raw64 = new String(rawchars);
|
||||
return PrivDestination.fromBase64String(raw64);
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PrivDestination object, from the binary key data
|
||||
* stored in a file.
|
||||
* @param path the pathname of the file from which to read the binary private key data
|
||||
* @return a PrivDestination object encapsulating that key
|
||||
*/
|
||||
public static PrivDestination fromBinFile(String path)
|
||||
throws FileNotFoundException, IOException, DataFormatException
|
||||
{
|
||||
byte [] raw = new SimpleFile(path, "r").readBytes();
|
||||
return new PrivDestination(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new random I2P private key
|
||||
* @return a PrivDestination object encapsulating that key
|
||||
*/
|
||||
public static PrivDestination newKey() throws I2PException, IOException
|
||||
{
|
||||
return new PrivDestination();
|
||||
}
|
||||
|
||||
public ByteArrayInputStream getInputStream()
|
||||
{
|
||||
return new ByteArrayInputStream(_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the key's full contents to a string
|
||||
* @return A base64-format string containing the full contents
|
||||
* of this private key. The string can be used in any subsequent
|
||||
* call to the .fromBase64String static constructor method.
|
||||
*/
|
||||
/*
|
||||
public String toBase64()
|
||||
{
|
||||
return Base64.encode(_bytes);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exports the key's full contents to a byte array
|
||||
* @return A byte array containing the full contents
|
||||
* of this private key.
|
||||
*/
|
||||
/*
|
||||
public byte [] toBytes()
|
||||
{
|
||||
return _bytes;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts this key to a public destination.
|
||||
* @return a standard I2P Destination object containing the
|
||||
* public portion of this private key.
|
||||
*/
|
||||
/*
|
||||
public Destination toDestination() throws DataFormatException
|
||||
{
|
||||
Destination dest = new Destination();
|
||||
dest.readBytes(_bytes, 0);
|
||||
return dest;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts this key to a base64 string representing a public destination
|
||||
* @return a string containing a base64 representation of the destination
|
||||
* corresponding to this private key.
|
||||
*/
|
||||
public String getDestinationBase64() throws DataFormatException
|
||||
{
|
||||
return getDestination().toBase64();
|
||||
}
|
||||
|
||||
public void readBytes(java.io.InputStream strm)
|
||||
throws net.i2p.data.DataFormatException, java.io.IOException
|
||||
{
|
||||
_dest = new Destination();
|
||||
_privKey = new PrivateKey();
|
||||
_signingPrivKey = new SigningPrivateKey();
|
||||
|
||||
_dest.readBytes(strm);
|
||||
_privKey.readBytes(strm);
|
||||
_signingPrivKey.readBytes(strm);
|
||||
}
|
||||
|
||||
public void writeBytes(java.io.OutputStream outputStream)
|
||||
throws net.i2p.data.DataFormatException, java.io.IOException
|
||||
{
|
||||
_dest.writeBytes(outputStream);
|
||||
_privKey.writeBytes(outputStream);
|
||||
_signingPrivKey.writeBytes(outputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
209
apps/q/java/src/net/i2p/aum/PropertiesFile.java
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* PropertiesFile.java
|
||||
*
|
||||
* Created on 20 March 2005, 19:30
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* builds on Properties with methods to load/save directly to/from file
|
||||
*/
|
||||
public class PropertiesFile extends Properties {
|
||||
|
||||
public String _path;
|
||||
public File _file;
|
||||
public boolean _fileExists;
|
||||
|
||||
/**
|
||||
* Creates a new instance of PropertiesFile
|
||||
* @param path Absolute pathname of file where properties are to be stored
|
||||
*/
|
||||
public PropertiesFile(String path) throws IOException {
|
||||
super();
|
||||
_path = path;
|
||||
_file = new File(path);
|
||||
_fileExists = _file.isFile();
|
||||
|
||||
if (_file.canRead()) {
|
||||
loadFromFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new PropertiesFile, updating its content with the
|
||||
* keys/values in given hashtable
|
||||
* @param path absolute pathname where properties file is located in filesystem
|
||||
* @param h instance of Hashtable (or subclass). its content
|
||||
* will be written to this object (note that string representations of keys/vals
|
||||
* will be used)
|
||||
*/
|
||||
public PropertiesFile(String path, Hashtable h) throws IOException
|
||||
{
|
||||
this(path);
|
||||
Enumeration keys = h.keys();
|
||||
Object key;
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
key = keys.nextElement();
|
||||
} catch (NoSuchElementException e) {
|
||||
break;
|
||||
}
|
||||
setProperty(key.toString(), h.get(key).toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads this object from the file
|
||||
*/
|
||||
public void loadFromFile() throws IOException, FileNotFoundException {
|
||||
if (_file.canRead()) {
|
||||
InputStream fis = new FileInputStream(_file);
|
||||
load(fis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this object to the file
|
||||
*/
|
||||
public void saveToFile() throws IOException, FileNotFoundException {
|
||||
|
||||
if (!_fileExists) {
|
||||
_file.createNewFile();
|
||||
_fileExists = true;
|
||||
}
|
||||
OutputStream fos = new FileOutputStream(_file);
|
||||
store(fos, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores attribute
|
||||
*/
|
||||
public Object setProperty(String key, String value) {
|
||||
Object o = super.setProperty(key, value);
|
||||
try {
|
||||
saveToFile();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as an int, fall back on default if not found or invalid
|
||||
*/
|
||||
public int getIntProperty(String key, int dflt) {
|
||||
try {
|
||||
return new Integer((String)getProperty(key)).intValue();
|
||||
} catch (Exception e) {
|
||||
setIntProperty(key, dflt);
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as an int
|
||||
*/
|
||||
public int getIntProperty(String key) {
|
||||
return new Integer((String)getProperty(key)).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* set a property as an int
|
||||
*/
|
||||
public void setIntProperty(String key, int value) {
|
||||
setProperty(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a long, fall back on default if not found or invalid
|
||||
*/
|
||||
public long getIntProperty(String key, long dflt) {
|
||||
try {
|
||||
return new Long((String)getProperty(key)).longValue();
|
||||
} catch (Exception e) {
|
||||
setLongProperty(key, dflt);
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as an int
|
||||
*/
|
||||
public long getLongProperty(String key) {
|
||||
return new Long((String)getProperty(key)).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* set a property as an int
|
||||
*/
|
||||
public void setLongProperty(String key, long value) {
|
||||
setProperty(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a float
|
||||
*/
|
||||
public double getFloatProperty(String key) {
|
||||
return new Float((String)getProperty(key)).floatValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a float, fall back on default if not found or invalid
|
||||
*/
|
||||
public double getFloatProperty(String key, float dflt) {
|
||||
try {
|
||||
return new Float((String)getProperty(key)).floatValue();
|
||||
} catch (Exception e) {
|
||||
setFloatProperty(key, dflt);
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set a property as a float
|
||||
*/
|
||||
public void setFloatProperty(String key, float value) {
|
||||
setProperty(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a double
|
||||
*/
|
||||
public double getDoubleProperty(String key) {
|
||||
return new Double((String)getProperty(key)).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* return a property as a double, fall back on default if not found
|
||||
*/
|
||||
public double getDoubleProperty(String key, double dflt) {
|
||||
try {
|
||||
return new Double((String)getProperty(key)).doubleValue();
|
||||
} catch (Exception e) {
|
||||
setDoubleProperty(key, dflt);
|
||||
return dflt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set a property as a double
|
||||
*/
|
||||
public void setDoubleProperty(String key, double value) {
|
||||
setProperty(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* increment an integer property value
|
||||
*/
|
||||
public void incrementIntProperty(String key) {
|
||||
setIntProperty(key, getIntProperty(key)+1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
120
apps/q/java/src/net/i2p/aum/SimpleFile.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* SimpleFile - subclass of File which adds some python-like
|
||||
* methods. Cuts out a lot of the red tape involved with reading
|
||||
* from and writing to files
|
||||
*/
|
||||
public class SimpleFile {
|
||||
|
||||
public RandomAccessFile _file;
|
||||
public String _path;
|
||||
|
||||
public SimpleFile(String path, String mode) throws FileNotFoundException {
|
||||
|
||||
_path = path;
|
||||
_file = new RandomAccessFile(path, mode);
|
||||
}
|
||||
|
||||
public byte [] readBytes() throws IOException {
|
||||
return readBytes((int)_file.length());
|
||||
}
|
||||
|
||||
public byte[] readBytes(int n) throws IOException {
|
||||
byte [] buf = new byte[n];
|
||||
_file.readFully(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
public char [] readChars() throws IOException {
|
||||
return readChars((int)_file.length());
|
||||
}
|
||||
|
||||
public char[] readChars(int n) throws IOException {
|
||||
char [] buf = new char[n];
|
||||
//_file.readFully(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all remaining content from the file
|
||||
* @return the content as a String
|
||||
* @throws IOException
|
||||
*/
|
||||
public String read() throws IOException {
|
||||
|
||||
return read((int)_file.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one or more bytes of data from the file
|
||||
* @return the content as a String
|
||||
* @throws IOException
|
||||
*/
|
||||
public String read(int nbytes) throws IOException {
|
||||
|
||||
return new String(readBytes(nbytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes one or more bytes of data to a file
|
||||
* @param buf a String containing the data to write
|
||||
* @return the number of bytes written, as an int
|
||||
* @throws IOException
|
||||
*/
|
||||
public int write(String buf) throws IOException {
|
||||
|
||||
return write(buf.getBytes());
|
||||
}
|
||||
|
||||
public int write(byte [] buf) throws IOException {
|
||||
|
||||
_file.write(buf);
|
||||
return buf.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* convenient one-hit write
|
||||
* @param path pathname of file to write to
|
||||
* @param buf data to write
|
||||
*/
|
||||
public static int write(String path, String buf) throws IOException {
|
||||
return new SimpleFile(path, "rws").write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if argument refers to an actual file
|
||||
* @param path pathname to test
|
||||
* @return true if a file, false if not
|
||||
*/
|
||||
public boolean isFile() {
|
||||
return new File(_path).isFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if argument refers to a directory
|
||||
* @param path pathname to test
|
||||
* @return true if a directory, false if not
|
||||
*/
|
||||
public boolean isDir() {
|
||||
return new File(_path).isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if a file or directory exists
|
||||
* @param path pathname to test
|
||||
* @return true if exists, or false
|
||||
*/
|
||||
public boolean exists() {
|
||||
return new File(_path).exists();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
123
apps/q/java/src/net/i2p/aum/SimpleFile_old.java
Normal file
@@ -0,0 +1,123 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* SimpleFile - subclass of File which adds some python-like
|
||||
* methods. Cuts out a lot of the red tape involved with reading
|
||||
* from and writing to files
|
||||
*/
|
||||
public class SimpleFile_old extends File {
|
||||
|
||||
public FileReader _reader;
|
||||
public FileWriter _writer;
|
||||
|
||||
public SimpleFile_old(String path) {
|
||||
|
||||
super(path);
|
||||
|
||||
_reader = null;
|
||||
_writer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all remaining content from the file
|
||||
* @return the content as a String
|
||||
* @throws IOException
|
||||
*/
|
||||
public String read() throws IOException {
|
||||
|
||||
return read((int)length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one or more bytes of data from the file
|
||||
* @return the content as a String
|
||||
* @throws IOException
|
||||
*/
|
||||
public String read(int nbytes) throws IOException {
|
||||
|
||||
// get a reader, if we don't already have one
|
||||
if (_reader == null) {
|
||||
_reader = new FileReader(this);
|
||||
}
|
||||
|
||||
char [] cbuf = new char[nbytes];
|
||||
|
||||
int nread = _reader.read(cbuf);
|
||||
|
||||
if (nread == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return new String(cbuf, 0, nread);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes one or more bytes of data to a file
|
||||
* @param buf a String containing the data to write
|
||||
* @return the number of bytes written, as an int
|
||||
* @throws IOException
|
||||
*/
|
||||
public int write(String buf) throws IOException {
|
||||
|
||||
// get a reader, if we don't already have one
|
||||
if (_writer == null) {
|
||||
_writer = new FileWriter(this);
|
||||
}
|
||||
|
||||
_writer.write(buf);
|
||||
_writer.flush();
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
public int write(byte [] buf) throws IOException {
|
||||
|
||||
return write(new String(buf));
|
||||
}
|
||||
|
||||
/**
|
||||
* convenient one-hit write
|
||||
* @param path pathname of file to write to
|
||||
* @param buf data to write
|
||||
*/
|
||||
public static int write(String path, String buf) throws IOException {
|
||||
SimpleFile_old f = new SimpleFile_old(path);
|
||||
return f.write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if argument refers to an actual file
|
||||
* @param path pathname to test
|
||||
* @return true if a file, false if not
|
||||
*/
|
||||
public static boolean isFile(String path) {
|
||||
return new File(path).isFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if argument refers to a directory
|
||||
* @param path pathname to test
|
||||
* @return true if a directory, false if not
|
||||
*/
|
||||
public static boolean isDir(String path) {
|
||||
return new File(path).isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if a file or directory exists
|
||||
* @param path pathname to test
|
||||
* @return true if exists, or false
|
||||
*/
|
||||
public static boolean exists(String path) {
|
||||
return new File(path).exists();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
138
apps/q/java/src/net/i2p/aum/SimpleQueue.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* SimpleQueue.java
|
||||
*
|
||||
* Created on March 24, 2005, 11:14 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Implements simething similar to python's 'Queue' class
|
||||
*/
|
||||
public class SimpleQueue {
|
||||
|
||||
public Vector items;
|
||||
|
||||
/** Creates a new instance of SimpleQueue */
|
||||
public SimpleQueue() {
|
||||
items = new Vector();
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches the item at head of queue, blocking if queue is empty
|
||||
*/
|
||||
public synchronized Object get()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
if (items.size() == 0)
|
||||
wait();
|
||||
|
||||
// someone has added
|
||||
Object item = items.get(0);
|
||||
items.remove(0);
|
||||
return item;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new object to the queue
|
||||
*/
|
||||
public synchronized void put(Object item)
|
||||
{
|
||||
items.addElement(item);
|
||||
notify();
|
||||
}
|
||||
|
||||
private static class TestThread extends Thread {
|
||||
|
||||
String id;
|
||||
|
||||
SimpleQueue q;
|
||||
|
||||
public TestThread(String id, SimpleQueue q) {
|
||||
this.id = id;
|
||||
this.q = q;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
print("waiting for queue");
|
||||
|
||||
Object item = q.get();
|
||||
|
||||
print("got item: '"+item+"'");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void print(String msg) {
|
||||
System.out.println("thread '"+id+"': "+msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
int i;
|
||||
int nthreads = 7;
|
||||
|
||||
Thread [] threads = new Thread[nthreads];
|
||||
|
||||
SimpleQueue q = new SimpleQueue();
|
||||
|
||||
// populate the queue with some stuff
|
||||
q.put("red");
|
||||
q.put("orange");
|
||||
q.put("yellow");
|
||||
|
||||
// populate threads array
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
threads[i] = new TestThread("thread"+i, q);
|
||||
}
|
||||
|
||||
// and launch the threads
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
// wait a bit and see what happens
|
||||
String [] items = {"green", "blue", "indigo", "violet", "black", "white", "brown"};
|
||||
for (i = 0; i < items.length; i++) {
|
||||
String item = items[i];
|
||||
System.out.println("main: adding '"+item+"'...");
|
||||
q.put(item);
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("main: terminating");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
108
apps/q/java/src/net/i2p/aum/SimpleSemaphore.java
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* SimpleSemaphore.java
|
||||
*
|
||||
* Created on March 24, 2005, 11:51 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
/**
|
||||
* Simple implementation of semaphores
|
||||
*/
|
||||
public class SimpleSemaphore {
|
||||
|
||||
protected int count;
|
||||
|
||||
/** Creates a new instance of SimpleSemaphore */
|
||||
public SimpleSemaphore(int size) {
|
||||
count = size;
|
||||
}
|
||||
|
||||
public synchronized void acquire() throws InterruptedException
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
wait();
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
public synchronized void release()
|
||||
{
|
||||
count += 1;
|
||||
notify();
|
||||
}
|
||||
|
||||
private static class TestThread extends Thread
|
||||
{
|
||||
String id;
|
||||
SimpleSemaphore sem;
|
||||
|
||||
public TestThread(String id, SimpleSemaphore sem)
|
||||
{
|
||||
this.id = id;
|
||||
this.sem = sem;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
try {
|
||||
print("waiting for semaphore");
|
||||
sem.acquire();
|
||||
|
||||
print("got semaphore");
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
print("releasing semaphore");
|
||||
|
||||
sem.release();
|
||||
|
||||
print("terminating");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void print(String msg) {
|
||||
System.out.println("thread '"+id+"': "+msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
int i;
|
||||
|
||||
Thread [] threads = new Thread[10];
|
||||
|
||||
SimpleSemaphore sem = new SimpleSemaphore(3);
|
||||
|
||||
// populate threads array
|
||||
for (i = 0; i < 10; i++) {
|
||||
threads[i] = new TestThread("thread"+i, sem);
|
||||
}
|
||||
|
||||
// and launch the threads
|
||||
for (i = 0; i < 10; i++) {
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
// wait a bit and see what happens
|
||||
System.out.println("main: threads launched, waiting 20 secs");
|
||||
|
||||
try {
|
||||
Thread.sleep(20000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("main: terminating");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
17
apps/q/java/src/net/i2p/aum/helloworld.java
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
72
apps/q/java/src/net/i2p/aum/http/HtmlPage.java
Normal 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;
|
||||
}
|
||||
}
|
||||
50
apps/q/java/src/net/i2p/aum/http/I2PHttpRequestHandler.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
119
apps/q/java/src/net/i2p/aum/http/I2PHttpServer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
apps/q/java/src/net/i2p/aum/http/MiniDemoXmlRpcHandler.java
Normal 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+"'";
|
||||
}
|
||||
}
|
||||
|
||||
567
apps/q/java/src/net/i2p/aum/http/MiniHttpRequestHandler.java
Normal 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 : " ");
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
86
apps/q/java/src/net/i2p/aum/http/MiniHttpRequestPage.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/** 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();
|
||||
}
|
||||
|
||||
}
|
||||
225
apps/q/java/src/net/i2p/aum/http/MiniHttpServer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
358
apps/q/java/src/net/i2p/aum/http/Tag.java
Normal 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 <br> on the fly */
|
||||
public Tag br() {
|
||||
return add("br/");
|
||||
}
|
||||
|
||||
/** insert a <hr> 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();
|
||||
}
|
||||
}
|
||||
|
||||
61
apps/q/java/src/net/i2p/aum/q/Favicon.java
Normal 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,
|
||||
};
|
||||
}
|
||||
187
apps/q/java/src/net/i2p/aum/q/QClientAPI.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
610
apps/q/java/src/net/i2p/aum/q/QClientNode.java
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
751
apps/q/java/src/net/i2p/aum/q/QClientWebInterface.java
Normal 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' -> '/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_<somepath>', (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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
310
apps/q/java/src/net/i2p/aum/q/QDataItem.java
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* QSSK.java
|
||||
*
|
||||
* Created on April 4, 2005, 11:35 AM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.crypto.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
|
||||
|
||||
/**
|
||||
* Singleton class with various static methods
|
||||
* for processing and validating metadata
|
||||
*/
|
||||
public class QDataItem extends Hashtable implements Serializable {
|
||||
|
||||
/** set to true to enable verbose QUtil.debug output to stdout */
|
||||
public static boolean _debug = false;
|
||||
//public static boolean _debug = true;
|
||||
|
||||
public byte [] _data;
|
||||
|
||||
public String _path = null;
|
||||
|
||||
/** Number of chars to truncate the 'public SSK key' base64 string to.
|
||||
* This weakens cryptographic security of the SSK, but makes for
|
||||
* slightly less ugly URIs. Set to -1 to disable truncation (and thus
|
||||
* get the full sha256 security
|
||||
*/
|
||||
private static final int _pubHashTruncation = -1;
|
||||
|
||||
public QDataItem(Hashtable h, byte [] data) {
|
||||
super(h);
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public QDataItem(Hashtable h) {
|
||||
this(h, null);
|
||||
}
|
||||
|
||||
public QDataItem(byte [] data) {
|
||||
super();
|
||||
_data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monolithic main method - processes a block of metadata according to the
|
||||
* rules in the Q metadata specification
|
||||
* @param metadata a block of metadata, received via a getItem or
|
||||
* putItem request
|
||||
* @param data the raw binary data
|
||||
* @param isFromApp true if this metadata has been freshly passed in from
|
||||
* a Q application, false if it's in the Q works
|
||||
* @throws QException if the metadata is invalid
|
||||
*/
|
||||
public void processAndValidate(boolean isClient) throws QException {
|
||||
|
||||
String uri;
|
||||
String ext;
|
||||
|
||||
SigningPrivateKey _privKey = null;
|
||||
SigningPublicKey _pubKey = null;
|
||||
|
||||
// -----------------------------------------
|
||||
// barf if no data
|
||||
if (_data == null) {
|
||||
throw new QException("No data");
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// barf if privKey given and not from client
|
||||
if (containsKey("privateKey") && !isClient) {
|
||||
throw new QException("Only Q applications can pass in a privateKey");
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// generate dataHash (or verify existing)
|
||||
if (containsKey("dataHash")) {
|
||||
if (!get("dataHash").equals(QUtil.sha64(_data))) {
|
||||
throw new QException("Invalid dataHash");
|
||||
}
|
||||
} else {
|
||||
put("dataHash", QUtil.sha64(_data));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// generate size, or verify existing
|
||||
if (containsKey("size")) {
|
||||
if (!get("size").toString().equals(String.valueOf(_data.length))) {
|
||||
throw new QException("Invalid size");
|
||||
}
|
||||
} else {
|
||||
put("size", new Integer(_data.length));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// default 'title' to dataHash
|
||||
if (!containsKey("title")) {
|
||||
put("title", get("dataHash"));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// default the type
|
||||
if (!containsKey("type")) {
|
||||
put("type", "other");
|
||||
}
|
||||
|
||||
// ------------------------------------------------
|
||||
// default the path
|
||||
if (containsKey("path")) {
|
||||
_path = get("path").toString();
|
||||
if (_path.length() > 0) {
|
||||
if (!_path.startsWith("/")) {
|
||||
_path = "/" + _path;
|
||||
put("path", _path);
|
||||
}
|
||||
}
|
||||
|
||||
// determine file extension
|
||||
String [] bits = _path.split("/");
|
||||
String name = bits[bits.length-1];
|
||||
bits = name.split("\\.", 2);
|
||||
ext = "." + bits[bits.length-1];
|
||||
}
|
||||
else {
|
||||
// path is empty - set to '/<dataHash>.ext' where 'ext' is the
|
||||
// file extension guessed from present mimetype value, and dataHash
|
||||
// is a shortened hash of the content
|
||||
String mime = (String)get("mimetype");
|
||||
if (mime == null) {
|
||||
mime = "application/octet-stream";
|
||||
put("mimetype", mime);
|
||||
}
|
||||
|
||||
// determine file extension
|
||||
ext = Mimetypes.guessExtension(mime);
|
||||
|
||||
// and determine final path
|
||||
_path = "/" + ((String)get("dataHash")).substring(0, 10) + ext;
|
||||
put("path", _path);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// default the mimetype
|
||||
if (!containsKey("mimetype")) {
|
||||
String mimetype = Mimetypes.guessType(ext);
|
||||
put("mimetype", mimetype);
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// barf if contains mutually-exclusive signed space keys
|
||||
if (containsKey("privateKey") && (containsKey("publicKey") || containsKey("signature"))) {
|
||||
throw new QException("Metadata must NOT contain privateKey and one of publicKey or signature");
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// barf if exactly one of publicKey and signature are present
|
||||
if (containsKey("publicKey") ^ containsKey("signature")) {
|
||||
throw new QException("Either both or neither of 'publicKey' and 'signature' must be present");
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// now discern between plain hash items and
|
||||
// signed space items
|
||||
if (containsKey("privateKey") || containsKey("publicKey")) {
|
||||
|
||||
DSAEngine dsa = DSAEngine.getInstance();
|
||||
|
||||
// process/validate remaining data in signed space context
|
||||
|
||||
if (containsKey("privateKey")) {
|
||||
// only private key given - uplift, remove, replace with public key
|
||||
_privKey = new SigningPrivateKey();
|
||||
String priv64 = get("privateKey").toString();
|
||||
try {
|
||||
_privKey.fromBase64(priv64);
|
||||
} catch (Exception e) {
|
||||
throw new QException("Invalid privateKey", e);
|
||||
}
|
||||
|
||||
// ok, got valid privateKey
|
||||
|
||||
// expunge privKey from metadata, replace with publicKey
|
||||
this.remove("privateKey");
|
||||
_pubKey = _privKey.toPublic();
|
||||
put("publicKey", _pubKey.toBase64());
|
||||
|
||||
// create and insert a signature
|
||||
QUtil.debug("before sig, asSortedString give:\n"+asSortedString());
|
||||
|
||||
Signature sig = dsa.sign(asSortedString().getBytes(), _privKey);
|
||||
String sigBase64 = sig.toBase64();
|
||||
put("signature", sigBase64);
|
||||
}
|
||||
else {
|
||||
// barf if not both signature and pubkey present
|
||||
if (!(containsKey("publicKey") && containsKey("signature"))) {
|
||||
throw new QException("need both publicKey and signature");
|
||||
}
|
||||
_pubKey = new SigningPublicKey();
|
||||
String pub64 = get("publicKey").toString();
|
||||
try {
|
||||
_pubKey.fromBase64(pub64);
|
||||
} catch (Exception e) {
|
||||
throw new QException("Invalid publicKey", e);
|
||||
}
|
||||
}
|
||||
|
||||
// now, whether we just signed or not, validate the signature/pubkey
|
||||
byte [] thisAsBytes = asSortedString().getBytes();
|
||||
|
||||
String sig64 = get("signature").toString();
|
||||
Signature sig1 = new Signature();
|
||||
try {
|
||||
sig1.fromBase64(sig64);
|
||||
} catch (DataFormatException e) {
|
||||
throw new QException("Invalid signature string", e);
|
||||
}
|
||||
|
||||
if (!dsa.verifySignature(sig1, thisAsBytes, _pubKey)) {
|
||||
throw new QException("Invalid signature");
|
||||
}
|
||||
|
||||
// last step - determine the correct URI
|
||||
String pubHash = QUtil.hashPubKey(_pubKey);
|
||||
uri = "Q:"+pubHash+_path;
|
||||
|
||||
} // end of 'signed space' mode processing
|
||||
else {
|
||||
// -----------------------------------------------------
|
||||
// process/validate remaining data in plain hash context
|
||||
String thisHashed = QUtil.sha64(asSortedString());
|
||||
uri = "Q:"+ thisHashed + ext;
|
||||
|
||||
} // end of plain hash mode processing
|
||||
|
||||
|
||||
// -----------------------------------------------------
|
||||
// final step - add or validate uri
|
||||
if (containsKey("uri")) {
|
||||
if (!get("uri").toString().equals(uri)) {
|
||||
throw new QException("Invalid URI");
|
||||
}
|
||||
} else {
|
||||
put("uri", uri);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a filename under which this item should be stored
|
||||
*/
|
||||
public String getStoreFilename() throws QException {
|
||||
if (!containsKey("uri")) {
|
||||
throw new QException("Missing URI");
|
||||
}
|
||||
return QUtil.sha64((String)get("uri"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes this set of metadata, excluding any 'signature' key
|
||||
* @return Base64 hash of metadata
|
||||
*/
|
||||
public String hashThisAsBase64() {
|
||||
|
||||
return QUtil.sha64(asSortedString());
|
||||
}
|
||||
|
||||
public byte [] hashThis() {
|
||||
|
||||
return QUtil.sha(asSortedString());
|
||||
}
|
||||
|
||||
/**
|
||||
* alphabetise thie metadata to a single string, containing one
|
||||
* 'key=value' entry per line. Excludes keys 'uri' and 'signature'
|
||||
*/
|
||||
public String asSortedString() {
|
||||
|
||||
TreeSet t = new TreeSet(keySet());
|
||||
Iterator keys = t.iterator();
|
||||
int nkeys = t.size();
|
||||
int i;
|
||||
String metaStr = "";
|
||||
for (i = 0; i < nkeys; i++)
|
||||
{
|
||||
String metaKey = (String)keys.next();
|
||||
if (!(metaKey.equals("signature") || metaKey.equals("uri"))) {
|
||||
metaStr += metaKey + "=" + get(metaKey) + "\n";
|
||||
}
|
||||
}
|
||||
return metaStr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
48
apps/q/java/src/net/i2p/aum/q/QException.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* QException.java
|
||||
*
|
||||
* Created on April 6, 2005, 2:05 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Base class of Q exceptions
|
||||
* @author jrandom (shamelessly rebadged by aum)
|
||||
*/
|
||||
|
||||
public class QException extends Exception {
|
||||
private Throwable _source;
|
||||
|
||||
public QException() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public QException(String msg) {
|
||||
this(msg, null);
|
||||
}
|
||||
|
||||
public QException(String msg, Throwable source) {
|
||||
super(msg);
|
||||
_source = source;
|
||||
}
|
||||
|
||||
public void printStackTrace() {
|
||||
if (_source != null) _source.printStackTrace();
|
||||
super.printStackTrace();
|
||||
}
|
||||
|
||||
public void printStackTrace(PrintStream ps) {
|
||||
if (_source != null) _source.printStackTrace(ps);
|
||||
super.printStackTrace(ps);
|
||||
}
|
||||
|
||||
public void printStackTrace(PrintWriter pw) {
|
||||
if (_source != null) _source.printStackTrace(pw);
|
||||
super.printStackTrace(pw);
|
||||
}
|
||||
}
|
||||
|
||||
227
apps/q/java/src/net/i2p/aum/q/QIndexFile.java
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* QIndexFile.java
|
||||
*
|
||||
* Created on March 24, 2005, 11:55 AM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Implements a binary-searchable file for storing (time, hash) records.
|
||||
* This makes it faster for server nodes to determine which content entries,
|
||||
* catalog entries and peer entries have appeared since time t.</p>
|
||||
*
|
||||
* <p>To ease inter-operation with other programs, as well as human troubleshooting,
|
||||
* The file is implemented as a plain text file, with records in the
|
||||
* following format:
|
||||
* <ul>
|
||||
* <li><b>time</b> unixtime, as 10-byte decimal string</li>
|
||||
* <li><b>=</b> single-char delimiter</li>
|
||||
* <li><b>hash</b> - a 44-byte Base64 representation of an sha256 hash</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public class QIndexFile {
|
||||
|
||||
public String path;
|
||||
File fileObj;
|
||||
RandomAccessFile file;
|
||||
public long rawLength;
|
||||
public int numRecs;
|
||||
FileReader reader;
|
||||
FileWriter writer;
|
||||
|
||||
/** length of base64 representation of sha256 hash */
|
||||
static public int hashLen = 43;
|
||||
|
||||
/** length of unixtime milliseconds in decimal format */
|
||||
static public int timeLen = 13;
|
||||
|
||||
/**
|
||||
* length of records, allowing for time field, delimter (,),
|
||||
* hash field and terminating newline
|
||||
*/
|
||||
static public int recordLen = hashLen + timeLen + 2;
|
||||
|
||||
/**
|
||||
* Create a new index file
|
||||
* @param path absolute pathname on filesystem
|
||||
*/
|
||||
public QIndexFile(String path) throws IOException {
|
||||
this.path = path;
|
||||
fileObj = new File(path);
|
||||
|
||||
// if file doesn't exist, ensure parent dir exists, so subsequent
|
||||
// file creation will (hopefully) succeed
|
||||
if (!fileObj.exists())
|
||||
{
|
||||
// create parent directory if not already existing
|
||||
String parentDir = fileObj.getParent();
|
||||
File parentFile = new File(parentDir);
|
||||
if (!parentFile.isDirectory())
|
||||
{
|
||||
parentFile.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
// get a random access object, creating file if not yet existing
|
||||
file = new RandomAccessFile(fileObj, "rws");
|
||||
|
||||
// barf if file's length is not a multiple of record length
|
||||
rawLength = file.length();
|
||||
if (rawLength % recordLen != 0) {
|
||||
throw new IOException("File size not a multiple of record length ("+recordLen+")");
|
||||
}
|
||||
|
||||
// note record count
|
||||
numRecs = (int)(rawLength / recordLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch an iterator for items after a given time
|
||||
*/
|
||||
public synchronized Iterator getItemsSince(int time) throws IOException
|
||||
{
|
||||
//System.out.println("getItemsSince: time="+time);
|
||||
|
||||
// if no records, return an empty iterator
|
||||
if (numRecs == 0)
|
||||
{
|
||||
return new QIndexFileIterator(this, 0);
|
||||
}
|
||||
|
||||
// otherwise, binary search till we find an item time-stamped
|
||||
// after given time
|
||||
long mtime = ((long)time) * 1000;
|
||||
int lo = 0;
|
||||
int hi = numRecs;
|
||||
int lastguess = -1;
|
||||
while (hi - lo > 0)
|
||||
{
|
||||
int guess = (hi + lo) / 2;
|
||||
//System.out.println("getItemsSince: lo="+lo+" guess="+guess+" hi="+hi);
|
||||
if (guess == lastguess) // && hi - lo == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
lastguess = guess;
|
||||
|
||||
Object [] rec = getRecord(guess);
|
||||
long t = ((Long)rec[0]).longValue();
|
||||
if (t <= mtime)
|
||||
{
|
||||
// guess too low, go for upper range
|
||||
lo = guess;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// guess too high, pick lower range
|
||||
hi = guess;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// found
|
||||
return new QIndexFileIterator(this, hi);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new base64 hash value record, saving it with current time
|
||||
*/
|
||||
public synchronized void add(String h) throws IOException
|
||||
{
|
||||
// barf if hash is incorrect length
|
||||
if (h.length() != hashLen)
|
||||
{
|
||||
System.out.println("hash="+h);
|
||||
throw new IOException("Incorrect hash length ("+h.length()+"), should be "+hashLen);
|
||||
}
|
||||
|
||||
// format current date/time as decimal string, pad with leading zeroes
|
||||
Date d = new Date();
|
||||
String ds = String.valueOf(d.getTime());
|
||||
while (ds.length() < timeLen)
|
||||
{
|
||||
ds = "0" + ds;
|
||||
}
|
||||
|
||||
// now can construct record
|
||||
String rec = ds + "," + h + "\n";
|
||||
|
||||
// append it to file
|
||||
file.seek(numRecs * recordLen);
|
||||
file.writeBytes(rec);
|
||||
|
||||
// and update count
|
||||
numRecs += 1;
|
||||
rawLength += recordLen;
|
||||
}
|
||||
|
||||
public long getRecordTime(int n) throws IOException
|
||||
{
|
||||
Object [] rec = getRecord(n);
|
||||
|
||||
return ((Long)rec[0]).longValue();
|
||||
}
|
||||
|
||||
/** return number of records currently within file */
|
||||
public int length()
|
||||
{
|
||||
return numRecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the hash field of record n
|
||||
*/
|
||||
public String getRecordHash(int n) throws IOException
|
||||
{
|
||||
Object [] rec = getRecord(n);
|
||||
return (String)rec[1];
|
||||
}
|
||||
|
||||
public synchronized Object [] getRecord(int n) throws IOException
|
||||
{
|
||||
Object [] rec = new Object[2];
|
||||
|
||||
String recStr = getRecordStr(n);
|
||||
String [] flds = recStr.split(",");
|
||||
Long t = new Long(flds[0]);
|
||||
String h = flds[1];
|
||||
rec[0] = t;
|
||||
rec[1] = h;
|
||||
return rec;
|
||||
}
|
||||
|
||||
protected synchronized String getRecordStr(int n) throws IOException
|
||||
{
|
||||
// barf if over or under-reaching
|
||||
if (n < 0 || n > numRecs - 1)
|
||||
{
|
||||
throw new IOException("Record number ("+n+") out of range");
|
||||
}
|
||||
|
||||
// position to location of the record
|
||||
file.seek(n * recordLen);
|
||||
|
||||
// read, trim and return
|
||||
return file.readLine().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
QIndexFile q = new QIndexFile("/home/david/.quartermaster_client/content/index.dat");
|
||||
Iterator i = q.getItemsSince((int)(new Date().getTime() / 1000));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
apps/q/java/src/net/i2p/aum/q/QIndexFileIterator.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* QIndexFileIterator.java
|
||||
*
|
||||
* Created on March 24, 2005, 1:49 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Implements an Iterator for index files
|
||||
*/
|
||||
public class QIndexFileIterator implements Iterator
|
||||
{
|
||||
public QIndexFile file;
|
||||
int recNum;
|
||||
|
||||
/** Creates an iterator starting from beginning of index file */
|
||||
public QIndexFileIterator(QIndexFile qif)
|
||||
{
|
||||
this(qif, 0);
|
||||
}
|
||||
|
||||
/** Creates a new instance of QIndexFileIterator */
|
||||
public QIndexFileIterator(QIndexFile qif, int recNum)
|
||||
{
|
||||
file = qif;
|
||||
this.recNum = recNum;
|
||||
}
|
||||
|
||||
public boolean hasNext()
|
||||
{
|
||||
return recNum < file.length();
|
||||
}
|
||||
|
||||
public Object next() throws NoSuchElementException
|
||||
{
|
||||
String rec;
|
||||
try {
|
||||
rec = file.getRecordHash(recNum);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new NoSuchElementException("Reached end of index");
|
||||
}
|
||||
recNum += 1;
|
||||
return rec;
|
||||
}
|
||||
|
||||
public void remove()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
57
apps/q/java/src/net/i2p/aum/q/QKademliaComparator.java
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* QKademliaComparator.java
|
||||
*
|
||||
* Created on March 30, 2005, 12:30 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.util.*;
|
||||
import java.math.*;
|
||||
|
||||
/**
|
||||
* implements a Comparator class which compares two QPeerRec objects
|
||||
* for kademlia-closeness to a given base64 sha hash value
|
||||
*/
|
||||
public class QKademliaComparator implements Comparator {
|
||||
|
||||
QNode node;
|
||||
BigInteger hashed;
|
||||
|
||||
/**
|
||||
* Creates a kademlia comparator, which given a base64 sha256 hash
|
||||
* of something, can compare two nodes for their kademlia-closeness to
|
||||
* that hash
|
||||
* @param node a QNode object - needed for access to its base64 routines
|
||||
* @param base64hash - string - a base64 representation of the sha256 hash
|
||||
* of anything
|
||||
*/
|
||||
public QKademliaComparator(QNode node, String base64hash) {
|
||||
|
||||
this.node = node;
|
||||
hashed = new BigInteger(node.base64Dec(base64hash).getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* compares two given QPeerRec objects for how close each one's ID
|
||||
* is to the stored hash
|
||||
*/
|
||||
public int compare(Object o1, Object o2) {
|
||||
|
||||
QPeer peer1 = (QPeer)o1;
|
||||
QPeer peer2 = (QPeer)o2;
|
||||
|
||||
String id1 = peer1.getId();
|
||||
String id2 = peer2.getId();
|
||||
|
||||
BigInteger i1 = new BigInteger(id1.getBytes());
|
||||
BigInteger i2 = new BigInteger(id2.getBytes());
|
||||
|
||||
BigInteger xor1 = i1.xor(hashed);
|
||||
BigInteger xor2 = i2.xor(hashed);
|
||||
|
||||
return xor1.compareTo(xor2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
921
apps/q/java/src/net/i2p/aum/q/QMgr.java
Normal file
@@ -0,0 +1,921 @@
|
||||
/*
|
||||
* QLaunch.java
|
||||
*
|
||||
* Created on March 30, 2005, 10:09 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
|
||||
/**
|
||||
* <p>Command Line Interface (CLI) for starting/stopping Q nodes,
|
||||
* and also, executing commands on Q nodes such as inserting, retrieving
|
||||
* and searching for content.</p>
|
||||
*
|
||||
* <p>Commands include:
|
||||
* <ul>
|
||||
* <li>Start a server or client Node</li>
|
||||
* <li>Stop a server or client Node</li>
|
||||
* <li>Get status of a server or client Node</li>
|
||||
* <li>Export a server node's dest</li>
|
||||
* <li>Import a foreign dest to a server or client node</li>
|
||||
* <li>Insert a file to a client node, with metadata</li>
|
||||
* <li>Retrieve data/metadata from a client node</li>
|
||||
* <li>Search a client node for content</li>
|
||||
*/
|
||||
public class QMgr {
|
||||
|
||||
public Runtime runtime;
|
||||
public XmlRpcClient node;
|
||||
public String nodePrivKey;
|
||||
public String nodeDest;
|
||||
public String nodeDirStr;
|
||||
public File nodeDir;
|
||||
public boolean isServer = false;
|
||||
|
||||
public String [] args;
|
||||
public String cmd;
|
||||
public int cmdIdx;
|
||||
public int argc;
|
||||
public int argi;
|
||||
|
||||
public static String [] commonI2PSystemPropertyKeys = {
|
||||
"i2cp.tcp.host",
|
||||
"i2cp.tcp.port",
|
||||
"eepproxy.tcp.host",
|
||||
"eepproxy.tcp.port",
|
||||
"q.xmlrpc.tcp.host",
|
||||
"q.xmlrpc.tcp.port",
|
||||
"inbound.length",
|
||||
"outbound.length",
|
||||
"inbound.lengthVariance",
|
||||
"outbound.lengthVariance",
|
||||
};
|
||||
|
||||
/** Creates a new instance of QLaunch */
|
||||
public QMgr() {
|
||||
}
|
||||
|
||||
public void notimplemented() {
|
||||
usage(1, "Command '"+cmd+"' not yet implemented, sorry");
|
||||
}
|
||||
|
||||
/** procures an XML-RPC client for node interaction */
|
||||
public void getXmlRpcClient() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
public int doHelp() {
|
||||
if (argi == argc) {
|
||||
// output short help
|
||||
System.out.println(
|
||||
"I2P QMgr - Brief command summary:\n"
|
||||
+"Synopsis:"
|
||||
+" java net.i2p.aum.q.QMgr [-dir <path>] [server] [<cmd> [<args>]]\n"
|
||||
+"Commands:\n"
|
||||
+" help - print this help summary\n"
|
||||
+" help verbose - print detailed verbose usage info\n"
|
||||
+" start - start a node in background\n"
|
||||
+" foreground - run a node in foreground\n"
|
||||
+" stop - terminate node\n"
|
||||
+" status - display node status\n"
|
||||
+" getref [<file>] - output the node's noderef (its base64 dest)\n"
|
||||
+" addref [<file>] - add one or more node refs to node\n"
|
||||
+" get key [<file>] - get key to stdout (or to file)\n"
|
||||
+" put [<file>] [-m <metadata>] - insert content\n"
|
||||
+" search item1=val1 item2=val2... - search for content\n"
|
||||
);
|
||||
}
|
||||
else if (args[argi].equals("verbose")) {
|
||||
System.out.println(
|
||||
"----------------------------\n"
|
||||
+"Welcome to the I2P Q network\n"
|
||||
+"----------------------------\n"
|
||||
+"\n"
|
||||
+"This program, QMgr, is a command-line interface to the Q network,\n"
|
||||
+"(an in-I2P distributed file store)\n"
|
||||
+"and allows you to perform basic operations, including:\n"
|
||||
+"\n"
|
||||
+" - create, startup and shutdown Q server and client nodes\n"
|
||||
+" - determine status of running Q nodes\n"
|
||||
+" - import and export noderefs to/from these nodes\n"
|
||||
+" - search for, insert and retrieve content\n"
|
||||
+"\n"
|
||||
+"Command syntax:\n"
|
||||
+" java net.i2p.aum.q.QMgr [-dir <path>] [-port <port>] [server] [<cmd> [<args>]]\n"
|
||||
+"\n"
|
||||
+"Explanation of commands and arguments:"
|
||||
+"\n"
|
||||
+"* 'server'\n"
|
||||
+" Specifies that we're operating on a server node (otherwise it's\n"
|
||||
+" assumed we're operating on a client node)\n"
|
||||
+"\n"
|
||||
+"* '-dir=<path>'\n"
|
||||
+" Server nodes by default reside at ~/.quartermaster_server,\n"
|
||||
+" and client nodes at ~/.quartermaster_client.\n"
|
||||
+" Nodes are uniquely identified by the directory at which they\n"
|
||||
+" reside. Specifying this argument allows you to operate on a\n"
|
||||
+" server or client node which resides at a different location\n"
|
||||
+"\n"
|
||||
+"* '-port=<port>'\n"
|
||||
+" Applies to client nodes only. Valid only for startup command.\n"
|
||||
+" Permanently changes the port on which a given client listens\n"
|
||||
+" for cmmands.\n"
|
||||
+"\n"
|
||||
+"* Commands - the basic commands are:\n"
|
||||
+"\n"
|
||||
+" help\n"
|
||||
+" - display a help summary\n"
|
||||
+"\n"
|
||||
+" help verbose\n"
|
||||
+" - display this long-winded help\n"
|
||||
+"\n"
|
||||
+" start\n"
|
||||
+" - start the node. If a nonexistent directory path is given,\n"
|
||||
+" a whole new unique server or client node will be created\n"
|
||||
+" at that path\n"
|
||||
+"\n"
|
||||
+" foreground\n"
|
||||
+" - as for start, but run the server in foreground rather\n"
|
||||
+" than as a background daemon\n"
|
||||
+"\n"
|
||||
+" stop\n"
|
||||
+" - shutdown the node\n"
|
||||
+"\n"
|
||||
+" status\n"
|
||||
+" - print a dump of node status and statistics to stdout\n"
|
||||
+"\n"
|
||||
+" newkeys\n"
|
||||
+" - generate and print out a new keypair for signed-space\n"
|
||||
+" data item inserts\n"
|
||||
+"\n"
|
||||
+" getref [<file>]\n"
|
||||
+" - print the node's noderef (its base64 destination) to\n"
|
||||
+" stdout. If <file> arg is given, writes the destination\n"
|
||||
+" to this file instead.\n"
|
||||
+"\n"
|
||||
+" addref [<file>]\n"
|
||||
+" - add one or more noderefs to the node. If [<file>] argument\n"
|
||||
+" is given, the refs are read from this file, which is expected\n"
|
||||
+" to contain one base64 destination per line\n"
|
||||
+"\n"
|
||||
+"The following commands are only valid for client nodes:\n"
|
||||
+"\n"
|
||||
+" get <key> [<file>]\n"
|
||||
+" - Try to retrieve a content item, (identified by <key>), from the\n"
|
||||
+" node. If the item is retrieved, its raw data will be printed\n"
|
||||
+" to stdout, or to <file> if given. NOTE - REDIRECTING TO STDOUT\n"
|
||||
+" IS PRESENTLY UNRELIABLE, SO SPECIFY AN EXPLICIT FILENAME FOR NOW\n"
|
||||
+"\n"
|
||||
+" put [<file>] [-m item=val ...]\n"
|
||||
+" - Inserts an item of content to the node, and prints its key to\n"
|
||||
+" stdout. Reads content data from <file> if given, or from standard\n"
|
||||
+" input if not. Metadata arguments may be given as '-m' followed by\n"
|
||||
+" a space-separated sequence of 'item=value' specifiers.\n"
|
||||
+" Typical metadata items include:\n"
|
||||
+" - type (one of text/html/image/audio/video/archive)\n"
|
||||
+" - title - a short (<80 char) descriptive title\n"
|
||||
+" - filename - a recommended filename under which to store this\n"
|
||||
+" item on retrieve.\n"
|
||||
+" - abstract - a longer (<256 char) summary of content\n"
|
||||
+" - keywords - a comma-separated list of keywords\n"
|
||||
+"\n"
|
||||
+" search -m item=val [ item=val ...]\n"
|
||||
+" - searches node for content matching a set of metadata criteria\n"
|
||||
+" each 'item=val' specifies an 'item' of metadata, to be matched\n"
|
||||
+" against regular expression 'val'. For example:\n"
|
||||
+" java net.i2p.aum.q.QMgr search -m title=\"^Madonna\" type=\"music\"\n"
|
||||
);
|
||||
}
|
||||
else {
|
||||
System.out.println(
|
||||
"Unrecognised help qualifier '"+args[argi]+"'\n"
|
||||
+"type 'java net.i2p.aum.q.QMgr help' for more info"
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int doStart() {
|
||||
//notimplemented();
|
||||
|
||||
String [] startForegroundArgs;
|
||||
int i;
|
||||
|
||||
// Detect/add any '-D' settings
|
||||
// search our list of known i2p-relevant sysprops, detect
|
||||
// if they've been set in System properties, and if so, copy
|
||||
// them to a customProps table
|
||||
Hashtable customProps = new Hashtable();
|
||||
Properties sysprops = System.getProperties();
|
||||
for (i=0; i<commonI2PSystemPropertyKeys.length; i++) {
|
||||
String key = commonI2PSystemPropertyKeys[i];
|
||||
if (sysprops.containsKey(key)) {
|
||||
customProps.put(key, sysprops.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
// Create args list
|
||||
args[cmdIdx] = "foreground"; // kludge = substitute 'foreground' command
|
||||
|
||||
// need original args plus 2 ('java' and 'classname') plus
|
||||
// number of custom properties
|
||||
startForegroundArgs = new String[argc+2+customProps.size()];
|
||||
|
||||
// create a set of startup args for child vm
|
||||
startForegroundArgs[0] = "java";
|
||||
i = 1;
|
||||
Enumeration keys = customProps.keys();
|
||||
while (keys.hasMoreElements()) {
|
||||
String key = (String)keys.nextElement();
|
||||
String val = (String)customProps.get(key);
|
||||
startForegroundArgs[i++] = "-D"+key+"="+val;
|
||||
}
|
||||
startForegroundArgs[i++] = "net.i2p.aum.q.QMgr";
|
||||
for (int j = 0; j < args.length; j++) {
|
||||
startForegroundArgs[i+j] = args[j];
|
||||
}
|
||||
|
||||
// and spawn the start job
|
||||
try {
|
||||
//runtime.exec(startForegroundArgs, propLines);
|
||||
runtime.exec(startForegroundArgs, null);
|
||||
for (i=0; i<startForegroundArgs.length; i++) {
|
||||
System.out.println("start: arg["+i+"]="+startForegroundArgs[i]);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** this gets invoked after a 'start' command, and runs in a detached process */
|
||||
public int doStartForeground() {
|
||||
|
||||
//new File("/tmp/blahblah").mkdirs();
|
||||
/**
|
||||
String s = new String();
|
||||
int i = 0;
|
||||
for (i=0; i<args.length; i++) {
|
||||
s = s + args[i] + "\n";
|
||||
}
|
||||
try {
|
||||
new SimpleFile("/tmp/qq", "rws").write(s);
|
||||
} catch (IOException e) {}
|
||||
//notimplemented();
|
||||
**/
|
||||
|
||||
QNode node;
|
||||
|
||||
try {
|
||||
if (isServer) {
|
||||
node = new QServerNode(nodeDirStr);
|
||||
}
|
||||
else {
|
||||
node = new QClientNode(nodeDirStr);
|
||||
}
|
||||
|
||||
node.run();
|
||||
|
||||
// enter endless loop
|
||||
//while (true) {
|
||||
// Thread.sleep(1000000000);
|
||||
//}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int doStop() {
|
||||
|
||||
System.out.println("Stopping node at '"+nodeDirStr+"'...");
|
||||
Vector nodeArgs = new Vector();
|
||||
nodeArgs.addElement(nodePrivKey);
|
||||
try {
|
||||
Hashtable res = (Hashtable)node.execute("i2p.q.shutdown", nodeArgs);
|
||||
System.out.println("Shutdown failed: got "+res);
|
||||
return 1;
|
||||
} catch (IOException e) {
|
||||
System.out.println("Shutdown apparently ok");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Problem with shutdown");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int doStatus() {
|
||||
System.out.println("Pinging node at '"+nodeDirStr+"'...");
|
||||
try {
|
||||
Hashtable res = (Hashtable)node.execute("i2p.q.ping", new Vector());
|
||||
System.out.println("Node Ping:");
|
||||
Enumeration eres = res.keys();
|
||||
while (eres.hasMoreElements()) {
|
||||
String key = (String)eres.nextElement();
|
||||
Object val = res.get(key);
|
||||
System.out.println(" "+key+"="+val);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to ping node");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** executes a 'getref' command */
|
||||
public int doGetRef() {
|
||||
if (!isServer) {
|
||||
System.err.println("Cannot get noderefs for client nodes");
|
||||
return 1;
|
||||
}
|
||||
if (argi < argc) {
|
||||
// write it to a file
|
||||
String path = args[argi];
|
||||
try {
|
||||
new SimpleFile(path, "rws").write(nodeDest);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
usage("getref: Cannot create/write output file "+path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println(nodeDest);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* attempts to add a single dest
|
||||
* @return true if dest added successfully, false if not
|
||||
*/
|
||||
public boolean doAddOneRef(String ref) {
|
||||
|
||||
Destination d;
|
||||
Hashtable res;
|
||||
|
||||
// don't trust user or xmlrpc link, try to create a dest
|
||||
// from purported string now
|
||||
try {
|
||||
d = new Destination();
|
||||
d.fromBase64(ref);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Invalid destination: '"+ref+"'");
|
||||
return false;
|
||||
}
|
||||
|
||||
// looks ok, try to pass it to node
|
||||
try {
|
||||
Vector v = new Vector();
|
||||
v.addElement(ref);
|
||||
res = (Hashtable)node.execute("i2p.q.hello", v);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to add noderef '"+ref+"'");
|
||||
return false;
|
||||
}
|
||||
|
||||
System.out.println("doAddRef: res="+res);
|
||||
|
||||
// see what result we got
|
||||
String status = (String)res.get("status");
|
||||
|
||||
System.out.println("doAddRef: status="+status);
|
||||
|
||||
if (status == null || !status.equals("ok")) {
|
||||
String error = (String)res.get("error");
|
||||
System.out.println("Error '"+error+"' trying to add node dest '"+ref+"'");
|
||||
return false;
|
||||
}
|
||||
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
|
||||
/** executes an 'addref' command */
|
||||
public int doAddRef() {
|
||||
|
||||
if (argi < argc) {
|
||||
// open file, split into lines, submit each line as dest
|
||||
String path = args[argi];
|
||||
String raw;
|
||||
try {
|
||||
raw = new SimpleFile(path, "r").read().trim();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("addref: failed to open file '"+path+"'");
|
||||
return 1;
|
||||
}
|
||||
String [] lines = raw.split("\\s+");
|
||||
//int i;
|
||||
for (int i=0; i<lines.length; i++) {
|
||||
|
||||
String line = lines[i];
|
||||
|
||||
// ignore empty and comment lines
|
||||
if (line.substring(0,1).equals("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!doAddOneRef(line)) {
|
||||
// failed
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// read lines from stdin
|
||||
InputStreamReader rIn = new InputStreamReader(System.in);
|
||||
BufferedReader brIn = new BufferedReader(rIn);
|
||||
|
||||
String line;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
line = brIn.readLine();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 1;
|
||||
}
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!doAddOneRef(line)) {
|
||||
// failed
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int doGet() {
|
||||
|
||||
if (argi == argc) {
|
||||
usage("get: missing key");
|
||||
}
|
||||
|
||||
String key = args[argi++];
|
||||
|
||||
System.err.println("Trying to retrieve key '"+key+"'...");
|
||||
|
||||
Hashtable res;
|
||||
try {
|
||||
Vector getArgs = new Vector();
|
||||
getArgs.addElement(key);
|
||||
res = (Hashtable)node.execute("i2p.q.getItem", getArgs);
|
||||
//System.err.println("get: res="+res);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to ping node");
|
||||
return 1;
|
||||
}
|
||||
|
||||
String status = (String)res.get("status");
|
||||
if (!status.equals("ok")) {
|
||||
String err = (String)res.get("error");
|
||||
System.err.println("Key retrieve error: "+err);
|
||||
String comment = (String)res.get("comment");
|
||||
if (comment != null) {
|
||||
System.err.println(" "+comment);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// got something
|
||||
byte [] data = (byte [])res.get("data");
|
||||
|
||||
// save to file, or spit out to stdout?
|
||||
if (argi < argc) {
|
||||
// spit to file
|
||||
String path = args[argi];
|
||||
try {
|
||||
new SimpleFile(path, "rws").write(data);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
System.err.println("Failed to save data to file '"+path);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// dump to stdout
|
||||
//System.out.print(data);
|
||||
for (int i=0; i<data.length; i++) {
|
||||
char c = (char)data[i];
|
||||
System.out.print(c);
|
||||
}
|
||||
System.out.flush();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int doPut() {
|
||||
|
||||
Hashtable metadata = new Hashtable();
|
||||
String path = null;
|
||||
|
||||
// different ways of sourcing data/metadata
|
||||
if (argi < argc) {
|
||||
// provided filename and/or metadata
|
||||
if (!args[argi].equals("-m")) {
|
||||
// read from file
|
||||
path = args[argi++];
|
||||
}
|
||||
|
||||
// now expect -m, or error
|
||||
if (argi >= argc || !args[argi].equals("-m")) {
|
||||
usage("Bad put command syntax");
|
||||
}
|
||||
|
||||
// now skip over the '-m'
|
||||
argi++;
|
||||
|
||||
metadata = readMetadataSpec();
|
||||
}
|
||||
|
||||
byte [] data = null;
|
||||
|
||||
if (path != null) {
|
||||
// easy way - suck the file or barf
|
||||
try {
|
||||
data = new SimpleFile(path, "r").readBytes();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
usage("get: Failed to read input file '"+path+"'");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// the crap option - suck it from stdin
|
||||
// read lines from stdin
|
||||
ByteArrayOutputStream bo = new ByteArrayOutputStream();
|
||||
int c;
|
||||
try {
|
||||
while (true) {
|
||||
c = System.in.read();
|
||||
if (c < 0) {
|
||||
break;
|
||||
}
|
||||
bo.write(c);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
usage("put: error reading from input stream");
|
||||
}
|
||||
|
||||
data = bo.toByteArray();
|
||||
}
|
||||
|
||||
// ok, got data (and possibly metadata too)
|
||||
Vector putArgs = new Vector();
|
||||
Hashtable res;
|
||||
putArgs.addElement(metadata);
|
||||
putArgs.addElement(data);
|
||||
|
||||
System.out.println("data length="+data.length);
|
||||
|
||||
try {
|
||||
res = (Hashtable)node.execute("i2p.q.putItem", putArgs);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
System.err.println("Failed to put");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// got a res
|
||||
String status = (String)res.get("status");
|
||||
if (!status.equals("ok")) {
|
||||
String error = (String)res.get("error");
|
||||
usage("put: failure - "+error);
|
||||
}
|
||||
|
||||
// success
|
||||
String key = (String)res.get("key");
|
||||
System.out.print(key);
|
||||
System.out.flush();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int doNewKeys() {
|
||||
|
||||
System.err.println("Generating new signed-space keypair...");
|
||||
|
||||
String [] keys = QUtil.newKeys();
|
||||
System.out.println("Public: "+keys[0]);
|
||||
System.out.println("Private: "+keys[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int doSearch() {
|
||||
|
||||
if (argi == argc) {
|
||||
usage("Missing search metadata");
|
||||
}
|
||||
|
||||
// expect -m, or error
|
||||
if (argi >= argc || !args[argi].equals("-m")) {
|
||||
usage("Bad search command syntax");
|
||||
}
|
||||
|
||||
// now skip over the '-m'
|
||||
argi++;
|
||||
|
||||
if (argi == argc) {
|
||||
usage("Missing search metadata");
|
||||
}
|
||||
|
||||
Hashtable metadata = readMetadataSpec();
|
||||
|
||||
// ok, got data (and possibly metadata too)
|
||||
Vector searchArgs = new Vector();
|
||||
Hashtable res;
|
||||
searchArgs.addElement(metadata);
|
||||
try {
|
||||
res = (Hashtable)node.execute("i2p.q.search", searchArgs);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
System.err.println("Failed to search");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// got a res
|
||||
String status = (String)res.get("status");
|
||||
if (!status.equals("ok")) {
|
||||
String error = (String)res.get("error");
|
||||
usage("search: failure - "+error);
|
||||
}
|
||||
|
||||
// success
|
||||
Vector items = (Vector)res.get("items");
|
||||
|
||||
//System.out.println(items);
|
||||
|
||||
for (int i=0; i<items.size(); i++) {
|
||||
Hashtable rec = (Hashtable)items.get(i);
|
||||
String key = (String)rec.get("key");
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
System.out.println(key);
|
||||
Enumeration keys = rec.keys();
|
||||
while (keys.hasMoreElements()) {
|
||||
Object mkey = keys.nextElement();
|
||||
if (!mkey.toString().equals("key")) {
|
||||
Object val = rec.get(mkey);
|
||||
System.out.println(" "+mkey+"="+val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Hashtable readMetadataSpec() {
|
||||
Hashtable meta = new Hashtable();
|
||||
|
||||
//dumpArgs();
|
||||
|
||||
// rest of args are metadata
|
||||
while (argi < argc) {
|
||||
String metaArg = args[argi++];
|
||||
String [] parts = metaArg.split("=", 2);
|
||||
// barf if just 1 part
|
||||
if (parts.length < 2) {
|
||||
usage("Illegal metadata arg '"+metaArg+"'");
|
||||
}
|
||||
|
||||
// add to search map
|
||||
meta.put(parts[0], parts[1]);
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
public void dumpArgs() {
|
||||
System.out.println("Dump of QMgr shell args:");
|
||||
for (int i=0; i<args.length; i++) {
|
||||
System.out.println("args["+i+"]='"+args[i]+"'");
|
||||
}
|
||||
}
|
||||
|
||||
public int execute(String [] args) {
|
||||
|
||||
argi = 0;
|
||||
argc = args.length;
|
||||
this.args = args;
|
||||
|
||||
runtime = Runtime.getRuntime();
|
||||
|
||||
// barf if no cmds given
|
||||
if (argc == 0) {
|
||||
usage("Missing command");
|
||||
}
|
||||
|
||||
// test if specifying a directory
|
||||
if (args[argi].equals("-dir")) {
|
||||
// barf if no dir arg
|
||||
argi++;
|
||||
if (argi == argc) {
|
||||
usage("-dir: missing directory");
|
||||
}
|
||||
nodeDirStr = args[argi++];
|
||||
}
|
||||
|
||||
// test if specifying a port
|
||||
if (args[argi].equals("-port")) {
|
||||
// barf if no port arg
|
||||
argi++;
|
||||
if (argi == argc) {
|
||||
usage("-port: missing port num");
|
||||
}
|
||||
System.setProperty("q.xmlrpc.tcp.port", args[argi++]);
|
||||
}
|
||||
|
||||
// test if server
|
||||
if (argi < argc && args[argi].equals("server")) {
|
||||
isServer = true;
|
||||
argi++;
|
||||
}
|
||||
|
||||
// barf if no more arg
|
||||
if (argi == argc){
|
||||
usage("Missing command");
|
||||
}
|
||||
|
||||
// cool, got at least a keyword
|
||||
cmdIdx = argi++;
|
||||
cmd = args[cmdIdx];
|
||||
|
||||
// and dispatch off to appropriate handler
|
||||
if (cmd.equals("help")) {
|
||||
return doHelp();
|
||||
}
|
||||
|
||||
// following commands deal with a node, so gotta get a handle
|
||||
if (nodeDirStr == null) {
|
||||
// fall back on defaults
|
||||
if (isServer) {
|
||||
nodeDirStr = System.getProperties().getProperty("user.home")
|
||||
+ QServerNode.sep
|
||||
+ QServerNode.defaultStoreDir;
|
||||
}
|
||||
else {
|
||||
nodeDirStr = System.getProperties().getProperty("user.home")
|
||||
+ QClientNode.sep
|
||||
+ QClientNode.defaultStoreDir;
|
||||
}
|
||||
}
|
||||
nodeDir = new File(nodeDirStr);
|
||||
|
||||
//System.out.println("nodeDirStr='"+nodeDirStr+"'");
|
||||
|
||||
if (cmd.equals("start")) {
|
||||
return doStart();
|
||||
}
|
||||
|
||||
else if (cmd.equals("foreground")) {
|
||||
// secret option, used when starting node
|
||||
return doStartForeground();
|
||||
}
|
||||
|
||||
// the following commands require that the node actually exists
|
||||
if (!nodeDir.isDirectory()) {
|
||||
usage(
|
||||
"Nonexistent node directory '"+nodeDirStr+"'\n"
|
||||
+"The '"+cmd+"' command requires that the node already\n"
|
||||
+"exists. You may use the 'start' command to create a\n"
|
||||
+"whole new node at that directory if you wish"
|
||||
);
|
||||
}
|
||||
|
||||
// yay, found a node (we hope), create an xmlrpc client for talking
|
||||
// to that node
|
||||
String propPath = nodeDirStr + QNode.sep + "node.conf";
|
||||
PropertiesFile pf;
|
||||
try {
|
||||
pf = new PropertiesFile(propPath);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to load node properties");
|
||||
return -1;
|
||||
}
|
||||
|
||||
nodePrivKey = pf.getProperty("privKey");
|
||||
nodeDest = pf.getProperty("dest");
|
||||
|
||||
if (isServer) {
|
||||
// gotta create I2P xmlrpc client
|
||||
try {
|
||||
node = new I2PXmlRpcClientFactory().newClient(nodeDest);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to create I2P XML-RPC connection to node");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
int xmlRpcServerPort = pf.getIntProperty(
|
||||
"xmlRpcServerPort", QNode.defaultXmlRpcServerPort);
|
||||
// create normal xmlrpc client
|
||||
try {
|
||||
node = new XmlRpcClient("http://localhost:"+xmlRpcServerPort);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to create I2P XML-RPC connection to node");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd.equals("stop")) {
|
||||
return doStop();
|
||||
}
|
||||
else if (cmd.equals("status")) {
|
||||
return doStatus();
|
||||
}
|
||||
else if (cmd.equals("getref")) {
|
||||
return doGetRef();
|
||||
}
|
||||
else if (cmd.equals("addref")) {
|
||||
return doAddRef();
|
||||
}
|
||||
else if (cmd.equals("get")) {
|
||||
return doGet();
|
||||
}
|
||||
else if (cmd.equals("put")) {
|
||||
return doPut();
|
||||
}
|
||||
else if (cmd.equals("newkeys")) {
|
||||
return doNewKeys();
|
||||
}
|
||||
else if (cmd.equals("search")) {
|
||||
return doSearch();
|
||||
}
|
||||
else {
|
||||
usage("unrecognised command '"+cmd+"'");
|
||||
}
|
||||
|
||||
return 0; // needed to shut the stupid compiler up
|
||||
}
|
||||
|
||||
// barf-o-matic methods
|
||||
|
||||
public int usage() {
|
||||
return usage(1);
|
||||
}
|
||||
|
||||
public int usage(String msg) {
|
||||
return usage(1, msg);
|
||||
}
|
||||
|
||||
public int usage(int retval) {
|
||||
return usage(retval, null);
|
||||
}
|
||||
|
||||
public int usage(int retval, String msg) {
|
||||
System.out.println(msg);
|
||||
System.out.println(
|
||||
"Usage: java net.i2p.aum.q.QMgr [-dir=<path>] [server] [cmd [args]]\n"
|
||||
+"Type 'java net.i2p.aum.q.QMgr help' for help summary\n"
|
||||
+"or 'java net.i2p.aum.q.QMgr help verbose' for long-winded help"
|
||||
);
|
||||
System.exit(retval);
|
||||
return 0; // stop silly compiler from whingeing
|
||||
}
|
||||
|
||||
/**
|
||||
* Startup a Q server or client node, or send a command to a running node
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
QMgr mgr = new QMgr();
|
||||
int retval = mgr.execute(args);
|
||||
System.exit(retval);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1940
apps/q/java/src/net/i2p/aum/q/QNode.java
Normal file
105
apps/q/java/src/net/i2p/aum/q/QPeer.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* QPeer.java
|
||||
*
|
||||
* Created on March 28, 2005, 2:13 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.aum.*;
|
||||
|
||||
/**
|
||||
* Wrapper for a peer record file.
|
||||
* Implements a bunch of accessor methods for getting/setting numerical attribs
|
||||
*/
|
||||
public class QPeer implements Serializable {
|
||||
|
||||
QNode node;
|
||||
protected Destination dest;
|
||||
protected String peerId;
|
||||
protected String destStr;
|
||||
|
||||
public PropertiesFile file;
|
||||
|
||||
/** Creates a whole new peer */
|
||||
public QPeer(QNode node, Destination dest) throws IOException {
|
||||
|
||||
file = new PropertiesFile(node.peersDir + node.sep + node.destToId(dest));
|
||||
|
||||
this.dest = dest;
|
||||
destStr = dest.toBase64();
|
||||
peerId = node.destToId(dest);
|
||||
|
||||
file.setProperty("id", peerId);
|
||||
file.setProperty("dest", destStr);
|
||||
file.setProperty("timeLastUpdate", "0");
|
||||
file.setProperty("timeLastContact", "0");
|
||||
file.setProperty("timeNextContact", "0");
|
||||
}
|
||||
|
||||
/** Loads an existing peer, barfs if nonexistent */
|
||||
public QPeer(QNode node, String destId) throws IOException, DataFormatException {
|
||||
|
||||
file = new PropertiesFile(node.peersDir + node.sep + destId);
|
||||
|
||||
// barf if file doesn't exist
|
||||
if (!file._fileExists) {
|
||||
throw new IOException("Missing peer record file");
|
||||
}
|
||||
|
||||
destStr = file.getProperty("dest");
|
||||
dest = new Destination();
|
||||
dest.fromBase64(destStr);
|
||||
peerId = destId;
|
||||
}
|
||||
|
||||
public Destination getDestination() {
|
||||
return dest;
|
||||
}
|
||||
|
||||
public String getDestStr() {
|
||||
return destStr;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return peerId;
|
||||
}
|
||||
|
||||
public int getTimeLastUpdate() {
|
||||
return new Integer(file.getProperty("timeLastUpdate")).intValue();
|
||||
}
|
||||
|
||||
public void setTimeLastUpdate(long when) {
|
||||
file.setProperty("timeLastUpdate", String.valueOf(when));
|
||||
}
|
||||
|
||||
public int getTimeLastContact() {
|
||||
return new Integer(file.getProperty("timeLastContact")).intValue();
|
||||
}
|
||||
|
||||
public void setTimeLastContact(int when) {
|
||||
file.setProperty("timeLastContact", String.valueOf(when));
|
||||
}
|
||||
|
||||
public int getTimeNextContact() {
|
||||
return new Integer(file.getProperty("timeNextContact")).intValue();
|
||||
}
|
||||
|
||||
public void setTimeNextContact(int when) {
|
||||
file.setProperty("timeNextContact", String.valueOf(when));
|
||||
}
|
||||
|
||||
public boolean hasBeenGreeted() {
|
||||
return file.containsKey("sentHello");
|
||||
}
|
||||
|
||||
public void markAsGreeted() {
|
||||
file.setProperty("sentHello", "1");
|
||||
}
|
||||
}
|
||||
|
||||
386
apps/q/java/src/net/i2p/aum/q/QServerMethods.java
Normal file
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* QServerMethods.java
|
||||
*
|
||||
* Created on 20 March 2005, 23:23
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/**
|
||||
* Defines the methods which will be exposed in the server's
|
||||
* XML-RPC interface. On the xml-rpc client side, these methods are invoked
|
||||
* through the 'peerXXXX' methods.
|
||||
* This class is just a shim, which invokes methods of the same name on
|
||||
* the QServerNode. It's separated off as a shim because the XML-RPC implementation
|
||||
* we're using (org.apache.xmlrpc) can only add entire objects and all their
|
||||
* methods as handlers, and doesn't support adding a-la-carte methods.
|
||||
*/
|
||||
public class QServerMethods {
|
||||
|
||||
private QNode node;
|
||||
|
||||
/**
|
||||
* Creates a new instance of QServerMethods,
|
||||
* with a ref to the server
|
||||
*/
|
||||
public QServerMethods(QNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* pings this peer node
|
||||
*/
|
||||
public Hashtable ping() {
|
||||
node.nodeLoadAfterHit();
|
||||
System.out.println("XMLRPC: ping");
|
||||
return node.ping();
|
||||
}
|
||||
|
||||
/**
|
||||
* pings this peer node
|
||||
* @param args a Hashtable (dict, struct, assoc array) of args, all of which are
|
||||
* completely ignored
|
||||
*/
|
||||
public Hashtable ping(Hashtable args) {
|
||||
return ping();
|
||||
}
|
||||
|
||||
/**
|
||||
* introduces ourself to this remote peer. From then on, caller will be expected
|
||||
* to maintain reasonable uptime
|
||||
* @param destStr our own base64 destination
|
||||
*/
|
||||
public Hashtable hello(String destStr) {
|
||||
node.nodeLoadAfterHit();
|
||||
System.out.println("XMLRPC: hello");
|
||||
return node.hello(destStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* introduces ourself to this remote peer. From then on, caller will be expected
|
||||
* to maintain reasonable uptime
|
||||
* @param args a Hashtable/dict/struct/assoc-array containing:
|
||||
* <ul>
|
||||
* <li>dest - base64 destination (noderef) for the remote peer to add</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Hashtable hello(Hashtable args) {
|
||||
String destStr;
|
||||
System.out.println("XMLRPC: hello");
|
||||
try {
|
||||
destStr = (String)args.get("dest");
|
||||
} catch (Exception e) {
|
||||
destStr = null;
|
||||
}
|
||||
if (destStr == null) {
|
||||
Hashtable res = new Hashtable();
|
||||
res.put("status", "error");
|
||||
res.put("error", "baddest");
|
||||
res.put("summary", "Bad or missing destination");
|
||||
node.nodeLoadAfterHit();
|
||||
return res;
|
||||
}
|
||||
return hello(destStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches node for all data items whose metadata keys match the keys
|
||||
* of the given mapping.
|
||||
* @param criteria a Hashtable (or python dict, etc) of search criteria. Each
|
||||
* 'key' is a metadata item to match, and corresponding value is a regular expression
|
||||
* to match.
|
||||
*/
|
||||
public Hashtable search(Hashtable criteria) {
|
||||
node.nodeLoadAfterHit();
|
||||
System.out.println("XMLRPC: search");
|
||||
System.out.println("XMLRPC: search: "+criteria);
|
||||
return node.search(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list of new content and/or peers which have
|
||||
* been stored on the server since a given time
|
||||
* @param since (int) unixtime in seconds
|
||||
* @param includePeers (int) set to 1 to include 'peers' list in update, 0 to omit
|
||||
* @param includeCatalog (int) set to 1 to include 'items' (catalog) list in
|
||||
* update, 0 to omit
|
||||
*/
|
||||
public Hashtable getUpdate(int since, int includePeers, int includeCatalog) {
|
||||
node.nodeLoadAfterHit();
|
||||
System.out.println("XMLRPC: getUpdate: "+since+" "+includePeers+" "+includeCatalog);
|
||||
return node.getUpdate(since, includePeers, includeCatalog);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list of new content and/or peers which have
|
||||
* been stored on the server since a given time
|
||||
* Wparam args a Hashtable/struct/dict/assoc-array of arguments, including:
|
||||
* <ul>
|
||||
* <li>since - (int) unixtime in seconds</li>
|
||||
* <li>includePeers - (int) set to nonzero to include 'peers' list in update, 0 to omit,
|
||||
* default 0</li>
|
||||
* <li>includeCatalog - (int) set to nonzero to include 'items' (catalog) list in
|
||||
* update, 0 to omit (default 0)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Hashtable getUpdate(Hashtable args) {
|
||||
int since;
|
||||
int includePeers = 0;
|
||||
int includeCatalog = 0;
|
||||
|
||||
// uplift 'since' key from args, or barf if invalid
|
||||
try {
|
||||
since = ((Integer)(args.get("since"))).intValue();
|
||||
} catch (Exception e) {
|
||||
Hashtable res = new Hashtable();
|
||||
res.put("status", "error");
|
||||
res.put("error", "badargument");
|
||||
res.put("summary", "Invalid value for 'since'");
|
||||
node.nodeLoadAfterHit();
|
||||
return res;
|
||||
}
|
||||
|
||||
// uplift 'includePeers' key from args, silently fall back
|
||||
// on default if invalid
|
||||
if (args.containsKey("includePeers")) {
|
||||
try {
|
||||
includePeers = ((Integer)(args.get("includePeers"))).intValue();
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
// uplift 'includeCatalog' key from args, silently fall back
|
||||
// on default if invalid
|
||||
if (args.containsKey("includeCatalog")) {
|
||||
try {
|
||||
includeCatalog = ((Integer)(args.get("includeCatalog"))).intValue();
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
return getUpdate(since, includePeers, includeCatalog);
|
||||
}
|
||||
|
||||
public Vector getJobsList() throws Exception {
|
||||
return node.getJobsList();
|
||||
}
|
||||
|
||||
/**
|
||||
* attempt to retrieve a data item from remote peer
|
||||
* @param key - the key under which the content item is assumedly stored in Q
|
||||
*/
|
||||
public Hashtable getItem(String uri) throws IOException, QException {
|
||||
node.nodeLoadAfterHit();
|
||||
System.out.println("XMLRPC: getItem: "+uri);
|
||||
return node.getItem(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* attempt to retrieve a data item from remote peer
|
||||
* @param args - a Hashtable/struct/dict/assoc-array, containing:
|
||||
* <ul>
|
||||
* <li>key - (string) the key under which the content item is assumedly stored in Q</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Hashtable getItem(Hashtable args) throws IOException, QException {
|
||||
String key;
|
||||
try {
|
||||
key = (String)args.get("key");
|
||||
} catch (Exception e) {
|
||||
Hashtable res = new Hashtable();
|
||||
res.put("status", "error");
|
||||
res.put("error", "badargs");
|
||||
node.nodeLoadAfterHit();
|
||||
return res;
|
||||
}
|
||||
|
||||
return getItem(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* puts an item of content to remote peer
|
||||
* @param args - a Hashtable/struct/dict/assoc-array, containing at least:
|
||||
* <ul>
|
||||
* <li>data - binary - the raw data to insert</li>
|
||||
* </ul>
|
||||
* Any other key/value pairs in this struct will be taken as metadata, and
|
||||
* inserted into the datastore as such.
|
||||
* @return the assigned key for the item, under which the item
|
||||
* can be subsequently retrieved. This key will be inserted into
|
||||
* the metadata
|
||||
*/
|
||||
public Hashtable putItem(Hashtable args)
|
||||
throws IOException, QException
|
||||
{
|
||||
byte [] data;
|
||||
try {
|
||||
data = (byte [])args.get("data");
|
||||
args.remove("data");
|
||||
} catch (Exception e) {
|
||||
Hashtable res = new Hashtable();
|
||||
res.put("status", "error");
|
||||
res.put("error", "baddata");
|
||||
node.nodeLoadAfterHit();
|
||||
return res;
|
||||
}
|
||||
return putItem(args, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* alternative wrapper method which allows data to be a String.
|
||||
* DO NOT USE if the string contains any control chars or bit-7-set chars
|
||||
*/
|
||||
public Hashtable putItem(Hashtable metadata, String data)
|
||||
throws IOException, QException
|
||||
{
|
||||
return putItem(metadata, data.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* alternative wrapper method which allows data to be a String.
|
||||
* DO NOT USE if the string contains any control chars or bit-7-set chars
|
||||
*/
|
||||
public Hashtable putItem(String data)
|
||||
throws IOException, QException
|
||||
{
|
||||
return putItem(data.getBytes());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* puts an item of content to remote peer
|
||||
* Wparam metadata a mapping object containing metadata
|
||||
* @param data raw data to insert
|
||||
* @return the assigned key for the item, under which the item
|
||||
* can be subsequently retrieved. This key will be inserted into
|
||||
* the metadata
|
||||
*/
|
||||
public Hashtable putItem(Hashtable metadata, byte [] data)
|
||||
throws IOException, QException
|
||||
{
|
||||
node.nodeLoadAfterHit();
|
||||
System.out.println("XMLRPC: putItem: "+metadata);
|
||||
return node.putItem(metadata, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* puts an item of data, without metadata, into the network
|
||||
* @param data - binary - the raw data to insert
|
||||
* @return the assigned key for the item
|
||||
*/
|
||||
public Hashtable putItem(byte [] data)
|
||||
throws IOException, QException
|
||||
{
|
||||
node.nodeLoadAfterHit();
|
||||
System.out.println("XMLRPC: putItem (no metadata)");
|
||||
return node.putItem(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the insertion of a qsite. Valid for client nodes only
|
||||
* @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
|
||||
{
|
||||
node.nodeLoadAfterHit();
|
||||
System.out.println("XMLRPC: insertQSite("+privKey64+", "+siteName+", "+rootPath+", "+metadata+")");
|
||||
return node.insertQSite(privKey64, siteName, rootPath, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new keypair for signed-space insertions
|
||||
* @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() {
|
||||
|
||||
return node.newKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* shuts down the node
|
||||
* for the purpose of security, the caller must quote the node's full
|
||||
* base64 private key
|
||||
* @param nodePrivKey the node's full base64 I2P private key
|
||||
* @return if shutdown succeeds, an XML-RPC error will result, because
|
||||
* the node will fail to send a reply. If an invalid key is given,
|
||||
* the reply Hashtable will contain {"status":"error", "error":"invalidkey"}
|
||||
*/
|
||||
public Hashtable shutdown(String nodePrivKey) {
|
||||
|
||||
Hashtable res = new Hashtable();
|
||||
|
||||
// sekkret h4x - kill the VM if key is the node's I2P base64 privkey
|
||||
//System.out.println("shutdown: our privkey="+node.privKeyStr);
|
||||
//System.out.println("shutdown: nodePrivKey="+nodePrivKey);
|
||||
if (nodePrivKey.equals(node.privKeyStr)) {
|
||||
// get a runtime
|
||||
System.out.println("Node at "+node.dataDir+" shutting down");
|
||||
Runtime r = Runtime.getRuntime();
|
||||
|
||||
// and terminate the vm
|
||||
r.exit(0);
|
||||
//r.halt(0);
|
||||
}
|
||||
else {
|
||||
res.put("status", "error");
|
||||
res.put("error", "invalidkey");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* shuts down the node
|
||||
* for the purpose of security, the caller must quote the node's full
|
||||
* base64 private key
|
||||
* @param args - a Hashtable/struct/dict/assoc-array, containing:
|
||||
* <ul>
|
||||
* <li>privKey - string - the node's full base64 I2P private key</li>
|
||||
* </ul>
|
||||
* @return if shutdown succeeds, an XML-RPC error will result, because
|
||||
* the node will fail to send a reply. If an invalid key is given,
|
||||
* the reply Hashtable will contain {"status":"error", "error":"invalidkey"}
|
||||
*/
|
||||
public Hashtable shutdown(Hashtable args) {
|
||||
String privKey;
|
||||
try {
|
||||
privKey = (String)args.get("privKey");
|
||||
} catch (Exception e) {
|
||||
Hashtable res = new Hashtable();
|
||||
res.put("status", "error");
|
||||
res.put("error", "badkey");
|
||||
node.nodeLoadAfterHit();
|
||||
return res;
|
||||
}
|
||||
return shutdown(privKey);
|
||||
}
|
||||
}
|
||||
|
||||
149
apps/q/java/src/net/i2p/aum/q/QServerNode.java
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* QServer.java
|
||||
*
|
||||
* Created on 20 March 2005, 23:23
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.aum.http.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* Implements Q Server nodes.
|
||||
*/
|
||||
public class QServerNode extends QNode {
|
||||
|
||||
/**
|
||||
* default datastore directory
|
||||
*/
|
||||
public static String defaultStoreDir = ".quartermaster_server";
|
||||
|
||||
/**
|
||||
* can set this to 0 before instantiating servers, to set tunnel length
|
||||
* for debugging purposes
|
||||
**/
|
||||
public static int tunLength = 2;
|
||||
|
||||
public I2PXmlRpcServerFactory xmlRpcServerFactory;
|
||||
|
||||
public String nodeType = "Server";
|
||||
|
||||
/** Creates a new instance of QServer */
|
||||
public QServerNode() throws IOException, DataFormatException, I2PException
|
||||
{
|
||||
super(System.getProperties().getProperty("user.home") + sep + defaultStoreDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Q node in server mode, using specified datastore directory
|
||||
* @param dataDir absolute pathname where this server's datastore tree is
|
||||
* located. If tree doesn't exist, it will be created along with new keys
|
||||
*/
|
||||
public QServerNode(String dataDir) throws IOException, DataFormatException, I2PException
|
||||
{
|
||||
super(dataDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* performs mode-specific node setup
|
||||
*/
|
||||
public void setup() throws DataFormatException, I2PException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets up and launches an xml-rpc 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 {
|
||||
/**
|
||||
* // get a server factory if none already existing
|
||||
* if (xmlRpcServerFactory == null) {
|
||||
* getTunnelLength();
|
||||
* log.info("Creating an xml-rpc server factory with tunnel length "+tunLength);
|
||||
* xmlRpcServerFactory = new I2PXmlRpcServerFactory(
|
||||
* tunLength, tunLength, tunLength, tunLength, i2p);
|
||||
* }
|
||||
*
|
||||
* log.info("Creating XML-RPC server listening within i2p");
|
||||
* xmlRpcServer = xmlRpcServerFactory.newServer(privKey);
|
||||
*
|
||||
* // bind in our interface class
|
||||
* log.info("Binding XML-RPC interface object");
|
||||
* xmlRpcServer.addHandler(baseXmlRpcServiceName, methods);
|
||||
*
|
||||
* // and fire it up
|
||||
* log.info("Launching XML-RPC server");
|
||||
* xmlRpcServer.start();
|
||||
**/
|
||||
|
||||
Properties httpProps = new Properties();
|
||||
|
||||
httpProps = new Properties();
|
||||
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);
|
||||
|
||||
// create in-i2p http server for xmlrpc and browser access
|
||||
MiniHttpServer 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);
|
||||
|
||||
}
|
||||
|
||||
public void getTunnelLength()
|
||||
{
|
||||
String tunLenStr = System.getProperty("quartermaster.tunnelLength");
|
||||
if (tunLenStr == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tunLength = new Integer(tunLenStr).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
QServerNode node;
|
||||
|
||||
try {
|
||||
if (args.length > 0) {
|
||||
node = new QServerNode(args[0]);
|
||||
}
|
||||
else {
|
||||
node = new QServerNode();
|
||||
}
|
||||
node.log.info("QServerNode: entering endless loop...");
|
||||
while (true) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
136
apps/q/java/src/net/i2p/aum/q/QTest.java
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* QTest.java
|
||||
*
|
||||
* Created on March 23, 2005, 11:34 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author david
|
||||
*/
|
||||
public class QTest {
|
||||
|
||||
QServerNode server;
|
||||
|
||||
QClientNode client;
|
||||
|
||||
/** Creates a new instance of QTest */
|
||||
public QTest() {
|
||||
}
|
||||
|
||||
/**
|
||||
* performs a series of tests on client node
|
||||
*/
|
||||
public void testClientNode()
|
||||
throws IOException, DataFormatException, I2PException, QException
|
||||
{
|
||||
print("Creating new client node");
|
||||
QClientNode node = new QClientNode();
|
||||
|
||||
print("Starting node background stuff");
|
||||
node.start();
|
||||
|
||||
print("Inserting new plain hash data item");
|
||||
byte [] data = "Hello, world".getBytes();
|
||||
Hashtable meta = new Hashtable();
|
||||
meta.put("title", "simple test");
|
||||
meta.put("type", "text");
|
||||
meta.put("path", "/test.txt");
|
||||
Hashtable res = node.putItem(meta, data);
|
||||
print("putItem result="+res);
|
||||
if (!res.get("status").equals("ok")) {
|
||||
print("putItem fail: error="+res.get("error"));
|
||||
node.interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
String uri = (String)res.get("uri");
|
||||
print("putItem successful: uri="+uri);
|
||||
|
||||
print("now attempting to retrieve");
|
||||
Hashtable res1 = node.getItem(uri);
|
||||
print("getItem: result="+res1);
|
||||
if (!res1.get("status").equals("ok")) {
|
||||
print("getItem fail: error="+res.get("error"));
|
||||
node.interrupt();
|
||||
return;
|
||||
}
|
||||
byte [] data1 = (byte [])res1.get("data");
|
||||
String dataStr = new String(data1);
|
||||
print("getItem: success, data="+dataStr);
|
||||
|
||||
print("now searching for what we just inserted");
|
||||
Hashtable crit = new Hashtable();
|
||||
crit.put("type", "text");
|
||||
Hashtable res1a = node.search(crit);
|
||||
print("After search: res="+res1a);
|
||||
|
||||
print("now creating a keypair");
|
||||
Hashtable keys = node.newKeys();
|
||||
String pub = (String)keys.get("publicKey");
|
||||
String priv = (String)keys.get("privateKey");
|
||||
print("public="+pub);
|
||||
print("private="+priv);
|
||||
|
||||
print("Inserting new secure space data item");
|
||||
byte [] data2 = "The quick brown fox".getBytes();
|
||||
Hashtable meta2 = new Hashtable();
|
||||
meta2.put("title", "simple test 2");
|
||||
meta2.put("type", "text");
|
||||
meta2.put("path", "/test.txt");
|
||||
meta2.put("privateKey", priv);
|
||||
Hashtable res2 = node.putItem(meta2, data2);
|
||||
print("putItem result="+res2);
|
||||
if (!res2.get("status").equals("ok")) {
|
||||
print("putItem fail: error="+res2.get("error"));
|
||||
node.interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
String uri2 = (String)res2.get("uri");
|
||||
print("putItem successful: uri="+uri2);
|
||||
|
||||
print("now attempting to retrieve");
|
||||
Hashtable res2a = node.getItem(uri2);
|
||||
print("getItem: result="+res2a);
|
||||
if (!res2a.get("status").equals("ok")) {
|
||||
print("getItem fail: error="+res.get("error"));
|
||||
node.interrupt();
|
||||
return;
|
||||
}
|
||||
byte [] data2a = (byte [])res2a.get("data");
|
||||
String dataStr2a = new String(data2a);
|
||||
print("getItem: success, data="+dataStr2a);
|
||||
|
||||
}
|
||||
|
||||
public void print(String msg) {
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
QTest test = new QTest();
|
||||
try {
|
||||
test.testClientNode();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
98
apps/q/java/src/net/i2p/aum/q/QUtil.java
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* QUtil.java
|
||||
*
|
||||
* Created on April 6, 2005, 2:11 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* A general collection of static utility methods
|
||||
*/
|
||||
public class QUtil {
|
||||
|
||||
public static boolean debugEnabled = true;
|
||||
|
||||
/**
|
||||
* Generates a new secure space public/private keypair
|
||||
* @return an array of 2 strings, first one is SSK Public Key, second one
|
||||
* is SSK Private Key.
|
||||
*/
|
||||
public static String [] newKeys() {
|
||||
Object [] keypair = I2PAppContext.getGlobalContext().keyGenerator().generateSigningKeypair();
|
||||
SigningPublicKey pub = (SigningPublicKey)keypair[0];
|
||||
SigningPrivateKey priv = (SigningPrivateKey)keypair[1];
|
||||
String [] sskKeypair = new String[2];
|
||||
sskKeypair[0] = hashPubKey(pub);
|
||||
sskKeypair[1] = priv.toBase64();
|
||||
return sskKeypair;
|
||||
}
|
||||
|
||||
/**
|
||||
* converts a signed space private key (in base64)
|
||||
* to its base64 ssk public equivalent
|
||||
* @param priv64 SSK private key string as base64
|
||||
* @return public key, base64-encoded
|
||||
*/
|
||||
public static String privateToPubHash(String priv)
|
||||
throws DataFormatException
|
||||
{
|
||||
return hashPubKey(new SigningPrivateKey(priv).toPublic());
|
||||
}
|
||||
|
||||
public static SigningPublicKey privateToPublic(String priv64)
|
||||
throws DataFormatException
|
||||
{
|
||||
SigningPrivateKey priv = new SigningPrivateKey(priv64);
|
||||
SigningPublicKey pub = priv.toPublic();
|
||||
return pub;
|
||||
}
|
||||
|
||||
public static String hashPubKey(String pub64)
|
||||
throws DataFormatException
|
||||
{
|
||||
return hashPubKey(new SigningPublicKey(pub64));
|
||||
}
|
||||
|
||||
/**
|
||||
* hashes a public key for use in signed space keypairs
|
||||
* possibly shorten this
|
||||
*/
|
||||
public static String hashPubKey(SigningPublicKey pub) {
|
||||
String hashed = sha64(pub.toByteArray());
|
||||
String abbrev = hashed.substring(0, 24);
|
||||
return abbrev;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns base64 of sha hash of a string
|
||||
*/
|
||||
public static String sha64(String raw) {
|
||||
return sha64(raw.getBytes());
|
||||
}
|
||||
|
||||
public static String sha64(byte [] raw) {
|
||||
//return stripEquals(Base64.encode(sha(raw)));
|
||||
return Base64.encode(sha(raw)).replaceAll("[=]", "");
|
||||
}
|
||||
|
||||
public static byte [] sha(String raw) {
|
||||
return sha(raw.getBytes());
|
||||
}
|
||||
|
||||
public static byte [] sha(byte [] raw) {
|
||||
return I2PAppContext.getGlobalContext().sha().calculateHash(raw).getData();
|
||||
}
|
||||
|
||||
public static void debug(String s) {
|
||||
if (debugEnabled) {
|
||||
System.out.println("QSSL:"+s);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
327
apps/q/java/src/net/i2p/aum/q/QWorkerThread.java
Normal file
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* QWorkerThread.java
|
||||
*
|
||||
* Created on April 17, 2005, 2:44 PM
|
||||
*/
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
|
||||
/**
|
||||
* Thread which performs a single background job for a nod
|
||||
*/
|
||||
|
||||
class QWorkerThread extends Thread {
|
||||
|
||||
QNode node;
|
||||
Hashtable job;
|
||||
String jobTime;
|
||||
String peerId;
|
||||
String jobDesc;
|
||||
|
||||
/*
|
||||
* Creates this thread for executing a background job for the node
|
||||
* @param node the node for which this job is to run
|
||||
* @param jobTime unixtime-milliseconds at which job is to run,
|
||||
* represented as string because it denotes a file in the node's jobs dir
|
||||
*/
|
||||
public QWorkerThread(QNode node, String jobTime) {
|
||||
this.node = node;
|
||||
this.jobTime = jobTime;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
node.log.info("worker: executing job: "+jobTime);
|
||||
|
||||
// reconstitute the job from its serialisation in jobs directory
|
||||
job = node.loadJob(jobTime);
|
||||
jobDesc = node.loadJobDescription(jobTime);
|
||||
|
||||
// a couple of details
|
||||
String cmd = (String)job.get("cmd");
|
||||
peerId = (String)job.get("peerId");
|
||||
|
||||
// dispatch off to required handler routine
|
||||
if (cmd.equals("getUpdate")) {
|
||||
doGetUpdate();
|
||||
}
|
||||
else if (cmd.equals("hello")) {
|
||||
doHello();
|
||||
}
|
||||
else if (cmd.equals("localPutItem")) {
|
||||
doLocalPutItem();
|
||||
}
|
||||
else if (cmd.equals("uploadItem")) {
|
||||
doUploadItem();
|
||||
}
|
||||
else if (cmd.equals("test")) {
|
||||
doTest();
|
||||
}
|
||||
else {
|
||||
node.log.error("workerthread.run: unrecognised command '"+cmd+"'");
|
||||
System.out.println("workerthread.run: unrecognised command '"+cmd+"'");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
node.log.warn("worker thread crashed");
|
||||
}
|
||||
|
||||
// finished (or failed), so replenish the jobs pool
|
||||
node.threadPool.release();
|
||||
|
||||
// and remove the job record and description
|
||||
try {
|
||||
new File(node.jobsDir + node.sep + jobTime).delete();
|
||||
new File(node.jobsDir + node.sep + jobTime + ".desc").delete();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void doTest() throws Exception {
|
||||
|
||||
String msg = (String)job.get("msg");
|
||||
System.out.println("TESTJOB: msg='"+msg+"'");
|
||||
}
|
||||
|
||||
public void doLocalPutItem() throws Exception {
|
||||
Hashtable metadata = (Hashtable)job.get("metadata");
|
||||
String path = (String)job.get("localDataFilePath");
|
||||
SimpleFile f = new SimpleFile(path, "r");
|
||||
byte [] data = f.readBytes();
|
||||
|
||||
System.out.println("doLocalPutItem: path='"+path+"' dataLen="+data.length+" metadata="+metadata);
|
||||
node.putItem(metadata, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Upload a locally-inserted data item to n remote hubs.</p>
|
||||
* <p>This is one intricate algorithm. The aim is to upload the content
|
||||
* item to the 3 peers which are closest (Kademlia-wise) to the item's URI.
|
||||
* Some requirements include:
|
||||
* <ul>
|
||||
* <li>If we discover new peers over time, we have to consider these peers
|
||||
* as upload targets</li>
|
||||
* <li>If upload to an individual peer fails, we have to retry a few times</li>
|
||||
* <li>If there aren't enough viable peers yet, we need to keep rescheduling this
|
||||
* job till enough peers come online</li>
|
||||
* <li>Don't hog a thread slot on the jobs queue, give other jobs a chance to run</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public void doUploadItem() throws QException {
|
||||
QDataItem item = (QDataItem)job.get("item");
|
||||
String uri = (String)item.get("uri");
|
||||
String desc = "uploadItem:uri="+uri;
|
||||
byte [] data = item._data;
|
||||
|
||||
Hashtable peersUploaded = (Hashtable)job.get("peersUploaded");
|
||||
Hashtable peersPending = (Hashtable)job.get("peersPending");
|
||||
Hashtable peersFailed = (Hashtable)job.get("peersFailed");
|
||||
Hashtable peersNumTries = (Hashtable)job.get("peersNumTries");
|
||||
|
||||
String itemHash = item.getStoreFilename();
|
||||
QPeer peerRec;
|
||||
|
||||
// get current list of up to 100 closest peers to item's URI
|
||||
Vector cPeers = node.peersClosestTo(uri, 100);
|
||||
|
||||
// loop on this list, try to upload item to n of them
|
||||
for (Enumeration en = cPeers.elements(); en.hasMoreElements();) {
|
||||
QPeer peer = (QPeer)en.nextElement();
|
||||
String peerId = peer.getId();
|
||||
|
||||
// skip this peer if we've already succeeded or failed with it
|
||||
if (peersFailed.containsKey(peerId) || peersUploaded.containsKey(peerId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if there are less than 3 or more pending peers, add this peer to
|
||||
// pending list, otherwise skip it
|
||||
if (!peersPending.containsKey(peerId)) {
|
||||
if (peersPending.size() < 3) {
|
||||
peersPending.put(peerId, "");
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// try to insert item to this peer
|
||||
boolean uploadedOk;
|
||||
try {
|
||||
Hashtable res = node.peerPutItem(peerId, item, item._data);
|
||||
if (res.containsKey("status") && ((String)res.get("status")).equals("ok")) {
|
||||
// successful upload
|
||||
uploadedOk = true;
|
||||
} else {
|
||||
// upload failed for some reason
|
||||
uploadedOk = false;
|
||||
System.out.println("upload failure:"+res);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// possibly because peer is offline or presently unreachable
|
||||
uploadedOk = false;
|
||||
e.printStackTrace();
|
||||
System.out.println("upload failure");
|
||||
}
|
||||
|
||||
// how'd the upload go?
|
||||
if (uploadedOk) {
|
||||
// successful - remove from pending list, add to success list
|
||||
peersPending.remove(peerId);
|
||||
peersNumTries.remove(peerId);
|
||||
peersUploaded.put(peerId, "");
|
||||
|
||||
// have we successfully uploaded to 3 or more peers yet?
|
||||
if (peersUploaded.size() >= 3) {
|
||||
// yep, this job has now run its course and can expire
|
||||
return;
|
||||
} else {
|
||||
// bust out so we don't hog a scheduler slot
|
||||
node.runAfter(5000, job, desc);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// insert failed
|
||||
// increment retry count, fail this peer if retries maxed out
|
||||
int numTries = ((Integer)peersNumTries.get(peerId)).intValue() + 1;
|
||||
if (numTries > 4) {
|
||||
// move peer from pending list to failed list
|
||||
peersPending.remove(peerId);
|
||||
peersNumTries.remove(peerId);
|
||||
peersFailed.put(peerId, "");
|
||||
}
|
||||
|
||||
// bust out so we don't hog a scheduler slot
|
||||
node.runAfter(30000, job, desc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// we'return out of peers, reschedule this job to retry in an hour's time
|
||||
node.runAfter(3600000, job, desc);
|
||||
}
|
||||
|
||||
public void doHello() {
|
||||
QPeer peerRec = (QPeer)node.peers.get(peerId);
|
||||
|
||||
node.log.debug("doHello: "+node.id+" -> "+peerId);
|
||||
|
||||
try {
|
||||
// execute peers list req on peer
|
||||
Hashtable result = node.peerHello(peerId, node.destStr);
|
||||
|
||||
// see what happened
|
||||
String status = (String)result.get("status");
|
||||
if (status.equals("ok")) {
|
||||
peerRec.markAsGreeted();
|
||||
|
||||
// and, schedule in regular peersList updates
|
||||
node.schedulePeerUpdateJob(peerRec);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
node.log.warn("Got an xmlrpc client failure, trying again in 1 hour", e);
|
||||
|
||||
// schedule another attempt in 2 hours
|
||||
Hashtable job = new Hashtable();
|
||||
job.put("cmd", "hello");
|
||||
job.put("peerId", peerId);
|
||||
node.runAfter(3600000, job, "hello:peerId="+peerId);
|
||||
}
|
||||
}
|
||||
|
||||
public void doGetUpdate() {
|
||||
QPeer peerRec = (QPeer)node.peers.get(peerId);
|
||||
int timeLastPeersUpdate = peerRec.getTimeLastUpdate();
|
||||
int timeNextContact;
|
||||
int doCatalog = ((Integer)(job.get("includeCatalog"))).intValue();
|
||||
int doPeers = ((Integer)(job.get("includePeers"))).intValue();
|
||||
Vector peers;
|
||||
Vector items;
|
||||
|
||||
node.log.info("doGetUpdate: "+node.id+" -> "+peerId);
|
||||
|
||||
try {
|
||||
// execute peers list req on peer
|
||||
Hashtable result = node.peerGetUpdate(
|
||||
peerId, timeLastPeersUpdate, doPeers, doCatalog);
|
||||
|
||||
// see what happened
|
||||
String status = (String)result.get("status");
|
||||
if (status.equals("ok")) {
|
||||
|
||||
node.log.debug("doGetUpdate: successful, result="+result);
|
||||
|
||||
int i;
|
||||
|
||||
// success - add all new peers
|
||||
peers = (Vector)result.get("peers");
|
||||
int npeers = peers.size();
|
||||
for (i=0; i<npeers; i++) {
|
||||
String destStr = (String)peers.get(i);
|
||||
String destId = node.destToId(destStr);
|
||||
// test if this is a new dest
|
||||
if (!node.peers.containsKey(destId)) {
|
||||
node.log.debug("doGetPeerList: adding new peer "+destId);
|
||||
node.newPeer(destStr);
|
||||
}
|
||||
else {
|
||||
node.log.debug("doGetPeerList: we already know peer "+destId);
|
||||
}
|
||||
}
|
||||
|
||||
// also add all new items
|
||||
items = (Vector)result.get("items");
|
||||
int nitems = items.size();
|
||||
for (i=0; i<nitems; i++) {
|
||||
Hashtable metadata = (Hashtable)(items.get(i));
|
||||
node.addMetadataToCatalog(metadata);
|
||||
String key = (String)metadata.get("key");
|
||||
node.addItemLocation(key, peerId);
|
||||
}
|
||||
|
||||
// get time of next update
|
||||
int nextTime = ((Integer)result.get("timeNextContact")).intValue();
|
||||
int lastTime = ((Integer)result.get("timeUpdateEnds")).intValue();
|
||||
peerRec.setTimeNextContact(nextTime);
|
||||
peerRec.setTimeLastUpdate(lastTime);
|
||||
|
||||
//System.out.println("doGetUpdate: timeNextContact="+nextTime);
|
||||
|
||||
// schedule another job at recommended time
|
||||
Hashtable job = new Hashtable();
|
||||
job.put("cmd", "getUpdate");
|
||||
job.put("peerId", peerId);
|
||||
job.put("includePeers", new Integer(doPeers));
|
||||
job.put("includeCatalog", new Integer(doCatalog));
|
||||
node.runAt(((long)nextTime)*1000, job, "getUpdate:peerId="+peerId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
node.log.warn("xmlrpc client failure, rescheduling 180 secs from now", e);
|
||||
|
||||
// schedule another attempt in 30 secs
|
||||
Hashtable job = new Hashtable();
|
||||
job.put("cmd", "getUpdate");
|
||||
job.put("peerId", peerId);
|
||||
job.put("includePeers", new Integer(doPeers));
|
||||
job.put("includeCatalog", new Integer(doCatalog));
|
||||
node.runAfter(180000, job, "getUpdate:peerId="+peerId);
|
||||
}
|
||||
|
||||
//peerRec.setProperty("timeLastPeersUpdate", "0");
|
||||
//peerRec.setProperty("timeLastContentUpdate", "0");
|
||||
//peerRec.setProperty("timeLastContact", "0");
|
||||
//peerRec.setProperty("timeNextContact", "0");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
59
apps/q/java/src/net/i2p/aum/test/HttpServerTest.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* HttpServerTest.java
|
||||
* Created on April 9, 2005, 2:59 AM
|
||||
*/
|
||||
package net.i2p.aum.test;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.aum.http.*;
|
||||
|
||||
public class HttpServerTest extends MiniHttpRequestHandlerBase {
|
||||
|
||||
public HttpServerTest(Object socket) throws Exception {
|
||||
super(socket);
|
||||
}
|
||||
|
||||
public void on_GET() throws Exception {
|
||||
|
||||
setContentType("text/html");
|
||||
setServer("aum's MiniHttpServer demo default - GET");
|
||||
head.nest("title").raw("aum's MiniHttpServer demo - GET");
|
||||
body.nest("h1")
|
||||
.raw("aum MiniHttpServer demo - GET");
|
||||
body.raw("You requested: "+reqFile)
|
||||
.br().br()
|
||||
.nest("form action=foo.html method=POST")
|
||||
.add("input type=submit name=doit value=doit")
|
||||
.br()
|
||||
.add("input type=text name=fred value=blah")
|
||||
.br()
|
||||
.nest("textarea name=mary")
|
||||
.raw("red green");
|
||||
body.br()
|
||||
.add(dumpVars());
|
||||
}
|
||||
|
||||
public void on_POST() {
|
||||
setContentType("text/html");
|
||||
setServer("aum's MiniHttpServer demo default - POST");
|
||||
|
||||
head.nest("title").raw("aum's MiniHttpServer demo - POST");
|
||||
body.nest("h1").raw("aum MiniHttpServer demo - POST");
|
||||
body.raw("You requested: "+reqFile)
|
||||
.br().br()
|
||||
.nest("form action=foo.html method=POST")
|
||||
.add("input type=submit name=doit value=doit")
|
||||
.br()
|
||||
.add("input type=text name=fred value=blah")
|
||||
.br()
|
||||
.nest("textarea name=mary")
|
||||
.raw("red green");
|
||||
body.br()
|
||||
.add(dumpVars());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
MiniHttpServer serv = new MiniHttpServer(HttpServerTest.class, 18000);
|
||||
serv.run();
|
||||
}
|
||||
}
|
||||
61
apps/q/java/src/net/i2p/aum/util/Ico2Java.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package net.i2p.aum.util;
|
||||
|
||||
import java.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.lang.*;
|
||||
|
||||
/**
|
||||
* development utility - converts Favicon.ico to
|
||||
* a java source file net.i2p.aum.q.Favicon.java, containing
|
||||
* the favicon image
|
||||
*/
|
||||
public class Ico2Java {
|
||||
|
||||
public static void convertToJava(String name) throws Exception {
|
||||
|
||||
File f = new File(name);
|
||||
|
||||
byte [] image = new byte[(int)f.length()];
|
||||
InputStream fi = new FileInputStream(f);
|
||||
fi.read(image);
|
||||
|
||||
// ok, now generate a java file
|
||||
String basename = name.substring(0, name.length()-4);
|
||||
String jName = "src/net/i2p/aum/q/" + basename + ".java";
|
||||
FileWriter fo = new FileWriter(jName);
|
||||
fo.write("package net.i2p.aum.q;\n");
|
||||
fo.write("public class "+basename+" {\n");
|
||||
fo.write(" public static byte [] image = {");
|
||||
for (int i=0; i<image.length; i++) {
|
||||
if (i % 16 == 0) {
|
||||
fo.write("\n ");
|
||||
}
|
||||
fo.write(String.valueOf(image[i])+", ");
|
||||
}
|
||||
|
||||
fo.write("\n };\n");
|
||||
fo.write("}\n");
|
||||
fo.close();
|
||||
}
|
||||
|
||||
public static void main(String [] args) {
|
||||
if (args.length != 1) {
|
||||
System.out.println("Usage: ico2java filename.ico");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
File f = new File(args[0]);
|
||||
if (!f.isFile()) {
|
||||
System.out.println("No such file '"+args[0]+"'");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
convertToJava(args[0]);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||