Compare commits

...

214 Commits

Author SHA1 Message Date
2a272f465c * 2005-09-17 0.6.0.6 released
2005-09-17  jrandom
    * Clean up syndie a bit more and bundle a default introductory post with
      both new installs and updates.
    * Typo fixes on the console (thanks bar!)
2005-09-18 01:29:58 +00:00
a8ecd32b45 2005-09-17 jrandom
* Updated the bandwidth limiter to use two tiers of bandwidth - our normal
      steady state rate, plus a new limit on how fast we transfer when
      bursting.  This is different from the old "burst as fast as possible
      until we're out of tokens" policy, and should help those with congested
      networks.  See /config.jsp to manage this rate.
    * Bugfixes in Syndie to handle missing cache files (no data was lost, the
      old posts just didn't show up).
    * Log properly in EepPost
2005-09-17 23:01:44 +00:00
20c42a175d 2005-09-17 jrandom
* Bugfixes in Syndie to handle missing cache files (no data was lost, the
      old posts just didn't show up).
    * Log properly in EepPost
2005-09-17 20:08:25 +00:00
d6c3ffde87 2005-09-17 jrandom
* Added the natively compiled jbigi and patched java service wrapper for
      OS X.  Thanks Bill Dorsey for letting me use your machine!
    * Don't build i2p.exe or i2pinstall.exe when run on OS X machines, as we
      don't bundle the binutils necessary (and there'd be a naming conflict
      if we did).
    * Added 'single user' functionality to syndie - if the single user
      checkbox on the admin page is checked, all users are allowed to control
      the instance and sync up with remote syndie nodes.
    * Temporarily disable the x-i2p-gzip in i2ptunnel until it is more closely
      debugged.
2005-09-17 07:31:48 +00:00
177e0ae6a3 2005-09-16 jrandom
* Reject unroutable IPs in SSU like we do for the TCP transport (unless
      you have i2np.udp.allowLocal=true defined - useful for private nets)
2005-09-16 21:24:42 +00:00
dab1b4d256 2005-09-16 jrandom
* Adjust I2PTunnelHTTPServer so it can be used for outproxy operators
      (just specify the spoofed host as an empty string), allowing them to
      honor x-i2p-gzip encoding.
    * Let windows users build the exes too (thanks bar and redzara!)
    * Allow I2PTunnel httpserver operators to disable gzip compression on
      individual tunnels with the i2ptunnel.gzip=false client option
      (good idea susi!)
2005-09-16 18:28:26 +00:00
3aba12631b use the logger, not stdout/stderr 2005-09-16 06:58:55 +00:00
cfee6430d4 xml 2005-09-16 04:54:48 +00:00
6b96df1cec runplain.sh, not startRouter.sh 2005-09-16 04:50:28 +00:00
deecfa5047 no message 2005-09-16 04:40:07 +00:00
6ca3f01038 launch4j 2005-09-16 04:34:59 +00:00
d89f589f2b 2005-09-16 jrandom
* Added the i2p.exe and i2pinstall.exe for windows users, using launch4j.
    * Added runplain.sh for *nix/osx users having problems using the java
      service wrapper (called from the install dir as: sh runplain.sh)
    * Bundle susidns and syndie, with links on the top nav
    * Have I2PTunnelHTTPClient and I2PTunnelHTTPServer use the x-i2p-gzip
      content-encoding (if offered), reducing the payload size before it
      reaches the streaming lib.  The existing compression is at the i2cp
      level, so we've been packetizing 4KB of uncompressed data and then
      compressing those messages, rather than compressing and then packetizing
      4KB of compressed data.  This should reduce the number of round trips
      to fetch web pages substantially.
    * Adjust the startup and timing of the addressbook so that susidns always
      has config to work off, and expose a method for susidns to tell it to
      reload its config and rerun.
2005-09-16 04:12:24 +00:00
8c1895e04f imported fixed susidns 2005-09-16 04:04:40 +00:00
c3d0132a98 test cvs again... 2005-09-15 05:57:28 +00:00
d955279d17 minor news (and test cvs...) 2005-09-15 05:56:10 +00:00
76266dce0d 2005-09-15 jrandom
* Error handling for failed intro packets (thanks red.hand!)
    * More carefully verify intro addresses
2005-09-15 05:39:31 +00:00
5694206b35 2005-09-13 jrandom
* More careful error handling with introductions (thanks dust!)
    * Fix the forceIntroducers checkbox on config.jsp (thanks Complication!)
    * Hide the shitlist on the summary so it doesn't confuse new users.
2005-09-13 23:02:35 +00:00
4293a18726 2005-09-12 comwiz
* Migrated the router tests to junit
2005-09-13 09:06:07 +00:00
9865af4174 2005-09-12 jrandom
* Removed guaranteed delivery mode entirely (so existing i2phex clients
      using it can get the benefits of mode=best_effort).  Guaranteed delivery
      is offered at the streaming lib level.
    * Improve the peer selection code for peer testing, as everyone now
      supports tests.
    * Give the watchdog its fangs - if it detects obscene job lag or if
      clients have been unable to get a leaseSet for more than 5 minutes,
      restart the router.  This was disabled a year ago due to spurious
      restarts, and can be disabled by "watchdog.haltOnHang=false", but the
      cause of the spurious restarts should be gone.
2005-09-13 03:32:29 +00:00
c8c109093d 2005-09-12 jrandom
* Bugfix for skewed store which could kill a UDP thread (causing complete
      comm failure and eventual OOM)
2005-09-13 01:12:43 +00:00
b5784d6025 2005-09-12 jrandom
* More aggressively publish updated routerInfo.
    * Expose the flag to force SSU introductions on the router console
    * Don't give people the option to disable SNTP time sync, at least not
      through the router console, because there is no reason to disable it.
      No, not even if your OS is "ntp synced", because chances are, its not.
2005-09-13 00:11:56 +00:00
31bdb8909a tino.i2p and fproxy.tino.i2p 2005-09-12 22:40:17 +00:00
ee921c22ae use the low level rates (thanks bar / complication) 2005-09-12 02:58:13 +00:00
172ffd0434 use the OS time, since it doesn't skew as much (especially on startup) 2005-09-11 04:37:15 +00:00
d9b4406c09 2005-09-10 jrandom
* Test the router's reachability earlier and more aggressively
    * Use the low level bandwidth limiter's rates for the router console, and
      if the router has net.i2p.router.transport.FIFOBandwidthLimiter=INFO in
      the logger config, keep track of the 1 second transfer rates as the stat
      'bw.sendBps1s' and 'bw.recvBps1s', allowing closer monitoring of burst
      behavior.
2005-09-11 03:22:51 +00:00
8ac0e85df4 updated to hq.postman.i2p 2005-09-11 01:40:15 +00:00
249ccd5e3c now that its all implemented... 2005-09-10 23:18:41 +00:00
727d76d43e deal with posts containing no tags by using the implicit tag "[none]" (thanks ardvark!) 2005-09-10 06:07:25 +00:00
44770b7c07 2005-09-09 jrandom
* Added preliminary support for NAT hole punching through SSU introducers
    * Honor peer test results from peers that we have an SSU session with if
      those sessions are idle for 3 minutes or more.
2005-09-10 04:30:36 +00:00
b5d571c75f 2005-09-09 cervantes
* New build due to change in build number :P (thanks ugha!)
2005-09-10 01:13:49 +00:00
da56d83716 First pass at a new naming system. Probably the last as well. So sad :). 2005-09-09 19:38:43 +00:00
f777e213ce search.i2p 2005-09-08 05:08:33 +00:00
79906f5a7d added search.i2p 2005-09-08 05:01:01 +00:00
54074e76b5 2005-09-07 BarkerJr
* HTML cleanup for the router console (thanks!)
2005-09-07  jrandom
    * Lay the foundation for 'client routers' - the ability for peers to opt
      out of participating in tunnels entirely due to firewall/NAT issues.
      Individual routers have control over where those peers are used in
      tunnels - in outbound or inbound, exploratory or client tunnels, or
      none at all.  The defaults with this build are to simply act as before -
      placing everyone as potential participants in any tunnel.
    * Another part of the foundation includes the option for netDb
      participants to refuse to answer queries regarding peers who are marked
      as unreachable, though this too is disabled by default (meaning the
      routerInfo is retrievable from the netDb).
2005-09-07 22:31:11 +00:00
c2ea8db683 Look for names in privatehosts.txt as well as userhosts.txt and hosts.txt. 2005-09-07 02:07:41 +00:00
744671a518 Adjusted wording on the bandwidth limiter controls to reflect new router defaults 2005-09-07 01:13:56 +00:00
7f5b127bbc fix0rz. 2005-09-06 20:45:21 +00:00
89eff0c628 tyop 2005-09-06 20:35:28 +00:00
177aeebb1c stuff 2005-09-06 20:15:55 +00:00
e0e6bde4a5 throw css around like mad (very minimal stylesheet in place) 2005-09-06 20:05:09 +00:00
e6b145716f allow publishing to a remote archive automatically when posting (optionally) with 0 additional clicks
allow transparently attaching any 'public' pet names in your addressbook to a blog post (with a checkbox)
2005-09-06 03:03:55 +00:00
f958342704 admin page - no more editing config props manually (w3wt) 2005-09-05 20:53:25 +00:00
5a1f738505 2005-09-05 jrandom
* Expose the HTTP headers to EepGet status listeners
    * Handle DSA key failures properly (if the signature is not invertable, it
      is obviously invalid)
2005-09-05 19:29:55 +00:00
8147cdf40c 2005-09-05 jrandom
* Expose the HTTP headers to EepGet status listeners
    * Handle DSA key failures properly (if the signature is not invertable, it
      is obviously invalid)
also, syndie now properly detects whether the remote archive can send a filtered export.zip
by examining the HTTP headers for X-Syndie-Export-Capable: true.  If the remote archive
does not set that header (and neither freesites, nor apache or anything other than the ArchiveServlet will),
it uses individual HTTP requests for individual blog posts and metadata fetches.
2005-09-05 19:27:08 +00:00
6afc64ac39 deal with locations that have : in them (aka http://glog.i2p/archive/archive.txt) 2005-09-05 17:09:19 +00:00
61b8e3598b added rss2.0 support via rss.jsp
rss.jsp can in turn receive all the filters that index.jsp can - e.g. ?blog=blah or ?selector=group://foo,
and by default returns the latest 10 values (overridden with ?wanted=15).  If you want it to pull
with a user's blog's preferences (filters, groups, etc), you can specify ?login=user&password=password
2005-09-05 05:33:33 +00:00
3bb445ff40 better filtering/ignoring
ui improvements (per isamoor's suggestions)
more petname integration
2005-09-05 01:26:19 +00:00
59a8037599 allow exporting eepsite destinations from the syndie database into userhosts.txt (so the eepproxy can get it) 2005-09-05 00:00:11 +00:00
09cb5fad59 allow you to bookmark syndie archives and later recall those bookmarks on the remote page 2005-09-04 22:43:22 +00:00
ee8e45ecf7 allow web based control of who gets to access remote repositories.
if the prop "syndie.remotePassword" is set, users can enter it while viewing their metadata
2005-09-04 21:51:17 +00:00
339868838d thanks BarkerJr :) 2005-09-04 20:26:42 +00:00
c5579fa349 (the filtered blogs may be out of order) 2005-09-04 19:33:00 +00:00
d4a859547c 2005-09-04 jrandom
* Don't persist peer profiles until we are shutting down, as the
      persistence process gobbles RAM and wall time.
    * Bugfix to allow you to check/uncheck the sharedClient setting on the
      I2PTunnel web interface.
    * Be more careful when expiring a failed tunnel message fragment so we
      don't drop the data while attempting to read it.
2005-09-04 19:15:49 +00:00
779aa240d2 added glog.i2p 2005-09-03 04:07:50 +00:00
9aaad00383 0.6.0.5 2005-09-02 19:10:05 +00:00
6422f7ef78 2005-09-02 jrandom
* Don't refuse to send a netDb store if the targetted peer has failed a
      bit (the value was an arbitrary amount).
    * Logging changes
2005-09-02 18:34:14 +00:00
3e51584b3c 0.6.0.4 2005-09-01 20:27:35 +00:00
4ff8a53084 2005-09-01 jrandom
* Don't send out a netDb store of a router if it is more than a few hours
      old, even if someone asked us for it.
2005-09-01 06:55:00 +00:00
ccb73437c4 2005-08-31 jrandom
* Don't publish leaseSets to the netDb if they will never be looked for -
      namely, if they are for destinations that only establish outbound
      streams.  I2PTunnel's 'client' and 'httpclient' proxies have been
      modified to tell the router that it doesn't need to publish their
      leaseSet (by setting the I2CP config option 'i2cp.dontPublishLeaseSet'
      to 'true').
    * Don't publish the top 10 peer rankings of each router in the netdb, as
      it isn't being watched right now.
2005-09-01 00:26:20 +00:00
b43114f61b 2005-08-31 jrandom
* Don't publish leaseSets to the netDb if they will never be looked for -
      namely, if they are for destinations that only establish outbound
      streams.  I2PTunnel's 'client' and 'httpclient' proxies have been
      modified to tell the router that it doesn't need to publish their
      leaseSet (by setting the I2CP config option 'i2cp.dontPublishLeaseSet'
      to 'true').
    * Don't publish the top 10 peer rankings of each router in the netdb, as
      it isn't being watched right now.
2005-09-01 00:20:16 +00:00
9bd87ab511 make it work with any host charset or content charset 2005-08-31 09:50:23 +00:00
b6ea55f7ef more error handling (thanks frosk) 2005-08-30 02:39:37 +00:00
5f18cec97d 2005-08-29 jrandom
* Added the new test Floodfill netDb
2005-08-30 02:04:17 +00:00
3ba921ec0e 2005-08-29 jrandom
* Added the new test Floodfill netDb
2005-08-30 01:59:11 +00:00
e313da254c 2005-08-27 jrandom
* Minor logging and optimization tweaks in the router and SDK
    * Use ISO-8859-1 in the XML files (thanks redzara!)
    * The consolePassword config property can now be used to bypass the router
      console's nonce checking, allowing CLI restarts
2005-08-27 22:46:22 +00:00
8660cf0d74 2005-08-27 jrandom
* Minor logging and optimization tweaks in the router and SDK
    * Use ISO-8859-1 in the XML files (thanks redzara!)
    * The consolePassword config property can now be used to bypass the router
      console's nonce checking, allowing CLI restarts
2005-08-27 22:15:35 +00:00
e0bfdff152 TZ asap 2005-08-25 21:08:13 +00:00
c27aed3603 fix up the entryId calc 2005-08-25 21:07:18 +00:00
cdc6002f0e no message 2005-08-25 21:01:15 +00:00
4cf3d9c1a2 HTTP file upload (rfc 1867) helper 2005-08-25 21:00:09 +00:00
0473e08e21 remote w0rks 2005-08-25 20:59:46 +00:00
346faa3de2 2005-08-24 jrandom
* Catch errors with corrupt tunnel messages more gracefully (no need to
      kill the thread and cause an OOM...)
    * Don't skip shitlisted peers for netDb store messages, as they aren't
      necessarily shitlisted by other people (though they probably are).
    * Adjust the netDb store per-peer timeout based on each particular peer's
      profile (timeout = 4x their average netDb store response time)
    * Don't republish leaseSets to *failed* peers - send them to peers who
      replied but just didn't know the value.
    * Set a 5 second timeout on the I2PTunnelHTTPServer reading the client's
      HTTP headers, rather than blocking indefinitely.  HTTP headers should be
      sent entirely within the first streaming packet anyway, so this won't be
      a problem.
    * Don't use the I2PTunnel*Server handler thread pool by default, as it may
      prevent any clients from accessing the server if the handlers get
      blocked by the streaming lib or other issues.
    * Don't overwrite a known status (OK/ERR-Reject/ERR-SymmetricNAT) with
      Unknown.
2005-08-24 22:55:25 +00:00
5ec6dca64d 2005-08-23 jrandom
* Removed the concept of "no bandwidth limit" - if none is specified, its
      16KBps in/out.
    * Include ack packets in the per-peer cwin throttle (they were part of the
      bandwidth limit though).
    * Tweak the SSU cwin operation to get more accurrate estimates under
      congestions.
    * SSU improvements to resend more efficiently.
    * Added a basic scheduler to eepget to fetch multiple files sequentially.
2005-08-23 22:43:51 +00:00
1a6b49cfb8 2005-08-23 jrandom
* Removed the concept of "no bandwidth limit" - if none is specified, its
      16KBps in/out.
    * Include ack packets in the per-peer cwin throttle (they were part of the
      bandwidth limit though).
    * Tweak the SSU cwin operation to get more accurrate estimates under
      congestions.
    * SSU improvements to resend more efficiently.
    * Added a basic scheduler to eepget to fetch multiple files sequentially.
2005-08-23 21:25:49 +00:00
c7b75df390 Added announcement about the new Irc2P server at irc.freshcoffee.i2p 2005-08-22 13:03:11 +00:00
f97c09291b 0.6.0.3 2005-08-21 19:21:50 +00:00
8f2a5b403c * 2005-08-21 0.6.0.3 released
2005-08-21  jrandom
    * If we already have an established SSU session with the Charlie helping
      test us, cancel the test with the status of "unknown".
2005-08-21 18:39:05 +00:00
ea41a90eae sanity checking 2005-08-21 18:37:57 +00:00
b1dd29e64d added syndie.i2p and syndiemedia.i2p 2005-08-21 18:33:58 +00:00
46e47c47ac ewps 2005-08-21 18:19:22 +00:00
b7bf431f0d [these are not the droids you are looking for] 2005-08-21 18:08:05 +00:00
7f432122d9 added irc.freshcoffee.i2p (new IRC server on the irc2p network) 2005-08-20 01:19:51 +00:00
e7be8c6097 Added references to the new irc2p server: irc.freshcoffee.i2p 2005-08-20 01:18:38 +00:00
adf56a16e1 2005-08-17 jrandom
* Revise the SSU peer testing protocol so that Bob verifies Charlie's
      viability before agreeing to Alice's request.  This doesn't work with
      older SSU peer test builds, but is backwards compatible (older nodes
      won't ask newer nodes to participate in tests, and newer nodes won't
      ask older nodes to either).
2005-08-17 20:16:27 +00:00
11204b8a2b 2005-08-17 jrandom
* Revise the SSU peer testing protocol so that Bob verifies Charlie's
      viability before agreeing to Alice's request.  This doesn't work with
      older SSU peer test builds, but is backwards compatible (older nodes
      won't ask newer nodes to participate in tests, and newer nodes won't
      ask older nodes to either).
2005-08-17 20:05:01 +00:00
cade27dceb added surrender.adab.i2p 2005-08-17 00:42:15 +00:00
5597d28e59 Removing references to irc.duck.i2p, adding references to irc.arcturus.i2p, and replacing current ircProxy default destination string with "irc.postman.i2p,irc.arcturus.i2p" 2005-08-16 09:35:58 +00:00
0502fec432 added terror.i2p 2005-08-15 18:44:04 +00:00
a6714fc2de Adding irc.arcturus.i2p, a new server for the soon-to-be Irc2P network 2005-08-14 15:52:12 +00:00
1219dadbd5 2005-08-12 jrandom
* Keep detailed stats on the peer testing, publishing the results in the
      netDb.
    * Don't overwrite the status with 'unknown' unless we haven't had a valid
      status in a while.
    * Make sure to avoid shitlisted peers for peer testing.
    * When we get an unknown result to a peer test, try again soon afterwards.
    * When a peer tells us that our address is different from what we expect,
      if we've done a recent peer test with a result of OK, fire off a peer
      test to make sure our IP/port is still valid.  If our test is old or the
      result was not OK, accept their suggestion, but queue up a peer test for
      later.
    * Don't try to do a netDb store to a shitlisted peer, and adjust the way
      we monitor netDb store progress (to clear up the high netDb.storePeers
      stat)
2005-08-12 23:54:46 +00:00
77b995f5ed 2005-08-10 jrandom
* Deployed the peer testing implementation to be run every few minutes on
      each router, as well as any time the user requests a test manually.  The
      tests do not reconfigure the ports at the moment, merely determine under
      what conditions the local router is reachable.  The status shown in the
      top left will be "ERR-SymmetricNAT" if the user's IP and port show up
      differently for different peers, "ERR-Reject" if the router cannot
      receive unsolicited packets or the peer helping test could not find a
      collaborator, "Unknown" if the test has not been run or the test
      participants were unreachable, or "OK" if the router can receive
      unsolicited connections and those connections use the same IP and port.
2005-08-10 23:55:40 +00:00
2f53b9ff68 0.6.0.2 2005-08-09 18:55:31 +00:00
d84d045849 deal with full windows without *cough* NPEs
(how many times can I cvs rtag -F before going crazy?)
2005-08-08 21:20:08 +00:00
d8e72dfe48 foo 2005-08-08 20:49:17 +00:00
88b9f7a74c "ERROR [eive on 8887] uter.transport.udp.UDPReceiver: Dropping inbound packet with 1 queued for 1912 packet handlers: Handlers: 3 handler 0 state: 2 handler 1 state: 2 handler 2 state: 2"
state = 2 means all three handlers are blocking on udpReceiver.receive())
this can legitimately happen if the bandwidth limiter or router throttle chokes the receive for >= 1s.
2005-08-08 20:42:13 +00:00
6a19501214 2005-08-08 jrandom
* Add a configurable throttle to the number of concurrent outbound SSU
      connection negotiations (via i2np.udp.maxConcurrentEstablish=4).  This
      may help those with slow connections to get integrated at the start.
    * Further fixlets to the streaming lib
2005-08-08 20:35:50 +00:00
ba30b56c5f 2005-08-07 Complication
* Display the average clock skew for both SSU and TCP connections
2005-08-07  jrandom
    * Fixed the long standing streaming lib bug where we could lose the first
      packet on retransmission.
    * Avoid an NPE when a message expires on the SSU queue.
    * Adjust the streaming lib's window growth factor with an additional
      Vegas-esque congestion detection algorithm.
    * Removed an unnecessary SSU session drop
    * Reduced the MTU (until we get a working PMTU lib)
    * Deferr tunnel acceptance until we know how to reach the next hop,
      rejecting it if we can't find them in time.
    * If our netDb store of our leaseSet fails, give it a few seconds before
      republishing.
2005-08-07 19:31:58 +00:00
a375e4b2ce added more postman services (w3wt) 2005-08-07 19:27:22 +00:00
44fd71e17f added i2p-bt.postman.i2p 2005-08-05 21:20:30 +00:00
b41c378de9 Removed reference and link to Invisiblechat/IIP from the router console greeting page (because IIP's dead, Jim... how many times does it need to be said?) and added irc.postman.i2p. 2005-08-05 19:20:52 +00:00
4ce6b308b3 * 2005-08-03 0.6.0.1 released
2005-08-03  jrandom
    * Backed out an inadvertant change to the netDb store redundancy factor.
    * Verify tunnel participant caching.
    * Logging cleanup
2005-08-03 18:58:12 +00:00
72c6e7d1c5 2005-08-01 duck
* Update IzPack to 3.7.2 (build 2005.04.22). This fixes bug #82.
2005-08-02 03:26:51 +00:00
7ca3f22e77 2005-08-01 duck
* Update IzPack to 3.7.2 (build 2005.04.22)
      This fixes bug #82
2005-08-02 03:25:51 +00:00
59790dafef 2005-08-01 duck
* Fix an addressbook NPE when a new hostname from the master addressbook
      didn't exist in the router addressbook.
    * Fix an addressbook bug which caused subscriptions not to be parsed at
      all. (Oops!)
2005-08-01 13:35:11 +00:00
7227cae6ef 2005-08-01 duck
* Fix an addressbook NPE when a new hostname from the master addressbook
      didn't exist in the router addressbook.
    * Fix an addressbook bug which caused subscriptions not to be parsed at
      all. (Oops!)
2005-08-01 13:34:10 +00:00
03bba51c1e * Fixed some issues with the merge logic that caused addressbooks to be written to disk even when unmodified.
* Fixed a bug that could result in a downloaded remote addressbook not being deleted, halting the update process.
2005-08-01 03:32:37 +00:00
0637050cbc No real reason for eepget to retry, addressbook will try again in an hour, and it makes updates take an absurdly long time. 2005-08-01 00:10:54 +00:00
7f58a68c5a Whoops! Forgot the new build file. I broke cvs! 2005-07-31 22:49:25 +00:00
8120b0397c Move addressbook off URL and on to EepGet. Should no longer leak dns lookups, but now only supports conditional GET with HTTP 1.1. If that's a big problem, it can be fixed in future. 2005-07-31 22:19:10 +00:00
fbe42b7dce Added HTTP 1.1 conditional GET support to EepGet. 2005-07-31 22:17:10 +00:00
def24e34ad 2005-07-31 jrandom
* Adjust the netDb search and store per peer timeouts to match the average
      measured per peer success times, rather than huge fixed values.
    * Optimized and reverified the netDb peer selection / retrieval process
      within the kbuckets.
    * Drop TCP connections that don't have any useful activity in 10 minutes.
    * If i2np.udp.fixedPort=true, never change the externally published port,
      even if we are autodetecting the IP address.
(also includes most of the new peer/NAT testing, but thats not used atm)
2005-07-31 21:35:26 +00:00
593253e6a3 update compilation target 2005-07-31 01:11:12 +00:00
56dd4cb8b5 * added luckypunk.i2p to hosts.txt 2005-07-30 02:27:12 +00:00
10c6f67500 oops 2005-07-28 20:33:27 +00:00
5c1f968afa no message 2005-07-27 20:16:44 +00:00
aaaf437d62 skip properly (DataHelper.read confusion) 2005-07-27 20:15:35 +00:00
a8a866b5f6 * 2005-07-27 0.6 released
2005-07-27  jrandom
    * Enabled SSU as the default top priority transport, adjusting the
      config.jsp page accordingly.
    * Add verification fields to the SSU and TCP connection negotiation (not
      compatible with previous builds)
    * Enable the backwards incompatible tunnel crypto change as documented in
      tunnel-alt.html (have each hop encrypt the received IV before using it,
      then encrypt it again before sending it on)
    * Disable the I2CP encryption, leaving in place the end to end garlic
      encryption (another backwards incompatible change)
    * Adjust the protocol versions on the TCP and SSU transports so that they
      won't talk to older routers.
    * Fix up the config stats handling again
    * Fix a rare off-by-one in the SSU fragmentation
    * Reduce some unnecessary netDb resending by inluding the peers queried
      successfully in the store redundancy count.
2005-07-27 19:03:43 +00:00
aeb8f02269 2005-07-22 jrandom
* Use the small thread pool for I2PTunnelHTTPServer (already used for
      I2PTunnelServer)
    * Minor memory churn reduction in I2CP
    * Small stats update
2005-07-23 00:15:56 +00:00
45767360ab 2005-07-21 jrandom
* Fix in the SDK for a bug which would manifest itself as misrouted
      streaming packets when a destination has many concurrent streaming
      connections (thanks duck!)
    * No more "Graceful shutdown in -18140121441141s"
2005-07-21 22:37:14 +00:00
3563aa2e4d 2005-07-20 jrandom
* Allow the user to specify an external port # for SSU even if the external
      host isn't specified (thanks duck!)
2005-07-20 19:24:47 +00:00
843d5b625a 2005-07-19 jrandom
* Further preparation for removing I2CP crypto
    * Added some validation to the DH key agreement (thanks $anon)
    * Validate tunnel data message expirations (though not really a problem,
      since tunnels expire)
    * Minor PRNG threading cleanup
2005-07-19 21:00:25 +00:00
0f8ede85ca 2005-07-15 cervantes
* Added workaround for an odd win32 bug in the stats configuration
	  console page which meant only the first checkbox selection was saved.

2005-07-15  Romster
	* Added per group selection toggles in the stats configuration console
	  page.
2005-07-16 12:52:35 +00:00
9267d7cae2 more n3ws 2005-07-13 21:59:01 +00:00
dade5a981b 2005-07-13 jrandom
* Fixed a recently injected bug in the multitransport bidding which had
      allowed an essentially arbitrary choice of transports, rather than the
      properly ordered choice.
(getLatency() != getLatencyMs().  duh)
2005-07-13 20:07:31 +00:00
f873cba27e 2005-07-13 jrandom
* Fixed a long standing bug where we weren't properly comparing session
      tags but instead largely depending upon comparing their hashCode,
      causing intermittent decryption errors.
2005-07-13 18:20:43 +00:00
108dec53a5 * mixing a revert and some logging updates... (crosses fingers) 2005-07-12 22:30:13 +00:00
e9592ed400 2005-07-12 jrandom
* Add some data duplication to avoid a recently injected concurrency problem
      in the session tag manager (thanks redzara and romster).
2005-07-12 21:26:07 +00:00
4c230522a2 typo *ahem* 2005-07-12 03:56:42 +00:00
16bd19c6dc added bash.i2p, stats.i2p 2005-07-11 23:23:22 +00:00
b4b6d49d34 ssu testing 2005-07-11 23:16:41 +00:00
9d5f16a889 2005-07-11 jrandom
* Reduced the growth factor on the slow start and congestion avoidance for
      the streaming lib.
    * Adjusted some of the I2PTunnelServer threading to use a small pool of
      handlers, rather than launching off new threads which then immediately
      launch off an I2PTunnelRunner instance (which launches 3 more threads..)
    * Don't persist session keys / session tags (not worth it, for now)
    * Added some detection and handling code for duplicate session tags being
      delivered (root cause still not addressed)
    * Make the PRNG's buffer size configurable (via the config property
      "i2p.prng.totalBufferSizeKB=4096")
    * Disable SSU flooding by default (duh)
    * Updates to the StreamSink apps for better throttling tests.
2005-07-11 23:06:23 +00:00
51c492b842 no message 2005-07-09 23:02:19 +00:00
d3380228ac * you mean 3f != 0x3f? [duh]
* minor cleanups
2005-07-09 22:58:22 +00:00
ad47bf5da3 * moved the inbound partial messages to the PeerState itself, reducing lock contention in the InboundMessageFragments and transparently dropping failed messages when we drop old peer states 2005-07-07 22:27:44 +00:00
76e8631e31 included IV tagging info 2005-07-07 21:16:57 +00:00
f688b9112d 2005-07-05
* Use a buffered PRNG, pulling the PRNG data off a larger precalculated
      buffer, rather than the underlying PRNG's (likely small) one, which in
      turn reduces the frequency of recalcing.
    * More tuning to reduce temporary allocation churn
2005-07-05 22:08:56 +00:00
18d3f5d25d 2005-07-04 jrandom
* Within the tunnel, use xor(IV, msg[0:16]) as the flag to detect dups,
      rather than the IV by itself, preventing an attack that would let
      colluding internal adversaries tag a message to determine that they are
      in the same tunnel.  Thanks dvorak for the catch!
    * Drop long inactive profiles on startup and shutdown
    * /configstats.jsp: web interface to pick what stats to log
    * Deliver more session tags to account for wider window sizes
    * Cache some intermediate values in our HMACSHA256 and BC's HMAC
    * Track the client send rate (stream.sendBps and client.sendBpsRaw)
    * UrlLauncher: adjust the browser selection order
    * I2PAppContext: hooks for dummy HMACSHA256 and a weak PRNG
    * StreamSinkClient: add support for sending an unlimited amount of data
    * Migrate the tests out of the default build jars

2005-06-22  Comwiz
    * Migrate the core tests to junit
2005-07-04 20:44:17 +00:00
440cf2c983 2005-03-23 Comwiz
* Phase 1 of the unit test bounty completed. (The router build script was modified not to build the router
 tests because of a broken dependancy on the core tests. This should be fixed in
 phase 3 of the unit test bounty.)
2005-06-23 02:11:04 +00:00
adeb09576a util/PooledRandomSource.java 2005-06-03 20:23:32 +00:00
fd52bcf8cd added archive.i2p, www.fr.i2p, romster.i2p, marshmallow.i2p, openforums.i2p 2005-05-26 04:51:24 +00:00
c2696bba00 2005-05-25 duck
* Fixed PRNG bug (bugzilla #107)
2005-05-25 21:32:38 +00:00
fef9d57483 removed duplicate manveru.i2p 2005-05-10 05:53:18 +00:00
c250692ef0 added bittorrent.i2p - new home for brittanytracker 2005-05-04 05:52:55 +00:00
2a6024e196 end of first round of ssu testing 2005-05-03 00:53:53 +00:00
835662b3c9 2005-05-01 jrandom
* Added a substantial optimization to the AES engine by caching the
      prepared session keys (duh).
2005-05-02 02:35:16 +00:00
6b5b880ab6 * replaced explicit NACKs and numACKs with ACK bitfields for high congestion links
* increased the maximum number of fragments allowed in a message from 31 to 127,
  reducing the maximum fragment size to 8KB and moving around some bits in the fragment
  info.  This is not backwards compatible.
* removed the old (hokey) congestion control description, replacing it with the TCP-esque
  algorithm implemented
note: the code for the ACK bitfields and fragment info changes have not yet been
implemented, so the old version of this document describes whats going on in the live net.
the new bitfields / fragment info should be deployed in the next day or so (hopefully :)
2005-05-01 20:08:08 +00:00
3de23d4206 2005-05-01 jrandom
* Cleaned up the peers page a bit more.
more udp stuff:
* add new config option: i2np.udp.alwaysPreferred=true to adjust the bidding
  so that UDP is picked first, even if a TCP connection exists
* fixed the initial clock skew problem (duh)
* reduced the MTU to 576 (largest nearly-universally-safe, and allows a
  tunnel message in 2 fragments)
* handle some races @ connection establishment (thanks duck!)
* if there are more ACKs than we can send in a packet, reschedule another
  ACK immediately
2005-05-01 17:21:48 +00:00
ea82f2a8cc oops (thanks newkid!) 2005-05-01 01:35:23 +00:00
b5ad7642bc 2005-04-30 jrandom
* Added a small new page to the web console (/peers.jsp) which contains
      the peer connection information.  This will be cleaned up a lot more
      before 0.6 is out, but its a start.
2005-05-01 00:48:15 +00:00
0fbe84e9f0 2005-04-30 jrandom
* Reduced some SimpleTimer churn
* add hooks for per-peer choking in the outbound message queue - if/when a
  peer reaches their cwin, no further messages will enter the 'active' pool
  until there are more bytes available.  other messages waiting (either later
  on in the same priority queue, or in the queues for other priorities) may
  take that slot.
* when we have a message acked, release the acked size to the congestion
  window (duh), rather than waiting for the second to expire and refill the
  capacity.
* send packets in a volley explicitly, waiting until we can allocate the full
  cwin size for that message
2005-04-30 23:26:18 +00:00
8063889d23 udp updates:
* more stats. including per-peer KBps (updated every second)
* improved blocking/timeout situations on the send queue
* added drop simulation hook
* provide logical RTO limits
2005-04-30 03:14:09 +00:00
6e1ac8e173 added elf.i2p, de-ebooks.i2p, i2pchan.i2p, longhorn.i2p 2005-04-29 22:26:12 +00:00
1b0bb5ea19 2005-04-29 jrandom
* Reduce the peer profile stat coallesce overhead by inlining it with the
      reorganize.
    * Limit each transport to at most one address (any transport that requires
      multiple entry points can include those alternatives in the address).
udp stuff:
* change the UDP transport's style from "udp" to "SSUv1"
* keep track of each peer's skew
* properly handle session reestablishment over an existing session, rather
  than requiring both sides to expire first
2005-04-29 06:24:12 +00:00
4ce51261f1 2005-04-28 jrandom
* More fixes for the I2PTunnel "other" interface handling (thanks nelgin!)
    * Add back the code to handle bids from multiple transports (though there
      is still only one transport enabled by default)
    * Adjust the router's queueing of outbound client messages when under
      heavy load by running the preparatory job in the client's I2CP handler
      thread, thereby blocking additional outbound messages when the router is
      hosed.
    * No need to validate or persist a netDb entry if we already have it
And for some udp stuff:
* only bid on what we know (duh)
* reduceed the queue size in the UDPSender itself, so that ACKs go
  through more quickly, leaving the payload messages to queue up in
  the outbound fragment scheduler
* rather than /= 2 on congestion, /= 2/3 (still AIMD, but less drastic)
* adjust the fragment selector so a wsiz throttle won't force extra
  volleys
* mark congestion when it occurs, not after the message has been
  ACKed
* when doing a round robin over the active messages, move on to the
  next after a full volley, not after each packet (causing less "fair"
  performance but better latency)
* reduced the lock contention in the inboundMessageFragments by
  moving the ack and complete queues to the ACKSender and
  MessageReceiver respectively (each of which have their own
  threads)
* prefer new and existing UDP sessions to new TCP sessions, but
  prefer existing TCP sessions to new UDP sessions
2005-04-28 21:54:27 +00:00
6e34d9b73e added amobius.i2p 2005-04-28 02:11:02 +00:00
6e01637400 added google.i2p 2005-04-27 21:30:53 +00:00
9a96798f9f added mrplod.i2p 2005-04-27 03:58:00 +00:00
c9db6f87d1 2005-04-25 smeghead
* Added button to router console for manual update checks.
    * Fixed bug in configupdate.jsp that caused the proxy port to be updated
      every time the form was submitted even if it hadn't changed.
2005-04-26 02:59:23 +00:00
567ce84e1e * randomized the shitlist duration (still with exponential backoff though)
* fail UDP sessions after two consecutive failed messages in different minutes
* honor UDP reconnections
2005-04-25 16:29:48 +00:00
cde7ac7e52 2005-04-24 jrandom
* Added a pool of PRNGs using a different synchronization technique,
      hopefully sufficient to work around IBM's PRNG bugs until we get our
      own Fortuna.
    * In the streaming lib, don't jack up the RTT on NACK, and have the window
      size bound the not-yet-ready messages to the peer, not the unacked
      message count (not sure yet whether this is worthwile).
    * Many additions to the messageHistory log.
    * Handle out of order tunnel fragment delivery (not an issue on the live
      net with TCP, but critical with UDP).
2005-04-24 18:44:59 +00:00
b2f0d17e94 2005-04-24 jrandom
* Added a pool of PRNGs using a different synchronization technique,
      hopefully sufficient to work around IBM's PRNG bugs until we get our
      own Fortuna.
    * In the streaming lib, don't jack up the RTT on NACK, and have the window
      size bound the not-yet-ready messages to the peer, not the unacked
      message count (not sure yet whether this is worthwile).
    * Many additions to the messageHistory log.
    * Handle out of order tunnel fragment delivery (not an issue on the live
      net with TCP, but critical with UDP).
and for udp stuff:
* implemented tcp-esque rto code in the udp transport
* make sure we don't ACK too many messages at once
* transmit fragments in a simple (nonrandom) order so that we can more easily
  adjust timeouts/etc.
* let the active outbound pool grow dynamically if there are outbound slots to
  spare
* use a simple decaying bloom filter at the UDP level to drop duplicate resent
  packets.
2005-04-24 18:42:02 +00:00
dae6be14b7 I removed those dumb platform specific makefiles. They weren't doing what they ought anyway. If there are platform specific issues, someone please tell me and I'll provide support for it here. Or patch it yourself.
And this is the big "Fix the Parser" patch.  It turns the sam_parse function in src/parse.c into something that actually works.  Generating the argument list from an incoming SAM thingy is a bit memory churn-y; perhaps when I have time I'll replace all those strdups with structures that simply track the (start,end) indices.
Oh and also I moved i2p-ping to the new system.  Which required 0 change in code.  All I did was fix the Makefile, and add shared library libtool support.  Anyway, so enjoy folks.  It's rare I'm this productive
- polecat
2005-04-23 03:28:40 +00:00
20cec857d2 signed with the latest 2005-04-21 16:26:46 +00:00
aum
739f694cfe Node shutdown now uses halt() 2005-04-21 03:10:16 +00:00
aum
84779002fb now builds a working Q console 2005-04-20 21:35:05 +00:00
df926fb60d * 2005-04-20 0.5.0.7 released 2005-04-20 20:14:17 +00:00
a2c7c5a516 2005-04-20 jrandom
* In the SDK, we don't actually need to block when we're sending a message
      as BestEffort (and these days, we're always sending BestEffort).
    * Pass out client messages in fewer (larger) steps.
    * Have the InNetMessagePool short circuit dispatch requests.
    * Have the message validator take into account expiration to cut down on
      false positives at high transfer rates.
    * Allow configuration of the probabalistic window size growth rate in the
      streaming lib's slow start and congestion avoidance phases, and default
      them to a more conservative value (2), rather than the previous value
      (1).
    * Reduce the ack delay in the streaming lib to 500ms
    * Honor choke requests in the streaming lib (only affects those getting
      insanely high transfer rates)
    * Let the user specify an interface besides 127.0.0.1 or 0.0.0.0 on the
      I2PTunnel client page (thanks maestro^!)
(plus minor udp tweaks)
2005-04-20 19:15:25 +00:00
aum
1861379d43 needed for QConsole 2005-04-20 18:54:39 +00:00
aum
408a344aae added QConsole 2005-04-20 18:53:08 +00:00
e9c1ed70d0 added sirup.i2p 2005-04-18 23:27:31 +00:00
916dcca2b0 * build with reference to the i2p.jar/mstreaming.jar/i2ptunnel.jar inline (building as necessary)
* removed unnecessary references to i2ptunnel (though i2ptunnelxmlobject still references i2ptunnelxmlwrapper)
2005-04-18 18:47:22 +00:00
aum
31e81bab17 fixed build failures 2005-04-18 18:12:32 +00:00
aum
6a5170c341 oops, forgot to add earlier 2005-04-18 18:05:50 +00:00
aum
42bff8093c removed obsolete ref to MiniHttpRequestHandlerBase, changed to MiniHttpRequestHandler 2005-04-18 18:04:12 +00:00
aum
d1df94f284 added needed html template files 2005-04-18 18:02:07 +00:00
aum
9cf1744291 restored images in binary mode 2005-04-18 17:24:33 +00:00
aum
f0545c8c9a removed images which were not checked in as binary 2005-04-18 17:22:27 +00:00
aum
ef9ed87d30 binary mode this time 2005-04-18 17:20:10 +00:00
aum
58ffd92a34 dammit, forgot binary mode 2005-04-18 17:19:25 +00:00
aum
418facc7e0 Added apps/q - the Q distributed file store framework, by aum 2005-04-18 17:03:21 +00:00
7f3c953e14 2005-04-17 sirup
* Added the possibility for i2ptunnel client and httpclient instances to
      have their own i2p session (and hence, destination and tunnels).  By
      default, tunnels are shared, but that can be changed on the web
      interface or with the sharedClient config option in i2ptunnel.config.
2005-04-17  jrandom
    * Marked the net.i2p.i2ptunnel.TunnelManager as deprecated.  Anyone use
      this?  If not, I want to drop it (lots of tiny details with lots of
      duplicated semantics).
2005-04-18 02:07:57 +00:00
addab1fa2a 2005-04-17 zzz
* Added new user-editable eepproxy error page templates.
2005-04-17  jrandom
    * Revamp the tunnel building throttles, fixing a situation where the
      rebuild may not recover, and defaulting it to unthrottled (users with
      slow CPUs may want to set "router.tunnel.shouldThrottle=true" in their
      advanced router config)
2005-04-17 23:23:20 +00:00
39343ce957 2005-04-16 jrandom
* Migrated to Bouncycastle's SHA256 and HMAC implementations for efficiency
2005-04-17 01:04:06 +00:00
7389cec78f 2005-04-16 jrandom
* Migrated to Bouncycastle's SHA256 and HMAC implementations for efficiency
(also lots of udp fixes)
2005-04-17 00:59:48 +00:00
9e5fe7d2b6 * fixed some stupid threading issues in the packet handler (duh)
* use the new raw i2np message format (the previous corruptions were due to above)
* add a new test component (UDPFlooder) which floods all peers at the rate desired
* packet munging fix for highly fragmented messages
* include basic slow start code
* fixed the UDP peer rate refilling
* cleaned up some nextSend scheduling
2005-04-16 15:18:09 +00:00
a7dfaee5ac added connelly.i2p 2005-04-13 02:29:59 +00:00
7beb92b1cc First pass of the UDP transport. No where near ready for use, but it does
the basics (negotiate a session and send I2NP messages back and forth).  Lots,
lots more left.
2005-04-12 16:48:43 +00:00
5b56d22da9 2005-04-12 jrandom
* Make sure we don't get cached updates (thanks smeghead!)
    * Clear out the callback for the TestJob after it passes (only affects the
      job timing accounting)
2005-04-12 15:22:11 +00:00
e6b343070a removed copy/paste error 2005-04-09 23:15:53 +00:00
8496b88518 2005-04-08 smeghead
* Added NativeBigInteger benchmark to scripts/i2pbench.sh.
2005-04-09 03:16:05 +00:00
aa542b7876 for implementation simplicity, include fragment size in the SessionConfirmed packets 2005-04-08 23:20:45 +00:00
3f7d46378b * specify exactly what gets in the DSA signatures for the connection establishment
* include a new signedOnTime so that we can prepare the packet at a different moment from
  when we encrypt & send it (also allowing us to reuse that signature on resends for the same
  establishment)
2005-04-08 14:21:26 +00:00
b36def1f72 2005-04-08 smeghead
* Security improvements to TrustedUpdate: signing and verification of the
      version string along with the data payload for signed update files
      (consequently the positions of the DSA signature and version string fields
      have been swapped in the spec for the update file's header); router will
      no longer perform a trusted update if the signed update's version is lower
      than or equal to the currently running router's version.
    * Added two new CLI commands to TrustedUpdate: showversion, verifyupdate.
    * Extended TrustedUpdate public API for use by third party applications.
2005-04-08 12:39:20 +00:00
5a6a3a5e8d oops, forgot to add new eepget script to build 2005-04-08 01:56:02 +00:00
c3bd26d9b4 added wspucktracker.i2p 2005-04-07 20:40:30 +00:00
aum
967e106ee7 fixed one last javadoc err 2005-04-07 04:36:06 +00:00
aum
7c73e59482 Fixed more javadoc errors 2005-04-07 04:26:55 +00:00
aum
03dfa913d1 Removed erroneous @author tag from methods 2005-04-07 04:05:13 +00:00
348e845793 *cough* thanks cervantes 2005-04-06 16:38:38 +00:00
80827c3aad * 2005-04-06 0.5.0.6 released 2005-04-06 15:43:25 +00:00
3b4cf0a024 added 55cancri.i2p 2005-04-06 15:14:00 +00:00
941252fd80 2005-04-05 jrandom
* Retry I2PTunnel startup if we are unable to build a socketManager for a
      client or httpclient tunnel.
    * Add some basic sanity checking on the I2CP settings (thanks duck!)
2005-04-05 22:24:32 +00:00
bc626ece2d 2005-04-05 jrandom
* After a successfull netDb search for a leaseSet, republish it to all of
      the peers we have tried so far who did not give us the key (up to 10),
      rather than the old K closest (which may include peers who had given us
      the key)
    * Don't wait 5 minutes to publish a leaseSet (duh!), and rather than
      republish it every 5 minutes, republish it every 3.  In addition, always
      republish as soon as the leaseSet changes (duh^2).
    * Minor fix for oddball startup race (thanks travis_bickle!)
    * Minor AES update to allow in-place decryption.
2005-04-05 16:06:14 +00:00
400feb3ba7 clarify crypto/hmac usage for simpler implementation 2005-04-05 15:28:54 +00:00
756a4e3995 added a section for congestion control describing what I hope to implement. what
/actually/ gets implemented will be documented further once its, er, implemented
2005-04-04 17:21:30 +00:00
aum
578301240e Added constructors to PrivateKey, PublicKey, SigningPrivateKey and
SigningPublicKey, which take a single String argument and construct
the object from the Base64 data in that string (where this data is
the product of a .toBase64() call on a prior instance).
2005-04-04 06:13:50 +00:00
aum
9b8f91c7f9 Added 'toPublic()' methods to PrivateKey and SigningPrivateKey, such
that these return PublicKey and SigningPublicKey objects, respectively.
2005-04-04 06:01:13 +00:00
c7c389d4fb added eepget wrapper script for *nix 2005-04-03 13:35:52 +00:00
68f7adfa0b *** keyword substitution change *** 2005-04-03 13:33:29 +00:00
c4ac5170c7 2005-04-03 jrandom
* EepGet fix for open-ended HTTP fetches (such as the news.xml
      feeding the NewsFetcher)
2005-04-03 12:50:11 +00:00
32e0c8ac71 updated status blurb 2005-04-03 07:22:28 +00:00
c9c1eae32f 2005-04-01 jrandom
* Allow editing I2PTunnel server instances with five digit ports
      (thanks nickless_head!)
    * More NewsFetcher debugging for reported weirdness
2005-04-01 13:29:26 +00:00
33366cc291 2005-04-01 jrandom
* Fix to check for missing news file (thanks smeghead!)
    * Added destination display CLI:
      java -cp lib/i2p.jar net.i2p.data.Destination privKeyFilename
    * Added destination display to the web interface (thanks pnspns)
    * Installed CIA backdoor
2005-04-01 11:28:06 +00:00
083ac1f125 n3wz0rz 2005-03-31 02:04:18 +00:00
718 changed files with 64462 additions and 6623 deletions

View File

@ -6,8 +6,7 @@
<property name="dist" location="dist"/>
<property name="jar" value="addressbook.jar"/>
<property name="war" value="addressbook.war"/>
<property name="servlet" value="../jetty/jettylib/javax.servlet.jar"/>
<target name="init">
<mkdir dir="${build}"/>
<mkdir dir="${dist}"/>
@ -22,7 +21,12 @@
<target name="compile" depends="init">
<javac debug="true" deprecation="on" source="1.3" target="1.3"
srcdir="${src}" destdir="${build}" classpath="${servlet}"/>
srcdir="${src}" destdir="${build}">
<classpath>
<pathelement location="../../core/java/build/i2p.jar" />
<pathelement location="../jetty/jettylib/javax.servlet.jar" />
</classpath>
</javac>
</target>
<target name="jar" depends="compile">

View File

@ -24,11 +24,12 @@ package addressbook;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.net.URL;
import java.net.HttpURLConnection;
import java.io.File;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.util.EepGet;
/**
* An address book for storing human readable names mapped to base64 i2p
* destinations. AddressBooks can be created from local and remote files, merged
@ -65,14 +66,18 @@ public class AddressBook {
* where key is a human readable name, and value is a base64 i2p
* destination.
*/
public AddressBook(URL url) {
this.location = url.getHost();
public AddressBook(String url, String proxyHost, int proxyPort) {
this.location = url;
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
proxyHost, proxyPort, 0, "addressbook.tmp", url, true,
null);
get.fetch();
try {
this.addresses = ConfigParser.parse(url);
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
} catch (IOException exp) {
this.addresses = new HashMap();
}
new File("addressbook.tmp").delete();
}
/**
@ -83,43 +88,19 @@ public class AddressBook {
* @param subscription
* A Subscription instance pointing at a remote address book.
*/
public AddressBook(Subscription subscription) {
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
this.location = subscription.getLocation();
try {
URL url = new URL(subscription.getLocation());
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
if (subscription.getEtag() != null) {
connection.addRequestProperty("If-None-Match", subscription
.getEtag());
}
if (subscription.getLastModified() != null) {
connection.addRequestProperty("If-Modified-Since", subscription
.getLastModified());
}
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
connection.disconnect();
this.addresses = new HashMap();
return;
}
if (connection.getHeaderField("ETag") != null) {
subscription.setEtag(connection.getHeaderField("ETag"));
}
if (connection.getHeaderField("Last-Modified") != null) {
subscription.setLastModified(connection
.getHeaderField("Last-Modified"));
}
} catch (IOException exp) {
}
try {
this.addresses = ConfigParser.parse(new URL(subscription
.getLocation()));
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
proxyHost, proxyPort, 0, "addressbook.tmp",
subscription.getLocation(), true, subscription.getEtag());
get.fetch();
subscription.setEtag(get.getETag());
try {
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
} catch (IOException exp) {
this.addresses = new HashMap();
}
new File("addressbook.tmp").delete();
}
/**
@ -181,7 +162,7 @@ public class AddressBook {
* @param log
* The log to write messages about new addresses or conflicts to.
*/
public void merge(AddressBook other, Log log) {
public void merge(AddressBook other, boolean overwrite, Log log) {
Iterator otherIter = other.addresses.keySet().iterator();
while (otherIter.hasNext()) {
@ -189,7 +170,7 @@ public class AddressBook {
String otherValue = (String) other.addresses.get(otherKey);
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
if (this.addresses.containsKey(otherKey)) {
if (this.addresses.containsKey(otherKey) && !overwrite) {
if (!this.addresses.get(otherKey).equals(otherValue)
&& log != null) {
log.append("Conflict for " + otherKey + " from "
@ -197,28 +178,19 @@ public class AddressBook {
+ ". Destination in remote address book is "
+ otherValue);
}
} else {
} else if (!this.addresses.containsKey(otherKey)
|| !this.addresses.get(otherKey).equals(otherValue)) {
this.addresses.put(otherKey, otherValue);
this.modified = true;
if (log != null) {
log.append("New address " + otherKey
+ " added to address book.");
+ " added to address book.");
}
}
}
}
}
/**
* Merge this AddressBook with other, without logging.
*
* @param other
* An AddressBook to merge with.
*/
public void merge(AddressBook other) {
this.merge(other, null);
}
/**
* Write the contents of this AddressBook out to the File file. If the file
* cannot be writen to, this method will silently fail.
@ -243,4 +215,4 @@ public class AddressBook {
public void write() {
this.write(new File(this.location));
}
}
}

View File

@ -27,7 +27,6 @@ import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.io.*;
import java.net.URL;
/**
* Utility class providing methods to parse and write files in config file
@ -86,24 +85,6 @@ public class ConfigParser {
return result;
}
/**
* Return a Map using the contents of the file at url. See
* parseBufferedReader for details of the input format.
*
* @param url
* A url pointing to a file to parse.
* @return A Map containing the key, value pairs from url.
* @throws IOException
* if url cannot be read.
*/
public static Map parse(URL url) throws IOException {
InputStream urlStream;
urlStream = url.openConnection().getInputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(
urlStream));
return ConfigParser.parse(input);
}
/**
* Return a Map using the contents of the File file. See parseBufferedReader
* for details of the input format.

View File

@ -36,6 +36,7 @@ import java.io.File;
*/
public class Daemon {
public static final String VERSION = "2.0.3";
private static final Daemon _instance = new Daemon();
/**
* Update the router and published address books using remote data from the
@ -56,17 +57,16 @@ public class Daemon {
* @param log
* The log to write changes and conflicts to.
*/
public static void update(AddressBook master, AddressBook router,
public void update(AddressBook master, AddressBook router,
File published, SubscriptionList subscriptions, Log log) {
String routerLocation = router.getLocation();
master.merge(router);
router.merge(master, true, null);
Iterator iter = subscriptions.iterator();
while (iter.hasNext()) {
master.merge((AddressBook) iter.next(), log);
router.merge((AddressBook) iter.next(), false, log);
}
master.write(new File(routerLocation));
router.write();
if (published != null)
master.write(published);
router.write(published);
subscriptions.write();
}
@ -78,7 +78,7 @@ public class Daemon {
* @param home
* The directory containing addressbook's configuration files.
*/
public static void update(Map settings, String home) {
public void update(Map settings, String home) {
File masterFile = new File(home, (String) settings
.get("master_addressbook"));
File routerFile = new File(home, (String) settings
@ -101,10 +101,11 @@ public class Daemon {
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
etagsFile, lastModifiedFile, defaultSubs);
etagsFile, lastModifiedFile, defaultSubs, (String) settings
.get("proxy_host"), Integer.parseInt((String) settings.get("proxy_port")));
Log log = new Log(logFile);
Daemon.update(master, router, published, subscriptions, log);
update(master, router, published, subscriptions, log);
}
/**
@ -118,6 +119,10 @@ public class Daemon {
* others are ignored.
*/
public static void main(String[] args) {
_instance.run(args);
}
public void run(String[] args) {
String settingsLocation = "config.txt";
Map settings = new HashMap();
String home;
@ -151,24 +156,36 @@ public class Daemon {
File settingsFile = new File(homeFile, settingsLocation);
settings = ConfigParser.parse(settingsFile, defaultSettings);
// wait
try {
Thread.currentThread().sleep(5*60*1000);
} catch (InterruptedException ie) {}
while (true) {
settings = ConfigParser.parse(settingsFile, defaultSettings);
System.setProperty("proxySet", "true");
System.setProperty("http.proxyHost", (String) settings
.get("proxy_host"));
System.setProperty("http.proxyPort", (String) settings
.get("proxy_port"));
long delay = Long.parseLong((String) settings.get("update_delay"));
if (delay < 1) {
delay = 1;
}
Daemon.update(settings, home);
update(settings, home);
try {
Thread.sleep(delay * 60 * 60 * 1000);
synchronized (this) {
wait(delay * 60 * 60 * 1000);
}
} catch (InterruptedException exp) {
}
settings = ConfigParser.parse(settingsFile, defaultSettings);
}
}
/**
* Call this to get the addressbook to reread its config and
* refetch its subscriptions.
*/
public static void wakeup() {
synchronized (_instance) {
_instance.notifyAll();
}
}
}

View File

@ -44,10 +44,10 @@ public class DaemonThread extends Thread {
* @see java.lang.Runnable#run()
*/
public void run() {
try {
Thread.sleep(5 * 60 * 1000);
} catch (InterruptedException exp) {
}
//try {
// Thread.sleep(5 * 60 * 1000);
//} catch (InterruptedException exp) {
//}
Daemon.main(this.args);
}
}

View File

@ -33,6 +33,8 @@ import java.util.List;
public class SubscriptionIterator implements Iterator {
private Iterator subIterator;
private String proxyHost;
private int proxyPort;
/**
* Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
@ -40,8 +42,10 @@ public class SubscriptionIterator implements Iterator {
* @param subscriptions
* List of Subscription objects that represent address books.
*/
public SubscriptionIterator(List subscriptions) {
public SubscriptionIterator(List subscriptions, String proxyHost, int proxyPort) {
this.subIterator = subscriptions.iterator();
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
}
@ -49,15 +53,15 @@ public class SubscriptionIterator implements Iterator {
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return subIterator.hasNext();
return this.subIterator.hasNext();
}
/* (non-Javadoc)
* @see java.util.Iterator#next()
*/
public Object next() {
Subscription sub = (Subscription) subIterator.next();
return new AddressBook(sub);
Subscription sub = (Subscription) this.subIterator.next();
return new AddressBook(sub, this.proxyHost, this.proxyPort);
}
/* (non-Javadoc)

View File

@ -42,6 +42,10 @@ public class SubscriptionList {
private File etagsFile;
private File lastModifiedFile;
private String proxyHost;
private int proxyPort;
/**
* Construct a SubscriptionList using the urls from locationsFile and, if
@ -58,15 +62,18 @@ public class SubscriptionList {
* GET. The file is in the format "url=leastmodified".
*/
public SubscriptionList(File locationsFile, File etagsFile,
File lastModifiedFile, List defaultSubs) {
File lastModifiedFile, List defaultSubs, String proxyHost,
int proxyPort) {
this.subscriptions = new LinkedList();
this.etagsFile = etagsFile;
this.lastModifiedFile = lastModifiedFile;
List locations;
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
Map etags;
Map lastModified;
String location;
locations = ConfigParser.parseSubscriptions(locationsFile, defaultSubs);
List locations = ConfigParser.parseSubscriptions(locationsFile,
defaultSubs);
try {
etags = ConfigParser.parse(etagsFile);
} catch (IOException exp) {
@ -80,11 +87,9 @@ public class SubscriptionList {
Iterator iter = locations.iterator();
while (iter.hasNext()) {
location = (String) iter.next();
subscriptions.add(new Subscription(location, (String) etags
this.subscriptions.add(new Subscription(location, (String) etags
.get(location), (String) lastModified.get(location)));
}
iter = this.iterator();
}
/**
@ -94,7 +99,8 @@ public class SubscriptionList {
* @return A SubscriptionIterator.
*/
public SubscriptionIterator iterator() {
return new SubscriptionIterator(this.subscriptions);
return new SubscriptionIterator(this.subscriptions, this.proxyHost,
this.proxyPort);
}
/**

View File

@ -111,12 +111,12 @@ public class Bogobot extends PircBot {
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
_ircServer = config.getProperty("ircServer", "irc.duck.i2p");
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
_logFilePrefix = config.getProperty("logFilePrefix", "irc.duck.i2p.i2p-chat");
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();

View File

@ -8,36 +8,49 @@ package net.i2p.i2ptunnel;
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.GZIPInputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.util.ByteCache;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Simple stream for delivering an HTTP response to
* the client, trivially filtered to make sure "Connection: close"
* is always in the response.
* is always in the response. Perhaps add transparent handling of the
* Content-encoding: x-i2p-gzip, adjusting the headers to say Content-encoding: identity?
* Content-encoding: gzip is trivial as well, but Transfer-encoding: chunked makes it
* more work than is worthwhile at the moment.
*
*/
class HTTPResponseOutputStream extends FilterOutputStream {
private static final Log _log = new Log(HTTPResponseOutputStream.class);
private I2PAppContext _context;
private Log _log;
private ByteCache _cache;
protected ByteArray _headerBuffer;
private boolean _headerWritten;
private byte _buf1[];
protected boolean _gzip;
private long _dataWritten;
private static final int CACHE_SIZE = 8*1024;
public HTTPResponseOutputStream(OutputStream raw) {
super(raw);
_context = I2PAppContext.getGlobalContext();
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
_log = _context.logManager().getLog(getClass());
_cache = ByteCache.getInstance(8, CACHE_SIZE);
_headerBuffer = _cache.acquire();
_headerWritten = false;
_gzip = false;
_dataWritten = 0;
_buf1 = new byte[1];
}
@ -51,6 +64,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
public void write(byte buf[], int off, int len) throws IOException {
if (_headerWritten) {
out.write(buf, off, len);
_dataWritten += len;
return;
}
@ -62,8 +76,11 @@ class HTTPResponseOutputStream extends FilterOutputStream {
if (headerReceived()) {
writeHeader();
_headerWritten = true;
if (i + 1 < len) // write out the remaining
if (i + 1 < len) {
// write out the remaining
out.write(buf, off+i+1, len-i-1);
_dataWritten += len-i-1;
}
return;
}
}
@ -128,7 +145,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
if ( (keyLen <= 0) || (valLen <= 0) )
throw new IOException("Invalid header @ " + j);
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
String val = new String(_headerBuffer.getData(), j+2, valLen);
String val = new String(_headerBuffer.getData(), j+2, valLen).trim();
if (_log.shouldLog(Log.INFO))
_log.info("Response header [" + key + "] = [" + val + "]");
if ("Connection".equalsIgnoreCase(key)) {
out.write("Connection: close\n".getBytes());
@ -136,6 +156,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
out.write("Proxy-Connection: close\n".getBytes());
proxyConnectionSent = true;
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
_gzip = true;
} else {
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
}
@ -152,13 +174,85 @@ class HTTPResponseOutputStream extends FilterOutputStream {
if (!proxyConnectionSent)
out.write("Proxy-Connection: close\n".getBytes());
out.write("\n".getBytes()); // end of the headers
finishHeaders();
boolean shouldCompress = shouldCompress();
if (_log.shouldLog(Log.INFO))
_log.info("After headers: gzip? " + _gzip + " compress? " + shouldCompress);
// done, shove off
if (_headerBuffer.getData().length == CACHE_SIZE)
_cache.release(_headerBuffer);
else
_headerBuffer = null;
if (shouldCompress) {
beginProcessing();
}
}
protected boolean shouldCompress() { return _gzip; }
protected void finishHeaders() throws IOException {
out.write("\n".getBytes()); // end of the headers
}
protected void beginProcessing() throws IOException {
out.flush();
PipedInputStream pi = new PipedInputStream();
PipedOutputStream po = new PipedOutputStream(pi);
new I2PThread(new Pusher(pi, out), "HTTP decompresser").start();
out = po;
}
private class Pusher implements Runnable {
private InputStream _in;
private OutputStream _out;
public Pusher(InputStream in, OutputStream out) {
_in = in;
_out = out;
}
public void run() {
OutputStream to = null;
InternalGZIPInputStream in = null;
long start = System.currentTimeMillis();
long written = 0;
try {
in = new InternalGZIPInputStream(_in);
byte buf[] = new byte[8192];
int read = -1;
while ( (read = in.read(buf)) != -1) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read " + read + " and writing it to the browser/streams");
_out.write(buf, 0, read);
_out.flush();
written += read;
}
if (_log.shouldLog(Log.INFO))
_log.info("Decompressed: " + written + ", " + in.getTotalRead() + "/" + in.getTotalExpanded());
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error decompressing: " + written + ", " + in.getTotalRead() + "/" + in.getTotalExpanded(), ioe);
} finally {
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
}
long end = System.currentTimeMillis();
long compressed = in.getTotalRead();
long expanded = in.getTotalExpanded();
double ratio = 0;
if (expanded > 0)
ratio = compressed/expanded;
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start);
_context.statManager().addRateData("i2ptunnel.httpCompressed", compressed, end-start);
_context.statManager().addRateData("i2ptunnel.httpExpanded", expanded, end-start);
}
}
private class InternalGZIPInputStream extends GZIPInputStream {
public InternalGZIPInputStream(InputStream in) throws IOException {
super(in);
}
public long getTotalRead() { return super.inf.getTotalIn(); }
public long getTotalExpanded() { return super.inf.getTotalOut(); }
}
public static void main(String args[]) {

View File

@ -27,6 +27,7 @@
* not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
@ -108,8 +109,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
_tunnelId = ++__tunnelId;
_log = _context.logManager().getLog(I2PTunnel.class);
_event = new EventDispatcherImpl();
_clientOptions = new Properties();
_clientOptions.putAll(System.getProperties());
Properties p = new Properties();
p.putAll(System.getProperties());
_clientOptions = p;
_sessions = new ArrayList(1);
addConnectionEventListener(lsnr);
@ -288,8 +290,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("textserver <host> <port> <privkey>");
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log("gentextkeys");
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile>");
l.log("httpclient <port>");
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log("lookup <name>");
l.log("quit");
l.log("close [forced] <jobnumber>|all");
@ -486,12 +488,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
* Also sets the event "openClientResult" = "error" or "ok" (before setting the value to "ok" it also
* adds "Ready! Port #" to the logger as well). In addition, it will also set "clientLocalPort" =
* Integer port number if the client is listening
* sharedClient parameter is a String "true" or "false"
*
* @param args {portNumber, destinationBase64 or "file:filename"}
* @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]}
* @param l logger to receive events and output
*/
public void runClient(String args[], Logging l) {
if (args.length == 2) {
boolean isShared = true;
if (args.length == 3)
isShared = Boolean.valueOf(args[2].trim()).booleanValue();
if ( (args.length == 2) || (args.length == 3) ) {
int portNum = -1;
try {
portNum = Integer.parseInt(args[0]);
@ -502,6 +508,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
return;
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this);
addtask(task);
@ -512,11 +519,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("clientTaskId", new Integer(-1));
}
} else {
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>");
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>]");
l.log(" creates a client that forwards port to the pubkey.\n"
+ " use 0 as port to get a free port assigned. If you specify\n"
+ " a comma delimited list of pubkeys, it will rotate among them\n"
+ " randomlyl");
+ " randomlyl. sharedClient indicates if this client shares \n"
+ " with other clients (true of false)");
notifyEvent("clientTaskId", new Integer(-1));
}
}
@ -526,12 +534,13 @@ public class I2PTunnel implements Logging, EventDispatcher {
*
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
* parameter sharedClient is a String, either "true" or "false"
*
* @param args {portNumber and (optionally) proxy to be used for the WWW}
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
* @param l logger to receive events and output
*/
public void runHttpClient(String args[], Logging l) {
if (args.length >= 1 && args.length <= 2) {
if (args.length >= 1 && args.length <= 3) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
@ -541,12 +550,32 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("httpclientTaskId", new Integer(-1));
return;
}
String proxy = "squid.i2p";
if (args.length == 2) {
proxy = args[1];
boolean isShared = true;
if (args.length > 1) {
if ("true".equalsIgnoreCase(args[1].trim())) {
isShared = true;
if (args.length == 3)
proxy = args[2];
} else if ("false".equalsIgnoreCase(args[1].trim())) {
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
isShared = false;
if (args.length == 3)
proxy = args[2];
} else if (args.length == 3) {
isShared = false; // not "true"
proxy = args[2];
_log.warn("args[1] == [" + args[1] + "] but rejected");
} else {
// isShared not specified, default to true
isShared = true;
proxy = args[1];
}
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelHTTPClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
addtask(task);
@ -557,8 +586,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("httpclientTaskId", new Integer(-1));
}
} else {
l.log("httpclient <port> [<proxy>]");
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log(" creates a client that distributes HTTP requests.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
l.log(" <proxy> (optional) indicates a proxy server to be used");
l.log(" when trying to access an address out of the .i2p domain");
l.log(" (the default proxy is squid.i2p).");
@ -1117,6 +1147,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
private String getPrefix() { return '[' + _tunnelId + "]: "; }
public I2PAppContext getContext() { return _context; }
/**
* Call this whenever we lose touch with the router involuntarily (aka the router

View File

@ -101,6 +101,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
this.l = l;
this.handlerName = handlerName + _clientId;
// no need to load the netDb with leaseSets for destinations that will never
// be looked up
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
while (sockMgr == null) {
synchronized (sockLock) {
if (ownDest) {
@ -110,7 +114,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
}
if (sockMgr == null) {
_log.log(Log.CRIT, "Unable to create socket manager");
_log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
@ -209,8 +213,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
props.putAll(System.getProperties());
else
props.putAll(tunnel.getClientOptions());
I2PSocketManager sockManager = I2PSocketManagerFactory.createManager(tunnel.host, Integer.parseInt(tunnel.port), props);
if (sockManager == null) return null;
int portNum = 7654;
if (tunnel.port != null) {
try {
portNum = Integer.parseInt(tunnel.port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + tunnel.port + "], reverting to " + portNum);
}
}
I2PSocketManager sockManager = null;
while (sockManager == null) {
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
if (sockManager == null) {
_log.log(Log.CRIT, "Unable to create socket manager");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
sockManager.setName("Client");
return sockManager;
}

View File

@ -23,6 +23,7 @@ import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
/**
@ -70,7 +71,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"wrong BASE64 I2P Destination or the link you are following is "+
"bad. The host (or the WWW proxy, if you're using one) could also "+
"be temporarily offline. You may want to <b>retry</b>. "+
"Could not find the following Destination:<BR><BR>")
"Could not find the following Destination:<BR><BR><div>")
.getBytes();
private final static byte[] ERR_TIMEOUT =
@ -190,6 +191,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "ISO-8859-1"));
String line, method = null, protocol = null, host = null, destination = null;
StringBuffer newRequest = new StringBuffer();
int ahelper = 0;
while ((line = br.readLine()) != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
@ -282,6 +284,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (addressHelper != null) {
destination = addressHelper;
host = getHostName(destination);
ahelper = 1;
}
}
line = method + " " + request.substring(pos);
@ -366,6 +369,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
if (line.length() == 0) {
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
boolean gzip = false;
if (ok != null)
gzip = Boolean.valueOf(ok).booleanValue();
if (gzip)
newRequest.append("Accept-Encoding: x-i2p-gzip\r\n");
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
newRequest.append("Connection: close\r\n\r\n");
break;
@ -403,7 +413,19 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
l.log("Could not resolve " + destination + ".");
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, destination);
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else if(ahelper != 0)
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
s.close();
return;
}
@ -476,10 +498,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (out != null) {
out.write(errMessage);
if (targetRequest != null) {
out.write(targetRequest.getBytes());
int protopos = targetRequest.indexOf(" ");
String uri = targetRequest.substring(0, protopos);
out.write("<a href=\"http://".getBytes());
out.write(uri.getBytes());
out.write("\">http://".getBytes());
out.write(uri.getBytes());
out.write("</a>".getBytes());
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
}
out.write("<p /><i>Generated on: ".getBytes());
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
@ -493,7 +521,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
if (out != null) {
try {
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, wwwProxy);
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
} catch (IOException ioe) {
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
}

View File

@ -3,14 +3,13 @@
*/
package net.i2p.i2ptunnel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.Iterator;
import java.util.Properties;
import java.util.zip.GZIPOutputStream;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
@ -24,7 +23,9 @@ import net.i2p.util.Log;
/**
* Simple extension to the I2PTunnelServer that filters the HTTP
* headers sent from the client to the server, replacing the Host
* header with whatever this instance has been configured with.
* header with whatever this instance has been configured with, and
* if the browser set Accept-encoding: x-i2p-gzip, gzip the http
* message body and set Content-encoding: x-i2p-gzip.
*
*/
public class I2PTunnelHTTPServer extends I2PTunnelServer {
@ -35,87 +36,201 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
}
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
}
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
_spoofHost = spoofHost;
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
}
public void run() {
try {
I2PServerSocket i2pss = sockMgr.getServerSocket();
while (true) {
I2PSocket i2ps = i2pss.accept();
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
I2PThread t = new I2PThread(new Handler(i2ps));
t.start();
}
} catch (I2PException ex) {
_log.error("Error while waiting for I2PConnections", ex);
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
}
/**
* Async handler to keep .accept() from blocking too long.
* todo: replace with a thread pool so we dont get overrun by threads if/when
* receiving a lot of connection requests concurrently.
* Called by the thread pool of I2PSocket handlers
*
*/
private class Handler implements Runnable {
private I2PSocket _handleSocket;
public Handler(I2PSocket socket) {
_handleSocket = socket;
}
public void run() {
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many
//threads.
try {
_handleSocket.setReadTimeout(readTimeout);
String modifiedHeader = getModifiedHeader();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Modified header: [" + modifiedHeader + "]");
Socket s = new Socket(remoteHost, remotePort);
afterSocket = I2PAppContext.getGlobalContext().clock().now();
new I2PTunnelRunner(s, _handleSocket, slock, null, modifiedHeader.getBytes(), null);
} catch (SocketException ex) {
try {
_handleSocket.close();
} catch (IOException ioe) {
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
if (timeToHandle > 1000)
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: "
+ (afterSocket-afterAccept) + "]");
}
private String getModifiedHeader() throws IOException {
InputStream in = _handleSocket.getInputStream();
protected void blockingHandle(I2PSocket socket) {
long afterAccept = getTunnel().getContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many
//threads.
try {
// give them 5 seconds to send in the HTTP request
socket.setReadTimeout(5*1000);
InputStream in = socket.getInputStream();
StringBuffer command = new StringBuffer(128);
Properties headers = readHeaders(in, command);
headers.setProperty("Host", _spoofHost);
if ( (_spoofHost != null) && (_spoofHost.trim().length() > 0) )
headers.setProperty("Host", _spoofHost);
headers.setProperty("Connection", "close");
return formatHeaders(headers, command);
String modifiedHeader = formatHeaders(headers, command);
//String modifiedHeader = getModifiedHeader(socket);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Modified header: [" + modifiedHeader + "]");
socket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
afterSocket = getTunnel().getContext().clock().now();
// instead of i2ptunnelrunner, use something that reads the HTTP
// request from the socket, modifies the headers, sends the request to the
// server, reads the response headers, rewriting to include Content-encoding: x-i2p-gzip
// if it was one of the Accept-encoding: values, and gzip the payload
Properties opts = getTunnel().getClientOptions();
boolean allowGZIP = true;
if (opts != null) {
String val = opts.getProperty("i2ptunnel.gzip");
if ( (val != null) && (!Boolean.valueOf(val).booleanValue()) )
allowGZIP = false;
}
String enc = headers.getProperty("Accept-encoding");
if (_log.shouldLog(Log.INFO))
_log.info("HTTP server encoding header: " + enc);
if ( allowGZIP && (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) ) {
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
req.start();
} else {
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
}
} catch (SocketException ex) {
try {
socket.close();
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", ex);
}
long afterHandle = getTunnel().getContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0);
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
private class CompressedRequestor implements Runnable {
private Socket _webserver;
private I2PSocket _browser;
private String _headers;
public CompressedRequestor(Socket webserver, I2PSocket browser, String headers) {
_webserver = webserver;
_browser = browser;
_headers = headers;
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info("Compressed requestor running");
OutputStream serverout = null;
OutputStream browserout = null;
InputStream browserin = null;
InputStream serverin = null;
try {
serverout = _webserver.getOutputStream();
if (_log.shouldLog(Log.INFO))
_log.info("request headers: " + _headers);
serverout.write(_headers.getBytes());
browserin = _browser.getInputStream();
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), "http compressed sender");
sender.start();
browserout = _browser.getOutputStream();
serverin = _webserver.getInputStream();
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
if (_log.shouldLog(Log.INFO))
_log.info("Before pumping the compressed response");
s.run(); // same thread
if (_log.shouldLog(Log.INFO))
_log.info("After pumping the compressed response: " + compressedOut.getTotalRead() + "/" + compressedOut.getTotalCompressed());
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("error compressing", ioe);
} finally {
if (browserout != null) try { browserout.close(); } catch (IOException ioe) {}
if (serverout != null) try { serverout.close(); } catch (IOException ioe) {}
if (browserin != null) try { browserin.close(); } catch (IOException ioe) {}
if (serverin != null) try { serverin.close(); } catch (IOException ioe) {}
}
}
}
private class Sender implements Runnable {
private OutputStream _out;
private InputStream _in;
private String _name;
public Sender(OutputStream out, InputStream in, String name) {
_out = out;
_in = in;
_name = name;
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": Begin sending");
try {
byte buf[] = new byte[4096];
int read = 0;
int total = 0;
while ( (read = _in.read(buf)) != -1) {
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": read " + read + " and sending through the stream");
_out.write(buf, 0, read);
total += read;
}
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": Done sending: " + total);
_out.flush();
} catch (IOException ioe) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error sending", ioe);
} finally {
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
}
}
}
private class CompressedResponseOutputStream extends HTTPResponseOutputStream {
private InternalGZIPOutputStream _gzipOut;
public CompressedResponseOutputStream(OutputStream o) {
super(o);
}
protected boolean shouldCompress() { return true; }
protected void finishHeaders() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Including x-i2p-gzip as the content encoding in the response");
out.write("Content-encoding: x-i2p-gzip\n".getBytes());
super.finishHeaders();
}
protected void beginProcessing() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Beginning compression processing");
out.flush();
_gzipOut = new InternalGZIPOutputStream(out);
out = _gzipOut;
}
public long getTotalRead() { return _gzipOut.getTotalRead(); }
public long getTotalCompressed() { return _gzipOut.getTotalCompressed(); }
}
private class InternalGZIPOutputStream extends GZIPOutputStream {
public InternalGZIPOutputStream(OutputStream target) throws IOException {
super(target);
}
public long getTotalRead() { return super.def.getTotalIn(); }
public long getTotalCompressed() { return super.def.getTotalOut(); }
}
private String formatHeaders(Properties headers, StringBuffer command) {
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
buf.append(command.toString()).append('\n');
@ -150,6 +265,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
String name = buf.substring(0, split);
String value = buf.substring(split+2); // ": "
if ("Accept-encoding".equalsIgnoreCase(name))
name = "Accept-encoding";
headers.setProperty(name, value);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read the header [" + name + "] = [" + value + "]");

View File

@ -11,6 +11,7 @@ import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.ConnectException;
import java.util.Iterator;
import java.util.Properties;
@ -39,6 +40,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
protected InetAddress remoteHost;
protected int remotePort;
private boolean _usePool;
private Logging l;
@ -46,15 +48,27 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
/** default timeout to 3 minutes - override if desired */
protected long readTimeout = DEFAULT_READ_TIMEOUT;
private static final boolean DEFAULT_USE_POOL = false;
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
init(host, port, bais, privData, l);
}
public I2PTunnelServer(InetAddress host, int port, File privkey, String privkeyname, Logging l,
EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
try {
init(host, port, new FileInputStream(privkey), privkeyname, l);
} catch (IOException ioe) {
@ -65,6 +79,11 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
init(host, port, privData, privkeyname, l);
}
@ -75,9 +94,18 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
I2PClient client = I2PClientFactory.createClient();
Properties props = new Properties();
props.putAll(getTunnel().getClientOptions());
int portNum = 7654;
if (getTunnel().port != null) {
try {
portNum = Integer.parseInt(getTunnel().port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
}
}
while (sockMgr == null) {
synchronized (slock) {
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, Integer.parseInt(getTunnel().port),
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, portNum,
props);
}
@ -150,58 +178,103 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
}
private static final String PROP_HANDLER_COUNT = "i2ptunnel.blockingHandlerCount";
private static final int DEFAULT_HANDLER_COUNT = 10;
protected int getHandlerCount() {
int rv = DEFAULT_HANDLER_COUNT;
String cnt = getTunnel().getClientOptions().getProperty(PROP_HANDLER_COUNT);
if (cnt != null) {
try {
rv = Integer.parseInt(cnt);
if (rv <= 0)
rv = DEFAULT_HANDLER_COUNT;
} catch (NumberFormatException nfe) {
rv = DEFAULT_HANDLER_COUNT;
}
}
return rv;
}
public void run() {
try {
if (shouldUsePool()) {
I2PServerSocket i2pss = sockMgr.getServerSocket();
int handlers = getHandlerCount();
for (int i = 0; i < handlers; i++) {
I2PThread handler = new I2PThread(new Handler(i2pss), "Handle Server " + i);
handler.start();
}
} else {
I2PServerSocket i2pss = sockMgr.getServerSocket();
while (true) {
I2PSocket i2ps = i2pss.accept();
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
I2PThread t = new I2PThread(new Handler(i2ps));
t.start();
try {
final I2PSocket i2ps = i2pss.accept();
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
new I2PThread(new Runnable() { public void run() { blockingHandle(i2ps); } }).start();
} catch (I2PException ipe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error accepting - KILLING THE TUNNEL SERVER", ipe);
return;
} catch (ConnectException ce) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error accepting", ce);
// not killing the server..
}
}
} catch (I2PException ex) {
_log.error("Error while waiting for I2PConnections", ex);
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
}
public boolean shouldUsePool() { return _usePool; }
/**
* Async handler to keep .accept() from blocking too long.
* todo: replace with a thread pool so we dont get overrun by threads if/when
* receiving a lot of connection requests concurrently.
* minor thread pool to pull off the accept() concurrently. there are still lots
* (and lots) of wasted threads within the I2PTunnelRunner, but its a start
*
*/
private class Handler implements Runnable {
private I2PSocket _handleSocket;
public Handler(I2PSocket socket) {
_handleSocket = socket;
private I2PServerSocket _serverSocket;
public Handler(I2PServerSocket serverSocket) {
_serverSocket = serverSocket;
}
public void run() {
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many
//threads.
try {
_handleSocket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
afterSocket = I2PAppContext.getGlobalContext().clock().now();
new I2PTunnelRunner(s, _handleSocket, slock, null, null);
} catch (SocketException ex) {
while (open) {
try {
_handleSocket.close();
} catch (IOException ioe) {
_log.error("Error while closing the received i2p con", ex);
blockingHandle(_serverSocket.accept());
} catch (I2PException ex) {
_log.error("Error while waiting for I2PConnections", ex);
return;
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
return;
}
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
if (timeToHandle > 1000)
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
}
protected void blockingHandle(I2PSocket socket) {
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many
//threads.
try {
socket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
afterSocket = I2PAppContext.getGlobalContext().clock().now();
new I2PTunnelRunner(s, socket, slock, null, null);
} catch (SocketException ex) {
try {
socket.close();
} catch (IOException ioe) {
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
if (timeToHandle > 1000)
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
}

View File

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

View File

@ -1,435 +0,0 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Quick and dirty socket listener to control an I2PTunnel.
* Basically run this class as TunnelManager [listenHost] [listenPort] and
* then send it commands on that port. Commands are one shot deals -
* Send a command + newline, get a response plus newline, then get disconnected.
* <p />
* <b>Implemented commands:</b>
* <pre>
* -------------------------------------------------
* lookup &lt;name&gt;\n
* --
* &lt;base64 of the destination&gt;\n
* or
* &lt;error message, usually 'Unknown host'&gt;\n
*
* Lookup the public key of a named destination (i.e. listed in hosts.txt)
* -------------------------------------------------
* genkey\n
* --
* &lt;base64 of the destination&gt;\t&lt;base64 of private data&gt;\n
*
* Generates a new public and private key pair
* -------------------------------------------------
* convertprivate &lt;base64 of privkey&gt;
* --
* &lt;base64 of destination&gt;\n
* or
* &lt;error message&gt;\n
*
* Returns the destination (pubkey) of a given private key.
* -------------------------------------------------
* listen_on &lt;ip&gt;\n
* --
* ok\n
* or
* error\n
*
* Sets the ip address clients will listen on. By default this is the
* localhost (127.0.0.1)
* -------------------------------------------------
* openclient &lt;listenPort&gt; &lt;peer&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open a tunnel on the given &lt;listenport&gt; to the destination specified
* by &lt;peer&gt;. If &lt;listenPort&gt; is 0 a free port is picked and returned in
* the reply message. Otherwise the short reply message is used.
* Peer can be the base64 of the destination, a file with the public key
* specified as 'file:&lt;filename&gt;' or the name of a destination listed in
* hosts.txt. The &lt;jobId&gt; returned together with "ok" and &lt;listenport&gt; can
* later be used as argument for the "close" command.
* -------------------------------------------------
* openhttpclient &lt;listenPort&gt; [&lt;proxy&gt;]\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open an HTTP proxy through the I2P on the given
* &lt;listenport&gt;. &lt;proxy&gt; (optional) specifies a
* destination to be used as an outbound proxy, to access normal WWW
* sites out of the .i2p domain. If &lt;listenPort&gt; is 0 a free
* port is picked and returned in the reply message. Otherwise the
* short reply message is used. &lt;proxy&gt; can be the base64 of the
* destination, a file with the public key specified as
* 'file:&lt;filename&gt;' or the name of a destination listed in
* hosts.txt. The &lt;jobId&gt; returned together with "ok" and
* &lt;listenport&gt; can later be used as argument for the "close"
* command.
* -------------------------------------------------
* opensockstunnel &lt;listenPort&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open an SOCKS tunnel through the I2P on the given
* &lt;listenport&gt;. If &lt;listenPort&gt; is 0 a free port is
* picked and returned in the reply message. Otherwise the short
* reply message is used. The &lt;jobId&gt; returned together with
* "ok" and &lt;listenport&gt; can later be used as argument for the
* "close" command.
* -------------------------------------------------
* openserver &lt;serverHost&gt; &lt;serverPort&gt; &lt;serverKeys&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* error\n
*
* Starts receiving traffic for the destination specified by &lt;serverKeys&gt;
* and forwards it to the &lt;serverPort&gt; of &lt;serverHost&gt;.
* &lt;serverKeys&gt; is the base 64 encoded private key set of the local
* destination. The &lt;joId&gt; returned together with "ok" can later be used
* as argument for the "close" command.
* -------------------------------------------------
* close [forced] &lt;jobId&gt;\n
* or
* close [forced] all\n
* --
* ok\n
* or
* error\n
*
* Closes the job specified by &lt;jobId&gt; or all jobs. Use the list command
* for a list of running jobs.
* Normally a connection job is not closed when it still has an active
* connection. Use the optional 'forced' keyword to close connections
* regardless of their use.
* -------------------------------------------------
* list\n
* --
* Example output:
*
* [0] i2p.dnsalias.net/69.55.226.145:5555 &lt;- C:\i2pKeys\squidPriv
* [1] 8767 -&gt; HTTPClient
* [2] 7575 -&gt; file:C:\i2pKeys\squidPub
* [3] 5252 -&gt; sCcSANIO~f4AQtCNI1BvDp3ZBS~9Ag5O0k0Msm7XBWWz5eOnZWL3MQ-2rxlesucb9XnpASGhWzyYNBpWAfaIB3pux1J1xujQLOwscMIhm7T8BP76Ly5jx6BLZCYrrPj0BI0uV90XJyT~4UyQgUlC1jzFQdZ9HDgBPJDf1UI4-YjIwEHuJgdZynYlQ1oUFhgno~HhcDByXO~PDaO~1JDMDbBEfIh~v6MgmHp-Xchod1OfKFrxFrzHgcJbn7E8edTFjZA6JCi~DtFxFelQz1lSBd-QB1qJnA0g-pVL5qngNUojXJCXs4qWcQ7ICLpvIc-Fpfj-0F1gkVlGDSGkb1yLH3~8p4czYgR3W5D7OpwXzezz6clpV8kmbd~x2SotdWsXBPRhqpewO38coU4dJG3OEUbuYmdN~nJMfWbmlcM1lXzz2vBsys4sZzW6dV3hZnbvbfxNTqbdqOh-KXi1iAzXv7CVTun0ubw~CfeGpcAqutC5loRUq7Mq62ngOukyv8Z9AAAA
*
* Lists descriptions of all running jobs. The exact format of the
* description depends on the type of job.
* -------------------------------------------------
* </pre>
*/
public class TunnelManager implements Runnable {
private final static Log _log = new Log(TunnelManager.class);
private I2PTunnel _tunnel;
private ServerSocket _socket;
private boolean _keepAccepting;
public TunnelManager(int listenPort) {
this(null, listenPort);
}
public TunnelManager(String listenHost, int listenPort) {
_tunnel = new I2PTunnel();
_keepAccepting = true;
try {
if (listenHost != null) {
_socket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
_log.info("Listening for tunnel management clients on " + listenHost + ":" + listenPort);
} else {
_socket = new ServerSocket(listenPort);
_log.info("Listening for tunnel management clients on localhost:" + listenPort);
}
} catch (Exception e) {
_log.error("Error starting up tunnel management listener on " + listenPort, e);
}
}
public static void main(String args[]) {
int port = 7676;
String host = null;
if (args.length == 1) {
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
_log.error("Usage: TunnelManager [host] [port]");
return;
}
} else if (args.length == 2) {
host = args[0];
try {
port = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
_log.error("Usage: TunnelManager [host] [port]");
return;
}
}
TunnelManager mgr = new TunnelManager(host, port);
Thread t = new I2PThread(mgr, "Listener");
t.start();
}
public void run() {
if (_socket == null) {
_log.error("Unable to start listening, since the socket was not bound. Already running?");
return;
}
_log.debug("Running");
try {
while (_keepAccepting) {
Socket socket = _socket.accept();
_log.debug("Client accepted");
if (socket != null) {
Thread t = new I2PThread(new TunnelManagerClientRunner(this, socket));
t.setName("TunnelManager Client");
t.setPriority(I2PThread.MIN_PRIORITY);
t.start();
}
}
} catch (IOException ioe) {
_log.error("Error accepting connections", ioe);
} catch (Exception e) {
_log.error("Other error?!", e);
} finally {
if (_socket != null) try {
_socket.close();
} catch (IOException ioe) {
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
}
}
public void error(String msg, OutputStream out) throws IOException {
out.write(msg.getBytes());
out.write('\n');
}
public void processQuit(OutputStream out) throws IOException {
out.write("Nice try".getBytes());
out.write('\n');
}
public void processList(OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
long startCommand = Clock.getInstance().now();
_tunnel.runCommand("list", buf);
Object obj = _tunnel.waitEventValue("listDone");
long endCommand = Clock.getInstance().now();
String str = buf.getBuffer();
_log.debug("ListDone complete after " + (endCommand - startCommand) + "ms: [" + str + "]");
out.write(str.getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processListenOn(String ip, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("listen_on " + ip, buf);
String status = (String) _tunnel.waitEventValue("listen_onResult");
out.write((status + "\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* "lookup <name>" returns with the result in base64, else "Unknown host" [or something like that],
* then a newline.
*
*/
public void processLookup(String name, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("lookup " + name, buf);
String rv = (String) _tunnel.waitEventValue("lookupResult");
out.write(rv.getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processTestDestination(String destKey, OutputStream out) throws IOException {
try {
Destination d = new Destination();
d.fromBase64(destKey);
out.write("valid\n".getBytes());
} catch (DataFormatException dfe) {
out.write("invalid\n".getBytes());
}
out.flush();
}
public void processConvertPrivate(String priv, OutputStream out) throws IOException {
try {
Destination dest = new Destination();
dest.fromBase64(priv);
String str = dest.toBase64();
out.write(str.getBytes());
out.write('\n');
} catch (DataFormatException dfe) {
_log.error("Error converting private data", dfe);
out.write("Error converting private key\n".getBytes());
}
}
public void processClose(String which, boolean forced, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand((forced ? "close forced " : "close ") + which, buf);
String str = (String) _tunnel.waitEventValue("closeResult");
out.write((str + "\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* "genkey" returns with the base64 of the destination, followed by a tab, then the base64 of that
* destination's private keys, then a newline.
*
*/
public void processGenKey(OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("gentextkeys", buf);
String priv = (String) _tunnel.waitEventValue("privateKey");
String pub = (String) _tunnel.waitEventValue("publicDestination");
out.write((pub + "\t" + priv).getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processOpenClient(int listenPort, String peer, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("client " + listenPort + " " + peer, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("clientTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String) _tunnel.waitEventValue("openClientResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenHTTPClient(int listenPort, String proxy, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("httpclient " + listenPort + " " + proxy, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("httpclientTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String) _tunnel.waitEventValue("openHTTPClientResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenSOCKSTunnel(int listenPort, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("sockstunnel " + listenPort, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("sockstunnelTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String) _tunnel.waitEventValue("openSOCKSTunnelResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenServer(String serverHost, int serverPort, String privateKeys, OutputStream out)
throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("textserver " + serverHost + " " + serverPort + " " + privateKeys, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("serverTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String) _tunnel.waitEventValue("openServerResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* Frisbee.
*
*/
public void unknownCommand(String command, OutputStream out) throws IOException {
out.write("Unknown command: ".getBytes());
out.write(command.getBytes());
out.write("\n".getBytes());
}
}

View File

@ -1,194 +0,0 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.StringTokenizer;
import net.i2p.util.Log;
/**
* Runner thread that reads commands from the socket and fires off commands to
* the TunnelManager
*
*/
class TunnelManagerClientRunner implements Runnable {
private final static Log _log = new Log(TunnelManagerClientRunner.class);
private TunnelManager _mgr;
private Socket _clientSocket;
public TunnelManagerClientRunner(TunnelManager mgr, Socket socket) {
_clientSocket = socket;
_mgr = mgr;
}
public void run() {
_log.debug("Client running");
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(_clientSocket.getInputStream()));
OutputStream out = _clientSocket.getOutputStream();
String cmd = reader.readLine();
if (cmd != null) processCommand(cmd, out);
} catch (IOException ioe) {
_log.error("Error processing client commands", ioe);
} finally {
if (_clientSocket != null) try {
_clientSocket.close();
} catch (IOException ioe) {
}
}
_log.debug("Client closed");
}
/**
* Parse the command string and fire off the appropriate tunnelManager method,
* sending the results to the output stream
*/
private void processCommand(String command, OutputStream out) throws IOException {
_log.debug("Processing [" + command + "]");
StringTokenizer tok = new StringTokenizer(command);
if (!tok.hasMoreTokens()) {
_mgr.unknownCommand(command, out);
} else {
String cmd = tok.nextToken();
if ("quit".equalsIgnoreCase(cmd)) {
_mgr.processQuit(out);
} else if ("lookup".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processLookup(tok.nextToken(), out);
else
_mgr.error("Usage: lookup <hostname>", out);
} else if ("testdestination".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processTestDestination(tok.nextToken(), out);
else
_mgr.error("Usage: testdestination <publicDestination>", out);
} else if ("convertprivate".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processConvertPrivate(tok.nextToken(), out);
else
_mgr.error("Usage: convertprivate <privateData>", out);
} else if ("close".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens()) {
String closeArg;
if ((closeArg = tok.nextToken()).equals("forced")) {
if (tok.hasMoreTokens()) {
_mgr.processClose(tok.nextToken(), true, out);
} else {
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
}
} else {
_mgr.processClose(closeArg, false, out);
}
} else {
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
}
} else if ("genkey".equalsIgnoreCase(cmd)) {
_mgr.processGenKey(out);
} else if ("list".equalsIgnoreCase(cmd)) {
_mgr.processList(out);
} else if ("listen_on".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens()) {
_mgr.processListenOn(tok.nextToken(), out);
} else {
_mgr.error("Usage: listen_on <ip>", out);
}
} else if ("openclient".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String peer = null;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenPort> <peer>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenport> <peer>", out);
return;
}
peer = tok.nextToken();
_mgr.processOpenClient(listenPort, peer, out);
} else if ("openhttpclient".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String proxy = "squid.i2p";
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openhttpclient <listenPort> [<proxy>]", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (tok.hasMoreTokens()) {
proxy = tok.nextToken();
}
if (tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenport> [<proxy>]", out);
return;
}
_mgr.processOpenHTTPClient(listenPort, proxy, out);
} else if ("opensockstunnel".equalsIgnoreCase(cmd)) {
int listenPort = 0;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: opensockstunnel <listenPort>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (tok.hasMoreTokens()) {
_mgr.error("Usage: opensockstunnel <listenport>", out);
return;
}
_mgr.processOpenSOCKSTunnel(listenPort, out);
} else if ("openserver".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String serverHost = null;
String serverKeys = null;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
serverHost = tok.nextToken();
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
serverKeys = tok.nextToken();
_mgr.processOpenServer(serverHost, listenPort, serverKeys, out);
} else {
_mgr.unknownCommand(command, out);
}
}
}
}

View File

@ -36,14 +36,6 @@ public class EditBean extends IndexBean {
}
}
public String getInternalType(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getType();
else
return "";
}
public String getTargetHost(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
@ -81,6 +73,14 @@ public class EditBean extends IndexBean {
return false;
}
public boolean isSharedClient(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return "true".equalsIgnoreCase(tun.getSharedClient());
else
return true;
}
public boolean shouldDelay(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {

View File

@ -52,6 +52,7 @@ public class IndexBean {
private String _privKeyFile;
private String _profile;
private boolean _startOnLoad;
private boolean _sharedClient;
private boolean _privKeyGenerate;
private boolean _removeConfirmed;
@ -204,7 +205,8 @@ public class IndexBean {
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = (TunnelController)controllers.get(i);
if (c == cur) continue;
if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
//only change when they really are declared of beeing a sharedClient
if (("httpclient".equals(c.getType()) || "client".equals(c.getType())) && "true".equalsIgnoreCase(c.getSharedClient())) {
Properties cOpt = c.getConfig("");
if (_tunnelCount != null) {
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
@ -311,6 +313,14 @@ public class IndexBean {
else return internalType;
}
public String getInternalType(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getType();
else
return "";
}
public String getClientInterface(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
@ -335,6 +345,14 @@ public class IndexBean {
return "";
}
public String getSharedClient(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getSharedClient();
else
return "";
}
public String getClientDestination(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun == null) return "";
@ -350,6 +368,19 @@ public class IndexBean {
return "";
}
public String getDestinationBase64(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
String rv = tun.getMyDestination();
if (rv != null)
return rv;
else
return "";
} else {
return "";
}
}
///
/// bean props for form submission
///
@ -448,6 +479,12 @@ public class IndexBean {
public void setStartOnLoad(String moo) {
_startOnLoad = true;
}
public void setShared(String moo) {
_sharedClient=true;
}
public void setShared(boolean val) {
_sharedClient=val;
}
public void setConnectDelay(String moo) {
_connectDelay = true;
}
@ -475,8 +512,14 @@ public class IndexBean {
if (_proxyList != null)
config.setProperty("proxyList", _proxyList);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
if (_name != null && !_sharedClient) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
config.setProperty("sharedClient", _sharedClient + "");
} else if ("client".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
@ -489,6 +532,11 @@ public class IndexBean {
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
if (_name != null && !_sharedClient) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
config.setProperty("sharedClient", _sharedClient + "");
} else if ("server".equals(_type)) {
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
@ -519,8 +567,10 @@ public class IndexBean {
config.setProperty("description", _description);
if (_i2cpHost != null)
config.setProperty("i2cpHost", _i2cpHost);
if (_i2cpPort != null)
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) )
config.setProperty("i2cpPort", _i2cpPort);
else
config.setProperty("i2cpPort", "7654");
if (_customOptions != null) {
StringTokenizer tok = new StringTokenizer(_customOptions);
@ -544,7 +594,7 @@ public class IndexBean {
}
config.setProperty("startOnLoad", _startOnLoad + "");
if (_tunnelCount != null) {
config.setProperty("option.inbound.quantity", _tunnelCount);
config.setProperty("option.outbound.quantity", _tunnelCount);
@ -558,7 +608,7 @@ public class IndexBean {
else
config.setProperty("option.i2p.streaming.connectDelay", "0");
if (_name != null) {
if ( (!"client".equals(_type)) && (!"httpclient".equals(_type)) ) {
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))) || (!_sharedClient) ) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
} else {

View File

@ -93,7 +93,7 @@ if (curTunnel >= 0) {
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachablyByOther" size="20" />
<input type="text" name="reachableByOther" size="20" />
<% } else if ("0.0.0.0".equals(clientInterface)) { %>
<option value="127.0.0.1">Locally (127.0.0.1)</option>
<option value="0.0.0.0" selected="true">Everyone (0.0.0.0)</option>
@ -102,7 +102,7 @@ if (curTunnel >= 0) {
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachablyByOther" size="20" />
<input type="text" name="reachableByOther" size="20" />
<% } else { %>
<option value="127.0.0.1">Locally (127.0.0.1)</option>
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
@ -111,7 +111,7 @@ if (curTunnel >= 0) {
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachablyByOther" size="20" value="<%=clientInterface%>" />
<input type="text" name="reachableByOther" size="20" value="<%=clientInterface%>" />
<% } %>
</td>
@ -161,10 +161,23 @@ if (curTunnel >= 0) {
</td>
</tr>
<tr>
<td>
<b>Shared Client</b>
</td>
<td>
<% if (editBean.isSharedClient(curTunnel)) { %>
<input type="checkbox" value="true" name="shared" checked="true" />
<% } else { %>
<input type="checkbox" value="true" name="shared" />
<% } %>
<i>(Share tunnels with other clients and httpclients? Change requires restart of client proxy)</i>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<b><hr size="1">
Advanced networking options<br />
<span style="color:#dd0000;">(Those are shared between ALL your Client proxies!)</span></b>
<span style="color:#dd0000;">(NOTE: when this client proxy is configured to share tunnels, then these options are for all the shared proxy clients!)</span></b>
</td>
</tr>
<tr>

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
@ -14,4 +14,4 @@
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
</web-app>

View File

@ -15,6 +15,7 @@ import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.data.Destination;
import net.i2p.data.DataFormatException;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
@ -74,61 +75,67 @@ public class StreamSinkClient {
} finally {
if (fis == null) try { fis.close(); } catch (IOException ioe) {}
}
System.out.println("Send " + _sendSize + "KB to " + peer.calculateHash().toBase64());
try {
I2PSocket sock = mgr.connect(peer);
byte buf[] = new byte[32*1024];
Random rand = new Random();
OutputStream out = sock.getOutputStream();
long beforeSending = System.currentTimeMillis();
for (int i = 0; i < _sendSize; i+= 32) {
rand.nextBytes(buf);
out.write(buf);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send " + _sendSize + "KB to " + peer.calculateHash().toBase64());
while (true) {
try {
I2PSocket sock = mgr.connect(peer);
byte buf[] = new byte[Math.min(32*1024, _sendSize*1024)];
Random rand = new Random();
OutputStream out = sock.getOutputStream();
long beforeSending = System.currentTimeMillis();
for (int i = 0; (_sendSize < 0) || (i < _sendSize); i+= buf.length/1024) {
rand.nextBytes(buf);
out.write(buf);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Wrote " + ((1+i*buf.length)/1024) + "/" + _sendSize + "KB");
if (_writeDelay > 0) {
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
}
}
sock.close();
long afterSending = System.currentTimeMillis();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Wrote " + (i+32) + "/" + _sendSize + "KB");
if (_writeDelay > 0) {
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
}
}
sock.close();
long afterSending = System.currentTimeMillis();
System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
} catch (InterruptedIOException iie) {
_log.error("Timeout connecting to the peer", iie);
return;
} catch (NoRouteToHostException nrthe) {
_log.error("Unable to connect to the peer", nrthe);
return;
} catch (ConnectException ce) {
_log.error("Connection already dropped", ce);
return;
} catch (I2PException ie) {
_log.error("Error connecting to the peer", ie);
return;
} catch (IOException ioe) {
_log.error("IO error sending", ioe);
return;
_log.debug("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
} catch (InterruptedIOException iie) {
_log.error("Timeout connecting to the peer", iie);
//return;
} catch (NoRouteToHostException nrthe) {
_log.error("Unable to connect to the peer", nrthe);
//return;
} catch (ConnectException ce) {
_log.error("Connection already dropped", ce);
//return;
} catch (I2PException ie) {
_log.error("Error connecting to the peer", ie);
return;
} catch (IOException ioe) {
_log.error("IO error sending", ioe);
return;
}
}
}
/**
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile</code> <br />
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile [concurrentSends]</code> <br />
* <ul>
* <li><b>sendSizeKB</b>: how many KB to send</li>
* <li><b>sendSizeKB</b>: how many KB to send, or -1 for unlimited</li>
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
* <li><b>serverDestFile</b>: file containing the StreamSinkServer's binary Destination</li>
* <li><b>concurrentSends</b>: how many concurrent threads should send to the server at once</li>
* </ul>
*/
public static void main(String args[]) {
StreamSinkClient client = null;
int sendSizeKB = -1;
int writeDelayMs = -1;
int concurrent = 1;
switch (args.length) {
case 3:
case 3: // fall through
case 4:
try {
sendSizeKB = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
@ -141,9 +148,13 @@ public class StreamSinkClient {
System.err.println("Write delay ms invalid [" + args[1] + "]");
return;
}
if (args.length == 4) {
try { concurrent = Integer.parseInt(args[3]); } catch (NumberFormatException nfe) {}
}
client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
break;
case 5:
case 5: // fall through
case 6:
try {
int port = Integer.parseInt(args[1]);
sendSizeKB = Integer.parseInt(args[2]);
@ -152,11 +163,26 @@ public class StreamSinkClient {
} catch (NumberFormatException nfe) {
System.err.println("arg error");
}
if (args.length == 6) {
try { concurrent = Integer.parseInt(args[5]); } catch (NumberFormatException nfe) {}
}
break;
default:
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile");
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile [concurrentSends]");
}
if (client != null) {
for (int i = 0; i < concurrent; i++)
new I2PThread(new Runner(client), "Client " + i).start();
}
}
private static class Runner implements Runnable {
private StreamSinkClient _client;
public Runner(StreamSinkClient client) {
_client = client;
}
public void run() {
_client.runClient();
}
if (client != null)
client.runClient();
}
}

View File

@ -6,6 +6,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
@ -26,6 +28,7 @@ public class StreamSinkServer {
private String _destFile;
private String _i2cpHost;
private int _i2cpPort;
private int _handlers;
/**
* Create but do not start the streaming server.
@ -34,13 +37,14 @@ public class StreamSinkServer {
* @param ourDestFile filename to write our binary destination to
*/
public StreamSinkServer(String sinkDir, String ourDestFile) {
this(sinkDir, ourDestFile, null, -1);
this(sinkDir, ourDestFile, null, -1, 3);
}
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort) {
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort, int handlers) {
_sinkDir = sinkDir;
_destFile = ourDestFile;
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
_handlers = handlers;
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class);
}
@ -56,7 +60,8 @@ public class StreamSinkServer {
else
mgr = I2PSocketManagerFactory.createManager();
Destination dest = mgr.getSession().getMyDestination();
System.out.println("Listening for connections on: " + dest.calculateHash().toBase64());
if (_log.shouldLog(Log.INFO))
_log.info("Listening for connections on: " + dest.calculateHash().toBase64());
FileOutputStream fos = null;
try {
fos = new FileOutputStream(_destFile);
@ -72,24 +77,16 @@ public class StreamSinkServer {
}
I2PServerSocket sock = mgr.getServerSocket();
while (true) {
try {
I2PSocket curSock = sock.accept();
handle(curSock);
} catch (I2PException ie) {
_log.error("Error accepting connection", ie);
return;
} catch (ConnectException ce) {
_log.error("Connection already dropped", ce);
return;
}
}
startup(sock);
}
private void handle(I2PSocket socket) {
I2PThread t = new I2PThread(new ClientRunner(socket));
t.setName("Handle " + socket.getPeerDestination().calculateHash().toBase64().substring(0,4));
t.start();
public void startup(I2PServerSocket sock) {
for (int i = 0; i < _handlers; i++) {
I2PThread t = new I2PThread(new ClientRunner(sock));
t.setName("Handler " + i);
t.setDaemon(false);
t.start();
}
}
/**
@ -97,27 +94,44 @@ public class StreamSinkServer {
*
*/
private class ClientRunner implements Runnable {
private I2PSocket _sock;
private FileOutputStream _fos;
public ClientRunner(I2PSocket socket) {
_sock = socket;
private I2PServerSocket _socket;
public ClientRunner(I2PServerSocket socket) {
_socket = socket;
}
public void run() {
while (true) {
try {
I2PSocket socket = _socket.accept();
if (socket != null)
handle(socket);
} catch (I2PException ie) {
_log.error("Error accepting connection", ie);
return;
} catch (ConnectException ce) {
_log.error("Connection already dropped", ce);
return;
}
}
}
private void handle(I2PSocket sock) {
FileOutputStream fos = null;
try {
File sink = new File(_sinkDir);
if (!sink.exists())
sink.mkdirs();
File cur = File.createTempFile("clientSink", ".dat", sink);
_fos = new FileOutputStream(cur);
System.out.println("Writing to " + cur.getAbsolutePath());
fos = new FileOutputStream(cur);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Writing to " + cur.getAbsolutePath());
} catch (IOException ioe) {
_log.error("Error creating sink", ioe);
_fos = null;
return;
}
}
public void run() {
if (_fos == null) return;
long start = System.currentTimeMillis();
try {
InputStream in = _sock.getInputStream();
InputStream in = sock.getInputStream();
byte buf[] = new byte[4096];
long written = 0;
int read = 0;
@ -125,47 +139,55 @@ public class StreamSinkServer {
//_fos.write(buf, 0, read);
written += read;
if (_log.shouldLog(Log.DEBUG))
_log.debug("read and wrote " + read);
_log.debug("read and wrote " + read + " (" + written + ")");
}
_fos.write(("written: [" + written + "]\n").getBytes());
fos.write(("written: [" + written + "]\n").getBytes());
long lifetime = System.currentTimeMillis() - start;
_log.error("Got EOF from client socket [written=" + written + " lifetime=" + lifetime + "]");
_log.info("Got EOF from client socket [written=" + written + " lifetime=" + lifetime + "]");
} catch (IOException ioe) {
_log.error("Error writing the sink", ioe);
} finally {
if (_fos != null) try { _fos.close(); } catch (IOException ioe) {}
if (_sock != null) try { _sock.close(); } catch (IOException ioe) {}
_log.error("Client socket closed");
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
if (sock != null) try { sock.close(); } catch (IOException ioe) {}
_log.debug("Client socket closed");
}
}
}
/**
* Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile</code><br />
* Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [numHandlers]</code><br />
* <ul>
* <li><b>sinkDir</b>: Directory to store received files in</li>
* <li><b>ourDestFile</b>: filename to write our binary destination to</li>
* <li><b>numHandlers</b>: how many concurrent connections to handle</li>
* </ul>
*/
public static void main(String args[]) {
StreamSinkServer server = null;
switch (args.length) {
case 0:
server = new StreamSinkServer("dataDir", "server.key", "localhost", 7654);
server = new StreamSinkServer("dataDir", "server.key", "localhost", 7654, 3);
break;
case 2:
server = new StreamSinkServer(args[0], args[1]);
break;
case 4:
case 5:
int handlers = 3;
if (args.length == 5) {
try {
handlers = Integer.parseInt(args[4]);
} catch (NumberFormatException nfe) {}
}
try {
int port = Integer.parseInt(args[1]);
server = new StreamSinkServer(args[2], args[3], args[0], port);
server = new StreamSinkServer(args[2], args[3], args[0], port, handlers);
} catch (NumberFormatException nfe) {
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [handlers]");
}
break;
default:
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [handlers]");
}
if (server != null)
server.runServer();

View File

@ -149,7 +149,8 @@ public class TestSwarm {
public void run() {
_started = _context.clock().now();
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
byte data[] = new byte[32*1024];
byte data[] = new byte[4*1024];
_context.random().nextBytes(data);
long value = 0;
long lastSend = _context.clock().now();
if (_socket == null) {
@ -167,15 +168,19 @@ public class TestSwarm {
try {
OutputStream out = _socket.getOutputStream();
while (!_closed) {
out.write(data);
// out.flush();
_totalSent += data.length;
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
//try { Thread.sleep(100); } catch (InterruptedException ie) {}
long now = _context.clock().now();
_log.debug("Sending " + _connectionId + " after " + (now-lastSend));
lastSend = now;
try { Thread.sleep(20); } catch (InterruptedException ie) {}
if (shouldSend()) {
out.write(data);
// out.flush();
_totalSent += data.length;
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
//try { Thread.sleep(100); } catch (InterruptedException ie) {}
long now = _context.clock().now();
//_log.debug("Sending " + _connectionId + " after " + (now-lastSend));
lastSend = now;
//try { Thread.sleep(20); } catch (InterruptedException ie) {}
} else {
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
}
}
} catch (Exception e) {
_log.error("Error sending", e);
@ -188,13 +193,13 @@ public class TestSwarm {
long now = lastRead;
try {
InputStream in = _socket.getInputStream();
byte buf[] = new byte[32*1024];
byte buf[] = new byte[8*1024];
int read = 0;
while ( (read = in.read(buf)) != -1) {
now = System.currentTimeMillis();
_totalReceived += read;
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
_log.debug("Receiving " + _connectionId + " with " + read + " after " + (now-lastRead));
//_log.debug("Receiving " + _connectionId + " with " + read + " after " + (now-lastRead));
lastRead = now;
}
} catch (Exception e) {
@ -203,4 +208,8 @@ public class TestSwarm {
}
}
}
private boolean shouldSend() {
return Boolean.valueOf(_context.getProperty("shouldSend", "false")).booleanValue();
}
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

26
apps/q/doc/diagrams.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q System Diagrams</title>
</head>
<body>
<h1>Q Diagrams</h1>
Informal system diagrams of Q network, hubs and clients.
<center>
<hr>
<img src="overall.jpg">
<hr>
<img src="client.jpg">
<hr>
<img src="hub.jpg">
</center>
<hr>
<address><a href="mailto:aum@mail.i2p">aum</a></address>
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
<!-- hhmts start -->
Last modified: Mon Apr 18 14:06:02 NZST 2005
<!-- hhmts end -->
</body>
</html>

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

80
apps/q/doc/index.html Normal file
View File

@ -0,0 +1,80 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Quartermaster - I2P Distributed File Store</title>
</head>
<body>
<center>
<h1>Quartermaster<br>an I2P Distributed File Store</h1>
<h3>STATUS<h3>
<i>Whole new (incompatible) version currently in development;
ETA for release approx 4-7 days;
view screenshots <a href="screenshots.html">here</a>
</i>
<br>
<hr>
<small>
<a href="manual/index.html">User Manual</a> |
<a href="spec/index.html">Protocol Spec</a> |
<a href="metadata.html">Metadata Spec</a> |
<a href="diagrams.html">Q Pr0n (diagrams)</a> |
<a href="api/index.html">API Spec</a> |
<a href="qnoderefs.txt">qnoderefs.txt</a> |
Full Download |
Updated jar
</small>
</center>
<hr>
<h2>Intro</h2>
Quartermaster, or Q for short, is a distributed file storage framework for I2P.
<h2>Features</h2>
<ul>
<li>Now features 'QSites' - the Q equivalent of Freenet freesites,
static websites which are retrievable even if author is offline</li>
<li>Easy web interface - interact with Q (and view/insert QSites)
from your web browser</li>
<li>Maximum expectations of content retrievability</li>
<li>Content security akin to Freenet CHK and SSK keys</li>
<li>Powerful, flexible search engine</li>
<li>Comfortably accommodates both permanent and transient
nodes without significant network disruption (for instance,
no flooding of the I2P network with futile
calls to offline nodes)</li>
<li>Rapid query resolution, due to distributed catalogue
mirroring which eliminates all in-network query traffic</li>
<li>Modular, extensible architecture</li>
<li>Simple interfaces for 3rd-party app developers</li>
<li>Is custom-designed and built around I2P, so no duplication of
I2P's encryption/anonymity features</li>
<li>Simple XML-RPC interface for all inter-node communication, makes it easy to
implement user-level clients in any language; also allows alternative
implementations of core server and/or client nodes.</li>
</ul>
<hr>
<h2>Status</h2>
Q is presently under development, and a test release is expected soon.
<hr>
<h2>Architecture</h2>
Refer to the <a href="spec/index.html">Protocol Specification</a> for more information.
<hr>
<!-- Created: Sat Mar 26 11:09:12 NZST 2005 -->
<!-- hhmts start -->
Last modified: Mon Apr 18 18:55:19 NZST 2005
<!-- hhmts end -->
</body>
</html>

View File

@ -0,0 +1,805 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q User/Programmer Manual</title>
<style type="text/css">
<!--
td { vertical-align: top; }
code { font-family: courier, monospace; font-weight: bold }
-->
</style>
</head>
<body style="font-family: arial, helvetica, sans-serif">
<center>
<h1>Q User/Programmer Manual</h1>
<i>A brief but hopefully easy guide to installing and using the Q distributed file
store within the I2P network</i>
<br><br>
<i>(Return to <a href="../index.html">Q Homepage</a>)</i>
<br>
<br>
<small>
<a href="#intro">Introduction</a> |
<a href="#checklist">Checklist</a> |
<a href="#serverorclient">Server?orClient?</a> |
<a href="#walkthrough">Walkthrough</a> |
<a href="#server">Server Nodes</a> |
<a href="#qmgr">About QMgr</a> |
<a href="#contact">Contact us</a>
</small>
</center>
<a name="intro"/>
<hr>
<h2>1. Introduction</h2>
<blockquote>
Q is a distributed Peer2Peer file storage/retrieval network that aims to deliver optimal
performance by respecting the properties of the I2P network.<br>
<br>
This manual serves as a 'walkthrough' guide, to take you through the steps from initial
download, to everyday usage. It also provides information for the benefit of higher-level
client application authors.
</blockquote>
<a name="checklist"/>
<hr>
<h2>2. Preliminary Checklist</h2>
<blockquote>
OK, we assume here that you've already cracked the tarball, and are looking at
the distribution files.<br>
<br>
In order to get Q set up and running, you'll need:
<ol>
<li>An I2P router installed, set up and (permanently or transiently) running</li>
<li>Your system shell set up with at the environment variables:
<ul>
<li><b>CLASSPATH</b> - this should include:
<ul>
<li>The regular I2P jar files and 3rd party support jar files (eg <b>i2p.jar</b>,
<b>i2ptunnel.jar</b>, <b>streaming.jar</b>,
<b>mstreaming.jar</b>, <b>jbigi.jar</b>)</li>
<li>Apache's XML-RPC support jarfile - included in this Q distro as
<b>xmlrpc.jar</b></li>
<li>Aum's jarfile <b>aum.jar</b>, which includes Q and all needed support code</li>
</ul>
</li>
<li><b>PATH</b> - your execution search path <b><i>must</i></b> include the directory
in which your main java VM execution program (<b>java</b>, or on windows systems,
<b>java.exe</b>) resides.<br>
<b>NOTE</b> - if <b>java[.exe]</b> is not on your <b>PATH</b>, then Q <i>will
not run</i>.</li>
</ul>
</ol>
</blockquote>
<a name="serverorclient"/>
<hr>
<h2>3. Q Server or Q Client?</h2>
<blockquote>
Nearly everyone will want to run a <b>Q Client Node</b>.<br>
<Br>
It is only client nodes which provide users with full access to the Q network.<br>
<br>
However, if you have a (near-) permanently running I2P Router, and you're a kind and
generous soul, you might <i>also</i> be willing to run a <b>Q Server Node</b> in addition
to your <b>Q Client Node</b>.<br>
<br>
If you do choose to run a server node, you'll be expected to keep it running as near as
possible to 24/7. While transience of client nodes - frequent entering and leaving the
Q network - causes little or no disruption, transience of server nodes can significantly
impair Q's usability for everyone, particularly if this transience occurs frequently amongst
more than the smallest percentage of the server node pool.<br>
<br>
Until you're feeling well "settled in" with Q, your best approach is to just run a
client node for now, and add a server node later when you feel ready.<br>
</blockquote>
<a name="walkthrough"/>
<hr>
<h2>4. Q Walkthrough</h2>
<h3>4.1. Introduction</h3>
<blockquote>
This chapter discusses the deployment and usage of a Q Client Node, and will take you
through the steps of:
<ol>
<li>Double-checking that you've met the installation requirements</li>
<li>Launching a Q Client Node</li>
<li>Verifying that your Q Client Node is running</li>
<li>If your node fails to launch, figuring out why</li>
<li>Importing one or more noderefs into your node</li>
<li>Observing that your node is discovering other nodes on the network</li>
<li>Observing that your node is discovering content on the network</li>
<li>Searching for items of content that match chosen criteria</li>
<li>Retrieving stuff from the network</li>
<li>Inserting stuff to the network</li>
<li>Shutting down your client node</li>
</ol>
Setup and running of Q Server Nodes will be discussed in a later chapter.
</blockquote>
<hr>
<h3>4.2. Verify Your Q Installation Is Correct</h3>
<blockquote>
Ensure that all the needed I2P jarfiles, as well as <b>xmlrpc.jar</b> and
Q's very own <b>aum.jar</b> are correctly listed in your <b>CLASSPATH</b> environment
varaible, and your main java launcher is correctly listed in your <b>PATH</b> environment
variable.<br>
<br>
Typically, you will likely copy the jarfiles <b>aum.jar</b> and <b>xmlrpc.jar</b>
into the <b>lib/</b> subdirectory of your I2P router installation, along with all
the other I2P jar files. Wherever you choose to put these files, make sure they're
correctly listed in your <b>CLASSPATH</b>.
<br>
Also, you'll want to add execute permission to your <b>qmgr</b> (or <b>qmgr.bat</b>)
wrapper script, and copy it into one of the directories listed in your <b>PATH</b>
environment variable.<br>
</blockquote>
<hr>
<h3>4.3. Get Familiar With qmgr</h3>
<blockquote>
<b>qmgr</b> (or <b>qmgr.bat</b>) is a convenience wrapper script to save your
sore fingers from needless typing. It's just a wrapper which passes arguments
to the java command <b><code>java&nbsp;net.i2p.aum.q.QMgr</code></b><br>
<br>
You can verify you've set up qmgr correctly with the command:
<blockquote><code><pre>
qmgr help</pre></code></blockquote>
This displays a brief summary of qmgr commands. On the other hand, the command:
<blockquote><code><pre>
qmgr help verbose</pre></code></blockquote>
floods your terminal window with a detailed explanation of all the qmgr commands
and their arguments.<br>
</blockquote>
<hr>
<h3>4.4. Running A Q Client Node For The First Time</h3>
<blockquote>
Provided you've successfully completed the preliminaries, you can launch your
Q Client Node with the command:
<blockquote><code><pre>
qmgr start</pre></code></blockquote>
All going well, you should have a Q Client Node now running in background.
</blockquote>
<hr>
<h3>4.5. Verify that your Q Client Node is actually Running</h3>
<blockquote>
After typed the <b>qmgr start</b> command, you will see little or no
evidence that Q is actually running.<br>
<br>
You can test if the node is actually up by typing the command:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
If your Q Client Node is running, this <b>status</b> command should produce
something like:
<blockquote><code><pre>
Pinging node at '/home/myusername/.quartermaster_client'...
Node Ping:
status=ok
numPeers=0
dest=-3LQaE215uIYwl-DsirnzXGQBI31EZQj9u~xx45E823WqjN5i2Umi37GPFTWc8KyislDjF37J7jy5newLUp-qrDpY7BZum3bRyTXo3Udl8a3sUjuu4qR5oBEWFfoghQiqDGYDQyJV9Rtz7DEGaKHGlhtoGsAYRXGXEa8a43T2llqZx2fqaXs~836g8t6sLZjryA5A9fpq98nE5lT0hcTalPieFpluJVairZREXpUiAUmGHG7wAIjF6iszXLEHSZ8Qc622Xgwy0d1yrPojL2yhZ64o05aueYcr~xNCiFxYoHyEJO3XYmkx~q-W-mzS3nn6pRevRda74MnX1~3fFDZ0u~OG6cLZoFkWgnxrwrWGFUUVMR87Yz251xMCKJAX6zErcoGjGFpqGZsWxl4~yq7yfkjPnq3GuTxp2cB75bRAOZRIAieqBOVJDEodFYW5amCinu4AxYE7G1ezz4ghqHFe~0yaAdO74Q1XoUny138YT6P33oNOOlISO1cAAAA
uptime=4952
load=0.0
id=6LVZ9-~GgJJ52WUF1fLHt3UnH50TnXSoPQXy7WZ4GA=
numLocalItems=47
numRemoteItems=2173</pre></code></blockquote>
If you see something like this, then smile, because Q is now up on your system.<br>
<br>
If the node launch failed, you might see something like:
<blockquote><code><pre>
Pinging node at '/home/myusername/.quartermaster_client'...
java.io.IOException: Connection refused
at org.apache.xmlrpc.XmlRpcClient$Worker.execute(Unknown Source)
at org.apache.xmlrpc.XmlRpcClient.execute(Unknown Source)
at net.i2p.aum.q.QMgr.doStatus(QMgr.java:310)
at net.i2p.aum.q.QMgr.execute(QMgr.java:813)
at net.i2p.aum.q.QMgr.main(QMgr.java:869)
Failed to ping node</pre></code></blockquote>
This indicates that your Q client node has either crashed, or failed to launch in the
first place.<br>
<br>
If you're having trouble like this, you might like to try running your Q client node
in foreground, instead of spawning it off into background.<br>
<br>
The command to run a Q client node in foreground is:
<blockquote><code><pre>
qmgr foreground</pre></code></blockquote>
You should see some meaningless startup messages, and no return to your shell prompt.<br>
</blockquote>
<hr>
<h3>4.6. Diversion - Q Storage Directories</h3>
<blockquote>
By default, when you run a Q Client Node, it creates a datastore directory tree
at <b>~/.quartermaster_client</b>. (Windows users note - you'll find this directory
wherever your user home directory is - this depends on what version of Windows
you have installed).<br>
<br>
Within this directory tree, you should see a file called <b>node.log</b>, which
will contain various debug log messages, and can help you to rectify any problems
with your Q installation. If you hit a wall and can't rectify the problems
yourself, you should send this file to the Q author (aum).<br>
<br>
It's possible to run your Q node from another directory, by passing that directory
as a <b>-dir &lt;path&gt;</b> argument to the
<b>qmgr</b> <b>start</b>, <b>foreground</b> and <b>stop</b>
commands. See <b>qmgr help verbose</b> for more information.
</blockquote>
<hr>
<h3>4.7. Importing a Noderef</h3>
<blockquote>
Note from the prior <b>qmgr status</b> command the line:
<blockquote><code><pre>
numPeers=0</pre></code></blockquote>
This means that your Q client node is running standalone, and doesn't have any contact
with any Q network. As such, your node is effectively useless. We need to hook up
your node with other nodes in the Q network.<br>
<br>
Q doesn't ship with any means for new client nodes to automatically connect to any Q
server nodes. This is deliberate.<br>
<br>
In all likelihood, there will be one 'main' Q network running within I2P, largely
based around the author's own Q server node, and most people will likely want to
use this Q network. But the author doesn't want to stop other people running their
own private Q networks, for whatever purpose has meaning for them.
<blockquote><i><small>
<hr>
This is especially relevant for Q as opposed to Freenet. With Freenet, there's
no way for a user to know of the existence of any item of content without
first being given its 'key'. However, since Q works with published catalogs,
any user can know everything that's available on a Q network, which might
not be desirable to those wishing to share content in a private situation.<br>
<Br>
The Q author anticipates, and warmly supports, people running their own
private Q networks within I2P, in addition to accessing the mainstream
'official' Q network.<br>
<br>
The way Q is designed and implemented, there is no way for anyone, including
Q's author, to know of the existence of anyone else's private Q network.
It is beyond the author's control, (and thus arguably the author's
legal responsibility), what private Q networks people set up, and what
kind of content is trafficked on these networks. This claim of plausible
deniability on the part of Q's author parallels that of a hardware retailer
denying responsibility for what people do with tools that they purchase.
<hr>
</small>
</i></blockquote>
Ok, getting back on topic - your brand new virgin Q client node is useless and lonely,
and desperately needs some Q server nodes to talk to. So let's hook up your node to
the mainstream Q network.<br>
<br>
You'll need to get one or more 'noderefs' for Q server nodes.<br>
<br>
There's nothing fancy about a Q noderef. It's just a regular I2P 'destination', with
which your Q Client Node can connect with a Q Server Node.<br>
<br>
A 'semi-official' list of noderefs for the mainstream Q network can be downloaded
from the url: <a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.<br>
<br>
Download this file, save it as (say) <b>qnoderefs.txt</b>. (Alternatively, if you're
wanting to subscribe into a private Q network, then get a noderef for at least one
of that network's server nodes from someone on that network who trusts you).<br>
<br>
Import these noderefs into your Q client node via the command:
<blockquote><code><pre>
qmgr addref qnoderefs.txt</pre></code></blockquote>
If all goes well, you should see no output from this command, or (possibly) a brief
line or two suggesting success.<br>
<br>
Your client node is now subscribed into the Q network of your choice. Verify this
with the command:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
In the output from that command, you should see the <b>numPeers=</b> line showing at least
1 peer.<br>
<br>
If there is more than one Q Server Node on the Q network you've just subscribed to,
then your local node should sooner or later discover all these server nodes, and
the <b>numPeers</b> value should increase over time.<br>
<br>
<blockquote>
<hr>
While Q is in its early development and testing stages, the author may abdicate
the mainstream Q network, and publish nodrefs for a whole new mainstream Q network.
This will especially happen if the author makes any substantial changes to the
inter-node protocol, and/or releases incompatible new versions of Q client/server
nodes. Remember that
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a> will
serve as the authoritative source for noderefs for the mainstream Q network within
the mainstream I2P network.
<hr>
</blockquote>
When your client node gets its noderefs to a Q network, it will periodically,
from then on, retrieve differential peer list and catalog updates from servers
it knows about.<br>
<br>
Even if you only feed your client just one ref for a single server node, it will
in time discover all other operating server nodes on that Q network, and will
build up a full local catalog of everything that's available on that Q network.<br>
<br>
Provided that your client is running ok, and has been fed with at least one
ref for a live Q network that contains content, then over time, successive:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
commands should report increasing values in the fields:
<ul>
<li><b>numPeers</b> - number of peers this client node knows about</li>
<li><b>numLocalItems</b> - number of locally stored content items, ie items
which you have either inserted to, or retrieved from, your client node</li>
<li><b>numRemoteItems</b> - number of unique data items which are available
on remote server nodes in the Q network, and which can be retrieved through
your local client node.</li>
</ul>
<blockquote>
<hr>
<h4>4.7.1. One Big Warning</h4>
If you are participating in more than one distinct Q network, then <b>do not</b>
insert noderefs for different networks into the same running instance of a
local Q client, unless you don't plan on inserting content via that client.<br>
<Br>
For instance, let's say you are participating in two different Q networks:
<ul>
<li>The 'mainstream' Q netowrk</li>
<li>A secret Q network - "My friends' teen angst diaries"</li>
</ul>
If you get a noderef for both these networks, and insert both of these into the
same running Q client node, then this local client node will be transparently
connected to both networks.<br>
<br>
If you only ever plan on retrieving content, and never inserting content, this
won't be a problem, except that you won't be able to tell which content
resides on the mainstream Q network, and which resides in the secret Q network.<br>
<Br>
The big problem arises from inserting content. Whenever you insert data through this
'contaminated'
Q client node, this node picks 3 different servers to which upload a copy of this
data. You won't have any control over whether the data gets inserted to the mainstream
Q network, or your secret Q network. You might insert something sensitive, intending it
to go only into the secret Q network, where in fact it also ends up in the mainstream
network, with consequences you might not want.
</blockquote>
</blockquote>
<hr>
<h3>4.8. Content Data and Metadata</h3>
<blockquote>
Whenever content gets stored on Q, it is actually stored as two separate items:
<ul>
<li>The <b>raw data</b> - whether a text file, or the raw bytes of image files,
audio files etc</li>
<li>The <b>metadata</b>, which contains human-readable and machine-readable
descriptions of the data</li>
</ul>
Metadata consists of a set of <b>category=value</b> pairs.<br>
<br>
Confused yet? Don't worry, I'm confused as well. Let's illustrate this with an
example of metadata for an MP3 audio recording:
<ul>
<li>title=Fight_Last_Thursday.mp3</li>
<li>type=audio</li>
<li>mimetype=audio/mpeg</li>
<li>abstract=upcoming single recorded in our garage last April</li>
<li>keywords=grunge,country,indie</li>
<li>artist=Ring of Fire</li>
<li>size=4379443</li>
<li>contact=ring-of-fire@mail.i2p</li>
<li>key=blah37blah24-yada23hfhyada</li>
</ul>
All metadata categories are optional. In fact, you can insert content with no metadata
at all.<br>
<br>
If you fail to provide metadata when inserting an item, a blank set of metadata will
be created with at least the following categories:
<ul>
<li><b>key</b> - the derived key, under which the item will later be retrievable
by yourself and others</li>
<li><b>title</b> - if not provided at insert time, this will be set to the key</li>
<li><b>size</b> - size of the item's raw data, in bytes</li>
</ul>
Within Q, there is a convention to supply a minimal amount of metadata. While this
is not expected or enforced, including all these categories is most strongly
recommended. These core categories are:
<ul>
<li><b>title</b> - a meaningful title for the data item, consisting only of characters
which are legal in filenames on all platforms, and which ends with a file extension.</li>
<li><b>type</b> - one of a superset of eMule classifiers, such as:
<ul>
<li><b>text</b> - plain text</li>
<li><b>html</b> - HTML content</li>
<li><b>image</b> - content is in an image format, such as .png, .jpg, .gif etc</li>
<li><b>audio</b> - content is an audio sample, such as .ogg, .mp3, .wav etc</li>
<li><b>video</b> - due to the sheer size of video files, and Q's present design,
it's unlikely people will be inserting video content anytime soon (unless it's
very short)</li>
<li><b>archive</b> - packed file collections, such as .tar.gz, .zip, .rar etc</li>
<li><b>misc</b> - content does not fit into any of the above categories</li>
</ul>
</li>
<li><b>mimetype</b> - not as important as the <b>type</b> category, but providing
this category in your metadata is still strongly encouraged. Value for this category
should be one of the standard mimetypes, eg <b>text/html</b>, <b>audio/ogg</b> etc.</li>
<li><b>abstract</b> - a short description (<255 characters), intended for human reading</li>
<li><b>keywords</b> - a comma-separated list of keywords, intended for
machine-readability, should be all lowercase, no spaces</li>
</ul>
Note that you can supply extra metadata categories in addition to the above, and that
people searching for content can search on these extra categories if they know about
them.
</blockquote>
<hr>
<h3>4.9. Searching For Content</h3>
<blockquote>
As mentioned earlier - in constrast with Freenet, local Q nodes build up a complete
catalog of all available content on whatever Q network they are connected to.<br>
<br>
This is a design decision, based on the choice to eliminate query traffic.<br>
<br>
The author hopes that this will result in a distributed storage network with a
high retrievability guarantee, in contrast with freenet which offers no such
guarantee.<br>
<br>
With Freenet, you only ever know of the existence of something if someone tells
you about it.<br>
<br>
But with Q, your local client node builds up a global catalog of everything that's
available within the whole network.<br>
<br>
The QMgr client has a command for searching your Q client node:
<blockquote><code><pre>
qmgr search -m category1=pattern1 category2=pattern2 ...</pre></code></blockquote>
For example:
<blockquote><code><pre>
qmgr search -m type=audio artist=Mozart keywords=symphony</pre></code></blockquote>
or:
<blockquote><code><pre>
qmgr search -m type=text title="bible|biblical|(Nag Hammadi)" keywords="apocrypha|Magdalene"</pre></code></blockquote>
As implied in the latter example, search patterns are regular expressions. This example will
locate all text items, whose <b>title</b> metadata category contains one of <b>bible</b>, <b>biblical</b> or <b>Nag&nbsp;Hammadi</b>, <i>and</i> whose <b>keywords</b> category contains either
or both the words <b>apocrypha</b> or <b>Magdalene</b>.<br>
<br>
Please use the search function carefully, otherwise (if and when Q usage grows) you
could be inundated with thousands or even millions of entries.<br>
<br>
If a search turns up nothing, qmgr will simply exit. But if it turns up one or more items,
it will the items out one at a time, with the key first, then each metadata entry
on an indented line following.
</blockquote>
<hr>
<h3>4.10. Retrieving Content</h3>
<blockquote>
Now, we're actually going to retrieve something.<br>
<br>
Presumably, after following the previous section, you will have seen one or more search
results come up, with the 'keys' under which the items can be accessed.<br>
<br>
Now, choose one of the keys, preferably for a short text item. Try either of the following
commands:
<blockquote><code><pre>
qmgr get &lt;keystring&gt; something.txt</pre></code></blockquote>
<i>or</i>:
<blockquote><code><pre>
qmgr get &lt;keystring&gt; &gt; something.txt</pre></code></blockquote>
(both have the same effect - the first one explicitly writes to the named file, the second
one dumps the raw data to stdout, which we shell-redirect into the file.<br>
<br>
<b><i>Note - redirection of fetched data to a file via shell is not working at present. Use only
the first form till we fix the bug.</i></b>
</blockquote>
<hr>
<h3>4.11. Inserting Content</h3>
<blockquote>
Our last example in this walkthrough relates to inserting content.<br>
<br>
Firstly, create a small text file with 2-3 lines of text, and save it as (say)
myqinsert.txt.<br>
<br>
Now, think of some metadata to insert along with the file. Or, you can just use
the set:
<blockquote><code><pre>
type=text
keywords=test
abstract=My simple test of inserting into Q
title=myqinsert.txt</pre></code></blockquote>
Now, let's insert the file. Ensure your Q client node is running, then type:
<blockquote><code><pre>
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
abstract="My simple test of inserting into Q"</pre></code></blockquote>
If all went well, this command should produce half a line of gibberish, followed
immediately by your shell prompt, eg:
<blockquote><code><pre>
aRoFC~9MU~pM2C-uCTDBp5B7j79spFD8gUeu~BNkUf0=<b>$</b>
</pre></code></blockquote>
The '$' at the end is your shell prompt, and all the characters before it are the 'key'
which was derived from the content you just inserted.<br>
<br>
To avoid the hassle of copying/pasting the key, you could just add output redirection
to the above command, eg:
<blockquote><code><pre>
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
abstract="My simple test of inserting into Q" \
> myqinsert.key</pre></code></blockquote>
This will cause the generated key to be written safe and sound into the file
<b>myqinsert.key</b>.<br>
<br>
You can verify that this insert worked by a 'get' command, as in:
<blockquote><code><pre>
qmgr get `cat myqinsert.key` somefilename.ext</pre></code></blockquote>
(Note that this won't work on windows because the DOS shell is irredeemably brain-damaged. If
you're using Windows, you <b>will</b> have to cut/paste the key.
</blockquote>
<hr>
<h3>4.12. Shutting Down your Node</h3>
<blockquote>
If you've worked through to here, then congratulations! You've got your Q Client Node set up
and working, and ready to meet all your distributed file storage and retrieval needs.<br>
<br>
You can leave your client node running 24/7 if you want. In fact, we recommend you keep your
client node running as much of the time as possible, so that you get prompt catalog updates,
and can more quickly stay in touch with new content.<br>
<br>
However, if you need to shut down your node, the command for doing this is:
<blockquote><code><pre>
qmgr stop</pre></code></blockquote>
This command will take a while to complete (since the node has to wait for the I2P
java shutdown hooks to complete before it can rest in peace). But once your node is
shut down, you can start it up again at any time and pick up where you left off.
</blockquote>
<a name="server"/>
<hr>
<h2>5. Running a Q Server Node</h2>
<h3>5.1. Introduction</h3>
<blockquote>
This section describes the requirements for, and procedures involved with, running
a Q Server Node.<br>
<br>
We'll use a similar 'walkthrough' style to that which we used in the previous section
on client nodes.
</blockquote>
<hr>
<h3>5.2. Requirements and Choices</h3>
<blockquote>
Running a Q server is a generous thing to do, and helps substantially with making
Q work at its best for everyone. However, please do make sure you can meet some
basic requirements:
<ul>
<li>You are running a permanent (24/7) I2P Router, on a box with at least (say)
98% uptime.</li>
<li>You have a little bandwidth to spare, and don't mind the extra memory, disk and
CPU-usage footprint of running a fulltime Q server node</li>
<li>You have already been able to successfully run a Q client node.</li>
</ul>
Also, please decide whether you want your server node to contribute to the mainstream
Q network, or whether you want to create your own private Q network, or join someone
else's private network. Your contribution will be most appreciated, though, if you
can run a server within the mainstream Q network.
</blockquote>
<hr>
<h3>5.3. Starting Your Server Node</h3>
<blockquote>
Starting up a Q Server node is very similar to starting up a Q client node, except
that with the qmgr command line, you must put the keyword arg <b>server</b> before the
command word. So the command is:
<blockquote><code><pre>
qmgr server start</pre></code></blockquote>
Similar to Q client nodes, you can check the status of a running Q server node with
the command:
<blockquote><code><pre>
qmgr server status</pre></code></blockquote>
(Note that this command will take longer to complete than with client nodes, because
the communication passes through a multi-hop I2P tunnel, rather than just through
localhost TCP).<br>
<br>
If the status command succeeds, then you'll know your new Q Server Node is happily
running in background.
</blockquote>
<hr>
<h3>5.4. Joining A Q Network</h3>
<blockquote>
When a Q Server node starts up for the first time, it is in a private network
all by itself.<br>
<br>
If you want to link your server into an existing Q network, you'll have to add a
noderef for at least one other server on that network. The command to do this
is similar to that for subscribing a client node to a network:
<blockquote><code><pre>
qmgr server addref &lt;noderef-file&gt;</pre></code></blockquote>
where &lt;noderef-file&gt; is a file into which you've saved the noderef for
the network you want to join.
<blockquote>
<hr><i><small>
Recall from the section on client nodes that the authoritative noderefs
for the mainstream Q network can be downloaded from
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.
</small></i><hr>
</blockquote>
After you've added the noderef, subsequent <b>qmgr server status</b> commands
should show <b>numPeers</b> having a value of at least 1 (and growing, as more
server nodes come online in the mainstream Q network.)
</blockquote>
<hr>
<h3>5.5. Private Networks - Exporting Your Server's Noderef</h3>
<blockquote>
If you're planning to start your own private Q network, and want to include other
server operators in this network, then you'll have to export your server's noderef
and make it available to the others you want to invite into your network.<br>
<br>
The command to export your Q Server noderef is:
<blockquote><code><pre>
qmgr server getref &lt;noderef-file&gt;</pre></code></blockquote>
This will extract the <i>I2P Destination</i> of your running server node, and
write it into &lt;noderef-file&gt;. You can then privately share this file with
others who you want to invite into your private network. Each recipient of
this file will do a <b>qmgr server addref &lt;noderef-file&gt;</b> command
to import your ref into their servers.<br>
<br>
Don't forget that if you're running, or participating in, a private Q network, then
you'll need to run a separate client for accessing this network, separate from any
mainstream Q network client you may already be running.<br>
<br>
To start this extra client, you'll have to choose a directory where you want this
client to reside, a port number you want your client to listen on locally for
user commands, and run the command:
<blockquote><code><pre>
qmgr -dir /path/to/my/new/client -port &lt;portnum&gt; start</pre></code></blockquote>
You need the <b>-port &lt;portnum&gt;</b> command, because otherwise it'll fail
to launch (if you already have a client node running off the mainstream Q network).<br>
<br>
This will create, and launch, a new instance of a Q client, accessing your private
Q network. Don't forget to import your server's noderef into this client. Also,
note that you'll have to use this same <b>-port &lt;portnum&gt;</b> argument when
doing any operation on this client instance, such as get, put, status, search.
</blockquote>
<a name="qmgr"/>
<hr>
<h2>6. About the qmgr Utility</h2>
qmgr (or, to people fluent in Java, <b>net.i2p.aum.q.QMgr</b>), is just one simple
Q client application, that happens to be bundled in with the Q distro.<br>
<br>
It is by no means the only, or even main facility for accessing the Q network. We
anticipate that folks will write all manner of client apps, including fancy GUI
apps.<br>
<br>
Anyway, qmgr does give you a rudimentary yet workable client for basic access
to the Q network. Until fancy apps get written, qmgr will have to do.<br>
<br>
Don't forget that qmgr has very detailed inbuilt help. Run:
<blockquote><code><pre>
qmgr help</pre></code></blockquote>
for a quick help summary, or:
<blockquote><code><pre>
qmgr help verbose</pre></code></blockquote>
for the 'War and Peace' treatise.<br>
<br>
<blockquote><hr>
One crucial concept to remember with qmgr is that client and server node instances
are uniquely identified by the directories at which they reside. If you are running
multiple server and/or client instances, you can specify an instance with the
<b>-dir &lt;dirpath&gt;</b> option - see the help for details.
<hr></blockquote>
<hr>
One last note - we strongly discourage any writing of client apps that spawn a qmgr
process, pass it arguments and parse its results. This is most definitely a path to
pain, since qmgr's shell interface is subject to radical change at any time without
notice.<br>
<br>
qmgr is for human usage, or at most, inclusion in init/at/cron scripts. Please respect
this.<br>
<br>
If you want to write higher-level clients, your best course of action is to use the
official client api library, which we anticipate will have versions available in
Java, Python, Perl and C++. If you want to write in another language, such as
OCaml, Scheme etc, then the existing api lib implementations should serve as an excellent
reference to support you in writing a native port for your own language.
<a name="contact"/>
<hr>
<h2>8. Contacting the Author</h2>
I am <b>aum</b>, and can be reached as <b>aum</b> on in-I2P IRC networks, and also
at the in-I2P email address of <b>aum@mail.i2p</b>.<br>
<br>
<hr>
<center>
Return to <a href="../index.html">Q Homepage</a><br>
<br>
<small>
<a href="#intro">Introduction</a> |
<a href="#checklist">Checklist</a> |
<a href="#serverorclient">Server?orClient?</a> |
<a href="#walkthrough">Walkthrough</a> |
<a href="#server">Server Nodes</a> |
<a href="#qmgr">About QMgr</a> |
<a href="#contact">Contact us</a>
</small>
</center>
<hr>
<!-- Created: Fri Apr 1 11:03:27 NZST 2005 -->
<!-- hhmts start -->
Last modified: Sun Apr 3 20:06:53 NZST 2005
<!-- hhmts end -->
</body>
</html>

23
apps/q/doc/manual/notes Normal file
View File

@ -0,0 +1,23 @@
rise on each hit:
dy = (1 - y) / kRise
fall after each time unit:
dy = y / kFall
fall after time dt:
dy = - y ** - (dt / kFall)
after the next hit:
y = y - y ** (- dt / kFall) + (1 - y) / kRise
first attempt at a load measurement algorithm:
- kFall is an arbitrary constant which dictates decay rate of load
in the absence of hits
- kRise is another constant which dictates rise of load with each hit
- dt is the time between each hit

372
apps/q/doc/metadata.html Normal file
View File

@ -0,0 +1,372 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q Metadata Specification</title>
<style type="text/css">
<!--
td { vertical-align: top; }
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
-->
</style>
</head>
<body>
<h1>Q Metadata Specification</h1>
<h2>1. Introduction</h2>
This document lists the standard metadata keys for Q data items,
discussing the rules of metadata insertion, processing and validation.<br>
<hr>
<h3>1.1. Definitions</h3>
To avoid confusions in terminology, this document will strictly abide the following definitions:
<br>
<br>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Term</td>
<td>Definition</td>
</tr>
<tr>
<td><code>key</code></td>
<td>A metadata category name, technically a <code>key</code> as the word is used with
Java <code>Hashtable</code> and Python <code>dict</code> objects.</td>
</tr>
<tr>
<td><code>uri</code></td>
<td>A Uniform Resource Indicator for an item of content stored within the Q network.<br>
Q URIs have the form: <code>Q:&lt;basename&gt;[,&lt;cryptoKey&gt;][&lt;path&gt;]</code>
<br>
<br>
Some examples:
<ul>
<li><code>Q:fhvnr3HFSK234khsf90sdh42fsh</code> (a plain hash uri, no cryptoKey)</li>
<li><code>Q:e54fhjeo39schr2kcy4osEH478D/files/johnny.mp3</code> (a secure space URI,
no cryptoKey)</li>
<li><code>Q:vhfh4se987WwfkhwWFEwkh3234S,47fhh2dkhseiyu</code> (a plain hash URI, with
a cryptoKey)</li>
</td>
</tr>
<tr>
<td><code>basename</code></td>
<td>The basic element of a Q uri. This will be a base64-encoded hash - refer below to
URI calculation procedures</td>
</tr>
<tr>
<td><code>cryptoKey</code></td>
<td>An optional session encryption key for the stored data, encoded as base64.
This affords some protection to server node operators, and gives them a level
of plausible deniability for whatever gets stored in their server's
datastore without their direct human awareness.</td>
</tr>
<tr>
<td><code>path</code></td>
<td>Whever an item of content is inserted in <code>secure space</code> mode, this path
serves as a pseudo-pathname, and is conceptually similar to the <code>path</code>
component in (for example) standard HTTP URLs
<code>http://&lt;domainname&gt;[:&lt;port&gt;][&lt;path&gt;]</code>, such as
<code>http://slashdot.org/faq/editorial.shtml</code> (whose <code>path</code>
is <code>/faq/editorial.shtml</code>).<br>
<br>
Paths, if not empty, should contain a leading slash ("/").
If an application specifies a non-empty <code>path</code> that doesn't begin with a
leading '/', a '/' will be automatically prepended by the receiving node.
</td>
</tr>
<tr>
<td><code>plain hash</code></td>
<td>A mode of inserting items, whereby the security of the resulting URI comes from
computing the URI from a hash of the item's data and metadata (and imposing a
mathematical barrier against spoofing content under a given URI). Corresponds to
Freenet's <code>CHK@</code> keys.</td>
</tr>
<tr>
<td><code>secure space</code></td>
<td>A mode of inserting items where the security of the URI is based not on a hash of the
item's data and metadata (as with <code>plain hash</code> mode),
but on the <code>privateKey</code> provided by the
application, and a content signature created from that private key.
Corresponds to Freenet's <code>SSK@</code> keys. Within a secure space, you
can insert any number of items under different pseudo-pathnames (as is the case
with Freenet SSK keys).
</li>
</table>
<br><br>
<hr>
<h3>2.1. Keys Inserted By Application Before sending <code>putItem</code> RPCs</h3>
As the heading suggests, this is a list of metadata keys which should be inserted by a
Q application prior to invoking a <code>putItem</code> RPC on the local Q client node.<br>
<br>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Key</td>
<td>Data Type</td>
<td>Description</td>
</tr>
<tr>
<td><code>title</code></td>
<td>String</td>
<td>Optional but strongly recommended. A free-text short description of the item,
should be less than 80 characters. The idea is that applications should
support a 'view' of catalogue data that shows item titles. (Prior Q convention of
titles expressed as valid filename syntax has been abandoned).
</td>
</tr>
<tr>
<td><code>path</code></td>
<td>String</td>
<td>Optional but strongly recommended.
A virtual 'pathname' for the item, which should be in valid *nix
absolute pathname syntax (beginning with '/', containing no '//', consisting
only of alphanumerics, '-', '_', '.' and '/'.<br>
<br>
In Q web interfaces, the <code>filename</code> component of this path will
serve as the recommended filename when downloading/saving the item.<br>
<br>
If the application also provides a
<code>privateKey</code> key, the path
is used in conjunction with the private key to generate <code>publicKey</code>
and <code>signature</code> keys (see below), and ultimately the final <code>uri</code>
under which the item can be retrieved by others.<br>
<br>
Refer also to <code>mimetype</code> below.
</td>
</tr>
<tr>
<td><code>encrypt</code></td>
<td>String</td>
<td>Optional. If this key is present, and has a value "1", "yes" or "true",
this indicates that the application wishes the data to be stored on servers in
encrypted form.<br>
<br>
If this key is present and set to a positive value, the Q node, on receiving the
<code>putItem</code> RPC, will:
<ol>
<li>Generate a random symmetric encryption key</li>
<li>Encrypt the item's data using this encryption key</li>
<li>Delete the <code>encrypt</code> key from the metadata</li>
<li>Enclose a base64 representation of this encryption key in the RPC response
it sends back to the application (embedded in the <code>uri</code></li>
</ol>
</td>
</tr>
<tr>
<td><code>type</code></td>
<td>String</td>
<td>Optional but strongly recommended. A standard ed2k specifier, one of <code>text html image
audio video archive other</code></td>
</tr>
<tr>
<td><code>mimetype</code></td>
<td>String</td>
<td>Optional but moderately recommended. Mimetype designation of data, eg <code>text/html</code>,
<code>image/jpeg</code> etc. If not specified, an attempt will be made to guess
a mometype from the value of the <code>path</code> key. If this attempt fails, then
this key will be set to <code>application/x-octet-stream</code> by the node receiving
the <code>putItem</code> RPC.</td>
</tr>
<tr>
<td><code>keywords</code></td>
<td>String</td>
<td>Optional but moderately recommended.
A set of keywords, under which the inserting app would like this item to be
discoverable. Keywords should be entirely lower case and comma-separated. Content
inserts should consider keywords carefully, and only use space characters inside
keywords when necessary (eg, for flagging a distinctive phrase containing very
common words).</td>
<tr>
<td><code>privateKey</code></td>
<td>String</td>
<td>Optional. A Base64-encoded signing private key, in cases where the application wishes
to insert an item in <code>signed space</code> mode. This can be accompanied by another key,
<code>path</code>, indicating a 'path' within the signed space. If 'path'
is not given, it will default to '/'.<br>
<br>
Either way, when a node receives a
<code>putItem</code> RPC containing a <code>privateKey</code> in its metadata,
it removes this key and replaces it with <code>publicKey</code> and
<code>signature</code>.
</td>
</tr>
<tr>
<td><code>path</code></td>
<td>String</td>
<td>Optional. The virtual pathname, within signed space, under which to store the item.
This gets ignored and deleted unless the application also provides a
<code>privateKey</code> as well. But if the private key is given, the path
is used in conjunction with the private key to generate <code>publicKey</code>
and <code>signature</code> keys (see below).<br>
<code>path</code> should be a 'unix-style pathname', ie, containing only slashes
as (pseudo) directory delimiters, and alphanumeric, '-', '_' and '.' characters,
and preferably ending in a meaningful file extension such as <code>.html</code>
</td>
</tr>
<tr>
<td><code>expiry</code></td>
<td>int</td>
<td>Unixtime at which the inserted item should expire. When this expiry time
is reached, the item won't necessarily be deleted straight away, but may
be deleted whenever a node's data store is full.<br>
<br>
If this is not provided, it will default to a given duration according to
the client node's configuration.<br>
<br>
If it is provided, by an application, then the client node will transparently
generate the required 'rent payment' before caching the data item and uploading
it to servers.
</td>
</tr>
</table>
<br><br>
<hr>
<h3>2.2. Keys Inserted By Node Upon Receipt Of <code>putItem</code> RPC</h3>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Key</td>
<td>Data Type</td>
<td>Description</td>
</tr>
<tr>
<td><code>size</code></td>
<td>Integer</td>
<td>Size of the data to be inserted, in bytes.</td>
</tr>
<tr>
<td><code>dataHash</code></td>
<td>String</td>
<td>base64-encoded SHA256 hash of data.</td>
</tr>
<tr>
<td><code>uri</code></td>
<td>String</td>
<td>This depends on whether the item is being inserted in <i>plain</i> or
<i>signed space</i> mode.<br>
<br>
If inserting in <i>plain</i> mode, then the uri is in the form
<code>Q:somebase64hash</code>, where the hash is computed according to
the <a href="#plainhash">plain hash calculation procedure</a>.<br>
<br>
If inserting in <i>signed space</i> mode, then the uri will be in the form
<code>Q:somebase64hash/path.ext</code>, where the hash is computed as per
the <a href="#signedhash">signed space hash calculation procedure</a>, and
the <code>/path.ext</code> is the verbatim value of the app-supplied
<code>path</code> key.
</td>
</tr>
<tr>
<td><code>publicKey</code></td>
<td>String</td>
<td>Base64-encoded signing public key. In cases where app provides
<code>privateKey</code>,
a node will derive the signing public key from the private key,
delete the private key from the metadata, and replace it with its corresponding
public key
key.</td>
</tr>
<tr>
<td><code>signature</code></td>
<td>String</td>
<td>Base64-encoded signature of <code>path+dataHash</code>, created using
the app-provided <code>privateKey</code>.</td>
</tr>
<tr>
<td><code>rent</code></td>
<td>String</td>
<td>A rent payment for the data's accommodation on the server.<br>
Intention is to support a variety of payment tokens. Initially, the
only acceptable form of payment will be a hashcash-like token,
in the form <code>hashcash:base64string</code>. The <code>hashcash:</code>
prefix indicates that this payment is in hashcash currency, in which case
the <code>base64String</code> should decode to a 16-byte string whose
SHA256 hash partially collides with <code>dataHash</code>.
The greater the number of bits in the collision,
the longer the data's accommodation will be 'paid up for'.<br>
<br>
If this key is already present, a Q node will verify the hashcash,
and adjust the <code>expiry</code> key value to the time the item's accommodation
is paid up till.<br>
<br>
If the key is not present:
<ul>
<li>A client node will generate a value for this key with enough collision bits
to pay the accommodation up till the given app-specified <code>expiry</code> date.</li>
<li>A server node will grant temporary free accommodation, and adjust the <code>expiry</code>
key to the end of the free accommodation period.</li>
</ul>
</td>
</tr>
</table>
<br><br>
<a name="plainhash"/>
<hr>
<h2>3. URI Determination Procedures</h2>
<h3>3.1. Plain Hash URI Calculation Procedure</h3>
When items are inserted in <code>plain</code> mode, the final URI is determined from
a hash of the data and metadata. Security of the item is based on the mathematical difficulty
of creating an arbitrary data+metadata set whose hash collides with the target URI.<br>
<br>
Specifically, the recipe for calculating plain hash URIs is:
<ol>
<li>If the key <code>size</code> is missing, set this to the size of the data,
in bytes</li>
<li>If the key <code>dataHash</code> is missing, set this to the base64-encoded
SHA256(data)</li>
<li>If the key <code>title</code> is missing, set this to the value of <code>dataHash</code></li>
<li>From the metadata, create a set of strings, each in the form <code>key=value</code>,
where each line contains a metadata <code>key</code> and its <code>value</code>, and
is terminated by an ASCII linefeed (\n, 0x10).</li>
<li>Ensure that key <code>uri</code> is omitted</li>
<li>Sort the strings into ascending ASCII sort order</li>
<li>Concatenate the strings together into one big string</li>
<li>Calculate the SHA256 hash of this string</li>
<li>Encode the hash into Base64</li>
<li>Prepend the string <code>Q:</code> to this</li>
</ol>
<a name="signedhash"/>
<hr>
<h3>3.2. Signed Space URI Calculation Procedure</h3>
This is much simpler than determining plain hash URI, since the security of the URI
is based not on hashes of data and metadata, but on the cryptographic <code>privateKey</code>
given by the application.<br>
<br>
Calculation recipe for Signed Space URIs is:
<ol>
<li>Calculate the SHA256 hash of the private key's binary data (not its base64 representation)</li>
<li>Encode this hash into base64, dropping any trailing '=' characters</li>
<li>Append to this the value of metadata item <code>path</code> (recall that <code>path</code>,
if not empty, must begin with a '/')</li>
<li>Prepend the string <code>Q:</code> to this</li>
</ol>
The resulting URI then is in the form <code>Q:pubkeyHash/path.ext</code>
<hr>
<!-- Created: Tue Apr 5 00:56:45 NZST 2005 -->
<!-- hhmts start -->
Last modified: Wed Apr 6 00:36:37 NZST 2005
<!-- hhmts end -->
</body>
</html>

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q Screenshots</title>
</head>
<body>
<h1>Q Screenshots</h1>
<ul>
<li><a href="screenshot-search.jpg">Search Screen</li>
<li><a href="screenshot-qsite.jpg">QSite Insertion Form</li>
<li><a href="screenshot-iewarn.jpg">Q Security Features</li>
</ul>
<hr>
<address><a href="mailto:aum@mail.i2p">aum</a></address>
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
<!-- hhmts start -->
Last modified: Mon Apr 18 14:06:02 NZST 2005
<!-- hhmts end -->
</body>
</html>

1460
apps/q/doc/spec/index.html Normal file

File diff suppressed because it is too large Load Diff

90
apps/q/java/build.xml Normal file
View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="aum">
<!-- Written to assume that classpath is rooted in the current directory. -->
<!-- So this should be OK if you make this script in the root of a filesystem. -->
<!-- If not, just change src.dir to be the root of your sources' package tree -->
<!-- and use e.g. View over a Filesystem to mount that subdirectory with all capabilities. -->
<!-- The idea is that both Ant and NetBeans have to know what the package root is -->
<!-- for the classes in your application. -->
<!-- Don't worry if you don't know the Ant syntax completely or need help on some tasks! -->
<!-- The standard Ant documentation can be downloaded from AutoUpdate and -->
<!-- and then you can access the Ant manual in the online help. -->
<target name="init">
<property location="build" name="classes.dir"/>
<property location="src" name="src.dir"/>
<property location="doc/q/api" name="javadoc.dir"/>
<property name="project.name" value="${ant.project.name}"/>
<property location="${project.name}.jar" name="jar"/>
<property location="q.war" name="war"/>
</target>
<target name="builddep">
<ant dir="../../i2ptunnel/java/" target="build" />
<!-- i2ptunnel builds ministreaming and core -->
</target>
<target depends="init,builddep" name="compile">
<!-- Both srcdir and destdir should be package roots. -->
<mkdir dir="${classes.dir}"/>
<javac debug="true"
deprecation="true"
destdir="${classes.dir}"
srcdir="${src.dir}"
classpath="xmlrpc.jar:../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar:../../i2ptunnel/java/build/i2ptunnel.jar" >
<!-- To add something to the classpath: -->
<!-- <classpath><pathelement location="${mylib}"/></classpath> -->
<!-- To exclude some files: -->
<!-- <exclude name="com/foo/SomeFile.java"/><exclude name="com/foo/somepackage/"/> -->
</javac>
</target>
<target depends="init,compile" name="jar">
<!-- To make a standalone app, insert into <jar>: -->
<!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> -->
<!-- <jar basedir="${classes.dir}" compress="true" jarfile="${jar}"> -->
<jar compress="true" jarfile="${jar}">
<!-- <jar basedir="." compress="true" jarfile="${jar}" includes="**/*.class,doc/**/*.html"> -->
<fileset dir="${classes.dir}"/>
<fileset dir="." includes="qresources/**"/>
<manifest>
<attribute name="Main-Class" value="net.i2p.aum.q.QMgr"/>
<attribute name="Class-Path" value="i2p.jar xmlrpc.jar mstreaming.jar streaming.jar jbigi.jar"/>
</manifest>
</jar>
</target>
<target depends="init,compile" name="war">
<!-- To make a standalone app, insert into <jar>: -->
<!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> -->
<war compress="true" jarfile="${war}" webxml="web.xml">
<!-- <fileset file="build/net/i2p/aum/q/QConsole.class"/> -->
<classes dir="build" includes="**/QConsole.class"/>
<classes dir="build" includes="**/HTML/**"/>
<!-- <fileset includes="**/HTML/*.class"/> -->
<lib file="xmlrpc.jar"/>
</war>
</target>
<target depends="init,jar,war" description="Build everything." name="all"/>
<target depends="init" description="Javadoc for my API." name="javadoc">
<mkdir dir="${javadoc.dir}"/>
<javadoc destdir="${javadoc.dir}" packagenames="*">
<sourcepath>
<pathelement location="${src.dir}"/>
</sourcepath>
<sourcepath>
<pathelement location="/java/xmlrpc-1.2-b1/src/java"/>
</sourcepath>
</javadoc>
</target>
<target depends="init" description="Clean all build products." name="clean">
<delete dir="${classes.dir}"/>
<delete dir="${javadoc.dir}"/>
<delete file="${jar}"/>
</target>
</project>

View File

@ -0,0 +1,10 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Item Not Found</td>
</tr>
<tr>
<td align=center>Failed to retrieve item:<br>
<code><tmpl_var 404_uri></code>
</td>
</tr>
</table>

View File

@ -0,0 +1,27 @@
<TABLE align="center" class="mainpaneitem">
<TR>
<TD class="formHeading">About Q</TD>
</tr>
<tr class="formBody">
<TD align=center>
<p>Version
<tmpl_if version>
<tmpl_var version></p>
<tmpl_else>
0.0.1
</tmpl_if>
<p>Designed and engineered by <a href="mailto:aum@mail.i2p">aum</a></p>
<p>Copyright &copy; 2005 by aum.
<br>Released under the
<br>GNU Lesser General Public License</p>
<p style="font-size: smaller; font-style: italic">
Many thanks to jrandom, smeghead and frosk<br>
for their patient and knowledgeable support<br>
in helping this python programmer<br>
get partly proficient in java.</p>
</TD>
</TR>
</TABLE>

View File

@ -0,0 +1,40 @@
<table align="center" class="mainpaneitem" width=80%>
<tr>
<td class="formHeading">Add Hub Noderef</td>
</tr>
<form action="/tools" method="POST" class="formBody">
<input type="hidden" name="cmd" value="addref">
<tr>
<td align="center">
<table width=70%>
<tr>
<td>
In order for your node to join a Q network, it must have a ref to at
least one of the running Q Hubs. You need to get a Q Hub noderef and
paste it here.
</td>
</tr>
<tr>
<td align=center>
<textarea name="noderef" cols=40 rows=6></textarea>
</td>
</tr>
<tr>
<td align="center">
<input type="submit" name="submit" value="Add Hub Noderef!">
</td>
</tr>
</table>
</td>
</tr>
</form>
</table>

View File

@ -0,0 +1,29 @@
<table align="center" class="mainpaneitem" width=80%>
<tr>
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
</tr>
<tr>
<td>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td>
<p>You are attempting to view a QSite via the Internet Explorer web browser.
</p>
<p>We have blocked the connection to protect your anonymity.
</p>
<p>As a matter of policy, Q does not support QSite browsing via Microsoft
Internet Explorer (MSIE), because of that browser's abysmal track record with
regard to security. If we did allow you to browse Q via MSIE, it would
be easy for a malicious QSite author to embed hostile content which
undermines your computer's security and compromises your anonymity.
</p>
<p>If you want to surf I2P QSites, you'll need to use a more secure web
browser such as <a href="http://www.mozilla.org">Mozilla or Firefox</a>.
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -0,0 +1,52 @@
<table align="center" class="mainpaneitem" width=80%>
<tr>
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
</tr>
<tr>
<td>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td>
<p>You are attempting to view a QSite via an unprotected link.
</p>
<p>We have blocked the connection to protect your anonymity. If we don't
do this, then a malicious QSite author can insert content into the QSite
which triggers oubound hits to arbitrary servers on the mainstream web,
which in turn can easily reveal a lot of personal information about you
and the QSite you are accessing.
</p>
<p>If you want to browse QSites with your web browser with greater safety,
you'll have to follow these simple steps:
</p>
<ol>
<li>Edit your I2P <code>hosts.txt</code> file and add the following entry
(all on one line):
<blockquote><code><textarea rows=5 cols=50>q.i2p=<tmpl_var dest></textarea></code><blockquote>
(and make sure you do NOT reveal this entry to anyone else)
</li>
<li>Configure one of your web browsers to use the proxy server:
<blockquote><code>localhost:4444</code></blockquote>
(or whatever address you have configured your I2P EEProxy to listen on,
if you've changed it)<br><br>
</li>
<li>Start up the browser you have just configured, and enter the web address:
<blockquote><code>http://q.i2p</code></blockquote>
</li>
</ol>
<p>Even if you do this, you still won't have a 100% guarantee of anonymity, since
a malicious QSite author can send code to your browser which exploits a vulnerability
in the browser to compromise your anonymity (eg, accessing third party cookies, executing
arbitrary code, accessing your local filesystem, uploading compromising information
about you to hostile I2P EEPsites etc). But you can relax (somewhat) in the knowledge
that such attacks are much more difficult, particularly if you use a decent web browser.
</p>
<p>Thank you for your co-operation. We wish you happy and safe browsing.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -0,0 +1,9 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Q Downloads</td>
</tr>
<tr>
<td>Not implemented yet</td>
</tr>
</table>

View File

@ -0,0 +1,28 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Generate New Keypair</td>
</tr>
<tr>
<td align="center">
<table>
<tr>
<td>
Click the button to generate a new keypair for
future inserts of signed space keys.
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center">
<form action="/tools" method="POST" class="formBody">
<input type="hidden" name="cmd" value="genkeys">
<input type="submit" name="submit" value="Generate Keys!">
</form>
</td>
</tr>
</table>

View File

@ -0,0 +1,32 @@
<table align="center" class="mainpaneitem">
<tr>
<td colspan=2 class="formHeading">Your New Keys</td>
</tr>
<tr>
<td align="center" colspan=2>
<table>
<tr>
<td>
Please save these keys in a safe place,
preferably on an encrypted partition:
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Public Key: </td>
<td>
<input readonly type="text" size=32 style="font-family: courier, monospace" value="<tmpl_var publickey>">
</td>
</tr>
<tr>
<td>Private Key: </td>
<td><input readonly type="text" size=32 style="font-family: courier, monospace" value="<tmpl_var privatekey>"></td>
</tr>
</table>

View File

@ -0,0 +1,15 @@
<table align="center" class="mainpaneitem" border=0>
<TR>
<TD class="formHeading" colspan="3">Retrieve Content</TD>
</tr>
<tr class="formBody">
<form action="/home" method="POST">
<input type="hidden" name="cmd" value="get">
<TD>URI:</TD>
<td><INPUT type="text" name="uri" value="Q:" size="60" maxlength="128"></td>
<td><INPUT type="submit" name="submit" value="Retrieve it"></td>
</form>
</TR>
</table>

View File

@ -0,0 +1,11 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Q Help</td>
</tr>
<tr>
<td>
Not written yet
</td>
</tr>
</table>

View File

@ -0,0 +1,34 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading" colspan=2>Current Background Jobs</td>
</tr>
<tr>
<td>
<tmpl_if items>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td><b>Secs From Now</b></td>
<td><b>Description</b></td>
</tr>
<tmpl_loop items>
<tr>
<td><tmpl_var future></td>
<td><tmpl_var description></td>
</tr>
</tmpl_loop>
</table>
<tmpl_else>
<tr><td colspan=2 align=center><i>(No current jobs)</i></td></tr>
</tmpl_if>
</td>
</tr>
</table>

View File

@ -0,0 +1,122 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<TITLE>Q Web Interface</TITLE>
<style type="text/css">
<!--
body { font-family: helvetica, arial, sans-serif; background-color: #000080 }
td { }
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
.logocell {
border-color: #ffe000;
border-width: 1px;
border-top-style:none;
border-bottom-style:solid;
border-left-style:none;
border-right-style:none;
text-align: right;
font-weight: bold;
color: #f0f0ff;
}
.tabcell {
font-weight: bold;
background-color: #fffff0;
padding:5px;
border-color:#ffe000;
border-width: 1px;
border-top-style:solid;
border-bottom-style:none;
border-left-style:solid;
border-right-style:solid;
}
.tabcell_inactive {
background-color: #f0f0ff;
padding:5px;
border-color:#ffe000;
border-width: 1px;
border-top-style:solid;
border-bottom-style:solid;
border-left-style:solid;
border-right-style:solid;
}
.tablink {
font-size: smaller;
text-decoration: none;
}
.mainpane { border-color:#ffe000;
border-width: 1px;
border-top-style:none;
border-bottom-style:solid;
border-left-style:solid;
border-right-style:solid;
}
.mainpaneitem1 { border-style:solid; border-color:#0000ff; border-width: thin; }
.mainpaneitem { border-style:solid;
border-color:#0000ff;
border-width: thin;
background-color: #f0f0ff;
}
.formHeading { font-size:larger; font-weight: bolder; text-align:center; }
.btn1 { font-weight: bold;
}
input1 { background-color: #fffff0;
color: #000080;
}
-->
</style>
</head>
<body bgcolor="#ffffff">
<TABLE width="99%" height=99% align="center" cellspacing="0" cellpadding="0" border=0>
<TR>
<TD>
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td>
<table align=right cellspacing=0 cellpadding=0 border=0>
<tr>
<tmpl_loop tabs>
<td class=<tmpl_if active>"tabcell"<tmpl_else>"tabcell_inactive"</tmpl_if>>
<a class="tablink" href="/<tmpl_var name>"><tmpl_var label></a>
</td>
</tmpl_loop>
<tmpl_if _ignore>
<TD class="tabcell_inactive">
<a class="tablink" href="/status">Status</a>
</TD>
<TD class="tabcell">
<a class="tablink" href="/settings">Settings</a>
</TD>
</tmpl_if>
</tr>
</table>
</td>
<TD width=100% class="logocell">
Q <tmpl_var nodeType> Node
</TD>
</TR>
</table>
</TD> </TR>
<tr height=99% bgcolor="#fffff0">
<TD class="mainpane">
<br>
<center>
<tmpl_loop items>
<br>
<tmpl_var item>
<br>
</tmpl_loop>
</center>
</TD>
</tr>
</TABLE>
</body>
</html>

View File

@ -0,0 +1,29 @@
<table align="center" class="mainpaneitem" width=80%>
<tr>
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
</tr>
<tr>
<td>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td>
<p>You are attempting to view a QSite via the Internet Explorer web browser.
</p>
<p>We have blocked the connection to protect your anonymity.
</p>
<p>As a matter of policy, Q does not support QSite browsing via Microsoft
Internet Explorer (MSIE), because of that browser's abysmal track record with
regard to security. If we did allow you to browse Q via MSIE, it would
be easy for a malicious QSite author to embed hostile content which
undermines your computer's security and compromises your anonymity.
</p>
<p>If you want to surf I2P QSites, you'll need to use a more secure web
browser such as <a href="http://www.mozilla.org">Mozilla or Firefox</a>.
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -0,0 +1,10 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Error Inserting <tmpl_if is_site>QSite<tmpl_else>Item</tmpl_if></td>
</tr>
<tr>
<td align=center>Your insert failed because:<br>
<code><tmpl_var error></code>
</td>
</tr>
</table>

View File

@ -0,0 +1,139 @@
<TABLE align="center" class="mainpaneitem" width=90% >
<TR>
<TD class="formHeading" colspan="2">Insert Content</TD>
</tr>
<TR>
<TD colspan="2" align=center><a href="/insert?mode=site">Insert A QSite Instead</a></TD>
</tr>
<tr><td colspan=2><hr></td></tr>
<form method="POST" ENCTYPE="multipart/form-data" action="/insert" class="formBody">
<!-- <form method="POST" action="/insert" class="formBody"> -->
<input type=hidden name="cmd" value="put">
<tr>
<td valign=top>Type: </td>
<td style="font-size:smaller">
<input type="radio" name="type" value="text">Text
<input type="radio" name="type" value="html">HTML
<input type="radio" name="type" value="image">Image
<input type="radio" name="type" value="audio">Audio
<input type="radio" name="type" value="video">Video
<input type="radio" name="type" value="archive">Archive
<input type="radio" name="type" value="other" checked>Other
</td>
</tr>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Title: </TD>
<td>
<input type="text" name="title" size="60" maxlength="58">
<div style="font-style: italic; font-size: smaller">
(A short descriptive title for this item)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Path: </TD>
<td>
<input type="text" name="path" size="60" maxlength="128">
<div style="font-style: italic; font-size: smaller">
(If you're inserting in plain-hash mode, this should be a suggested
filename for people to save the item as when downloading it; If you're
inserting in signed-space mode, this can be a longer pathname)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Mimetype: </TD>
<td>
<input type="text" name="mimetype" size="40" maxlength="40">
<div style="font-style: italic; font-size: smaller">
(Leave blank unless you know exactly what this means)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Keywords: </TD>
<td>
<input type="text" name="keywords" size="60" maxlength="80">
<div style="font-style: italic; font-size: smaller">
(Comma-separated list of short descriptive keywords)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Abstract: </TD>
<td>
<textarea name="abstract" cols="50" rows="2"></textarea>
<div style="font-style: italic; font-size: smaller">
(A short descriptive summary for the item, preferably no
longer than 256 characters)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>File: </TD>
<td>
<INPUT type="file" name="data" size="40" maxlength="128">
<div style="font-style: italic; font-size: smaller">
(Leave this blank if you are entering the raw data in the text box below)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Private Key: </TD>
<td>
<INPUT type="text" name="privkey" size="30" maxlength="60">
<div style="font-size: smaller; font-style: italic">
(Only if inserting in signed space mode)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD colspan=2 align=center>
<INPUT class="btn" type="submit" name="submit" value="Insert it">
</TD>
</tr>
<tr><td colspan=2><hr></td></tr>
<tr>
<td valign=top>Raw Data:</td>
<td>
<div style="font-size: smaller; font-style:italic">
(You may enter the raw data here as text instead of selecting a
file above)
</div>
<textarea name="rawdata" cols=60 rows=16></textarea>
</td>
</tr>
</form>
</TABLE>

View File

@ -0,0 +1,10 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading"><tmpl_if is_site>QSite<tmpl_else>Item</tmpl_if> Inserted Successfully</td>
</tr>
<tr>
<td align=center>Item URI:<br>
<code><input type="text" size=60 readonly value="<tmpl_var uri>"></code>
</td>
</tr>
</table>

View File

@ -0,0 +1,101 @@
<table align="center" class="mainpaneitem" width=90% >
<TR>
<TD class="formHeading" colspan="2">Insert A QSite</TD>
</tr>
<TR>
<TD colspan="2" align=center><a href="/insert?mode=file">Insert A Single File Instead</a></TD>
</tr>
<tr><td colspan=2><hr></td></tr>
<form method="POST" action="/insert" class="formBody">
<!-- <form method="POST" action="/insert" class="formBody"> -->
<input type=hidden name="cmd" value="putsite">
<input type=hidden name="type" value="qsite">
<tr>
<TD valign=top>Name: </TD>
<td>
<input type="text" name="name" size="32" maxlength="50">
<div style="font-style: italic; font-size: smaller">
(A short name for the QSite - should contain only alphanumerics, '-', and '_';
should definitely <i>not</i> contain '/', ':', '\', ' ' etc)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Private Key: </TD>
<td>
<INPUT type="text" name="privkey" size="30" maxlength="60">
<div style="font-size: smaller; font-style: italic">
(Mandatory - if you don't have a signed-space keypair, get one by
clicking on <b>Tools</b>)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Directory: </TD>
<td>
<INPUT type="text" name="dir" size="60" maxlength="128">
<div style="font-size: smaller; font-style: italic">
(Absolute directory path where the QSite's files reside)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Title: </TD>
<td>
<input type="text" name="title" size="60" maxlength="58">
<div style="font-style: italic; font-size: smaller">
(A short descriptive title for this QSite)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Keywords: </TD>
<td>
<input type="text" name="keywords" size="60" maxlength="80">
<div style="font-style: italic; font-size: smaller">
(Comma-separated list of short descriptive keywords)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Abstract: </TD>
<td>
<textarea name="abstract" cols="50" rows="2"></textarea>
<div style="font-style: italic; font-size: smaller">
(A short descriptive summary for the QSite, preferably no
longer than 256 characters)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD colspan=2 align=center>
<INPUT class="btn" type="submit" name="submit" value="Insert it">
</TD>
</tr>
</form>
</table>

View File

@ -0,0 +1,76 @@
<TABLE align="center" class="mainpaneitem">
<TR>
<TD class="formHeading" colspan="3">Search For Content</TD>
</tr>
<form method="GET" action="/search" class="formBody">
<input type=hidden name=cmd value="search">
<tr>
<td valign=top>Type: </td>
<td style="font-size:smaller" colspan=2>
<input type="radio" name="type" value="any" checked>ANY
<input type="radio" name="type" value="qsite">QSite
<input type="radio" name="type" value="text">Text
<input type="radio" name="type" value="html">HTML
<input type="radio" name="type" value="image">Image
<br>
<input type="radio" name="type" value="audio">Audio
<input type="radio" name="type" value="video">Video
<input type="radio" name="type" value="archive">Archive
<input type="radio" name="type" value="other">Other
</td>
</tr>
<tr>
<td>Title: </td>
<td><input type="text" name="title" size="60" value="<tmpl_var title>" maxlength="128"></td>
<td><input type="checkbox" name="title_re" <tmpl_if title_re>checked</tmpl_if>>regexp</td>
</tr>
<tr>
<TD>Path: </TD>
<td><INPUT type="text" name="path" size="60" value="<tmpl_var title>" maxlength="128"></td>
<td><input type="checkbox" name="path_re" <tmpl_if path_re>checked</tmpl_if>>regexp</td>
</TR>
<tr>
<TD>Mimetype: </TD>
<td><INPUT type="text" name="mimetype" size="40" maxlength="40" value="<tmpl_var mimetype>"></td>
<td><input type="checkbox" name="mimetype_re" <tmpl_if mimetype_re>checked</tmpl_if>>regexp</td>
</TR>
<tr>
<TD>Keywords: </TD>
<td><INPUT type="text" name="keywords" size="60" maxlength="80" value="<tmpl_var keywords>"></td>
<td><input type="checkbox" name="keywords_re" <tmpl_if keywords_re>checked</tmpl_if>>regexp</td>
</TR>
<tr>
<TD>Summary: </TD>
<td><textarea name="summary" cols="50" rows=2><tmpl_var summary></textarea></td>
<td><input type="checkbox" name="summary_re" <tmpl_if summary_re>checked</tmpl_if>>regexp</td>
</TR>
<tr>
<td>
Search Mode:
</td>
<td>
<input type=radio name="searchmode" value="and" checked>AND
<input type=radio name="searchmode" value="or">OR
</td>
</tr>
<tr><td colspan=3><hr></td></tr>
<tr>
<TD colspan=3 align=center>
<INPUT class="btn" type="submit" name="submit" value="Search!">
</TD>
</tr>
</form>
</TABLE>

View File

@ -0,0 +1,27 @@
<table align="center" class="mainpaneitem" <tmpl_if results>width="95%"</tmpl_if>>
<tr>
<td class="formHeading" colspan=2>Search Results</td>
</tr>
<tr>
<td style="font-style: italic" align="center"><tmpl_var numresults> items found</td>
</tr>
<tr>
<td width="90%">
<table cellspacing=0 cellpadding=5 align=center border=0 width=95%>
<tmpl_loop results>
<tr>
<td>
<div style="font-size: larger"><a href="/<tmpl_var uri>"><tmpl_var title></a></div>
<tmpl_if abstract>
<div style="font-style: italic"><tmpl_var abstract></div>
</tmpl_if>
<div style="font-size: smaller; color: #008000;"><tmpl_var uri></div>
<div style="font-size:smaller;"><tmpl_var type> (<tmpl_var mimetype>) <tmpl_var size> bytes</div>
<div/>
</td>
</tr>
</tmpl_loop>
</table>
</td>
</tr>
</table>

View File

@ -0,0 +1,9 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Q Settings</td>
</tr>
<tr>
<td>Not implemented yet</td>
</tr>
</table>

View File

@ -0,0 +1,24 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading" colspan=2>Q Node Status</td>
</tr>
<tr>
<td>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td><b>Item</b></td>
<td><b>Value</b></td>
</tr>
<tmpl_loop items>
<tr>
<td><tmpl_var key></td>
<td><tmpl_var value></td>
</tr>
</tmpl_loop>
</table>
</td>
</tr>
</table>

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,178 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*
* Modified by David McNab (david@rebirthing.co.nz) to allow nesting of
* templates (ie, passing a child Template object as a value argument
* to a .setParam() invocation on a parent Template object).
*
*/
package HTML.Tmpl.Element;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import HTML.*;
public class Conditional extends Element
{
private boolean control_val = false;
private Vector [] data;
public Conditional(String type, String name)
throws IllegalArgumentException
{
if(type.equalsIgnoreCase("if"))
this.type="if";
else if(type.equalsIgnoreCase("unless"))
this.type="unless";
else
throw new IllegalArgumentException(
"Unrecognised type: " + type);
this.name = name;
this.data = new Vector[2];
this.data[0] = new Vector();
}
public void addBranch() throws IndexOutOfBoundsException
{
if(data[1] != null)
throw new IndexOutOfBoundsException("Already have two branches");
if(data[0] == null)
data[0] = new Vector();
else if(data[1] == null)
data[1] = new Vector();
}
public void add(String text)
{
if(data[1] != null)
data[1].addElement(text);
else
data[0].addElement(text);
}
public void add(Element node)
{
if(data[1] != null)
data[1].addElement(node);
else
data[0].addElement(node);
}
public void setControlValue(Object control_val)
throws IllegalArgumentException
{
this.control_val = process_var(control_val);
}
public String parse(Hashtable params)
{
if(!params.containsKey(this.name))
this.control_val = false;
else
setControlValue(params.get(this.name));
StringBuffer output = new StringBuffer();
Enumeration de;
if(type.equals("if") && control_val ||
type.equals("unless") && !control_val)
de = data[0].elements();
else if(data[1] != null)
de = data[1].elements();
else
return "";
while(de.hasMoreElements()) {
Object e = de.nextElement();
String eType = e.getClass().getName();
if(eType.endsWith(".String"))
output.append((String)e);
else if (eType.endsWith(".Template"))
output.append(((Template)e).output());
else
output.append(((Element)e).parse(params));
}
return output.toString();
}
public String typeOfParam(String param)
throws NoSuchElementException
{
for(int i=0; i<data.length; i++)
{
if(data[i] == null)
continue;
for(Enumeration e = data[i].elements();
e.hasMoreElements();)
{
Object o = e.nextElement();
if(o.getClass().getName().endsWith(".String"))
continue;
if(((Element)o).Name().equals(param))
return ((Element)o).Type();
}
}
throw new NoSuchElementException(param);
}
private boolean process_var(Object control_val)
throws IllegalArgumentException
{
String control_class = "";
if(control_val == null)
return false;
control_class=control_val.getClass().getName();
if(control_class.indexOf(".") > 0)
control_class = control_class.substring(
control_class.lastIndexOf(".")+1);
if(control_class.equals("String")) {
return !(((String)control_val).equals("") ||
((String)control_val).equals("0"));
} else if(control_class.equals("Vector")) {
return !((Vector)control_val).isEmpty();
} else if(control_class.equals("Boolean")) {
return ((Boolean)control_val).booleanValue();
} else if(control_class.equals("Integer")) {
return (((Integer)control_val).intValue() != 0);
} else {
throw new IllegalArgumentException("Unrecognised type");
}
}
}

View File

@ -0,0 +1,66 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package HTML.Tmpl.Element;
import java.util.Hashtable;
import java.util.NoSuchElementException;
public abstract class Element
{
protected String type;
protected String name="";
public abstract String parse(Hashtable params);
public abstract String typeOfParam(String param)
throws NoSuchElementException;
public void add(String data){}
public void add(Element node){}
public boolean contains(String param)
{
try {
return (typeOfParam(param) != null?true:false);
} catch(NoSuchElementException nse) {
return false;
}
}
public final String Type()
{
return type;
}
public final String Name()
{
return name;
}
}

View File

@ -0,0 +1,39 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package HTML.Tmpl.Element;
public class If extends Conditional
{
public If(String control_var) throws IllegalArgumentException
{
super("if", control_var);
}
}

View File

@ -0,0 +1,183 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package HTML.Tmpl.Element;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.NoSuchElementException;
public class Loop extends Element
{
private boolean loop_context_vars=false;
private boolean global_vars=false;
private Vector control_val = null;
private Vector data;
public Loop(String name)
{
this.type = "loop";
this.name = name;
this.data = new Vector();
}
public Loop(String name, boolean loop_context_vars)
{
this(name);
this.loop_context_vars=loop_context_vars;
}
public Loop(String name, boolean loop_context_vars, boolean global_vars)
{
this(name);
this.loop_context_vars=loop_context_vars;
this.global_vars=global_vars;
}
public void add(String text)
{
data.addElement(text);
}
public void add(Element node)
{
data.addElement(node);
}
public void setControlValue(Vector control_val)
throws IllegalArgumentException
{
this.control_val = process_var(control_val);
}
public String parse(Hashtable p)
{
if(!p.containsKey(this.name))
this.control_val = null;
else {
Object o = p.get(this.name);
if(!o.getClass().getName().endsWith(".Vector") &&
!o.getClass().getName().endsWith(".List"))
throw new ClassCastException(
"Attempt to set <tmpl_loop> with a non-list. tmpl_loop=" + this.name);
setControlValue((Vector)p.get(this.name));
}
if(control_val == null)
return "";
StringBuffer output = new StringBuffer();
Enumeration iterator = control_val.elements();
boolean first=true;
boolean last=false;
boolean inner=false;
boolean odd=true;
int counter=1;
while(iterator.hasMoreElements()) {
Hashtable params = (Hashtable)iterator.nextElement();
if(params==null)
params = new Hashtable();
if(global_vars) {
for(Enumeration e = p.keys(); e.hasMoreElements();) {
Object key = e.nextElement();
if(!params.containsKey(key))
params.put(key, p.get(key));
}
}
if(loop_context_vars) {
if(!iterator.hasMoreElements())
last=true;
inner = !first && !last;
params.put("__FIRST__", first?"1":"");
params.put("__LAST__", last?"1":"");
params.put("__ODD__", odd?"1":"");
params.put("__INNER__", inner?"1":"");
params.put("__COUNTER__", "" + (counter++));
}
Enumeration de = data.elements();
while(de.hasMoreElements()) {
Object e = de.nextElement();
if(e.getClass().getName().indexOf("String")>-1)
output.append((String)e);
else
output.append(((Element)e).parse(params));
}
first = false;
odd = !odd;
}
return output.toString();
}
public String typeOfParam(String param)
throws NoSuchElementException
{
for(Enumeration e = data.elements(); e.hasMoreElements();)
{
Object o = e.nextElement();
if(o.getClass().getName().endsWith(".String"))
continue;
if(((Element)o).Name().equals(param))
return ((Element)o).Type();
}
throw new NoSuchElementException(param);
}
private Vector process_var(Vector control_val)
throws IllegalArgumentException
{
String control_class = "";
if(control_val == null)
return null;
control_class=control_val.getClass().getName();
if(control_class.indexOf("Vector") > -1) {
if(control_val.isEmpty())
return null;
} else {
throw new IllegalArgumentException("Unrecognised type");
}
return control_val;
}
}

View File

@ -0,0 +1,39 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package HTML.Tmpl.Element;
public class Unless extends Conditional
{
public Unless(String control_var) throws IllegalArgumentException
{
super("unless", control_var);
}
}

View File

@ -0,0 +1,144 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*
* Modified by David McNab (david@rebirthing.co.nz) to allow nesting of
* templates (ie, passing a child Template object as a value argument
* to a .setParam() invocation on a parent Template object).
*/
package HTML.Tmpl.Element;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import HTML.Tmpl.Util;
import HTML.Template;
public class Var extends Element
{
public static final int ESCAPE_NONE = 0;
public static final int ESCAPE_URL = 1;
public static final int ESCAPE_HTML = 2;
public static final int ESCAPE_QUOTE = 4;
public Var(String name, int escape, Object default_value)
throws IllegalArgumentException
{
this(name, escape);
this.default_value = stringify(default_value);
}
public Var(String name, int escape)
throws IllegalArgumentException
{
if(name == null)
throw new IllegalArgumentException("tmpl_var must have a name");
this.type = "var";
this.name = name;
this.escape = escape;
}
public Var(String name, String escape)
throws IllegalArgumentException
{
this(name, escape, null);
}
public Var(String name, String escape, Object default_value)
throws IllegalArgumentException
{
this(name, ESCAPE_NONE, default_value);
if(escape.equalsIgnoreCase("html"))
this.escape = ESCAPE_HTML;
else if(escape.equalsIgnoreCase("url"))
this.escape = ESCAPE_URL;
else if(escape.equalsIgnoreCase("quote"))
this.escape = ESCAPE_QUOTE;
}
public Var(String name, boolean escape)
throws IllegalArgumentException
{
this(name, escape?ESCAPE_HTML:ESCAPE_NONE);
}
public String parse(Hashtable params)
{
String value = null;
if(params.containsKey(this.name))
value = stringify(params.get(this.name));
else
value = this.default_value;
if(value == null)
return "";
if(this.escape == ESCAPE_HTML)
return Util.escapeHTML(value);
else if(this.escape == ESCAPE_URL)
return Util.escapeURL(value);
else if(this.escape == ESCAPE_QUOTE)
return Util.escapeQuote(value);
else
return value;
}
public String typeOfParam(String param)
throws NoSuchElementException
{
throw new NoSuchElementException(param);
}
private String stringify(Object o)
{
if(o == null)
return null;
String cname = o.getClass().getName();
if(cname.endsWith(".String"))
return (String)o;
else if(cname.endsWith(".Integer"))
return ((Integer)o).toString();
else if(cname.endsWith(".Boolean"))
return ((Boolean)o).toString();
else if(cname.endsWith(".Date"))
return ((java.util.Date)o).toString();
else if(cname.endsWith(".Vector"))
throw new ClassCastException("Attempt to set <tmpl_var> with a non-scalar. Var name=" + this.name);
else if(cname.endsWith(".Template"))
return ((Template)o).output();
else
throw new ClassCastException("Unknown object type: " + cname);
}
// Private data starts here
private int escape=ESCAPE_NONE;
private String default_value=null;
}

View File

@ -0,0 +1,145 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package HTML.Tmpl;
/**
* Pre-parse filters for HTML.Template templates.
* <p>
* The HTML.Tmpl.Filter interface allows you to write Filters
* for your templates. The filter is called after the template
* is read and before it is parsed.
* <p>
* You can use a filter to make changes in the template file before
* it is parsed by HTML.Template, so for example, use it to replace
* constants, or to translate your own tags to HTML.Template tags.
* <p>
* A common usage would be to do what you think you're doing when you
* do <code>&lt;TMPL_INCLUDE file="&lt;TMPL_VAR name="the_file"&gt;"&gt;</code>:
* <p>
* myTemplate.tmpl:
* <pre>
* &lt;TMPL_INCLUDE file="&lt;%the_file%&gt;"&gt;
* </pre>
* <p>
* myFilter.java:
* <pre>
* class myFilter implements HTML.Tmpl.Filter
* {
* private String myFile;
* private int type=SCALAR
*
* public myFilter(String myFile) {
* this.myFile = myFile;
* }
*
* public int format() {
* return this.type;
* }
*
* public String parse(String t) {
* // replace all &lt;%the_file%&gt; with myFile
* return t;
* }
*
* public String [] parse(String [] t) {
* throw new UnsupportedOperationException();
* }
* }
* </pre>
* <p>
* myClass.java:
* <pre>
* Hashtable params = new Hashtable();
* params.put("filename", "myTemplate.tmpl");
* params.put("filter", new myFilter("myFile.tmpl"));
* Template t = new Template(params);
* </pre>
*
* @author Philip S Tellis
* @version 0.0.1
*/
public interface Filter
{
/**
* Tells HTML.Template to call the parse(String) method of this filter.
*/
public final static int SCALAR=1;
/**
* Tells HTML.Template to call the parse(String []) method of this
* filter.
*/
public final static int ARRAY=2;
/**
* Tells HTML.Template what kind of filter this is.
* Should return either SCALAR or ARRAY to indicate which parse method
* must be called.
*
* @return the values SCALAR or ARRAY indicating which parse method
* is to be called
*/
public int format();
/**
* parses the template as a single string, and returns the parsed
* template as a single string.
* <p>
* Should throw an UnsupportedOperationException if it isn't implemented
*
* @param t a string containing the entire template
*
* @return a string containing the template after you've parsed it
*
* @throws UnsupportedOperationException if this method isn't
* implemented
*/
public String parse(String t);
/**
* parses the template as an array of strings, and returns the parsed
* template as an array of strings.
* <p>
* Should throw an UnsupportedOperationException if it isn't implemented
*
* @param t an array of strings containing the template - one line
* at a time
*
* @return an array of strings containing the parsed template -
* one line at a time
*
* @throws UnsupportedOperationException if this method isn't
* implemented
*/
public String [] parse(String [] t);
}

View File

@ -0,0 +1,385 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package HTML.Tmpl.Parsers;
import java.util.*;
import HTML.Tmpl.Element.*;
import HTML.Tmpl.Util;
public class Parser
{
private boolean case_sensitive=false;
private boolean strict=true;
private boolean loop_context_vars=false;
private boolean global_vars=false;
public Parser()
{
}
public Parser(String [] args)
throws ArrayIndexOutOfBoundsException,
IllegalArgumentException
{
if(args.length%2 != 0)
throw new ArrayIndexOutOfBoundsException("odd number of arguments passed");
for(int i=0; i<args.length; i+=2) {
if(args[i].equals("case_sensitive")) {
String cs = args[i+1];
if(cs.equals("") || cs.equals("0"))
this.case_sensitive=false;
else
this.case_sensitive=true;
} else if(args[i].equals("strict")) {
String s = args[i+1];
if(s.equals("") || s.equals("0"))
this.strict=false;
else
this.strict=true;
} else if(args[i].equals("loop_context_vars")) {
String s = args[i+1];
if(s.equals("") || s.equals("0"))
this.loop_context_vars=false;
else
this.loop_context_vars=true;
} else if(args[i].equals("global_vars")) {
String s = args[i+1];
if(s.equals("") || s.equals("0"))
this.global_vars=false;
else
this.global_vars=true;
} else {
throw new IllegalArgumentException(args[i]);
}
}
}
public Element getElement(Properties p)
throws NoSuchElementException
{
String type = p.getProperty("type");
if(type.equals("if"))
return new If(p.getProperty("name"));
else if(type.equals("unless"))
return new Unless(p.getProperty("name"));
else if(type.equals("loop"))
return new Loop(p.getProperty("name"),
loop_context_vars, global_vars);
else
throw new NoSuchElementException(type);
}
public Vector parseLine(String line)
throws IllegalArgumentException
{
int pos=0, endpos;
Vector parts = new Vector();
char [] c = line.toCharArray();
int i=0;
StringBuffer temp = new StringBuffer();
for(i=0; i<c.length; i++) {
if(c[i] != '<') {
temp.append(c[i]);
} else {
// found a tag
Util.debug_print("line so far: " + temp);
StringBuffer tag = new StringBuffer();
for(; i<c.length && c[i] != '>'; i++) {
tag.append(c[i]);
}
// > is not allowed inside a template tag
// so we can be sure that if this is a
// template tag, it ends with a >
// add the closing > as well
if(i<c.length)
tag.append(c[i]);
// if this contains more < inside it,
// then it could possibly be a template
// tag inside a html tag
// so remove external tag parts
while(tag.toString().substring(1).indexOf("<")
> -1)
{
do {
temp.append(tag.charAt(0));
tag=new StringBuffer(
tag.toString().substring(1));
} while(tag.charAt(0) != '<');
}
Util.debug_print("tag: " + tag);
String test_tag = tag.toString().toLowerCase();
// if it doesn't contain tmpl_ it is not
// a template tag
if(test_tag.indexOf("tmpl_") < 0) {
temp.append(tag);
continue;
}
// may be a template tag
// check if it starts with tmpl_
test_tag = cleanTag(test_tag);
Util.debug_print("clean: " + test_tag);
// check if it is a closing tag
if(test_tag.startsWith("/"))
test_tag = test_tag.substring(1);
// if it still doesn't start with tmpl_
// then it is not a template tag
if(!test_tag.startsWith("tmpl_")) {
temp.append(tag);
continue;
}
// now it must be a template tag
String tag_type=getTagType(test_tag);
if(tag_type == null) {
if(strict)
throw new
IllegalArgumentException(
tag.toString());
else
temp.append(tag);
}
Util.debug_print("type: " + tag_type);
// if this was an invalid key and we've
// reached so far, then next iteration
if(tag_type == null)
continue;
// now, push the previous stuff
// into the Vector
if(temp.length()>0) {
parts.addElement(temp.toString());
temp = new StringBuffer();
}
// it is a valid template tag
// get its properties
Util.debug_print("Checking: " + tag);
Properties tag_props =
getTagProps(tag.toString());
if(tag_props.containsKey("name"))
Util.debug_print("name: " +
tag_props.getProperty("name"));
else
Util.debug_print("no name");
parts.addElement(tag_props);
}
}
if(temp.length()>0)
parts.addElement(temp.toString());
return parts;
}
private String cleanTag(String tag)
throws IllegalArgumentException
{
String test_tag = new String(tag);
// first remove < and >
if(test_tag.startsWith("<"))
test_tag = test_tag.substring(1);
if(test_tag.endsWith(">"))
test_tag = test_tag.substring(0, test_tag.length()-1);
else
throw new IllegalArgumentException("Tags must start " +
"and end on the same line");
// remove any leading !-- and trailing
// -- in case of comment style tags
if(test_tag.startsWith("!--")) {
test_tag=test_tag.substring(3);
}
if(test_tag.endsWith("--")) {
test_tag=test_tag.substring(0, test_tag.length()-2);
}
// then leading and trailing spaces
test_tag = test_tag.trim();
return test_tag;
}
private String getTagType(String tag)
{
int sp = tag.indexOf(" ");
String tag_type="";
if(sp < 0) {
tag_type = tag.toLowerCase();
} else {
tag_type = tag.substring(0, sp).toLowerCase();
}
if(tag_type.startsWith("tmpl_"))
tag_type=tag_type.substring(5);
Util.debug_print("tag_type: " + tag_type);
if(tag_type.equals("var") ||
tag_type.equals("if") ||
tag_type.equals("unless") ||
tag_type.equals("loop") ||
tag_type.equals("include") ||
tag_type.equals("else")) {
return tag_type;
} else {
return null;
}
}
private Properties getTagProps(String tag)
throws IllegalArgumentException,
NullPointerException
{
Properties p = new Properties();
tag = cleanTag(tag);
Util.debug_print("clean: " + tag);
if(tag.startsWith("/")) {
p.put("close", "true");
tag=tag.substring(1);
} else {
p.put("close", "");
}
Util.debug_print("close: " + p.getProperty("close"));
p.put("type", getTagType(tag));
Util.debug_print("type: " + p.getProperty("type"));
if(p.getProperty("type").equals("else") ||
p.getProperty("close").equals("true"))
return p;
if(p.getProperty("type").equals("var"))
p.put("escape", "");
int sp = tag.indexOf(" ");
// if we've got so far, this must succeed
tag = tag.substring(sp).trim();
Util.debug_print("checking params: " + tag);
// now, we should have either name=value pairs
// or name space escape in case of old style vars
if(tag.indexOf("=") < 0) {
// no = means old style
// first will be var name
// second if any will be escape
sp = tag.toLowerCase().indexOf(" escape");
if(sp < 0) {
// no escape
p.put("name", tag);
p.put("escape", "0");
} else {
tag = tag.substring(0, sp);
p.put("name", tag);
p.put("escape", "html");
}
} else {
// = means name=value pairs.
// use a StringTokenizer
StringTokenizer st = new StringTokenizer(tag, " =");
while(st.hasMoreTokens()) {
String key, value;
key = st.nextToken().toLowerCase();
if(st.hasMoreTokens())
value = st.nextToken();
else if(key.equals("escape"))
value = "html";
else
throw new NullPointerException(
"parameter " + key + " has no value");
if(value.startsWith("\"") &&
value.endsWith("\""))
value = value.substring(1,
value.length()-1);
else if(value.startsWith("'") &&
value.endsWith("'"))
value = value.substring(1,
value.length()-1);
if(value.length()==0)
throw new NullPointerException(
"parameter " + key + " has no value");
if(key.equals("escape"))
value=value.toLowerCase();
p.put(key, value);
}
}
String name = p.getProperty("name");
// if not case sensitive, and not special variable, flatten case
// never flatten case for includes
if(!case_sensitive && !p.getProperty("type").equals("include")
&& !( name.startsWith("__") && name.endsWith("__") ))
{
p.put("name", name.toLowerCase());
}
if(!Util.isNameChar(name))
throw new IllegalArgumentException(
"parameter name may only contain " +
"letters, digits, ., /, +, -, _");
// __var__ is allowed in the template, but not in the
// code. this is so that people can reference __FIRST__,
// etc
return p;
}
}

View File

@ -0,0 +1,130 @@
/*
* HTML.Template: A module for using HTML Templates with java
*
* Copyright (c) 2002 Philip S Tellis (philip.tellis@iname.com)
*
* This module is free software; you can redistribute it
* and/or modify it under the terms of either:
*
* a) the GNU General Public License as published by the Free
* Software Foundation; either version 1, or (at your option)
* any later version, or
*
* b) the "Artistic License" which comes with this module.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See either the GNU General Public License or the
* Artistic License for more details.
*
* You should have received a copy of the Artistic License
* with this module, in the file ARTISTIC. If not, I'll be
* glad to provide one.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package HTML.Tmpl;
public class Util
{
public static boolean debug=false;
public static String escapeHTML(String element)
{
String s = new String(element); // don't change the original
String [] metas = {"&", "<", ">", "\""};
String [] repls = {"&amp;", "&lt;", "&gt;", "&quot;"};
for(int i = 0; i < metas.length; i++) {
int pos=0;
do {
pos = s.indexOf(metas[i], pos);
if(pos<0)
break;
s = s.substring(0, pos) + repls[i] + s.substring(pos+1);
pos++;
} while(pos >= 0);
}
return s;
}
public static String escapeURL(String url)
{
StringBuffer s = new StringBuffer();
String no_escape = "./-_";
for(int i=0; i<url.length(); i++)
{
char c = url.charAt(i);
if(!Character.isLetterOrDigit(c) &&
no_escape.indexOf(c)<0)
{
String h = Integer.toHexString((int)c);
s.append("%");
if(h.length()<2)
s.append("0");
s.append(h);
} else {
s.append(c);
}
}
return s.toString();
}
public static String escapeQuote(String element)
{
String s = new String(element); // don't change the original
String [] metas = {"\"", "'"};
String [] repls = {"\\\"", "\\'"};
for(int i = 0; i < metas.length; i++) {
int pos=0;
do {
pos = s.indexOf(metas[i], pos);
if(pos<0)
break;
s = s.substring(0, pos) + repls[i] + s.substring(pos+1);
pos++;
} while(pos >= 0);
}
return s;
}
public static boolean isNameChar(char c)
{
return true;
}
public static boolean isNameChar(String s)
{
String alt_valid = "./+-_";
for(int i=0; i<s.length(); i++)
if(!Character.isLetterOrDigit(s.charAt(i)) &&
alt_valid.indexOf(s.charAt(i))<0)
return false;
return true;
}
public static void debug_print(String msg)
{
if(!debug)
return;
System.err.println(msg);
}
public static void debug_print(Object o)
{
debug_print(o.toString());
}
}

View File

@ -0,0 +1,19 @@
/*
* AumUtil.java
*
* Created on March 24, 2005, 3:10 PM
*/
package net.i2p.aum;
/**
*
* @author david
*/
public class AumUtil {
/** Creates a new instance of AumUtil */
public AumUtil() {
}
}

View File

@ -0,0 +1,67 @@
/*
* NonUniqueProperties.java
*
* Created on April 9, 2005, 10:46 PM
*/
package net.i2p.aum;
import java.*;
import java.util.*;
/**
* similar in some ways to Properties, except that duplicate keys
* are allowed
*/
public class DupHashtable extends Hashtable {
/** Creates a new instance of NonUniqueProperties */
public DupHashtable() {
super();
}
/** Adds a value to be stored against key */
public void put(String key, String value) {
if (!containsKey(key)) {
put(key, new Vector());
}
((Vector)get(key)).addElement(value);
}
/** retrieves a Vector of values for key, or empty vector if none */
public Vector get(String key) {
if (!containsKey(key)) {
return new Vector();
} else {
return (Vector)super.get(key);
}
}
/** returns the i-th value for given key, or dflt if key not found */
public String get(String key, int idx, String dflt) {
if (containsKey(key)) {
return get(key, idx);
} else {
return dflt;
}
}
/** returns the i-th value for given key
* @throws ArrayIndexOutOfBoundsException if idx is out of range
*/
public String get(String key, int idx) {
return (String)((Vector)get(key)).get(idx);
}
/** returns the number of values for a given key */
public int numValuesFor(String key) {
if (!containsKey(key)) {
return 0;
} else {
return ((Vector)get(key)).size();
}
}
}

View File

@ -0,0 +1,155 @@
// a simple I2P stream client that makes connections to an EchoServer,
// sends in stuff, and gets replies
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.*;
import net.i2p.util.*;
/**
* a simple program which illustrates the use of I2P stream
* sockets from a client point of view
*/
public class EchoClient extends Thread
{
public I2PSocketManager socketManager;
public I2PSocket clientSocket;
public Destination dest;
protected static Log _log;
/**
* Creates an echoclient, given an I2P Destination object
*/
public EchoClient(Destination remdest)
{
_log = new Log("EchoServer");
_init(remdest);
}
/**
* Creates an EchoClient given a destination in base64
*/
public EchoClient(String destStr) throws DataFormatException
{
_log = new Log("EchoServer");
Destination remdest = new Destination();
remdest.fromBase64(destStr);
_init(remdest);
}
private void _init(Destination remdest)
{
dest = remdest;
System.out.println("Client: dest="+dest.toBase64());
System.out.println("Client: Creating client socketManager");
// get a socket manager
socketManager = I2PSocketManagerFactory.createManager();
}
/**
* runs the EchoClient demo
*/
public void run()
{
InputStream socketIn;
OutputStreamWriter socketOut;
OutputStream socketOutStream;
System.out.println("Client: Creating connected client socket");
System.out.println("dest="+dest.toBase64());
try {
// get a client socket
clientSocket = socketManager.connect(dest);
} catch (I2PException e) {
e.printStackTrace();
return;
} catch (ConnectException e) {
e.printStackTrace();
return;
} catch (NoRouteToHostException e) {
e.printStackTrace();
return;
} catch (InterruptedIOException e) {
e.printStackTrace();
return;
}
System.out.println("Client: Successfully connected!");
try {
socketIn = clientSocket.getInputStream();
socketOutStream = clientSocket.getOutputStream();
socketOut = new OutputStreamWriter(socketOutStream);
System.out.println("Client: created streams");
socketOut.write("Hi there server!\n");
socketOut.flush();
System.out.println("Client: sent to server, awaiting reply");
String line = DataHelper.readLine(socketIn);
System.out.println("Got reply: '" + line + "'");
clientSocket.close();
} catch (NoRouteToHostException e) {
e.printStackTrace();
return;
} catch (IOException e) {
System.out.println("IOException!!");
e.printStackTrace();
return;
}
}
/**
* allows the echo client to be run from a command shell
*/
public static void main(String [] args)
{
String dest64 = args[0];
System.out.println("dest="+dest64);
Destination d = new Destination();
try {
d.fromBase64(dest64);
} catch (DataFormatException e) {
e.printStackTrace();
return;
}
EchoClient client = new EchoClient(d);
System.out.println("client: running");
client.run();
System.out.println("client: done");
}
}

View File

@ -0,0 +1,163 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.*;
import net.i2p.util.*;
/**
* a simple program which illustrates the use of I2P stream
* sockets from a server point of view
*/
public class EchoServer extends Thread
{
//public I2PClient client;
//public PrivDestination privDest;
//public I2PSession serverSession;
public I2PSocketManager socketManager;
public I2PServerSocket serverSocket;
public PrivDestination key;
public Destination dest;
protected static Log _log;
public EchoServer() throws I2PException, IOException
{
_log = new Log("EchoServer");
System.out.println("Server: creating new key");
// key = PrivDestination.newKey();
// System.out.println("Server: dest=" + key.toDestinationBase64());
System.out.println("Server: creating socket manager");
Properties props = new Properties();
props.setProperty("inbound.length", "0");
props.setProperty("outbound.length", "0");
props.setProperty("inbound.lengthVariance", "0");
props.setProperty("outbound.lengthVariance", "0");
PrivDestination key = PrivDestination.newKey();
// get a socket manager
// socketManager = I2PSocketManagerFactory.createManager(key);
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
System.out.println("Server: getting server socket");
// get a server socket
serverSocket = socketManager.getServerSocket();
System.out.println("Server: got server socket, ready to run");
dest = socketManager.getSession().getMyDestination();
System.out.println("Server: getMyDestination->"+dest.toBase64());
start();
}
/**
* run this EchoServer
*/
public void run()
{
System.out.println("Server: listening on dest:");
/**
try {
System.out.println(key.toDestinationBase64());
} catch (DataFormatException e) {
e.printStackTrace();
}
*/
System.out.println(dest.toBase64());
while (true)
{
try {
I2PSocket sessSocket = serverSocket.accept();
System.out.println("Server: Got connection from client");
InputStream socketIn = sessSocket.getInputStream();
OutputStreamWriter socketOut = new OutputStreamWriter(sessSocket.getOutputStream());
System.out.println("Server: created streams");
// read a line from input, and echo it back
String line = DataHelper.readLine(socketIn);
System.out.println("Server: got '" + line + "'");
String reply = "EchoServer: got '" + line + "'\n";
socketOut.write(reply);
socketOut.flush();
System.out.println("Server: sent trply");
sessSocket.close();
System.out.println("Server: closed socket");
} catch (ConnectException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (I2PException e) {
e.printStackTrace();
}
}
}
public Destination getDest() throws DataFormatException
{
// return key.toDestination();
return dest;
}
public String getDestBase64() throws DataFormatException
{
// return key.toDestinationBase64();
return dest.toBase64();
}
/**
* runs EchoServer from the command shell
*/
public static void main(String [] args)
{
System.out.println("Constructing an EchoServer");
try {
EchoServer myServer = new EchoServer();
System.out.println("Got an EchoServer");
System.out.println("Here's the dest:");
System.out.println(myServer.getDestBase64());
myServer.run();
} catch (I2PException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,56 @@
// runs EchoServer and EchoClient as threads
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.*;
/**
* A simple program which runs the EchoServer and EchoClient
* demos as threads
*/
public class EchoTest
{
/**
* create one instance each of EchoServer and EchoClient,
* run the server as a thread, run the client in foreground,
* display detailed results
*/
public static void main(String [] args)
{
EchoServer server;
EchoClient client;
try {
server = new EchoServer();
Destination serverDest = server.getDest();
System.out.println("EchoTest: serverDest=" + serverDest.toBase64());
client = new EchoClient(serverDest);
} catch (I2PException e) {
e.printStackTrace(); return;
} catch (IOException e) {
e.printStackTrace(); return;
}
System.out.println("Starting server...");
//server.start();
System.out.println("Starting client...");
client.run();
}
}

View File

@ -0,0 +1,322 @@
/*
* SimpleScheduler.java
*
* Created on March 24, 2005, 11:14 PM
*/
package net.i2p.aum;
import java.*;
import java.lang.*;
import java.util.*;
/**
* <p>Implements a queue of objects, where each object is 'embargoed'
* against release until a given time. Threads which attempt to .get
* items from this queue will block if the queue is empty, or if the
* first item of the queue has a 'release time' which has not yet passed.</p>
*
* <p>Think of it like a news desk which receives media releases which are
* 'embargoed' till a certain time. These releases sit in a queue, and when
* their embargo expires, they are actioned and go to print or broadcast.
* The reporters at this news desk are the 'threads', which get blocked
* until the next item's embargo expires.</p>
*
* <p>Purpose of implementing this is to provide a mechanism for scheduling
* background jobs to be executed at precise times</p>.
*/
public class EmbargoedQueue extends Thread {
/**
* items which are waiting for dispatch - stored as 2-element vectors,
* where elem 0 is Integer dispatch time, and elem 1 is the object;
* note that this list is kept in strict ascending order of time.
* Whenever an object becomes ready, it is removed from this queue
* and appended to readyItems
*/
public Vector waitingItems;
/**
* items which are ready for dispatch (their time has come).
*/
public SimpleQueue readyItems;
/** set this true to enable verbose debug messages */
public boolean debug = false;
/** Creates a new embargoed queue */
public EmbargoedQueue() {
waitingItems = new Vector();
readyItems = new SimpleQueue();
// fire up scheduler thread
start();
}
/**
* fetches the item at head of queue, blocking if queue is empty
*/
public Object get()
{
return readyItems.get();
}
/**
* adds a new object to queue without any embargo (or, an embargo that expires
* immediately)
* @param item the object to be added
*/
public synchronized void putNow(Object item)
{
putAfter(0, item);
}
/**
* adds a new object to queue, embargoed until given number of milliseconds
* have elapsed
* @param delay number of milliseconds from now when embargo expires
* @param item the object to be added
*/
public synchronized void putAfter(long delay, Object item)
{
long now = new Date().getTime();
putAt(now+delay, item);
}
/**
* adds a new object to the queue, embargoed until given time
* @param time the unixtime in milliseconds when the object's embargo expires,
* and the object is to be made available
* @param item the object to be added
*/
public synchronized void putAt(long time, Object item)
{
Vector elem = new Vector();
elem.addElement(new Long(time));
elem.addElement(item);
long now = new Date().getTime();
long future = time - now;
//System.out.println("putAt: time="+time+" ("+future+"ms from now), job="+item);
// find where to insert
int i;
int nitems = waitingItems.size();
for (i = 0; i < nitems; i++)
{
// get item i
Vector itemI = (Vector)waitingItems.get(i);
long timeI = ((Long)(itemI.get(0))).longValue();
if (time < timeI)
{
// new item earlier than item i, insert here and bust out
waitingItems.insertElementAt(elem, i);
break;
}
}
// did we insert?
if (i == nitems)
{
// no - gotta append
waitingItems.addElement(elem);
}
// debugging
if (debug) {
printWaiting();
}
// awaken this scheduler object's thread, so it can
// see if any jobs are ready
//notify();
interrupt();
}
/**
* for debugging - prints out a list of waiting items
*/
public synchronized void printWaiting()
{
int i;
long now = new Date().getTime();
System.out.println("EmbargoedQueue dump:");
System.out.println(" Waiting items:");
int nwaiting = waitingItems.size();
for (i = 0; i < nwaiting; i++)
{
Vector item = (Vector)waitingItems.get(i);
long when = ((Long)item.get(0)).longValue();
Object job = item.get(1);
int delay = (int)(when - now)/1000;
System.out.println(" "+delay+"s, t="+when+", job="+job);
}
System.out.println(" Ready items:");
int nready = readyItems.items.size();
for (i = 0; i < nready; i++)
{
//Vector item = (Vector)readyItems.items.get(i);
Object item = readyItems.items.get(i);
System.out.println(" job="+item);
}
}
/**
* scheduling thread, which wakes up every time a new job is queued, and
* if any jobs are ready, transfers them to the readyQueue and notifies
* any waiting client threads
*/
public void run()
{
// monitor the waiting queue, waiting till one becomes ready
while (true)
{
try {
if (waitingItems.size() > 0)
{
// at least 1 waiting item
Vector item = (Vector)(waitingItems.get(0));
long now = new Date().getTime();
long then = ((Long)item.get(0)).longValue();
long delay = then - now;
// ready?
if (delay <= 0)
{
// yep, ready, remove job and stick on waiting queue
waitingItems.remove(0); // ditch from waiting
Object elem = item.get(1);
readyItems.put(elem); // and add to ready
if (debug)
{
System.out.println("embargo expired on "+elem);
printWaiting();
}
}
else
{
// not ready, hang about till we get woken, or the
// job becomes ready
if (debug)
{
System.out.println("waiting for "+delay+"ms");
}
Thread.sleep(delay);
}
}
else
{
// no items yet, hang out for an interrupt
if (debug)
{
System.out.println("queue is empty");
}
synchronized (this) {
wait();
}
}
} catch (Exception e) {
//System.out.println("exception");
if (debug)
{
System.out.println("exception ("+e.getClass().getName()+") "+e.getMessage());
}
}
}
}
private static class TestThread extends Thread {
String id;
EmbargoedQueue q;
public TestThread(String id, EmbargoedQueue q) {
this.id = id;
this.q = q;
}
public void run() {
try {
print("waiting for queue");
Object item = q.get();
print("got item: '"+item+"'");
} catch (Exception e) {
e.printStackTrace();
return;
}
}
public void print(String msg) {
System.out.println("thread '"+id+"': "+msg);
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
int i;
int nthreads = 7;
Thread [] threads = new Thread[nthreads];
EmbargoedQueue q = new EmbargoedQueue();
SimpleSemaphore threadPool = new SimpleSemaphore(nthreads);
// populate the queue with some stuff
q.putAfter(10000, "red");
q.putAfter(3000, "orange");
q.putAfter(6000, "yellow");
// populate threads array
for (i = 0; i < nthreads; i++) {
threads[i] = new TestThread("thread"+i, q);
}
// and launch the threads
for (i = 0; i < nthreads; i++) {
threads[i].start();
}
// wait, presumably till all these elements are actioned
try {
Thread.sleep(12000);
} catch (Exception e) {
e.printStackTrace();
return;
}
// add some more shit to the queue, randomly scheduled
Random r = new Random();
String [] items = {"green", "blue", "indigo", "violet", "black", "white", "brown"};
for (i = 0; i < items.length; i++) {
String item = items[i];
int delay = 2000 + r.nextInt(8000);
System.out.println("main: adding '"+item+"' after "+delay+"ms ...");
q.putAfter(delay, item);
}
// wait, presumably for all jobs to finish
try {
Thread.sleep(12000);
} catch (Exception e) {
e.printStackTrace();
return;
}
System.out.println("main: terminating");
}
}

View File

@ -0,0 +1,452 @@
// I2P equivalent of 'netcat'
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.naming.*;
import net.i2p.client.streaming.*;
import net.i2p.data.*;
import net.i2p.util.*;
/**
* A I2P equivalent of the much-beloved 'netcat' utility.
* This command-line utility can either connect to a remote
* destination, or listen on a private destination for incoming
* connections. Once a connection is established, input on stdin
* is sent to the remote peer, and anything received from the
* remote peer is printed to stdout
*/
public class I2PCat extends Thread
{
public I2PSocketManager socketManager;
public I2PServerSocket serverSocket;
public I2PSocket sessSocket;
public PrivDestination key;
public Destination dest;
public InputStream socketIn;
public OutputStream socketOutStream;
public OutputStreamWriter socketOut;
public SockInput rxThread;
protected static Log _log;
public static String defaultHost = "127.0.0.1";
public static int defaultPort = 7654;
/**
* a thread for reading from socket and displaying on stdout
*/
private class SockInput extends Thread {
InputStream _in;
protected Log _log;
public SockInput(InputStream i) {
_in = i;
}
public void run()
{
// the thread portion, receives incoming bytes on
// the socket input stream and spits them to stdout
byte [] ch = new byte[1];
print("Receiver thread listening...");
try {
while (true) {
//String line = DataHelper.readLine(socketIn);
if (_in.read(ch) != 1) {
print("failed to receive from socket");
break;
}
//System.out.println(line);
System.out.write(ch, 0, 1);
System.out.flush();
}
} catch (IOException e) {
e.printStackTrace();
print("Receiver thread crashed, terminating!!");
System.exit(1);
}
}
void print(String msg)
{
System.out.println("-=- I2PCat: "+msg);
if (_log != null) {
_log.debug(msg);
}
}
}
public I2PCat()
{
_log = new Log("I2PCat");
}
/**
* Runs I2PCat in server mode, listening on the given destination
* for one incoming connection. Once connection is established,
* copyies data between the remote peer and
* the local terminal console.
*/
public void runServer(String keyStr) throws IOException, DataFormatException
{
Properties props = new Properties();
props.setProperty("inbound.length", "0");
props.setProperty("outbound.length", "0");
props.setProperty("inbound.lengthVariance", "0");
props.setProperty("outbound.lengthVariance", "0");
// generate new key if needed
if (keyStr.equals("new")) {
try {
key = PrivDestination.newKey();
} catch (I2PException e) {
e.printStackTrace();
return;
} catch (IOException e) {
e.printStackTrace();
return;
}
print("Creating new server dest...");
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
print("Getting server socket...");
serverSocket = socketManager.getServerSocket();
print("Server socket created, ready to run...");
dest = socketManager.getSession().getMyDestination();
print("private key follows:");
System.out.println(key.toBase64());
print("dest follows:");
System.out.println(dest.toBase64());
}
else {
key = PrivDestination.fromBase64String(keyStr);
String dest64Abbrev = key.toBase64().substring(0, 16);
print("Creating server socket manager on dest "+dest64Abbrev+"...");
socketManager = I2PSocketManagerFactory.createManager(key.getInputStream(), props);
serverSocket = socketManager.getServerSocket();
print("Server socket created, ready to run...");
}
print("Awaiting client connection...");
I2PSocket sessSocket;
try {
sessSocket = serverSocket.accept();
} catch (I2PException e) {
e.printStackTrace();
return;
} catch (ConnectException e) {
e.printStackTrace();
return;
}
print("Got connection from client");
chat(sessSocket);
}
public void runClient(String destStr)
throws DataFormatException, IOException
{
runClient(destStr, defaultHost, defaultPort);
}
/**
* runs I2PCat in client mode, connecting to a remote
* destination then copying data between the remote peer and
* the local terminal console
*/
public void runClient(String destStr, String host, int port)
throws DataFormatException, IOException
{
// accept 'file:' prefix
if (destStr.startsWith("file:", 0))
{
String path = destStr.substring(5);
destStr = new SimpleFile(path, "r").read();
}
else if (destStr.length() < 255) {
// attempt hosts file lookup
I2PAppContext ctx = new I2PAppContext();
HostsTxtNamingService h = new HostsTxtNamingService(ctx);
Destination dest1 = h.lookup(destStr);
if (dest1 == null) {
usage("Cannot resolve hostname: '"+destStr+"'");
}
// successful lookup
runClient(dest1, host, port);
}
else {
// otherwise, bigger strings are assumed to be base64 dests
Destination dest = new Destination();
dest.fromBase64(destStr);
runClient(dest, host, port);
}
}
public void runClient(Destination dest) {
runClient(dest, "127.0.0.1", 7654);
}
/**
* An alternative constructor which accepts an I2P Destination object
*/
public void runClient(Destination dest, String host, int port)
{
this.dest = dest;
String destAbbrev = dest.toBase64().substring(0, 16)+"...";
print("Connecting via i2cp "+host+":"+port+" to destination "+destAbbrev+"...");
System.out.flush();
try {
// get a socket manager
socketManager = I2PSocketManagerFactory.createManager(host, port);
// get a client socket
print("socketManager="+socketManager);
sessSocket = socketManager.connect(dest);
} catch (I2PException e) {
e.printStackTrace();
return;
} catch (ConnectException e) {
e.printStackTrace();
return;
} catch (NoRouteToHostException e) {
e.printStackTrace();
return;
} catch (InterruptedIOException e) {
e.printStackTrace();
return;
}
print("Successfully connected!");
print("(Press Control-C to quit)");
// Perform console interaction
chat(sessSocket);
try {
sessSocket.close();
} catch (IOException e) {
e.printStackTrace();
return;
}
}
/**
* Launch the background thread to copy incoming data to stdout, then
* loop in foreground copying lines from stdin and sending them to remote peer
*/
public void chat(I2PSocket sessSocket) {
try {
socketIn = sessSocket.getInputStream();
socketOutStream = sessSocket.getOutputStream();
socketOut = new OutputStreamWriter(socketOutStream);
// launch receiver thread
start();
//launchRx();
while (true) {
String line = DataHelper.readLine(System.in);
print("sent: '"+line+"'");
socketOut.write(line+"\n");
socketOut.flush();
}
} catch (IOException e) {
e.printStackTrace();
return;
}
}
/**
* executes in a thread, receiving incoming bytes on
* the socket input stream and spitting them to stdout
*/
public void run()
{
byte [] ch = new byte[1];
print("Receiver thread listening...");
try {
while (true) {
//String line = DataHelper.readLine(socketIn);
if (socketIn.read(ch) != 1) {
print("failed to receive from socket");
break;
}
//System.out.println(line);
System.out.write(ch, 0, 1);
System.out.flush();
}
} catch (IOException e) {
e.printStackTrace();
print("Receiver thread crashed, terminating!!");
System.exit(1);
}
}
public void launchRx() {
rxThread = new SockInput(socketIn);
rxThread.start();
}
static void print(String msg)
{
System.out.println("-=- I2PCat: "+msg);
if (_log != null) {
_log.debug(msg);
}
}
public static void usage(String msg)
{
usage(msg, 1);
}
public static void usage(String msg, int ret)
{
System.out.println(msg);
usage(ret);
}
public static void usage(int ret)
{
System.out.print(
"This utility is an I2P equivalent of the standard *nix 'netcat' utility\n"+
"usage:\n"+
" net.i2p.aum.I2PCat [-h]\n"+
" - display this help\n"+
" net.i2p.aum.I2PCat dest [host [port]]\n"+
" - run in client mode, 'dest' should be one of:\n"+
" hostname.i2p - an I2P hostname listed in hosts.txt\n"+
" (only works with a hosts.txt in current directory)\n"+
" base64dest - a full base64 destination string\n"+
" file:b64filename - filename of a file containing base64 dest\n"+
" net.i2p.aum.I2PCat -l privkey\n"+
" - run in server mode, 'key' should be one of:\n"+
" base64privkey - a full base64 private key string\n"+
" file:b64filename - filename of a file containing base64 privkey\n"+
"\n"
);
System.exit(ret);
}
public static void main(String [] args) throws IOException, DataFormatException
{
int argc = args.length;
// barf if no args
if (argc == 0) {
usage("Missing argument");
}
// show help on request
if (args[0].equals("-h") || args[0].equals("--help")) {
usage(0);
}
// server or client?
if (args[0].equals("-l")) {
if (argc != 2) {
usage("Bad argument count");
}
new I2PCat().runServer(args[1]);
}
else {
// client mode - barf if not 1-3 args
if (argc < 1 || argc > 3) {
usage("Bad argument count");
}
try {
int port = defaultPort;
String host = defaultHost;
if (args.length > 1) {
host = args[1];
if (args.length > 2) {
port = new Integer(args[2]).intValue();
}
}
new I2PCat().runClient(args[0], host, port);
} catch (DataFormatException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,25 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.util.*;
import net.i2p.data.*;
/**
* Class which wraps an I2PSocket object with convenient methods.
* Nothing presently implemented here.
*/
public class I2PSocketHelper
{
}

View File

@ -0,0 +1,146 @@
package net.i2p.aum;
import org.apache.xmlrpc.*;
import java.lang.*;
import java.io.*;
import java.util.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.util.*;
import net.i2p.data.*;
import net.i2p.i2ptunnel.I2PTunnelXMLWrapper;
/**
* Defines the I2P tunnel management methods which will be
* exposed to XML-RPC clients
* Methods in this class are forwarded to an I2PTunnelXMLWrapper object
*/
public class I2PTunnelXMLObject
{
protected I2PTunnelXMLWrapper tunmgr;
/**
* Builds the interface object. You normally shouldn't have to
* instantiate this directly - leave it to I2PTunnelXMLServer
*/
public I2PTunnelXMLObject()
{
tunmgr = new I2PTunnelXMLWrapper();
}
/**
* Generates an I2P keypair, returning a dict with keys 'result' (usually 'ok'),
* priv' (private key as base64) and 'dest' (destination as base64)
*/
public Hashtable genkeys()
{
return tunmgr.xmlrpcGenkeys();
}
/**
* Get a list of active TCP tunnels currently being managed by this
* tunnel manager.
* @return a dict with keys 'status' (usually 'ok'),
* 'jobs' (a list of dicts representing each job, each with keys 'job' (int, job
* number), 'type' (string, 'server' or 'client'), port' (int, the port number).
* Also for server, keys 'host' (hostname, string) and 'ip' (IP address, string).
* For clients, key 'dest' (string, remote destination as base64).
*/
public Hashtable list()
{
return tunmgr.xmlrpcList();
}
/**
* Attempts to find I2P hostname in hosts.txt.
* @param hostname string, I2P hostname
* @return dict with keys 'status' ('ok' or 'fail'),
* and if successful lookup, 'dest' (base64 destination).
*/
public Hashtable lookup(String hostname)
{
return tunmgr.xmlrpcLookup(hostname);
}
/**
* Attempt to open client tunnel
* @param port local port to listen on, int
* @param dest remote dest to tunnel to, base64 string
* @return dict with keys 'status' (string - 'ok' or 'fail').
* If 'ok', also key 'result' with text output from tunnelmgr
*/
public Hashtable client(int port, String dest)
{
return tunmgr.xmlrpcClient(port, dest);
}
/**
* Attempts to open server tunnel
* @param host TCP hostname of TCP server to tunnel to
* @param port number of TCP server
* @param key - base64 private key to receive I2P connections on
* @return dict with keys 'status' (string, 'ok' or 'fail').
* if 'fail', also a key 'error' with explanatory text.
*/
public Hashtable server(String host, int port, String key)
{
return tunmgr.xmlrpcServer(host, port, key);
}
/**
* Close an existing tunnel
* @param jobnum (int) job number of connection to close
* @return dict with keys 'status' (string, 'ok' or 'fail')
*/
public Hashtable close(int jobnum)
{
return tunmgr.xmlrpcClose(jobnum);
}
/**
* Close an existing tunnel
* @param jobnum (string) job number of connection to close as string,
* 'all' to close all jobs.
* @return dict with keys 'status' (string, 'ok' or 'fail')
*/
public Hashtable close(String job)
{
return tunmgr.xmlrpcClose(job);
}
/**
* Close zero or more tunnels matching given criteria
* @param criteria A dict containing zero or more of the keys:
* 'job' (job number), 'type' (string, 'server' or 'client'),
* 'host' (hostname), 'port' (port number),
* 'ip' (IP address), 'dest' (string, remote dest)
*/
public Hashtable close(Hashtable criteria)
{
return tunmgr.xmlrpcClose(criteria);
}
/**
* simple method to help with debugging your client prog
* @param x an int
* @return x + 1
*/
public int bar(int x)
{
System.out.println("foo invoked");
return x + 1;
}
/**
* as for bar(int), but returns zero if no arg given
*/
public int bar()
{
return bar(0);
}
}

View File

@ -0,0 +1,71 @@
package net.i2p.aum;
import org.apache.xmlrpc.*;
import java.lang.*;
import java.io.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.util.*;
import net.i2p.data.*;
/**
* Provides a means for programs in any language to dynamically manage
* their own I2P <-> TCP tunnels, via simple TCP XML-RPC function calls.
* This server is presently hardwired to listen on port 22322.
*/
public class I2PTunnelXMLServer
{
protected WebServer ws;
protected I2PTunnelXMLObject tunobj;
public int port = 22322;
// constructor
public void _init()
{
ws = new WebServer(port);
tunobj = new I2PTunnelXMLObject();
ws.addHandler("i2p.tunnel", tunobj);
}
// default constructor
public I2PTunnelXMLServer()
{
super();
_init();
}
// constructor which takes shell args
public I2PTunnelXMLServer(String args[])
{
super();
_init();
}
// run the server
public void run()
{
ws.start();
System.out.println("I2PTunnel XML-RPC server listening on port "+port);
ws.run();
}
public static void main(String args[])
{
I2PTunnelXMLServer tun;
tun = new I2PTunnelXMLServer();
tun.run();
}
}

View File

@ -0,0 +1,71 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import org.apache.xmlrpc.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.Base64;
import net.i2p.util.*;
import net.i2p.data.*;
/**
* an object which is used to invoke methods on remote I2P XML-RPC
* servers. You should not instantiate these objects directly, but
* create them through
* {@link net.i2p.aum.I2PXmlRpcClientFactory#newClient(Destination) I2PXmlRpcClientFactory.newClient()}
* Note that this is really just a thin wrapper around XmlRpcClient, mostly for reasons
* of consistency with I2PXmlRpcServer[Factory].
*/
public class I2PXmlRpcClient extends XmlRpcClient
{
public static boolean debug = false;
protected static Log _log;
/**
* Construct an I2P XML-RPC client with this URL.
* Note that you should not
* use this constructor directly - use I2PXmlRpcClientFactory.newClient() instead
*/
public I2PXmlRpcClient(URL url)
{
super(url);
_log = new Log("I2PXmlRpcClient");
}
/**
* Construct a XML-RPC client for the URL represented by this String.
* Note that you should not
* use this constructor directly - use I2PXmlRpcClientFactory.newClient() instead
*/
public I2PXmlRpcClient(String url) throws MalformedURLException
{
super(url);
_log = new Log("I2PXmlRpcClientFactory");
}
/**
* Construct a XML-RPC client for the specified hostname and port.
* Note that you should not
* use this constructor directly - use I2PXmlRpcClientFactory.newClient() instead
*/
public I2PXmlRpcClient(String hostname, int port) throws MalformedURLException
{
super(hostname, port);
_log = new Log("I2PXmlRpcClient");
}
}

View File

@ -0,0 +1,229 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import org.apache.xmlrpc.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.Base64;
import net.i2p.util.*;
import net.i2p.data.*;
/**
* Creates I2P XML-RPC client objects, which you can use
* to issue XML-RPC function calls over I2P.
* Instantiating this class causes the vm-wide http proxy system
* properties to be set to the address of the I2P eepProxy host/port.
* I2PXmlRpcClient objects need to communicate with the I2P
* eepProxy. If your eepProxy is at the standard localhost:4444 address,
* you can use the default constructor. Otherwise, you can set this
* eepProxy address by either (1) passing eepProxy hostname/port to the
* constructor, or (2) running the jvm with 'eepproxy.tcp.host' and
* 'eepproxy.tcp.port' system properties set. Note that (1) takes precedence.
* Failure to set up EepProxy host/port correctly will result in an IOException
* when you invoke .execute() on your client objects.
* Invoke this class from your shell to see a demo
*/
public class I2PXmlRpcClientFactory
{
public static boolean debug = false;
public static String _defaultEepHost = "127.0.0.1";
public static int _defaultEepPort = 4444;
protected static Log _log;
/**
* Create an I2P XML-RPC client factory, and set it to create
* clients of a given class.
* @param clientClass a class to use when creating new clients
*/
public I2PXmlRpcClientFactory()
{
this(null, 0);
}
/**
* Create an I2P XML-RPC client factory, and set it to create
* clients of a given class, and dispatch calls through a non-standard
* eepProxy.
* @param eepHost the eepProxy TCP hostname
* @param eepPort the eepProxy TCP port number
*/
public I2PXmlRpcClientFactory(String eepHost, int eepPort)
{
String eepPortStr;
_log = new Log("I2PXmlRpcClientFactory");
_log.shouldLog(Log.DEBUG);
Properties p = System.getProperties();
// determine what actual eepproxy host/port we're using
if (eepHost == null) {
eepHost = p.getProperty("eepproxy.tcp.host", _defaultEepHost);
}
if (eepPort > 0) {
eepPortStr = String.valueOf(eepPort);
}
else {
eepPortStr = p.getProperty("eepproxy.tcp.port");
if (eepPortStr == null) {
eepPortStr = String.valueOf(_defaultEepPort);
}
}
p.put("proxySet", "true");
p.put("http.proxyHost", eepHost);
p.put("http.proxyPort", eepPortStr);
}
/**
* Create an I2P XML-RPC client object, which is subsequently used for
* dispatching XML-RPC requests.
* @param dest - an I2P destination object, comprising the
* destination of the remote
* I2P XML-RPC server.
* @return a new XmlRpcClient object (refer org.apache.xmlrpc.XmlRpcClient).
*/
public I2PXmlRpcClient newClient(Destination dest) throws MalformedURLException {
return newClient(new URL("http", "i2p/"+dest.toBase64(), "/"));
}
/**
* Create an I2P XML-RPC client object, which is subsequently used for
* dispatching XML-RPC requests.
* @param hostOrDest - an I2P hostname (listed in hosts.txt) or a
* destination base64 string, for the remote I2P XML-RPC server
* @return a new XmlRpcClient object (refer org.apache.xmlrpc.XmlRpcClient).
*/
public I2PXmlRpcClient newClient(String hostOrDest)
throws DataFormatException, MalformedURLException
{
String hostname;
URL u;
try {
// try to make a dest out of the string
Destination dest = new Destination();
dest.fromBase64(hostOrDest);
// converted ok, treat as valid dest, form i2p/blahblah url from it
I2PXmlRpcClient client = newClient(new URL("http", "i2p/"+hostOrDest, "/"));
client.debug = debug;
return client;
} catch (DataFormatException e) {
if (debug) {
e.printStackTrace();
print("hostOrDest length="+hostOrDest.length());
}
// failed to load up a dest, test length
if (hostOrDest.length() < 255) {
// short-ish, assume a hostname
u = new URL("http", hostOrDest, "/");
I2PXmlRpcClient client = newClient(u);
client.debug = debug;
return client;
}
else {
// too long for a host, barf
throw new DataFormatException("Bad I2P hostname/dest:\n"+hostOrDest);
}
}
}
/**
* Create an I2P XML-RPC client object, which is subsequently used for
* dispatching XML-RPC requests. This method is not recommended.
* @param u - a URL object, containing the URL of the remote
* I2P XML-RPC server, for example, "http://xmlrpc.aum.i2p" (assuming
* there's a hosts.txt entry for 'xmlrpc.aum.i2p'), or
* "http://i2p/base64destblahblah...". Note that if you use this method
* directly, the created XML-RPC client object will ONLY work if you
* instantiate the URL object as 'new URL("http", "i2p/"+host-or-dest, "/")'.
*/
protected I2PXmlRpcClient newClient(URL u)
{
Object [] args = { u };
//return new I2PXmlRpcClient(u);
// construct and return a client object of required class
return new I2PXmlRpcClient(u);
}
/**
* Runs a demo of an I2P XML-RPC client. Assumes you have already
* launched an I2PXmlRpcServerFactory demo, because it gets its
* dest from the file 'demo.dest64' created by I2PXmlRpcServerFactory demo.
*
* Ensure you have first launched net.i2p.aum.I2PXmlRpcServerFactory
* from your command line.
*/
public static void main(String [] args) {
String destStr;
debug = true;
try {
print("Creating client factory...");
I2PXmlRpcClientFactory f = new I2PXmlRpcClientFactory();
print("Creating new client...");
if (args.length == 0) {
print("Reading dest from demo.dest64");
destStr = new SimpleFile("demo.dest64", "r").read();
}
else {
destStr = args[0];
}
XmlRpcClient c = f.newClient(destStr);
print("Invoking foo...");
Vector v = new Vector();
v.add("one");
v.add("two");
Object res = c.execute("foo.bar", v);
print("Got back object: " + res);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Used for internal debugging
*/
protected static void print(String msg)
{
if (debug) {
System.out.println("I2PXmlRpcClient: " + msg);
if (_log != null) {
System.out.println("LOGGING SOME SHIT");
_log.debug(msg);
}
}
}
}

View File

@ -0,0 +1,34 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import org.apache.xmlrpc.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.Base64;
import net.i2p.util.*;
import net.i2p.data.*;
/**
* A simple class providing callable xmlrpc server methods, gets linked in to
* the server demo.
*/
public class I2PXmlRpcDemoClass
{
public int add1(int n) {
return n + 1;
}
public String bar(String arg1, String arg2) {
System.out.println("Demo: got hit to bar: arg1='"+arg1+"', arg2='"+arg2+"'");
return "I2P demo xmlrpc server(foo.bar): arg1='"+arg1+"', arg2='"+arg2+"'";
}
}

View File

@ -0,0 +1,428 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import org.apache.xmlrpc.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.Base64;
import net.i2p.util.*;
import net.i2p.data.*;
/**
* An XML-RPC server which works completely within I2P, listening
* on a dest for requests.
* You should not instantiate this class directly, but instead create
* an I2PXmlRpcServerFactory object, and use its .newServer() method
* to create a server object.
*/
public class I2PXmlRpcServer extends XmlRpcServer implements Runnable
{
public class I2PXmlRpcServerWorkerThread extends Thread {
I2PSocket _sock;
public I2PXmlRpcServerWorkerThread(I2PSocket sock) {
_sock = sock;
}
public void run() {
try {
System.out.println("I2PXmlRpcServer.run: got inbound XML-RPC I2P conn");
log.info("run: Got client connection, creating streams");
InputStream socketIn = _sock.getInputStream();
OutputStreamWriter socketOut = new OutputStreamWriter(_sock.getOutputStream());
log.info("run: reading http headers");
// read headers, determine size of req
int size = readHttpHeaders(socketIn);
if (size <= 0) {
// bad news
log.info("read req failed, terminating session");
_sock.close();
return;
}
log.info("run: reading request body of "+size+" bytes");
// get raw request body
byte [] reqBody = new byte[size];
for (int i=0; i<size; i++) {
int b = socketIn.read();
reqBody[i] = (byte)b;
}
//socketIn.read(reqBody);
//log.info("reqBody='" + (new String(reqBody)) + "'");
//System.out.println("reqBody='" + (new String(reqBody)) + "'");
//System.out.println("reqBody:");
//for (int ii=0; ii<reqBody.length; ii++) {
// System.out.println("i=" + ii + " ch="+reqBody[ii]);
//}
ByteArrayInputStream reqBodyStream = new ByteArrayInputStream(reqBody);
log.info("run: executing request");
System.out.println("run: executing request");
// read and execute full request
byte [] result;
try {
result = execute(reqBodyStream);
} catch (Exception e) {
System.out.println("run: execute failed, closing socket");
_sock.close();
System.out.println("run: closed socket");
throw e;
}
log.info("run: sending response");
// fudge - manual header and response generation
socketOut.write(
"HTTP/1.0 200 OK\r\n" +
"Server: I2P XML-RPC server by aum\r\n" +
"Date: " + (new Date().toString()) + "\r\n" +
"Content-type: text/xml\r\n" +
"Content-length: " + String.valueOf(result.length) + "\r\n" +
"\r\n");
socketOut.write(new String(result));
//socketOut.write(result);
socketOut.flush();
log.info("closing socket");
System.out.println("closing socket");
//response.setContentType ("text/xml");
//response.setContentLength (result.length());
//OutputStream out = response.getOutputStream();
//out.write (result);
//out.flush ();
_sock.close();
log.info("session complete");
} catch (Exception e) {
try {
e.printStackTrace();
_sock.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
// convenience - dest this server is listening on
public Destination dest;
// server's socket manager object
public I2PSocketManager socketMgr;
// server's socket
public I2PServerSocket serverSocket;
/** socket of latest incoming connection */
public I2PSocket sessSocket;
// set to enable debugging msgs
public static boolean debug = false;
// stream-proented xmlrpc server
protected net.i2p.util.Log log;
protected I2PAppContext i2p;
public Thread serverThread;
/**
* (do not use this constructor directly)
*/
public I2PXmlRpcServer(String keyStr, Properties props, I2PAppContext i2p)
throws DataFormatException, I2PException, IOException
{
this(PrivDestination.fromBase64String(keyStr), props, i2p);
}
/**
* (do not use this constructor directly)
*/
public I2PXmlRpcServer(PrivDestination privKey, Properties props, I2PAppContext i2p)
throws DataFormatException, I2PException
{
super();
log = i2p.logManager().getLog(this.getClass());
log.info("creating socket manager for key dest "
+ privKey.getDestinationBase64().substring(0, 16)
+ "...");
// start by getting a socket manager
socketMgr = I2PSocketManagerFactory.createManager(privKey.getInputStream(), props);
if (socketMgr == null) {
throw new I2PException("Failed to create socketManager, maybe can't reach i2cp port");
}
log.info("getting server socket, socketMgr="+socketMgr);
// get a server socket
serverSocket = socketMgr.getServerSocket();
log.info("got server socket, ready to run");
dest = socketMgr.getSession().getMyDestination();
log.info("full dest="+dest.toBase64());
System.out.println("full dest="+dest.toBase64());
}
/**
* Run this server within the current thread of execution.
* This function never returns. If you want to run the server
* in a background thread, use the .start() method instead.
*/
public void run()
{
log.info("run: listening for inbound XML-RPC requests...");
while (true)
{
System.out.println("I2PXmlRpcServer.run: waiting for inbound XML-RPC I2P conn...");
try {
sessSocket = serverSocket.accept();
I2PXmlRpcServerWorkerThread sessThread = new I2PXmlRpcServerWorkerThread(sessSocket);
sessThread.start();
/**
System.out.println("I2PXmlRpcServer.run: got inbound XML-RPC I2P conn");
log.info("run: Got client connection, creating streams");
InputStream socketIn = sessSocket.getInputStream();
OutputStreamWriter socketOut = new OutputStreamWriter(sessSocket.getOutputStream());
log.info("run: reading http headers");
// read headers, determine size of req
int size = readHttpHeaders(socketIn);
if (size <= 0) {
// bad news
log.info("read req failed, terminating session");
sessSocket.close();
continue;
}
log.info("run: reading request body of "+size+" bytes");
// get raw request body
byte [] reqBody = new byte[size];
for (int i=0; i<size; i++) {
int b = socketIn.read();
reqBody[i] = (byte)b;
}
//socketIn.read(reqBody);
//log.info("reqBody='" + (new String(reqBody)) + "'");
//System.out.println("reqBody='" + (new String(reqBody)) + "'");
//System.out.println("reqBody:");
//for (int ii=0; ii<reqBody.length; ii++) {
// System.out.println("i=" + ii + " ch="+reqBody[ii]);
//}
ByteArrayInputStream reqBodyStream = new ByteArrayInputStream(reqBody);
log.info("run: executing request");
System.out.println("run: executing request");
// read and execute full request
byte [] result;
try {
result = execute(reqBodyStream);
} catch (Exception e) {
System.out.println("run: execute failed, closing socket");
sessSocket.close();
System.out.println("run: closed socket");
throw e;
}
log.info("run: sending response");
// fudge - manual header and response generation
socketOut.write(
"HTTP/1.0 200 OK\r\n" +
"Server: I2P XML-RPC server by aum\r\n" +
"Date: " + (new Date().toString()) + "\r\n" +
"Content-type: text/xml\r\n" +
"Content-length: " + String.valueOf(result.length) + "\r\n" +
"\r\n");
socketOut.write(new String(result));
socketOut.flush();
log.info("closing socket");
System.out.println("closing socket");
//response.setContentType ("text/xml");
//response.setContentLength (result.length());
//OutputStream out = response.getOutputStream();
//out.write (result);
//out.flush ();
sessSocket.close();
log.info("session complete");
**/
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Called as part of an incoming XML-RPC request,
* reads and parses http headers from input stream.
* @param in the InputStream of the socket connection from the
* currently connected client
* @return value of 'Content-Length' field, as the number of bytes
* expected in the body of the request, or -1 if the request headers
* are invalid
* @throws IOException
*/
protected int readHttpHeaders(InputStream in) throws IOException
{
int contentLength = -1;
while (true) {
// read/parse one line
String line = readline(in);
String [] flds = line.split(":\\s+", 2);
log.debug("after split: flds='"+flds+"'");
String hdrKey = flds[0];
if (flds.length == 1) {
// not an HTTP header
log.info("skipping non-header, hdrKey='"+hdrKey+"'");
continue;
}
System.out.println("I2PXmlRpcServer: '"+flds[0]+"'='"+flds[1]+"'");
String hdrVal = flds[1];
log.info("hdrKey='"+hdrKey+"', hdrVal='"+hdrVal+"'");
if (hdrKey.equals("Content-Type")) {
if (!hdrVal.equals("text/xml")) {
// barf - not text/xml content type
return -1;
}
}
if (hdrKey.equals("Content-Length")) {
// got our length now - done with headers
contentLength = new Integer(hdrVal).intValue();
break;
}
}
log.info("Got content-length, now read remaining headers");
// read and discard any remaining headers
while (true) {
String line = readline(in);
int lineLen = line.length();
log.info("line("+lineLen+")='"+line+"'");
System.out.println("Disccarding superflous header: '"+line+"'");
if (lineLen == 0) {
break;
}
}
log.info("Content length is "+contentLength);
return contentLength;
}
/**
* Called as part of an incoming XML-RPC request,
* reads and parses http headers from input stream.
* @param in the InputStream of the socket connection from the
* currently connected client
* @return the line read, as a string
* @throws IOException
*/
protected String readline(InputStream in) throws IOException
{
ByteArrayOutputStream os = new ByteArrayOutputStream();
while (true) {
int ch = in.read();
switch (ch) {
case '\n':
case -1:
String s = os.toString();
log.debug("Got line '"+s+"'");
return os.toString();
case '\r':
break;
default:
os.write(ch);
}
}
}
/**
* Launches the server as a background thread.
* (To run within the calling thread, use the .run() method instead).
*/
public void start()
{
log.debug("Starting server as a thread");
serverThread = new Thread(this);
serverThread.start();
}
/**
public void stop()
{
if (serverThread != null) {
serverThread.stop();
}
}
**/
}

View File

@ -0,0 +1,162 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import org.apache.xmlrpc.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.Base64;
import net.i2p.util.*;
import net.i2p.data.*;
/**
* Generates I2P-compatible XML-RPC server objects
* (of class I2PXmlRpcServer). If you instead want to create
* instances of your own
*
* Invoke this class from your shell to see a demo
* @author aum
*/
public class I2PXmlRpcServerFactory
{
public I2PSocketManager socketManager;
public Properties props;
public static int defaultTunnelLength = 2;
// set to enable debugging msgs
public static boolean debug = false;
public static Log log;
protected I2PAppContext i2p;
// hostname/port of I2P router we're using
//public static String i2cpHost = "127.0.0.1";
//public static int i2cpPort = 7654;
/**
* Create an I2P XML-RPC server factory using default
* tunnel settings
*/
public I2PXmlRpcServerFactory(I2PAppContext i2p)
{
// get a socket manager
this(defaultTunnelLength, defaultTunnelLength,
defaultTunnelLength, defaultTunnelLength, i2p);
}
/**
* Create an I2P XML-RPC server factory, using settings provided
* by arguments
* @param lengthIn The value of 'inbound.length' property
* @param lengthOut The value of 'outbound.length' property
* @param lengthVarianceIn Value of 'inbound.lengthVariance' property
* @param lengthVarianceOut Value of 'outbound.lengthVariance' property
* @param log an I2P logger
*/
public I2PXmlRpcServerFactory(int lengthIn, int lengthOut,
int lengthVarianceIn, int lengthVarianceOut,
I2PAppContext i2p)
{
this.i2p = i2p;
log = i2p.logManager().getLog(this.getClass());
// set up tunnel properties for server objects
props = new Properties();
props.setProperty("inbound.length", String.valueOf(lengthIn));
props.setProperty("outbound.length", String.valueOf(lengthOut));
props.setProperty("inbound.lengthVariance", String.valueOf(lengthVarianceIn));
props.setProperty("outbound.lengthVariance", String.valueOf(lengthVarianceIn));
}
/**
* Creates a new I2PXmlRpcServer listening on a new randomly created destination
* @return a new I2PXmlRpcServer object, whose '.addHandler()' method you should
* invoke to add a handler object.
* @throws I2PException, IOException, DataFormatException
*/
public I2PXmlRpcServer newServer() throws I2PException, IOException, DataFormatException
{
return newServer(PrivDestination.newKey());
}
/**
* Creates a new I2PXmlRpcServer listening on a given dest, from key provided
* as a base64 string
* @param keyStr base64 representation of full private key for the destination
* the server is to listen on
* @return a new I2PXmlRpcServer object
* @throws DataFormatException
*/
public I2PXmlRpcServer newServer(String keyStr)
throws DataFormatException, I2PException, IOException
{
return newServer(PrivDestination.fromBase64String(keyStr));
}
/**
* Creates a new I2PXmlRpcServer listening on a given dest, from key provided
* as a PrivDestination object
* @param key a PrivDestination object representing the private destination
* the server is to listen on
* @return a new I2PXmlRpcServer object
* @throws DataFormatException
*/
public I2PXmlRpcServer newServer(PrivDestination key) throws DataFormatException, I2PException
{
// main newServer
I2PXmlRpcServer server = new I2PXmlRpcServer(key, props, i2p);
server.debug = debug;
return server;
}
/**
* Demonstration of I2P XML-RPC server.
* Creates a server listening on a random destination, and writes the base64
* destination into a file called "demo.dest64".
*
* After launching this program from a command shell, you should
* launch I2PXmlRpcClientFactory from another command shell
* to execute the client side of the demo.
*
* This program accepts no arguments.
*/
public static void main(String [] args)
{
debug = true;
I2PXmlRpcServer.debug = true;
I2PAppContext i2p = new I2PAppContext();
I2PXmlRpcServerFactory f = new I2PXmlRpcServerFactory(0,0,0,0, i2p);
try {
f.log.info("Creating new server on a new key");
I2PXmlRpcServer s = f.newServer();
f.log.info("Creating and adding handler object");
I2PXmlRpcDemoClass demo = new I2PXmlRpcDemoClass();
s.addHandler("foo", demo);
f.log.info("Saving dest for this server in file 'demo.dest64'");
new SimpleFile("demo.dest64", "rws").write(s.dest.toBase64());
f.log.info("Running server (Press Ctrl-C to kill)");
s.run();
} catch (Exception e) { e.printStackTrace(); }
}
}

View File

@ -0,0 +1,392 @@
package net.i2p.aum;
/**
* creates a convenient map of file extensions <-> mimetypes
*/
public class Mimetypes
{
public static String [][] _map = {
{ ".bz2", "application/x-bzip2" },
{ ".csm", "application/cu-seeme" },
{ ".cu", "application/cu-seeme" },
{ ".tsp", "application/dsptype" },
{ ".xls", "application/excel" },
{ ".spl", "application/futuresplash" },
{ ".hqx", "application/mac-binhex40" },
{ ".doc", "application/msword" },
{ ".dot", "application/msword" },
{ ".bin", "application/octet-stream" },
{ ".oda", "application/oda" },
{ ".pdf", "application/pdf" },
{ ".asc", "application/pgp-keys" },
{ ".pgp", "application/pgp-signature" },
{ ".ps", "application/postscript" },
{ ".ai", "application/postscript" },
{ ".eps", "application/postscript" },
{ ".ppt", "application/powerpoint" },
{ ".rtf", "application/rtf" },
{ ".wp5", "application/wordperfect5.1" },
{ ".zip", "application/zip" },
{ ".wk", "application/x-123" },
{ ".bcpio", "application/x-bcpio" },
{ ".pgn", "application/x-chess-pgn" },
{ ".cpio", "application/x-cpio" },
{ ".deb", "application/x-debian-package" },
{ ".dcr", "application/x-director" },
{ ".dir", "application/x-director" },
{ ".dxr", "application/x-director" },
{ ".dvi", "application/x-dvi" },
{ ".pfa", "application/x-font" },
{ ".pfb", "application/x-font" },
{ ".gsf", "application/x-font" },
{ ".pcf", "application/x-font" },
{ ".pcf.Z", "application/x-font" },
{ ".gtar", "application/x-gtar" },
{ ".tgz", "application/x-gtar" },
{ ".hdf", "application/x-hdf" },
{ ".phtml", "application/x-httpd-php" },
{ ".pht", "application/x-httpd-php" },
{ ".php", "application/x-httpd-php" },
{ ".php3", "application/x-httpd-php3" },
{ ".phps", "application/x-httpd-php3-source" },
{ ".php3p", "application/x-httpd-php3-preprocessed" },
{ ".class", "application/x-java" },
{ ".latex", "application/x-latex" },
{ ".frm", "application/x-maker" },
{ ".maker", "application/x-maker" },
{ ".frame", "application/x-maker" },
{ ".fm", "application/x-maker" },
{ ".fb", "application/x-maker" },
{ ".book", "application/x-maker" },
{ ".fbdoc", "application/x-maker" },
{ ".mif", "application/x-mif" },
{ ".nc", "application/x-netcdf" },
{ ".cdf", "application/x-netcdf" },
{ ".pac", "application/x-ns-proxy-autoconfig" },
{ ".o", "application/x-object" },
{ ".pl", "application/x-perl" },
{ ".pm", "application/x-perl" },
{ ".shar", "application/x-shar" },
{ ".swf", "application/x-shockwave-flash" },
{ ".swfl", "application/x-shockwave-flash" },
{ ".sit", "application/x-stuffit" },
{ ".sv4cpio", "application/x-sv4cpio" },
{ ".sv4crc", "application/x-sv4crc" },
{ ".tar", "application/x-tar" },
{ ".gf", "application/x-tex-gf" },
{ ".pk", "application/x-tex-pk" },
{ ".PK", "application/x-tex-pk" },
{ ".texinfo", "application/x-texinfo" },
{ ".texi", "application/x-texinfo" },
{ ".~", "application/x-trash" },
{ ".%", "application/x-trash" },
{ ".bak", "application/x-trash" },
{ ".old", "application/x-trash" },
{ ".sik", "application/x-trash" },
{ ".t", "application/x-troff" },
{ ".tr", "application/x-troff" },
{ ".roff", "application/x-troff" },
{ ".man", "application/x-troff-man" },
{ ".me", "application/x-troff-me" },
{ ".ms", "application/x-troff-ms" },
{ ".ustar", "application/x-ustar" },
{ ".src", "application/x-wais-source" },
{ ".wz", "application/x-wingz" },
{ ".au", "audio/basic" },
{ ".snd", "audio/basic" },
{ ".mid", "audio/midi" },
{ ".midi", "audio/midi" },
{ ".mpga", "audio/mpeg" },
{ ".mpega", "audio/mpeg" },
{ ".mp2", "audio/mpeg" },
{ ".mp3", "audio/mpeg" },
{ ".m3u", "audio/mpegurl" },
{ ".aif", "audio/x-aiff" },
{ ".aiff", "audio/x-aiff" },
{ ".aifc", "audio/x-aiff" },
{ ".gsm", "audio/x-gsm" },
{ ".ra", "audio/x-pn-realaudio" },
{ ".rm", "audio/x-pn-realaudio" },
{ ".ram", "audio/x-pn-realaudio" },
{ ".rpm", "audio/x-pn-realaudio-plugin" },
{ ".wav", "audio/x-wav" },
{ ".gif", "image/gif" },
{ ".ief", "image/ief" },
{ ".jpeg", "image/jpeg" },
{ ".jpg", "image/jpeg" },
{ ".jpe", "image/jpeg" },
{ ".png", "image/png" },
{ ".tiff", "image/tiff" },
{ ".tif", "image/tiff" },
{ ".ras", "image/x-cmu-raster" },
{ ".bmp", "image/x-ms-bmp" },
{ ".pnm", "image/x-portable-anymap" },
{ ".pbm", "image/x-portable-bitmap" },
{ ".pgm", "image/x-portable-graymap" },
{ ".ppm", "image/x-portable-pixmap" },
{ ".rgb", "image/x-rgb" },
{ ".xbm", "image/x-xbitmap" },
{ ".xpm", "image/x-xpixmap" },
{ ".xwd", "image/x-xwindowdump" },
{ ".csv", "text/comma-separated-values" },
{ ".html", "text/html" },
{ ".htm", "text/html" },
{ ".mml", "text/mathml" },
{ ".txt", "text/plain" },
{ ".rtx", "text/richtext" },
{ ".tsv", "text/tab-separated-values" },
{ ".h++", "text/x-c++hdr" },
{ ".hpp", "text/x-c++hdr" },
{ ".hxx", "text/x-c++hdr" },
{ ".hh", "text/x-c++hdr" },
{ ".c++", "text/x-c++src" },
{ ".cpp", "text/x-c++src" },
{ ".cxx", "text/x-c++src" },
{ ".cc", "text/x-c++src" },
{ ".h", "text/x-chdr" },
{ ".csh", "text/x-csh" },
{ ".c", "text/x-csrc" },
{ ".java", "text/x-java" },
{ ".moc", "text/x-moc" },
{ ".p", "text/x-pascal" },
{ ".pas", "text/x-pascal" },
{ ".etx", "text/x-setext" },
{ ".sh", "text/x-sh" },
{ ".tcl", "text/x-tcl" },
{ ".tk", "text/x-tcl" },
{ ".tex", "text/x-tex" },
{ ".ltx", "text/x-tex" },
{ ".sty", "text/x-tex" },
{ ".cls", "text/x-tex" },
{ ".vcs", "text/x-vCalendar" },
{ ".vcf", "text/x-vCard" },
{ ".dl", "video/dl" },
{ ".fli", "video/fli" },
{ ".gl", "video/gl" },
{ ".mpeg", "video/mpeg" },
{ ".mpg", "video/mpeg" },
{ ".mpe", "video/mpeg" },
{ ".qt", "video/quicktime" },
{ ".mov", "video/quicktime" },
{ ".asf", "video/x-ms-asf" },
{ ".asx", "video/x-ms-asf" },
{ ".avi", "video/x-msvideo" },
{ ".movie", "video/x-sgi-movie" },
{ ".vrm", "x-world/x-vrml" },
{ ".vrml", "x-world/x-vrml" },
{ ".wrl", "x-world/x-vrml" },
};
/**
* Attempts to determine a mimetype
* @param path - either a file extension string (containing the
* leading '.') or a full file pathname (in which case, the extension
* will be extracted).
* @return the mimetype that corresponds to the file extension, if the
* file extension is known, or "application/octet-stream" if the
* file extension is not known.
*/
public static String guessType(String path) {
// rip the file extension from the path
// first - split 'directories', and get last part
String [] dirs = path.split("/");
String filename = dirs[dirs.length-1];
String [] bits = filename.split("\\.");
String extension = "." + bits[bits.length-1];
// default mimetype applied to unknown file extensions
String type = "application/octet-stream";
for (int i=0; i<_map.length; i++) {
String [] rec = _map[i];
if (rec[0].equals(extension)) {
type = rec[1];
break;
}
}
return type;
}
/**
* Attempts to guess the file extension corresponding to a given
* mimetype.
* @param type a mimetype string
* @return a file extension commonly used for storing files of this type,
* or defaults to ".bin" if mimetype not known
*/
public static String guessExtension(String type) {
// default extension applied to unknown mimetype
String extension = ".bin";
for (int i=0; i<_map.length; i++) {
String [] rec = _map[i];
if (rec[1].equals(type)) {
extension = rec[0];
break;
}
}
return extension;
}
}
/**
suffix_map = {
'.tgz': '.tar.gz',
'.taz': '.tar.gz',
'.tz': '.tar.gz',
}
encodings_map = {
'.gz': 'gzip',
'.Z': 'compress',
}
# Before adding new types, make sure they are either registered with IANA, at
# http://www.isi.edu/in-notes/iana/assignments/media-types
# or extensions, i.e. using the x- prefix
# If you add to these, please keep them sorted!
types_map = {
'.a' : 'application/octet-stream',
'.ai' : 'application/postscript',
'.aif' : 'audio/x-aiff',
'.aifc' : 'audio/x-aiff',
'.aiff' : 'audio/x-aiff',
'.au' : 'audio/basic',
'.avi' : 'video/x-msvideo',
'.bat' : 'text/plain',
'.bcpio' : 'application/x-bcpio',
'.bin' : 'application/octet-stream',
'.bmp' : 'image/x-ms-bmp',
'.c' : 'text/plain',
# Duplicates :(
'.cdf' : 'application/x-cdf',
'.cdf' : 'application/x-netcdf',
'.cpio' : 'application/x-cpio',
'.csh' : 'application/x-csh',
'.css' : 'text/css',
'.dll' : 'application/octet-stream',
'.doc' : 'application/msword',
'.dot' : 'application/msword',
'.dvi' : 'application/x-dvi',
'.eml' : 'message/rfc822',
'.eps' : 'application/postscript',
'.etx' : 'text/x-setext',
'.exe' : 'application/octet-stream',
'.gif' : 'image/gif',
'.gtar' : 'application/x-gtar',
'.h' : 'text/plain',
'.hdf' : 'application/x-hdf',
'.htm' : 'text/html',
'.html' : 'text/html',
'.ief' : 'image/ief',
'.jpe' : 'image/jpeg',
'.jpeg' : 'image/jpeg',
'.jpg' : 'image/jpeg',
'.js' : 'application/x-javascript',
'.ksh' : 'text/plain',
'.latex' : 'application/x-latex',
'.m1v' : 'video/mpeg',
'.man' : 'application/x-troff-man',
'.me' : 'application/x-troff-me',
'.mht' : 'message/rfc822',
'.mhtml' : 'message/rfc822',
'.mif' : 'application/x-mif',
'.mov' : 'video/quicktime',
'.movie' : 'video/x-sgi-movie',
'.mp2' : 'audio/mpeg',
'.mp3' : 'audio/mpeg',
'.mpa' : 'video/mpeg',
'.mpe' : 'video/mpeg',
'.mpeg' : 'video/mpeg',
'.mpg' : 'video/mpeg',
'.ms' : 'application/x-troff-ms',
'.nc' : 'application/x-netcdf',
'.nws' : 'message/rfc822',
'.o' : 'application/octet-stream',
'.obj' : 'application/octet-stream',
'.oda' : 'application/oda',
'.p12' : 'application/x-pkcs12',
'.p7c' : 'application/pkcs7-mime',
'.pbm' : 'image/x-portable-bitmap',
'.pdf' : 'application/pdf',
'.pfx' : 'application/x-pkcs12',
'.pgm' : 'image/x-portable-graymap',
'.pl' : 'text/plain',
'.png' : 'image/png',
'.pnm' : 'image/x-portable-anymap',
'.pot' : 'application/vnd.ms-powerpoint',
'.ppa' : 'application/vnd.ms-powerpoint',
'.ppm' : 'image/x-portable-pixmap',
'.pps' : 'application/vnd.ms-powerpoint',
'.ppt' : 'application/vnd.ms-powerpoint',
'.ps' : 'application/postscript',
'.pwz' : 'application/vnd.ms-powerpoint',
'.py' : 'text/x-python',
'.pyc' : 'application/x-python-code',
'.pyo' : 'application/x-python-code',
'.qt' : 'video/quicktime',
'.ra' : 'audio/x-pn-realaudio',
'.ram' : 'application/x-pn-realaudio',
'.ras' : 'image/x-cmu-raster',
'.rdf' : 'application/xml',
'.rgb' : 'image/x-rgb',
'.roff' : 'application/x-troff',
'.rtx' : 'text/richtext',
'.sgm' : 'text/x-sgml',
'.sgml' : 'text/x-sgml',
'.sh' : 'application/x-sh',
'.shar' : 'application/x-shar',
'.snd' : 'audio/basic',
'.so' : 'application/octet-stream',
'.src' : 'application/x-wais-source',
'.sv4cpio': 'application/x-sv4cpio',
'.sv4crc' : 'application/x-sv4crc',
'.swf' : 'application/x-shockwave-flash',
'.t' : 'application/x-troff',
'.tar' : 'application/x-tar',
'.tcl' : 'application/x-tcl',
'.tex' : 'application/x-tex',
'.texi' : 'application/x-texinfo',
'.texinfo': 'application/x-texinfo',
'.tif' : 'image/tiff',
'.tiff' : 'image/tiff',
'.tr' : 'application/x-troff',
'.tsv' : 'text/tab-separated-values',
'.txt' : 'text/plain',
'.ustar' : 'application/x-ustar',
'.vcf' : 'text/x-vcard',
'.wav' : 'audio/x-wav',
'.wiz' : 'application/msword',
'.xbm' : 'image/x-xbitmap',
'.xlb' : 'application/vnd.ms-excel',
# Duplicates :(
'.xls' : 'application/excel',
'.xls' : 'application/vnd.ms-excel',
'.xml' : 'text/xml',
'.xpm' : 'image/x-xpixmap',
'.xsl' : 'application/xml',
'.xwd' : 'image/x-xwindowdump',
'.zip' : 'application/zip',
}
# These are non-standard types, commonly found in the wild. They will only
# match if strict=0 flag is given to the API methods.
# Please sort these too
common_types = {
'.jpg' : 'image/jpg',
'.mid' : 'audio/midi',
'.midi': 'audio/midi',
'.pct' : 'image/pict',
'.pic' : 'image/pict',
'.pict': 'image/pict',
'.rtf' : 'application/rtf',
'.xul' : 'text/xul'
}
**/

View File

@ -0,0 +1,18 @@
package net.i2p.aum;
public class OOTest
{
public int add(int a, int b)
{
return (a + b);
}
public static void main(String[] args)
{
OOTest mytest = new OOTest();
System.out.println(mytest.add(3,3));
}
}

View File

@ -0,0 +1,232 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.data.*;
import net.i2p.*;
import net.i2p.client.*;
import net.i2p.client.streaming.*;
import net.i2p.data.Base64;
import net.i2p.util.*;
import net.i2p.data.*;
/**
* A convenience class for encapsulating and manipulating I2P private keys
*/
public class PrivDestination
//extends ByteArrayInputStream
extends DataStructureImpl
{
protected byte [] _bytes;
protected Destination _dest;
protected PrivateKey _privKey;
protected SigningPrivateKey _signingPrivKey;
protected static Log _log;
/**
* Create a PrivDestination object.
* In most cases, you'll probably want to skip this constructor,
* and create PrivDestination objects by invoking the desired static methods
* of this class.
* @param raw an array of bytes containing the raw binary private key
*/
public PrivDestination(byte [] raw) throws DataFormatException, IOException
{
//super(raw);
_log = new Log("PrivDestination");
_bytes = raw;
readBytes(getInputStream());
}
/**
* reconstitutes a PrivDestination from previously exported Base64
*/
public PrivDestination(String b64) throws DataFormatException, IOException {
this(Base64.decode(b64));
}
/**
* generates a new PrivDestination with random keys
*/
public PrivDestination() throws I2PException, IOException
{
I2PClient client = I2PClientFactory.createClient();
ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
// create a dest
client.createDestination(streamOut);
_bytes = streamOut.toByteArray();
readBytes(getInputStream());
// construct from the stream
//return new PrivDestination(streamOut.toByteArray());
}
/** return the public Destination object for this private dest */
public Destination getDestination() {
return _dest;
}
/** return a PublicKey (encryption public key) object for this priv dest */
public PublicKey getPublicKey() {
return getDestination().getPublicKey();
}
/** return a PrivateKey (encryption private key) object for this priv dest */
public PrivateKey getPrivateKey() {
return _privKey;
}
/** return a SigningPublicKey object for this priv dest */
public SigningPublicKey getSigningPublicKey() {
return getDestination().getSigningPublicKey();
}
/** return a SigningPrivateKey object for this priv dest */
public SigningPrivateKey getSigningPrivateKey() {
return _signingPrivKey;
}
// static methods returning an instance
/**
* Creates a PrivDestination object
* @param base64 a string containing the base64 private key data
* @return a PrivDestination object encapsulating that key
*/
public static PrivDestination fromBase64String(String base64)
throws DataFormatException, IOException
{
return new PrivDestination(Base64.decode(base64));
}
/**
* Creates a PrivDestination object, from the base64 key data
* stored in a file.
* @param path the pathname of the file from which to read the base64 private key data
* @return a PrivDestination object encapsulating that key
*/
public static PrivDestination fromBase64File(String path)
throws FileNotFoundException, IOException, DataFormatException
{
return fromBase64String(new SimpleFile(path, "r").read());
/*
File f = new File(path);
char [] rawchars = new char[(int)(f.length())];
byte [] rawbytes = new byte[(int)(f.length())];
FileReader fr = new FileReader(f);
fr.read(rawchars);
String raw64 = new String(rawchars);
return PrivDestination.fromBase64String(raw64);
*/
}
/**
* Creates a PrivDestination object, from the binary key data
* stored in a file.
* @param path the pathname of the file from which to read the binary private key data
* @return a PrivDestination object encapsulating that key
*/
public static PrivDestination fromBinFile(String path)
throws FileNotFoundException, IOException, DataFormatException
{
byte [] raw = new SimpleFile(path, "r").readBytes();
return new PrivDestination(raw);
}
/**
* Generate a new random I2P private key
* @return a PrivDestination object encapsulating that key
*/
public static PrivDestination newKey() throws I2PException, IOException
{
return new PrivDestination();
}
public ByteArrayInputStream getInputStream()
{
return new ByteArrayInputStream(_bytes);
}
/**
* Exports the key's full contents to a string
* @return A base64-format string containing the full contents
* of this private key. The string can be used in any subsequent
* call to the .fromBase64String static constructor method.
*/
/*
public String toBase64()
{
return Base64.encode(_bytes);
}
*/
/**
* Exports the key's full contents to a byte array
* @return A byte array containing the full contents
* of this private key.
*/
/*
public byte [] toBytes()
{
return _bytes;
}
*/
/**
* Converts this key to a public destination.
* @return a standard I2P Destination object containing the
* public portion of this private key.
*/
/*
public Destination toDestination() throws DataFormatException
{
Destination dest = new Destination();
dest.readBytes(_bytes, 0);
return dest;
}
*/
/**
* Converts this key to a base64 string representing a public destination
* @return a string containing a base64 representation of the destination
* corresponding to this private key.
*/
public String getDestinationBase64() throws DataFormatException
{
return getDestination().toBase64();
}
public void readBytes(java.io.InputStream strm)
throws net.i2p.data.DataFormatException, java.io.IOException
{
_dest = new Destination();
_privKey = new PrivateKey();
_signingPrivKey = new SigningPrivateKey();
_dest.readBytes(strm);
_privKey.readBytes(strm);
_signingPrivKey.readBytes(strm);
}
public void writeBytes(java.io.OutputStream outputStream)
throws net.i2p.data.DataFormatException, java.io.IOException
{
_dest.writeBytes(outputStream);
_privKey.writeBytes(outputStream);
_signingPrivKey.writeBytes(outputStream);
}
}

View File

@ -0,0 +1,209 @@
/*
* PropertiesFile.java
*
* Created on 20 March 2005, 19:30
*/
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
/**
* builds on Properties with methods to load/save directly to/from file
*/
public class PropertiesFile extends Properties {
public String _path;
public File _file;
public boolean _fileExists;
/**
* Creates a new instance of PropertiesFile
* @param path Absolute pathname of file where properties are to be stored
*/
public PropertiesFile(String path) throws IOException {
super();
_path = path;
_file = new File(path);
_fileExists = _file.isFile();
if (_file.canRead()) {
loadFromFile();
}
}
/**
* Creates new PropertiesFile, updating its content with the
* keys/values in given hashtable
* @param path absolute pathname where properties file is located in filesystem
* @param h instance of Hashtable (or subclass). its content
* will be written to this object (note that string representations of keys/vals
* will be used)
*/
public PropertiesFile(String path, Hashtable h) throws IOException
{
this(path);
Enumeration keys = h.keys();
Object key;
while (true)
{
try {
key = keys.nextElement();
} catch (NoSuchElementException e) {
break;
}
setProperty(key.toString(), h.get(key).toString());
}
}
/**
* Loads this object from the file
*/
public void loadFromFile() throws IOException, FileNotFoundException {
if (_file.canRead()) {
InputStream fis = new FileInputStream(_file);
load(fis);
}
}
/**
* Saves this object to the file
*/
public void saveToFile() throws IOException, FileNotFoundException {
if (!_fileExists) {
_file.createNewFile();
_fileExists = true;
}
OutputStream fos = new FileOutputStream(_file);
store(fos, null);
}
/**
* Stores attribute
*/
public Object setProperty(String key, String value) {
Object o = super.setProperty(key, value);
try {
saveToFile();
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
/**
* return a property as an int, fall back on default if not found or invalid
*/
public int getIntProperty(String key, int dflt) {
try {
return new Integer((String)getProperty(key)).intValue();
} catch (Exception e) {
setIntProperty(key, dflt);
return dflt;
}
}
/**
* return a property as an int
*/
public int getIntProperty(String key) {
return new Integer((String)getProperty(key)).intValue();
}
/**
* set a property as an int
*/
public void setIntProperty(String key, int value) {
setProperty(key, String.valueOf(value));
}
/**
* return a property as a long, fall back on default if not found or invalid
*/
public long getIntProperty(String key, long dflt) {
try {
return new Long((String)getProperty(key)).longValue();
} catch (Exception e) {
setLongProperty(key, dflt);
return dflt;
}
}
/**
* return a property as an int
*/
public long getLongProperty(String key) {
return new Long((String)getProperty(key)).longValue();
}
/**
* set a property as an int
*/
public void setLongProperty(String key, long value) {
setProperty(key, String.valueOf(value));
}
/**
* return a property as a float
*/
public double getFloatProperty(String key) {
return new Float((String)getProperty(key)).floatValue();
}
/**
* return a property as a float, fall back on default if not found or invalid
*/
public double getFloatProperty(String key, float dflt) {
try {
return new Float((String)getProperty(key)).floatValue();
} catch (Exception e) {
setFloatProperty(key, dflt);
return dflt;
}
}
/**
* set a property as a float
*/
public void setFloatProperty(String key, float value) {
setProperty(key, String.valueOf(value));
}
/**
* return a property as a double
*/
public double getDoubleProperty(String key) {
return new Double((String)getProperty(key)).doubleValue();
}
/**
* return a property as a double, fall back on default if not found
*/
public double getDoubleProperty(String key, double dflt) {
try {
return new Double((String)getProperty(key)).doubleValue();
} catch (Exception e) {
setDoubleProperty(key, dflt);
return dflt;
}
}
/**
* set a property as a double
*/
public void setDoubleProperty(String key, double value) {
setProperty(key, String.valueOf(value));
}
/**
* increment an integer property value
*/
public void incrementIntProperty(String key) {
setIntProperty(key, getIntProperty(key)+1);
}
}

View File

@ -0,0 +1,120 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import net.i2p.data.*;
/**
* SimpleFile - subclass of File which adds some python-like
* methods. Cuts out a lot of the red tape involved with reading
* from and writing to files
*/
public class SimpleFile {
public RandomAccessFile _file;
public String _path;
public SimpleFile(String path, String mode) throws FileNotFoundException {
_path = path;
_file = new RandomAccessFile(path, mode);
}
public byte [] readBytes() throws IOException {
return readBytes((int)_file.length());
}
public byte[] readBytes(int n) throws IOException {
byte [] buf = new byte[n];
_file.readFully(buf);
return buf;
}
public char [] readChars() throws IOException {
return readChars((int)_file.length());
}
public char[] readChars(int n) throws IOException {
char [] buf = new char[n];
//_file.readFully(buf);
return buf;
}
/**
* Reads all remaining content from the file
* @return the content as a String
* @throws IOException
*/
public String read() throws IOException {
return read((int)_file.length());
}
/**
* Reads one or more bytes of data from the file
* @return the content as a String
* @throws IOException
*/
public String read(int nbytes) throws IOException {
return new String(readBytes(nbytes));
}
/**
* Writes one or more bytes of data to a file
* @param buf a String containing the data to write
* @return the number of bytes written, as an int
* @throws IOException
*/
public int write(String buf) throws IOException {
return write(buf.getBytes());
}
public int write(byte [] buf) throws IOException {
_file.write(buf);
return buf.length;
}
/**
* convenient one-hit write
* @param path pathname of file to write to
* @param buf data to write
*/
public static int write(String path, String buf) throws IOException {
return new SimpleFile(path, "rws").write(buf);
}
/**
* tests if argument refers to an actual file
* @param path pathname to test
* @return true if a file, false if not
*/
public boolean isFile() {
return new File(_path).isFile();
}
/**
* tests if argument refers to a directory
* @param path pathname to test
* @return true if a directory, false if not
*/
public boolean isDir() {
return new File(_path).isDirectory();
}
/**
* tests if a file or directory exists
* @param path pathname to test
* @return true if exists, or false
*/
public boolean exists() {
return new File(_path).exists();
}
}

View File

@ -0,0 +1,123 @@
package net.i2p.aum;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.net.*;
import net.i2p.data.*;
/**
* SimpleFile - subclass of File which adds some python-like
* methods. Cuts out a lot of the red tape involved with reading
* from and writing to files
*/
public class SimpleFile_old extends File {
public FileReader _reader;
public FileWriter _writer;
public SimpleFile_old(String path) {
super(path);
_reader = null;
_writer = null;
}
/**
* Reads all remaining content from the file
* @return the content as a String
* @throws IOException
*/
public String read() throws IOException {
return read((int)length());
}
/**
* Reads one or more bytes of data from the file
* @return the content as a String
* @throws IOException
*/
public String read(int nbytes) throws IOException {
// get a reader, if we don't already have one
if (_reader == null) {
_reader = new FileReader(this);
}
char [] cbuf = new char[nbytes];
int nread = _reader.read(cbuf);
if (nread == 0) {
return "";
}
return new String(cbuf, 0, nread);
}
/**
* Writes one or more bytes of data to a file
* @param buf a String containing the data to write
* @return the number of bytes written, as an int
* @throws IOException
*/
public int write(String buf) throws IOException {
// get a reader, if we don't already have one
if (_writer == null) {
_writer = new FileWriter(this);
}
_writer.write(buf);
_writer.flush();
return buf.length();
}
public int write(byte [] buf) throws IOException {
return write(new String(buf));
}
/**
* convenient one-hit write
* @param path pathname of file to write to
* @param buf data to write
*/
public static int write(String path, String buf) throws IOException {
SimpleFile_old f = new SimpleFile_old(path);
return f.write(buf);
}
/**
* tests if argument refers to an actual file
* @param path pathname to test
* @return true if a file, false if not
*/
public static boolean isFile(String path) {
return new File(path).isFile();
}
/**
* tests if argument refers to a directory
* @param path pathname to test
* @return true if a directory, false if not
*/
public static boolean isDir(String path) {
return new File(path).isDirectory();
}
/**
* tests if a file or directory exists
* @param path pathname to test
* @return true if exists, or false
*/
public static boolean exists(String path) {
return new File(path).exists();
}
}

View File

@ -0,0 +1,138 @@
/*
* SimpleQueue.java
*
* Created on March 24, 2005, 11:14 PM
*/
package net.i2p.aum;
import java.*;
import java.lang.*;
import java.util.*;
/**
* Implements simething similar to python's 'Queue' class
*/
public class SimpleQueue {
public Vector items;
/** Creates a new instance of SimpleQueue */
public SimpleQueue() {
items = new Vector();
}
/**
* fetches the item at head of queue, blocking if queue is empty
*/
public synchronized Object get()
{
while (true)
{
try {
if (items.size() == 0)
wait();
// someone has added
Object item = items.get(0);
items.remove(0);
return item;
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* adds a new object to the queue
*/
public synchronized void put(Object item)
{
items.addElement(item);
notify();
}
private static class TestThread extends Thread {
String id;
SimpleQueue q;
public TestThread(String id, SimpleQueue q) {
this.id = id;
this.q = q;
}
public void run() {
try {
print("waiting for queue");
Object item = q.get();
print("got item: '"+item+"'");
} catch (Exception e) {
e.printStackTrace();
return;
}
}
public void print(String msg) {
System.out.println("thread '"+id+"': "+msg);
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
int i;
int nthreads = 7;
Thread [] threads = new Thread[nthreads];
SimpleQueue q = new SimpleQueue();
// populate the queue with some stuff
q.put("red");
q.put("orange");
q.put("yellow");
// populate threads array
for (i = 0; i < nthreads; i++) {
threads[i] = new TestThread("thread"+i, q);
}
// and launch the threads
for (i = 0; i < nthreads; i++) {
threads[i].start();
}
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
return;
}
// wait a bit and see what happens
String [] items = {"green", "blue", "indigo", "violet", "black", "white", "brown"};
for (i = 0; i < items.length; i++) {
String item = items[i];
System.out.println("main: adding '"+item+"'...");
q.put(item);
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
return;
}
}
System.out.println("main: terminating");
}
}

View File

@ -0,0 +1,108 @@
/*
* SimpleSemaphore.java
*
* Created on March 24, 2005, 11:51 PM
*/
package net.i2p.aum;
/**
* Simple implementation of semaphores
*/
public class SimpleSemaphore {
protected int count;
/** Creates a new instance of SimpleSemaphore */
public SimpleSemaphore(int size) {
count = size;
}
public synchronized void acquire() throws InterruptedException
{
if (count == 0)
{
wait();
}
count -= 1;
}
public synchronized void release()
{
count += 1;
notify();
}
private static class TestThread extends Thread
{
String id;
SimpleSemaphore sem;
public TestThread(String id, SimpleSemaphore sem)
{
this.id = id;
this.sem = sem;
}
public void run()
{
try {
print("waiting for semaphore");
sem.acquire();
print("got semaphore");
Thread.sleep(1000);
print("releasing semaphore");
sem.release();
print("terminating");
} catch (Exception e) {
e.printStackTrace();
return;
}
}
public void print(String msg) {
System.out.println("thread '"+id+"': "+msg);
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
int i;
Thread [] threads = new Thread[10];
SimpleSemaphore sem = new SimpleSemaphore(3);
// populate threads array
for (i = 0; i < 10; i++) {
threads[i] = new TestThread("thread"+i, sem);
}
// and launch the threads
for (i = 0; i < 10; i++) {
threads[i].start();
}
// wait a bit and see what happens
System.out.println("main: threads launched, waiting 20 secs");
try {
Thread.sleep(20000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("main: terminating");
}
}

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