Compare commits

...

110 Commits

Author SHA1 Message Date
6019a03029 * 2005-10-01 0.6.1.1 released 2005-10-01 19:20:09 +00:00
df5736f571 * Add a notModified flag to Eepget and Eepget status listeners. 2005-10-01 00:57:32 +00:00
9a73c6defe * Support conditional get for remote archive imports. 2005-09-30 23:42:28 +00:00
9dfa87ba47 2005-09-30 jrandom
* Killed three more streaming lib bugs, one of which caused excess packets
      to be transmitted (dupacking dupacks), one that was the root of many of
      the old hung streams (shrinking highest received), and another that was
      releasing data too soon.
2005-09-30 23:12:57 +00:00
934a269753 2005-09-30 jrandom
* Only allow autodetection of our IP address if we haven't received an
      inbound connection in the last two minutes.
    * Increase the default max streaming resends to 8 from 5 (and down from
      the earlier 10)
2005-09-30 20:29:19 +00:00
1c0dfc242b * Fix history.txt formatting. 2005-09-30 17:51:32 +00:00
3bc3e5d47e * Export petnames from syndie to the router's petname db instead of userhosts.txt. 2005-09-30 07:32:46 +00:00
55869af2cc 2005-09-29 jrandom
* Support noreseed.i2p in addition to .i2pnoreseed for disabling automatic
      reseeding - useful on OSes that make it hard to create dot files.
      Thanks Complication (and anon)!
    * Fixed the installer version string (thanks Frontier!)
    * Added cleaner rejection of invalid IP addresses, shitlist those who send
      us invalid IP addresses, verify again that we are not sending invalid IP
      addresses, and log an error if it happens. (Thanks Complication, ptm,
      and adab!)
2005-09-30 07:17:56 +00:00
9f336dd05b Provide a store method on PetNameDB that takes no arguments, and writes the db back to where it was loaded from. 2005-09-30 05:28:31 +00:00
411ca5e6c3 Ignore case when checking network name. 2005-09-30 05:20:41 +00:00
c528e4db03 * 2005-09-29 0.6.1 released
2005-09-29  jrandom
    * Let syndie users modify their metadata.
    * Reseed the router on startup if there aren't enough peer references
      known locally.  This can be disabled by creating the file .i2pnoreseed
      in your home directory, and the existing detection and reseed handling
      on the web interface is unchanged.
2005-09-29 19:24:43 +00:00
848ead7683 * 2005-09-29 0.6.1 released
2005-09-29  jrandom
    * Let syndie users modify their metadata.
    * Reseed the router on startup if there aren't enough peer references
      known locally.  This can be disabled by creating the file .i2pnoreseed
      in your home directory, and the existing detection and reseed handling
      on the web interface is unchanged.
2005-09-29 19:19:22 +00:00
1b8419b9b5 added tracker-fr.i2p 2005-09-29 03:54:30 +00:00
900420719e 2005-09-28 jrandom
* Fix for at least some (all?) of the wrong stream errors in the streaming
      lib
2005-09-28 09:17:54 +00:00
ef7d1ba964 2005-09-27 jrandom
* Properly suggest filenames for attachments in Syndie (thanks all!)
    * Fixed the Syndie authorization scheme for single user vs. multiuser
2005-09-27 22:42:49 +00:00
ab1654c784 ; (1.181) added syncline.i2p, cerebrum.i2p, news.underscore.i2p,
;               onionforum.i2p, frostmirror.i2p, ptm.i2p, gloinsblog.i2p
;               underscore.i2p, mac7.i2p, wiht.i2p, jazzy.i2p, trwcln.i2p
2005-09-27 22:21:05 +00:00
24bad8e4bb 2005-09-26 jrandom
* I2PTunnel bugfix (thanks Complication!)
    * Increase the SSU cwin slower during congestion avoidance (at k/cwin^2
      instead of k/cwin)
    * Limit the number of inbound SSU sessions being built at once (using
      half of the i2np.udp.maxConcurrentEstablish config prop)
    * Don't shitlist on a message send failure alone (unless there aren't any
      common transports).
    * More careful bandwidth bursting
2005-09-27 07:17:40 +00:00
f6d8200bc8 oops (thanks Complication!) 2005-09-27 00:56:49 +00:00
aef33548b3 2005-09-26 jrandom
* Reworded the SSU introductions config section (thanks duck!)
    * Force identity content encoding for I2PTunnel httpserver requests
      (thanks redzara!)
    * Further x-i2p-gzip bugfixes for the end of streams
    * Reduce the minimum bandwidth limits to 3KBps steady and burst (though
      I2P's performance at 3KBps is another issue)
    * Cleaned up some streaming lib structures
2005-09-26 23:45:52 +00:00
56ecdcce82 2005-09-25 jrandom
* Allow reseeding on the console if the netDb knows less than 30 peers,
      rather than less than 10 (without internet connectivity, we keep the
      last 15 router references)
    * Reenable the x-i2p-gzip HTTP processing by default, flushing the stream
      more aggressively.
    * Show the status that used to be called "ERR-Reject" as "OK (NAT)"
    * Reduced the default maximum number of streaming lib resends of a packet
      (10 retransmits is a bit much with a reasonable RTO)
2005-09-25 23:52:58 +00:00
b9b59ff95f 2005-09-25 Complication
* Better i2paddresshelper handling in the I2PTunnel httpclient, plus a new
      conflict resolution page if the i2paddresshelper parameter differs from
      an existing name to destination mapping.
2005-09-25  jrandom
    * Fix a long standing streaming lib bug (in the inactivity detection code)
    * Improved handling of initial streaming lib packet retransmissions to
      kill the "lost first packet" bug (where a page shows up with the first
      few KB missing)
    * Add support for initial window sizes greater than 1 - useful for
      eepsites to transmit e.g. 4 packets full of data along with the initial
      ACK, thereby cutting down on the rtt latency.  The congestion window
      size can and does still shrink down to 1 packet though.
    * Adjusted the streaming lib retransmission calculation algorithm to be
      more TCP-like.
2005-09-25 09:28:59 +00:00
aa9dd3e5c6 mention syndie bug stuff (good idea jnymo) 2005-09-24 04:08:42 +00:00
30bd659149 2005-09-21 redzara
* Use ISO-8859-1 for the susidns xml
2005-09-21 23:01:00 +00:00
3286ca49c8 2005-09-21 susi
* Bugfix in susidns for deleting entries
2005-09-21  jrandom
    * Add support for HTTP POST to EepGet
    * Use HTTP POST for syndie bulk fetches, since there's a lot of data to
      put in that URL.
2005-09-21 06:43:04 +00:00
7700d12178 damn thee, syntax 2005-09-20 03:43:57 +00:00
557b7e3f2e only build exe files on ant dist or ant installer 2005-09-20 03:38:14 +00:00
3e1e9146e1 don't build the exe files on x86_64 or osx 2005-09-20 03:17:06 +00:00
40d8d1aac1 * Made MetaNamingService the default naming service. 2005-09-19 00:56:47 +00:00
1457b8efba include the lib64 wrapper (thanks mule) 2005-09-19 00:36:59 +00:00
3821e80ac8 2005-09-18 jrandom
* Added support for pure 64bit linux with jbigi and the java service
      wrapper (no need for jcpuid if we're on os.arch=amd64).  Thanks mule
      et al for help testing!
    * UI cleanup in Syndie (thanks gloin and bar!)
2005-09-18 23:08:16 +00:00
d40bb459ea * Get the PetNameDB for the PetNameNamingService from the router context. 2005-09-18 22:36:10 +00:00
edf04f07c9 * Implemented a MetaNamingService. 2005-09-18 08:50:56 +00:00
2bdea23986 * Updated history.txt. 2005-09-18 05:41:46 +00:00
6be0c4b694 * Moved PetName and PetNameDB to core.
* Implement PetNameNamingService.
2005-09-18 05:28:51 +00:00
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
380 changed files with 23076 additions and 2813 deletions

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,7 +57,7 @@ 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) {
router.merge(master, true, null);
Iterator iter = subscriptions.iterator();
@ -77,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
@ -104,7 +105,7 @@ public class Daemon {
.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,19 +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);
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

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

View File

@ -109,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);

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) {

View File

@ -96,6 +96,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
.getBytes();
private final static byte[] ERR_AHELPER_CONFLICT =
("HTTP/1.1 409 Conflict\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: Destination key conflict</H1>"+
"The addresshelper link you followed specifies a different destination key "+
"than a host entry in your host database. "+
"Someone could be trying to impersonate another eepsite, "+
"or people have given two eepsites identical names.<P/>"+
"You can resolve the conflict by considering which key you trust, "+
"and either discarding the addresshelper link, "+
"discarding the host entry from your host database, "+
"or naming one of them differently.<P/>")
.getBytes();
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
@ -179,6 +195,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
return opts;
}
private static final boolean DEFAULT_GZIP = true;
private static long __requestId = 0;
protected void clientConnectionRun(Socket s) {
OutputStream out = null;
@ -243,50 +261,102 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
// Quick hack for foo.bar.i2p
if (host.toLowerCase().endsWith(".i2p")) {
// Destination gets the host name
destination = host;
// Host becomes the destination key
host = getHostName(destination);
if ( (host != null) && ("i2p".equals(host)) ) {
int pos2;
if ((pos2 = request.indexOf("?")) != -1) {
// Try to find an address helper in the fragments
// and split the request into it's component parts for rebuilding later
String fragments = request.substring(pos2 + 1);
String uriPath = request.substring(0, pos2);
pos2 = fragments.indexOf(" ");
String protocolVersion = fragments.substring(pos2 + 1);
String urlEncoding = "";
fragments = fragments.substring(0, pos2);
fragments = fragments + "&";
String fragment;
while(fragments.length() > 0) {
pos2 = fragments.indexOf("&");
fragment = fragments.substring(0, pos2);
fragments = fragments.substring(pos2 + 1);
if (fragment.startsWith("i2paddresshelper")) {
pos2 = fragment.indexOf("=");
if (pos2 >= 0) {
addressHelpers.put(destination,fragment.substring(pos2 + 1));
}
} else {
// append each fragment unless it's the address helper
if ("".equals(urlEncoding)) {
urlEncoding = "?" + fragment;
} else {
urlEncoding = urlEncoding + "&" + fragment;
}
}
}
// reconstruct the request minus the i2paddresshelper GET var
request = uriPath + urlEncoding + " " + protocolVersion;
}
String addressHelper = (String) addressHelpers.get(destination);
if (addressHelper != null) {
destination = addressHelper;
host = getHostName(destination);
ahelper = 1;
int pos2;
if ((pos2 = request.indexOf("?")) != -1) {
// Try to find an address helper in the fragments
// and split the request into it's component parts for rebuilding later
String ahelperKey = null;
boolean ahelperConflict = false;
String fragments = request.substring(pos2 + 1);
String uriPath = request.substring(0, pos2);
pos2 = fragments.indexOf(" ");
String protocolVersion = fragments.substring(pos2 + 1);
String urlEncoding = "";
fragments = fragments.substring(0, pos2);
String initialFragments = fragments;
fragments = fragments + "&";
String fragment;
while(fragments.length() > 0) {
pos2 = fragments.indexOf("&");
fragment = fragments.substring(0, pos2);
fragments = fragments.substring(pos2 + 1);
// Fragment looks like addresshelper key
if (fragment.startsWith("i2paddresshelper=")) {
pos2 = fragment.indexOf("=");
ahelperKey = fragment.substring(pos2 + 1);
// Key contains data, lets not ignore it
if (ahelperKey != null) {
// Host resolvable only with addresshelper
if ( (host == null) || ("i2p".equals(host)) )
{
// Cannot check, use addresshelper key
addressHelpers.put(destination,ahelperKey);
} else {
// Host resolvable from database, verify addresshelper key
// Silently bypass correct keys, otherwise alert
if (!host.equals(ahelperKey))
{
// Conflict: handle when URL reconstruction done
ahelperConflict = true;
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + host + "], specified key [" + ahelperKey + "].");
}
}
}
} else {
// Other fragments, just pass along
// Append each fragment to urlEncoding
if ("".equals(urlEncoding)) {
urlEncoding = "?" + fragment;
} else {
urlEncoding = urlEncoding + "&" + fragment;
}
}
}
// Reconstruct the request minus the i2paddresshelper GET var
request = uriPath + urlEncoding + " " + protocolVersion;
// Did addresshelper key conflict?
if (ahelperConflict)
{
String str;
byte[] header;
str = FileUtil.readTextFile("docs/ahelper-conflict-header.ht", 100, true);
if (str != null) header = str.getBytes();
else header = ERR_AHELPER_CONFLICT;
if (out != null) {
long alias = I2PAppContext.getGlobalContext().random().nextLong();
String trustedURL = protocol + uriPath + urlEncoding;
String conflictURL = protocol + alias + ".i2p/?" + initialFragments;
out.write(header);
out.write(("To visit the destination in your host database, click <a href=\"" + trustedURL + "\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"" + conflictURL + "\">here</a>.<P/>").getBytes());
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
}
s.close();
return;
}
}
String addressHelper = (String) addressHelpers.get(destination);
if (addressHelper != null) {
destination = addressHelper;
host = getHostName(destination);
ahelper = 1;
}
line = method + " " + request.substring(pos);
} else if (host.indexOf(".") != -1) {
// The request must be forwarded to a WWW proxy
@ -369,6 +439,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
if (line.length() == 0) {
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
boolean gzip = DEFAULT_GZIP;
if (ok != null)
gzip = Boolean.valueOf(ok).booleanValue();
if (gzip)
newRequest.append("Accept-Encoding: x-i2p-gzip\r\n");
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
newRequest.append("Connection: close\r\n\r\n");
break;

View File

@ -3,16 +3,13 @@
*/
package net.i2p.i2ptunnel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.FilterOutputStream;
import java.io.*;
import java.net.Socket;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
@ -30,12 +27,38 @@ import net.i2p.util.Log;
*
*/
public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
private Log _log;
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
super(s, i2ps, slock, initialI2PData, sockList, onTimeout);
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class);
}
protected OutputStream getSocketOut() throws IOException {
OutputStream raw = super.getSocketOut();
return new HTTPResponseOutputStream(raw);
}
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
try {
i2pin.close();
i2pout.close();
} catch (IOException ioe) {
// ignore
if (_log.shouldLog(Log.DEBUG))
_log.debug("Unable to close the i2p socket output stream: " + i2pout, ioe);
}
try {
in.close();
out.close();
} catch (IOException ioe) {
// ignore
if (_log.shouldLog(Log.DEBUG))
_log.debug("Unable to close the browser output stream: " + out, ioe);
}
i2ps.close();
s.close();
t1.join(30*1000);
t2.join(30*1000);
}
}

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 {
@ -60,14 +61,48 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
//local is fast, so synchronously. Does not need that many
//threads.
try {
socket.setReadTimeout(readTimeout);
String modifiedHeader = getModifiedHeader(socket);
// 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);
if ( (_spoofHost != null) && (_spoofHost.trim().length() > 0) )
headers.setProperty("Host", _spoofHost);
headers.setProperty("Connection", "close");
// we keep the enc sent by the browser before clobbering it, since it may have
// been x-i2p-gzip
String enc = headers.getProperty("Accept-encoding");
headers.setProperty("Accept-encoding", "identity;q=1, *;q=0");
String modifiedHeader = formatHeaders(headers, command);
//String modifiedHeader = getModifiedHeader(socket);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Modified header: [" + modifiedHeader + "]");
socket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
afterSocket = getTunnel().getContext().clock().now();
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
// instead of i2ptunnelrunner, use something that reads the HTTP
// request from the socket, modifies the headers, sends the request to the
// server, reads the response headers, rewriting to include Content-encoding: x-i2p-gzip
// if it was one of the Accept-encoding: values, and gzip the payload
Properties opts = getTunnel().getClientOptions();
boolean allowGZIP = true;
if (opts != null) {
String val = opts.getProperty("i2ptunnel.gzip");
if ( (val != null) && (!Boolean.valueOf(val).booleanValue()) )
allowGZIP = false;
}
if (_log.shouldLog(Log.INFO))
_log.info("HTTP server encoding header: " + enc);
if ( allowGZIP && (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) ) {
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
req.start();
} else {
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
}
} catch (SocketException ex) {
try {
socket.close();
@ -86,17 +121,119 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
private String getModifiedHeader(I2PSocket handleSocket) throws IOException {
InputStream in = handleSocket.getInputStream();
StringBuffer command = new StringBuffer(128);
Properties headers = readHeaders(in, command);
headers.setProperty("Host", _spoofHost);
headers.setProperty("Connection", "close");
return formatHeaders(headers, command);
}
private class CompressedRequestor implements Runnable {
private Socket _webserver;
private I2PSocket _browser;
private String _headers;
public CompressedRequestor(Socket webserver, I2PSocket browser, String headers) {
_webserver = webserver;
_browser = browser;
_headers = headers;
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info("Compressed requestor running");
OutputStream serverout = null;
OutputStream browserout = null;
InputStream browserin = null;
InputStream serverin = null;
try {
serverout = _webserver.getOutputStream();
if (_log.shouldLog(Log.INFO))
_log.info("request headers: " + _headers);
serverout.write(_headers.getBytes());
browserin = _browser.getInputStream();
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), "http compressed sender");
sender.start();
browserout = _browser.getOutputStream();
serverin = _webserver.getInputStream();
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
if (_log.shouldLog(Log.INFO))
_log.info("Before pumping the compressed response");
s.run(); // same thread
if (_log.shouldLog(Log.INFO))
_log.info("After pumping the compressed response: " + compressedOut.getTotalRead() + "/" + compressedOut.getTotalCompressed());
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("error compressing", ioe);
} finally {
if (browserout != null) try { browserout.close(); } catch (IOException ioe) {}
if (serverout != null) try { serverout.close(); } catch (IOException ioe) {}
if (browserin != null) try { browserin.close(); } catch (IOException ioe) {}
if (serverin != null) try { serverin.close(); } catch (IOException ioe) {}
}
}
}
private class Sender implements Runnable {
private OutputStream _out;
private InputStream _in;
private String _name;
public Sender(OutputStream out, InputStream in, String name) {
_out = out;
_in = in;
_name = name;
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": Begin sending");
try {
byte buf[] = new byte[16*1024];
int read = 0;
int total = 0;
while ( (read = _in.read(buf)) != -1) {
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": read " + read + " and sending through the stream");
_out.write(buf, 0, read);
total += read;
}
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": Done sending: " + total);
_out.flush();
} catch (IOException ioe) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error sending", ioe);
} finally {
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
}
}
}
private class CompressedResponseOutputStream extends HTTPResponseOutputStream {
private InternalGZIPOutputStream _gzipOut;
public CompressedResponseOutputStream(OutputStream o) {
super(o);
}
protected boolean shouldCompress() { return true; }
protected void finishHeaders() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Including x-i2p-gzip as the content encoding in the response");
out.write("Content-encoding: x-i2p-gzip\n".getBytes());
super.finishHeaders();
}
protected void beginProcessing() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Beginning compression processing");
out.flush();
_gzipOut = new InternalGZIPOutputStream(out);
out = _gzipOut;
}
public long getTotalRead() { return _gzipOut.getTotalRead(); }
public long getTotalCompressed() { return _gzipOut.getTotalCompressed(); }
}
private class InternalGZIPOutputStream extends GZIPOutputStream {
public InternalGZIPOutputStream(OutputStream target) throws IOException {
super(target);
}
public long getTotalRead() { return super.def.getTotalIn(); }
public long getTotalCompressed() { return super.def.getTotalOut(); }
}
private String formatHeaders(Properties headers, StringBuffer command) {
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
buf.append(command.toString()).append('\n');
@ -131,6 +268,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
String name = buf.substring(0, split);
String value = buf.substring(split+2); // ": "
if ("Accept-encoding".equalsIgnoreCase(name))
name = "Accept-encoding";
headers.setProperty(name, value);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read the header [" + name + "] = [" + value + "]");

View File

@ -153,11 +153,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
onTimeout.run();
}
// now one connection is dead - kill the other as well.
s.close();
i2ps.close();
t1.join(30*1000);
t2.join(30*1000);
// now one connection is dead - kill the other as well, after making sure we flush
close(out, in, i2pout, i2pin, s, i2ps, t1, t2);
} catch (InterruptedException ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("Interrupted", ex);
@ -188,6 +185,27 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
}
}
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
try {
out.flush();
} catch (IOException ioe) {
// ignore
}
try {
i2pout.flush();
} catch (IOException ioe) {
// ignore
}
in.close();
i2pin.close();
// ok, yeah, there's a race here in theory, if data comes in after flushing and before
// closing, but its better than before...
s.close();
i2ps.close();
t1.join(30*1000);
t2.join(30*1000);
}
public void errorOccurred() {
synchronized (finishLock) {
finished = true;

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);
}
@ -178,22 +197,34 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
public void run() {
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..
}
}
*/
}
}
public boolean shouldUsePool() { return _usePool; }
/**
* minor thread pool to pull off the accept() concurrently. there are still lots

View File

@ -479,9 +479,12 @@ public class IndexBean {
public void setStartOnLoad(String moo) {
_startOnLoad = true;
}
public void setSharedClient(String moo) {
public void setShared(String moo) {
_sharedClient=true;
}
public void setShared(boolean val) {
_sharedClient=val;
}
public void setConnectDelay(String moo) {
_connectDelay = true;
}

View File

@ -166,9 +166,9 @@ if (curTunnel >= 0) {
</td>
<td>
<% if (editBean.isSharedClient(curTunnel)) { %>
<input type="checkbox" value="true" name="sharedClient" checked="true" />
<input type="checkbox" value="true" name="shared" checked="true" />
<% } else { %>
<input type="checkbox" value="true" name="sharedClient" />
<input type="checkbox" value="true" name="shared" />
<% } %>
<i>(Share tunnels with other clients and httpclients? Change requires restart of client proxy)</i>
</td>

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

@ -16,6 +16,7 @@ import java.util.Iterator;
import java.util.Set;
import net.i2p.time.Timestamper;
import net.i2p.router.transport.udp.UDPTransport;
/**
* Handler to deal with form submissions from the main config form and act
@ -29,11 +30,14 @@ public class ConfigNetHandler extends FormHandler {
private boolean _saveRequested;
private boolean _recheckReachabilityRequested;
private boolean _timeSyncEnabled;
private boolean _requireIntroductions;
private String _tcpPort;
private String _udpPort;
private String _inboundRate;
private String _inboundBurstRate;
private String _inboundBurst;
private String _outboundRate;
private String _outboundBurstRate;
private String _outboundBurst;
private String _reseedFrom;
private String _sharePct;
@ -57,6 +61,7 @@ public class ConfigNetHandler extends FormHandler {
public void setSave(String moo) { _saveRequested = true; }
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
public void setHostname(String hostname) {
_hostname = (hostname != null ? hostname.trim() : null);
@ -70,12 +75,18 @@ public class ConfigNetHandler extends FormHandler {
public void setInboundrate(String rate) {
_inboundRate = (rate != null ? rate.trim() : null);
}
public void setInboundburstrate(String rate) {
_inboundBurstRate = (rate != null ? rate.trim() : null);
}
public void setInboundburstfactor(String factor) {
_inboundBurst = (factor != null ? factor.trim() : null);
}
public void setOutboundrate(String rate) {
_outboundRate = (rate != null ? rate.trim() : null);
}
public void setOutboundburstrate(String rate) {
_outboundBurstRate = (rate != null ? rate.trim() : null);
}
public void setOutboundburstfactor(String factor) {
_outboundBurst = (factor != null ? factor.trim() : null);
}
@ -253,7 +264,14 @@ public class ConfigNetHandler extends FormHandler {
}
}
if (_timeSyncEnabled) {
if (_requireIntroductions) {
_context.router().setConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS, "true");
addFormNotice("Requiring SSU introduers");
} else {
_context.router().removeConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS);
}
if (true || _timeSyncEnabled) {
// Time sync enable, means NOT disabled
_context.router().setConfigSetting(Timestamper.PROP_DISABLED, "false");
} else {
@ -283,14 +301,22 @@ public class ConfigNetHandler extends FormHandler {
_context.router().setConfigSetting(ConfigNetHelper.PROP_OUTBOUND_KBPS, _outboundRate);
updated = true;
}
if ( (_inboundBurstRate != null) && (_inboundBurstRate.length() > 0) ) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_INBOUND_BURST_KBPS, _inboundBurstRate);
updated = true;
}
if ( (_outboundBurstRate != null) && (_outboundBurstRate.length() > 0) ) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_OUTBOUND_BURST_KBPS, _outboundBurstRate);
updated = true;
}
String inRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_INBOUND_KBPS);
String inBurstRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_INBOUND_BURST_KBPS);
if (_inboundBurst != null) {
int rateKBps = 0;
int burstSeconds = 0;
try {
rateKBps = Integer.parseInt(inRate);
rateKBps = Integer.parseInt(inBurstRate);
burstSeconds = Integer.parseInt(_inboundBurst);
} catch (NumberFormatException nfe) {
// ignore
@ -302,13 +328,13 @@ public class ConfigNetHandler extends FormHandler {
}
}
String outRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_OUTBOUND_KBPS);
String outBurstRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_OUTBOUND_BURST_KBPS);
if (_outboundBurst != null) {
int rateKBps = 0;
int burstSeconds = 0;
try {
rateKBps = Integer.parseInt(outRate);
rateKBps = Integer.parseInt(outBurstRate);
burstSeconds = Integer.parseInt(_outboundBurst);
} catch (NumberFormatException nfe) {
// ignore

View File

@ -2,6 +2,10 @@ package net.i2p.router.web;
import net.i2p.time.Timestamper;
import net.i2p.router.RouterContext;
import net.i2p.router.CommSystemFacade;
import net.i2p.data.RouterAddress;
import net.i2p.router.transport.udp.UDPAddress;
import net.i2p.router.transport.udp.UDPTransport;
public class ConfigNetHelper {
private RouterContext _context;
@ -43,19 +47,12 @@ public class ConfigNetHelper {
return "" + port;
}
public String getUdpPort() {
int port = 8887;
String val = _context.getProperty(PROP_I2NP_UDP_PORT);
if (val == null)
val = _context.getProperty(PROP_I2NP_INTERNAL_UDP_PORT);
if (val != null) {
try {
port = Integer.parseInt(val);
} catch (NumberFormatException nfe) {
// ignore, use default from above
}
}
return "" + port;
public String getUdpAddress() {
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
if (addr == null)
return "unknown";
UDPAddress ua = new UDPAddress(addr);
return ua.toString();
}
public String getEnableTimeSyncChecked() {
@ -66,8 +63,29 @@ public class ConfigNetHelper {
return " checked ";
}
public String getRequireIntroductionsChecked() {
short status = _context.commSystem().getReachabilityStatus();
switch (status) {
case CommSystemFacade.STATUS_OK:
if ("true".equalsIgnoreCase(_context.getProperty(UDPTransport.PROP_FORCE_INTRODUCERS, "false")))
return "checked=\"true\"";
return "";
case CommSystemFacade.STATUS_DIFFERENT:
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
return "checked=\"true\"";
case CommSystemFacade.STATUS_UNKNOWN:
if ("true".equalsIgnoreCase(_context.getProperty(UDPTransport.PROP_FORCE_INTRODUCERS, "false")))
return "checked=\"true\"";
return "";
default:
return "checked=\"true\"";
}
}
public static final String PROP_INBOUND_KBPS = "i2np.bandwidth.inboundKBytesPerSecond";
public static final String PROP_OUTBOUND_KBPS = "i2np.bandwidth.outboundKBytesPerSecond";
public static final String PROP_INBOUND_BURST_KBPS = "i2np.bandwidth.inboundBurstKBytesPerSecond";
public static final String PROP_OUTBOUND_BURST_KBPS = "i2np.bandwidth.outboundBurstKBytesPerSecond";
public static final String PROP_INBOUND_BURST = "i2np.bandwidth.inboundBurstKBytes";
public static final String PROP_OUTBOUND_BURST = "i2np.bandwidth.outboundBurstKBytes";
public static final String PROP_SHARE_PERCENTAGE = "router.sharePercentage";
@ -78,14 +96,28 @@ public class ConfigNetHelper {
if (rate != null)
return rate;
else
return "-1";
return "16";
}
public String getOutboundRate() {
String rate = _context.getProperty(PROP_OUTBOUND_KBPS);
if (rate != null)
return rate;
else
return "-1";
return "16";
}
public String getInboundBurstRate() {
String rate = _context.getProperty(PROP_INBOUND_BURST_KBPS);
if (rate != null)
return rate;
else
return "32";
}
public String getOutboundBurstRate() {
String rate = _context.getProperty(PROP_OUTBOUND_BURST_KBPS);
if (rate != null)
return rate;
else
return "32";
}
public String getInboundBurstFactorBox() {
String rate = _context.getProperty(PROP_INBOUND_KBPS);

View File

@ -20,6 +20,7 @@ public class FormHandler {
protected Log _log;
private String _nonce;
protected String _action;
protected String _passphrase;
private List _errors;
private List _notices;
private boolean _processed;
@ -32,6 +33,7 @@ public class FormHandler {
_processed = false;
_valid = true;
_nonce = null;
_passphrase = null;
}
/**
@ -51,6 +53,7 @@ public class FormHandler {
public void setNonce(String val) { _nonce = val; }
public void setAction(String val) { _action = val; }
public void setPassphrase(String val) { _passphrase = val; }
/**
* Override this to perform the final processing (in turn, adding formNotice
@ -119,8 +122,14 @@ public class FormHandler {
String noncePrev = System.getProperty(getClass().getName() + ".noncePrev");
if ( ( (nonce == null) || (!_nonce.equals(nonce)) ) &&
( (noncePrev == null) || (!_nonce.equals(noncePrev)) ) ) {
addFormError("Invalid nonce, are you being spoofed?");
_valid = false;
String expected = _context.getProperty("consolePassword");
if ( (expected != null) && (expected.trim().length() > 0) && (expected.equals(_passphrase)) ) {
// ok
} else {
addFormError("Invalid nonce, are you being spoofed?");
_valid = false;
}
}
}

View File

@ -198,7 +198,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
// ignore
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (_log.shouldLog(Log.INFO))
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
@ -224,4 +224,5 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
File temp = new File(TEMP_NEWS_FILE);
temp.delete();
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
}

View File

@ -28,14 +28,19 @@ public class ReseedHandler {
if (nonce == null) return;
if (nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.nonce")) ||
nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.noncePrev"))) {
synchronized (_reseedRunner) {
if (_reseedRunner.isRunning()) {
return;
} else {
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
reseed.start();
}
requestReseed();
}
}
public static void requestReseed() {
synchronized (_reseedRunner) {
if (_reseedRunner.isRunning()) {
return;
} else {
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
System.out.println("Reseeding");
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
reseed.start();
}
}
}
@ -46,7 +51,8 @@ public class ReseedHandler {
public boolean isRunning() { return _isRunning; }
public void run() {
_isRunning = true;
reseed();
reseed(false);
System.out.println("Reseeding complete");
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false");
_isRunning = false;
}
@ -59,7 +65,7 @@ public class ReseedHandler {
* save them into this router's netDb dir.
*
*/
private static void reseed() {
private static void reseed(boolean echoStatus) {
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
seedURL = DEFAULT_SEED_URL;
@ -85,10 +91,16 @@ public class ReseedHandler {
try {
fetchSeed(seedURL, (String)iter.next());
fetched++;
if (echoStatus) {
System.out.print(".");
if (fetched % 60 == 0)
System.out.println();
}
} catch (Exception e) {
errors++;
}
}
if (echoStatus) System.out.println();
} catch (Throwable t) {
I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class).error("Error reseeding", t);
}
@ -172,7 +184,11 @@ public class ReseedHandler {
}
public static void main(String args[]) {
reseed();
//System.out.println("Done reseeding");
if ( (args != null) && (args.length == 1) && (!Boolean.valueOf(args[0]).booleanValue()) ) {
System.out.println("Not reseeding, as requested");
return; // not reseeding on request
}
System.out.println("Reseeding");
reseed(true);
}
}

View File

@ -72,6 +72,24 @@ public class RouterConsoleRunner {
} catch (Throwable t) {
t.printStackTrace();
}
// we check the i2p installation directory (.) for a flag telling us not to reseed,
// but also check the home directory for that flag too, since new users installing i2p
// don't have an installation directory that they can put the flag in yet.
File noReseedFile = new File(new File(System.getProperty("user.home")), ".i2pnoreseed");
File noReseedFileAlt1 = new File(new File(System.getProperty("user.home")), "noreseed.i2p");
File noReseedFileAlt2 = new File(".i2pnoreseed");
File noReseedFileAlt3 = new File("noreseed.i2p");
if (!noReseedFile.exists() && !noReseedFileAlt1.exists() && !noReseedFileAlt2.exists() && !noReseedFileAlt3.exists()) {
File netDb = new File("netDb");
// sure, some of them could be "my.info" or various leaseSet- files, but chances are,
// if someone has those files, they've already been seeded (at least enough to let them
// get i2p started - they can reseed later in the web console)
String names[] = (netDb.exists() ? netDb.list() : null);
if ( (names == null) || (names.length < 15) ) {
ReseedHandler.requestReseed();
}
}
NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
I2PThread t = new I2PThread(fetcher, "NewsFetcher");

View File

@ -95,7 +95,7 @@ public class SummaryHelper {
}
public boolean allowReseed() {
return (_context.netDb().getKnownRouters() < 10);
return (_context.netDb().getKnownRouters() < 30);
}
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
@ -108,7 +108,7 @@ public class SummaryHelper {
case CommSystemFacade.STATUS_DIFFERENT:
return "ERR-SymmetricNAT";
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
return "ERR-Reject";
return "OK (NAT)";
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
default:
return "Unknown";
@ -206,14 +206,12 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
RateStat receiveRate = _context.statManager().getRate("bw.recvRate");
if (receiveRate == null) return "0.0";
Rate rate = receiveRate.getRate(60*1000);
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(bps);
double kbps = rate.getAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(kbps);
}
/**
* How fast we have been sending data over the last minute (pretty printed
@ -224,14 +222,12 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
RateStat receiveRate = _context.statManager().getRate("bw.sendRate");
if (receiveRate == null) return "0.0";
Rate rate = receiveRate.getRate(60*1000);
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(bps);
double kbps = rate.getAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(kbps);
}
/**
@ -243,14 +239,12 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
RateStat receiveRate = _context.statManager().getRate("bw.recvRate");
if (receiveRate == null) return "0.0";
Rate rate = receiveRate.getRate(5*60*1000);
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(bps);
double kbps = rate.getAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(kbps);
}
/**
@ -262,14 +256,12 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
RateStat receiveRate = _context.statManager().getRate("bw.sendRate");
if (receiveRate == null) return "0.0";
Rate rate = receiveRate.getRate(5*60*1000);
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(bps);
double kbps = rate.getAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(kbps);
}
/**
@ -281,20 +273,11 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes();
RateStat receiveRate = _context.statManager().getRate("bw.recvRate");
if (receiveRate == null) return "0.0";
double kbps = receiveRate.getLifetimeAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
// we use the unadjusted time, since thats what getWhenStarted is based off
long lifetime = _context.clock().now()-_context.clock().getOffset()
- _context.router().getWhenStarted();
lifetime /= 1000;
if (received > 0) {
double receivedKBps = received / (lifetime*1024.0);
return fmt.format(receivedKBps);
} else {
return "0.0";
}
return fmt.format(kbps);
}
/**
@ -306,20 +289,11 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes();
RateStat sendRate = _context.statManager().getRate("bw.sendRate");
if (sendRate == null) return "0.0";
double kbps = sendRate.getLifetimeAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
// we use the unadjusted time, since thats what getWhenStarted is based off
long lifetime = _context.clock().now()-_context.clock().getOffset()
- _context.router().getWhenStarted();
lifetime /= 1000;
if (sent > 0) {
double sendKBps = sent / (lifetime*1024.0);
return fmt.format(sendKBps);
} else {
return "0.0";
}
return fmt.format(kbps);
}
/**

View File

@ -3,6 +3,7 @@ package net.i2p.router.web;
import java.io.File;
import java.text.DecimalFormat;
import net.i2p.I2PAppContext;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
@ -93,7 +94,7 @@ public class UpdateHandler {
public void run() {
_isRunning = true;
update();
System.setProperty("net.i2p.router.web.ReseedHandler.updateInProgress", "false");
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
_isRunning = false;
}
private void update() {
@ -143,7 +144,7 @@ public class UpdateHandler {
buf.append(" transferred<br />");
_status = buf.toString();
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
_status = "<b>Update downloaded</b><br />";
TrustedUpdate up = new TrustedUpdate(_context);
boolean ok = up.migrateVerified(RouterVersion.VERSION, SIGNED_UPDATE_FILE, "i2pupdate.zip");
@ -165,6 +166,7 @@ public class UpdateHandler {
_status = "<b>Transfer failed</b><br />";
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
}
private void restart() {

View File

@ -2,7 +2,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - logs</title>
<title>I2P Router Console - config networking</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
@ -28,60 +28,38 @@
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigNetHandler.nonce")%>" />
<input type="hidden" name="action" value="blah" />
UDP port: <i><jsp:getProperty name="nethelper" property="udpPort" /></i><br />
<!-- <input name="udpPort" type="text" size="5" value="<jsp:getProperty name="nethelper" property="udpPort" />" /><br /> -->
<b>You must poke a hole in your firewall or NAT (if applicable) to receive new inbound UDP packets on
this port from arbitrary peers (this requirement will be removed in i2p 0.6.1, but is necessary now)</b><br />
TCP port: <input name="tcpPort" type="text" size="5" value="<jsp:getProperty name="nethelper" property="tcpPort" />" /> <br />
<b>You must poke a hole in your firewall or NAT (if applicable) so that you can receive inbound TCP
connections on it (this requirement will be removed in i2p 0.6.1, but is necessary now)</b>
<br />
<b>External UDP address:</b> <i><jsp:getProperty name="nethelper" property="udpAddress" /></i><br />
<b>Require SSU introductions? </b>
<input type="checkbox" name="requireIntroductions" value="true" <jsp:getProperty name="nethelper" property="requireIntroductionsChecked" /> /><br />
<p>If you can, please poke a hole in your NAT or firewall to allow unsolicited UDP packets to reach
you on your external UDP address. If you can't, I2P now includes supports UDP hole punching
with "SSU introductions" - peers who will relay a request from someone you don't know to your
router for your router so that you can make an outbound connection to them. I2P will use these
introductions automatically if it detects that the port is not forwarded (as shown by
the <i>Status: OK (NAT)</i> line), or you can manually require them here.
Users behind symmetric NATs, such as OpenBSD's pf, are not currently supported.</p>
<input type="submit" name="recheckReachability" value="Check network reachability..." />
<hr />
<b>Bandwidth limiter</b><br />
Inbound rate:
<input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBytes per second
<input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBps
bursting up to
<input name="inboundburstrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundBurstRate" />" /> KBps for
<jsp:getProperty name="nethelper" property="inboundBurstFactorBox" /><br />
Outbound rate:
<input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBytes per second
<input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBps
bursting up to
<input name="outboundburstrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundBurstRate" />" /> KBps for
<jsp:getProperty name="nethelper" property="outboundBurstFactorBox" /><br />
<i>A negative rate means there is no limit</i><br />
<i>KBps = kilobytes per second = 1024 bytes per second.<br />
A negative rate means a default limit of 16KBytes per second.</i><br />
Bandwidth share percentage:
<jsp:getProperty name="nethelper" property="sharePercentageBox" /><br />
Sharing a higher percentage will improve your anonymity and help the network
<hr />
Enable internal time synchronization? <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
<i>If disabled, your machine <b>must</b> be NTP synchronized - your clock must always
be within a few seconds of "correct". You will need to be able to send outbound UDP
packets on port 123 to one of the pool.ntp.org machines (or some other SNTP server).</i>
<hr />
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
<i>Changing the TCP or UDP port will force a 'soft restart' - dropping your connections and clients as
if the router was stopped and restarted. <b>Please be patient</b> - it may take
a few seconds to complete.</i>
</form>
<hr />
<b>Advanced network config:</b>
<p>
One advanced network option has to do with reseeding - you should never need to
reseed your router as long as you can find at least one other peer on the network. However,
when you do need to reseed, a link will show up on the left hand side which will
fetch all of the routerInfo-* files from http://dev.i2p.net/i2pdb/. That URL is just an
apache folder pointing at the netDb/ directory of a router - anyone can run one, and you can
configure your router to seed off an alternate URL by adding the java environmental property
"i2p.reseedURL=someURL" (e.g. java -Di2p.reseedURL=http://dev.i2p.net/i2pdb/ ...). You can
also do it manually by getting routerInfo-*.dat files from someone (a friend, someone on IRC,
whatever) and saving them to your netDb/ directory.</p>
<p>
With the SSU transport, the internal UDP port may be different from the external
UDP port (in case of a firewall/NAT) - the UDP port field above specifies the
external one and assumes they are the same, but if you want to set the internal
port to something else, you can add "i2np.udp.internalPort=1234" to the
<a href="configadvanced.jsp">advanced</a> config and restart the router.
</p>
</div>
</body>

View File

@ -3,7 +3,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - config clients</title>
<title>I2P Router Console - config logging</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
<jsp:useBean class="net.i2p.router.web.ConfigLoggingHelper" id="logginghelper" scope="request" />

View File

@ -3,7 +3,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - config clients</title>
<title>I2P Router Console - config service</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>

View File

@ -45,7 +45,7 @@ more information).</p>
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,
which other client applications (such the <a href="http://duck.i2p/i2p-bt/">bittorrent port</a>) can use.
There is also an optimized library for doing large number calculations - jbigi - which in turn uses the
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. For
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. Launchers for windows users are built with <a href="http://launch4j.sourceforge.net/">Launch4J</a>, and the installer is built with <a href="http://www.izforge.com/izpack/">IzPack</a>. For
details on other applications available, as well as their licenses, please see the
<a href="http://www.i2p.net/licenses">license policy</a>. Source for the I2P code and most bundled
client applications can be found on our <a href="http://www.i2p.net/download">download page</a>, and is

View File

@ -15,14 +15,16 @@
</div>
<h4>
<a href="susimail/susimail">Susimail</a> |
<a href="susidns/">SusiDNS</a> |
<a href="syndie/">Syndie</a> |
<a href="i2ptunnel/">I2PTunnel</a> |
<a href="tunnels.jsp">Tunnels</a> |
<a href="profiles.jsp">Profiles</a> |
<a href="netdb.jsp">NetDB</a> |
<a href="logs.jsp">Logs</a> |
<a href="oldconsole.jsp">Internals</a> |
<a href="oldstats.jsp">Stats</a> |
<a href="i2ptunnel/" target="_blank">I2PTunnel</a> |
<a href="susimail/susimail" target="_blank">Susimail</a>
<a href="oldconsole.jsp">Internals</a>
<jsp:useBean class="net.i2p.router.web.NavHelper" id="navhelper" scope="request" />
<jsp:setProperty name="navhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<jsp:getProperty name="navhelper" property="clientAppLinks" />

View File

@ -3,7 +3,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - home</title>
<title>I2P Router Console - internals</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>

View File

@ -3,7 +3,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - home</title>
<title>I2P Router Console - statistics</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>

View File

@ -40,7 +40,7 @@
<b>High capacity:</b> <jsp:getProperty name="helper" property="highCapacityPeers" /><br />
<b>Well integrated:</b> <jsp:getProperty name="helper" property="wellIntegratedPeers" /><br />
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br />
<!-- <b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /> -->
<b>Known:</b> <jsp:getProperty name="helper" property="allPeers" /><br /><%
if (helper.getActivePeers() <= 0) {
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%

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

@ -24,8 +24,8 @@ public class Connection {
private Log _log;
private ConnectionManager _connectionManager;
private Destination _remotePeer;
private byte _sendStreamId[];
private byte _receiveStreamId[];
private long _sendStreamId;
private long _receiveStreamId;
private long _lastSendTime;
private long _lastSendId;
private boolean _resetReceived;
@ -72,7 +72,7 @@ public class Connection {
private long _lifetimeDupMessageSent;
private long _lifetimeDupMessageReceived;
public static final long MAX_RESEND_DELAY = 5*1000;
public static final long MAX_RESEND_DELAY = 8*1000;
public static final long MIN_RESEND_DELAY = 3*1000;
/** wait up to 5 minutes after disconnection so we can ack/close packets */
@ -205,7 +205,7 @@ public class Connection {
_resetSent = true;
if (_resetSentOn <= 0)
_resetSentOn = _context.clock().now();
if ( (_remotePeer == null) || (_sendStreamId == null) ) return;
if ( (_remotePeer == null) || (_sendStreamId <= 0) ) return;
PacketLocal reply = new PacketLocal(_context, _remotePeer);
reply.setFlag(Packet.FLAG_RESET);
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
@ -256,14 +256,18 @@ public class Connection {
remaining = 0;
if (packet.isFlagSet(Packet.FLAG_CLOSE) || (remaining < 2)) {
packet.setOptionalDelay(0);
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
} else {
int delay = _options.getRTT() / 2;
int delay = _options.getRTO() / 2;
packet.setOptionalDelay(delay);
_log.debug("Requesting ack delay of " + delay + "ms for packet " + packet);
if (delay > 0)
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Requesting ack delay of " + delay + "ms for packet " + packet);
}
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
long timeout = _options.getRTT() + MIN_RESEND_DELAY;
long timeout = _options.getRTO();
if (timeout > MAX_RESEND_DELAY)
timeout = MAX_RESEND_DELAY;
if (_log.shouldLog(Log.DEBUG))
@ -307,16 +311,20 @@ public class Connection {
}
List ackPackets(long ackThrough, long nacks[]) {
if (nacks == null) {
_highestAckedThrough = ackThrough;
if (ackThrough < _highestAckedThrough) {
// dupack which won't tell us anything
} else {
long lowest = -1;
for (int i = 0; i < nacks.length; i++) {
if ( (lowest < 0) || (nacks[i] < lowest) )
lowest = nacks[i];
if (nacks == null) {
_highestAckedThrough = ackThrough;
} else {
long lowest = -1;
for (int i = 0; i < nacks.length; i++) {
if ( (lowest < 0) || (nacks[i] < lowest) )
lowest = nacks[i];
}
if (lowest - 1 > _highestAckedThrough)
_highestAckedThrough = lowest - 1;
}
if (lowest - 1 > _highestAckedThrough)
_highestAckedThrough = lowest - 1;
}
List acked = null;
@ -463,7 +471,9 @@ public class Connection {
_receiver.destroy();
if (_activityTimer != null)
SimpleTimer.getInstance().removeEvent(_activityTimer);
_activityTimer = null;
//_activityTimer = null;
if (_inputStream != null)
_inputStream.streamErrorOccurred(new IOException("disconnected!"));
if (_disconnectScheduledOn < 0) {
_disconnectScheduledOn = _context.clock().now();
@ -514,17 +524,30 @@ public class Connection {
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
private boolean _remotePeerSet = false;
/** who are we talking with */
public Destination getRemotePeer() { return _remotePeer; }
public void setRemotePeer(Destination peer) { _remotePeer = peer; }
public void setRemotePeer(Destination peer) {
if (_remotePeerSet) throw new RuntimeException("Remote peer already set [" + _remotePeer + ", " + peer + "]");
_remotePeerSet = true;
_remotePeer = peer;
}
private boolean _sendStreamIdSet = false;
/** what stream do we send data to the peer on? */
public byte[] getSendStreamId() { return _sendStreamId; }
public void setSendStreamId(byte[] id) { _sendStreamId = id; }
public long getSendStreamId() { return _sendStreamId; }
public void setSendStreamId(long id) {
if (_sendStreamIdSet) throw new RuntimeException("Send stream ID already set [" + _sendStreamId + ", " + id + "]");
_sendStreamIdSet = true;
_sendStreamId = id;
}
private boolean _receiveStreamIdSet = false;
/** stream the peer sends data to us on. (may be null) */
public byte[] getReceiveStreamId() { return _receiveStreamId; }
public void setReceiveStreamId(byte[] id) {
public long getReceiveStreamId() { return _receiveStreamId; }
public void setReceiveStreamId(long id) {
if (_receiveStreamIdSet) throw new RuntimeException("Receive stream ID already set [" + _receiveStreamId + ", " + id + "]");
_receiveStreamIdSet = true;
_receiveStreamId = id;
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
@ -651,7 +674,7 @@ public class Connection {
void waitForConnect() {
long expiration = _context.clock().now() + _options.getConnectTimeout();
while (true) {
if (_connected && (_receiveStreamId != null) && (_sendStreamId != null) ) {
if (_connected && (_receiveStreamId > 0) && (_sendStreamId > 0) ) {
// w00t
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): Connected and we have stream IDs");
@ -695,11 +718,19 @@ public class Connection {
}
private void resetActivityTimer() {
if (_options.getInactivityTimeout() <= 0) return;
if (_activityTimer == null) return;
if (_options.getInactivityTimeout() <= 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Resetting the inactivity timer, but its gone!", new Exception("where did it go?"));
return;
}
if (_activityTimer == null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Resetting the inactivity timer, but its gone!", new Exception("where did it go?"));
return;
}
long howLong = _activityTimer.getTimeLeft();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Resetting the inactivity timer to " + howLong);
_log.debug("Resetting the inactivity timer to " + howLong, new Exception("Reset by"));
// this will get rescheduled, and rescheduled, and rescheduled...
SimpleTimer.getInstance().addEvent(_activityTimer, howLong);
}
@ -707,15 +738,34 @@ public class Connection {
private class ActivityTimer implements SimpleTimer.TimedEvent {
public void timeReached() {
// uh, nothing more to do...
if (!_connected) return;
if (!_connected) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but we are already closed");
return;
}
// we got rescheduled already
if (getTimeLeft() > 0) return;
long left = getTimeLeft();
if (left > 0) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is time left (" + left + ")");
SimpleTimer.getInstance().addEvent(ActivityTimer.this, left);
return;
}
// these are either going to time out or cause further rescheduling
if (getUnackedPacketsSent() > 0) return;
if (getUnackedPacketsSent() > 0) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there are unacked packets");
return;
}
// wtf, this shouldn't have been scheduled
if (_options.getInactivityTimeout() <= 0) return;
if (_options.getInactivityTimeout() <= 0) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is no timer...");
return;
}
// if one of us can't talk...
if ( (_closeSentOn > 0) || (_closeReceivedOn > 0) ) return;
if ( (_closeSentOn > 0) || (_closeReceivedOn > 0) ) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but we are closing");
return;
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, with action=" + _options.getInactivityAction());
// bugger it, might as well do the hard work now
switch (_options.getInactivityAction()) {
@ -741,7 +791,9 @@ public class Connection {
_log.debug(buf.toString());
}
disconnect(true);
_inputStream.streamErrorOccurred(new IOException("Inactivity timeout"));
_outputStream.streamErrorOccurred(new IOException("Inactivity timeout"));
disconnect(false);
break;
}
}
@ -762,18 +814,19 @@ public class Connection {
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("[Connection ");
if (_receiveStreamId != null)
buf.append(Base64.encode(_receiveStreamId));
if (_receiveStreamId > 0)
buf.append(Packet.toId(_receiveStreamId));
else
buf.append("unknown");
buf.append("<-->");
if (_sendStreamId != null)
buf.append(Base64.encode(_sendStreamId));
if (_sendStreamId > 0)
buf.append(Packet.toId(_sendStreamId));
else
buf.append("unknown");
buf.append(" wsize: ").append(_options.getWindowSize());
buf.append(" cwin: ").append(_congestionWindowEnd - _highestAckedThrough);
buf.append(" rtt: ").append(_options.getRTT());
buf.append(" rto: ").append(_options.getRTO());
// not synchronized to avoid some kooky races
buf.append(" unacked outbound: ").append(_outboundPackets.size()).append(" ");
/*
@ -877,11 +930,14 @@ public class Connection {
}
// revamp various fields, in case we need to ack more, etc
_inputStream.updateAcks(_packet);
_packet.setOptionalDelay(getOptions().getChoke());
int choke = getOptions().getChoke();
_packet.setOptionalDelay(choke);
if (choke > 0)
_packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
_packet.setOptionalMaxSize(getOptions().getMaxMessageSize());
_packet.setResendDelay(getOptions().getResendDelay());
_packet.setReceiveStreamId(_receiveStreamId);
_packet.setSendStreamId(_sendStreamId);
//_packet.setReceiveStreamId(_receiveStreamId);
//_packet.setSendStreamId(_sendStreamId);
int newWindowSize = getOptions().getWindowSize();
@ -950,10 +1006,10 @@ public class Connection {
disconnect(false);
} else {
//long timeout = _options.getResendDelay() << numSends;
long rtt = _options.getRTT();
if (rtt < MIN_RESEND_DELAY)
rtt = MIN_RESEND_DELAY;
long timeout = rtt << (numSends-1);
long rto = _options.getRTO();
if (rto < MIN_RESEND_DELAY)
rto = MIN_RESEND_DELAY;
long timeout = rto << (numSends-1);
if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) )
timeout = MAX_RESEND_DELAY;
if (_log.shouldLog(Log.DEBUG))

View File

@ -2,6 +2,7 @@ package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
@ -142,15 +143,18 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
data.setValid(size);
data.setOffset(0);
packet.setPayload(data);
if ( (ackOnly && !forceIncrement) && (!isFirst) )
packet.setSequenceNum(0);
if ( (ackOnly && !forceIncrement) && (!isFirst) )
packet.setSequenceNum(0);
else
packet.setSequenceNum(con.getNextOutboundPacketNum());
packet.setSendStreamId(con.getSendStreamId());
packet.setReceiveStreamId(con.getReceiveStreamId());
con.getInputStream().updateAcks(packet);
packet.setOptionalDelay(con.getOptions().getChoke());
int choke = con.getOptions().getChoke();
packet.setOptionalDelay(choke);
if (choke > 0)
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
packet.setResendDelay(con.getOptions().getResendDelay());
if (con.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
@ -166,6 +170,9 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
packet.setOptionalFrom(con.getSession().getMyDestination());
packet.setOptionalMaxSize(con.getOptions().getMaxMessageSize());
}
if (DataHelper.eq(con.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) {
packet.setFlag(Packet.FLAG_NO_ACK);
}
// don't set the closed flag if this is a plain ACK and there are outstanding
// packets sent, otherwise the other side could receive the CLOSE prematurely,

View File

@ -127,7 +127,7 @@ class ConnectionHandler {
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setAckThrough(packet.getSequenceNum());
reply.setSendStreamId(packet.getReceiveStreamId());
reply.setReceiveStreamId(null);
reply.setReceiveStreamId(0);
reply.setOptionalFrom(_manager.getSession().getMyDestination());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending RST: " + reply + " because of " + packet);

View File

@ -31,9 +31,9 @@ public class ConnectionManager {
private PacketQueue _outboundQueue;
private SchedulerChooser _schedulerChooser;
private ConnectionPacketHandler _conPacketHandler;
/** Inbound stream ID (ByteArray) to Connection map */
/** Inbound stream ID (Long) to Connection map */
private Map _connectionByInboundId;
/** Ping ID (ByteArray) to PingRequest */
/** Ping ID (Long) to PingRequest */
private Map _pendingPings;
private boolean _allowIncoming;
private int _maxConcurrentStreams;
@ -71,16 +71,16 @@ public class ConnectionManager {
_context.statManager().createRateStat("stream.receiveActive", "How many streams are active when a new one is received (period being not yet dropped)", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
}
Connection getConnectionByInboundId(byte[] id) {
Connection getConnectionByInboundId(long id) {
synchronized (_connectionLock) {
return (Connection)_connectionByInboundId.get(new ByteArray(id));
return (Connection)_connectionByInboundId.get(new Long(id));
}
}
/**
* not guaranteed to be unique, but in case we receive more than one packet
* on an inbound connection that we havent ack'ed yet...
*/
Connection getConnectionByOutboundId(byte[] id) {
Connection getConnectionByOutboundId(long id) {
synchronized (_connectionLock) {
for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
@ -107,8 +107,7 @@ public class ConnectionManager {
*/
public Connection receiveConnection(Packet synPacket) {
Connection con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, new ConnectionOptions(_defaultOptions));
byte receiveId[] = new byte[4];
_context.random().nextBytes(receiveId);
long receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
boolean reject = false;
int active = 0;
int total = 0;
@ -122,16 +121,13 @@ public class ConnectionManager {
reject = true;
} else {
while (true) {
ByteArray ba = new ByteArray(receiveId);
Connection oldCon = (Connection)_connectionByInboundId.put(ba, con);
Connection oldCon = (Connection)_connectionByInboundId.put(new Long(receiveId), con);
if (oldCon == null) {
break;
} else {
_connectionByInboundId.put(ba, oldCon);
_connectionByInboundId.put(new Long(receiveId), oldCon);
// receiveId already taken, try another
// (need to realloc receiveId, as ba.getData() points to the old value)
receiveId = new byte[4];
_context.random().nextBytes(receiveId);
receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
}
}
}
@ -148,7 +144,7 @@ public class ConnectionManager {
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setAckThrough(synPacket.getSequenceNum());
reply.setSendStreamId(synPacket.getReceiveStreamId());
reply.setReceiveStreamId(null);
reply.setReceiveStreamId(0);
reply.setOptionalFrom(_session.getMyDestination());
// this just sends the packet - no retries or whatnot
_outboundQueue.enqueue(reply);
@ -160,7 +156,7 @@ public class ConnectionManager {
con.getPacketHandler().receivePacket(synPacket, con);
} catch (I2PException ie) {
synchronized (_connectionLock) {
_connectionByInboundId.remove(new ByteArray(receiveId));
_connectionByInboundId.remove(new Long(receiveId));
}
return null;
}
@ -179,8 +175,7 @@ public class ConnectionManager {
*/
public Connection connect(Destination peer, ConnectionOptions opts) {
Connection con = null;
byte receiveId[] = new byte[4];
_context.random().nextBytes(receiveId);
long receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
long expiration = _context.clock().now() + opts.getConnectTimeout();
if (opts.getConnectTimeout() <= 0)
expiration = _context.clock().now() + DEFAULT_STREAM_DELAY_MAX;
@ -213,11 +208,10 @@ public class ConnectionManager {
con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, opts);
con.setRemotePeer(peer);
ByteArray ba = new ByteArray(receiveId);
while (_connectionByInboundId.containsKey(ba)) {
_context.random().nextBytes(receiveId);
while (_connectionByInboundId.containsKey(new Long(receiveId))) {
receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
}
_connectionByInboundId.put(ba, con);
_connectionByInboundId.put(new Long(receiveId), con);
break; // stop looping as a psuedo-wait
}
}
@ -284,7 +278,7 @@ public class ConnectionManager {
public void removeConnection(Connection con) {
boolean removed = false;
synchronized (_connectionLock) {
Object o = _connectionByInboundId.remove(new ByteArray(con.getReceiveStreamId()));
Object o = _connectionByInboundId.remove(new Long(con.getReceiveStreamId()));
removed = (o == con);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Connection removed? " + removed + " remaining: "
@ -320,11 +314,9 @@ public class ConnectionManager {
return ping(peer, timeoutMs, blocking, null, null, null);
}
public boolean ping(Destination peer, long timeoutMs, boolean blocking, SessionKey keyToUse, Set tagsToSend, PingNotifier notifier) {
byte id[] = new byte[4];
_context.random().nextBytes(id);
ByteArray ba = new ByteArray(id);
Long id = new Long(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1);
PacketLocal packet = new PacketLocal(_context, peer);
packet.setSendStreamId(id);
packet.setSendStreamId(id.longValue());
packet.setFlag(Packet.FLAG_ECHO);
packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
packet.setOptionalFrom(_session.getMyDestination());
@ -336,7 +328,7 @@ public class ConnectionManager {
PingRequest req = new PingRequest(peer, packet, notifier);
synchronized (_pendingPings) {
_pendingPings.put(ba, req);
_pendingPings.put(id, req);
}
_outboundQueue.enqueue(packet);
@ -349,10 +341,10 @@ public class ConnectionManager {
}
synchronized (_pendingPings) {
_pendingPings.remove(ba);
_pendingPings.remove(id);
}
} else {
SimpleTimer.getInstance().addEvent(new PingFailed(ba, notifier), timeoutMs);
SimpleTimer.getInstance().addEvent(new PingFailed(id, notifier), timeoutMs);
}
boolean ok = req.pongReceived();
@ -364,17 +356,17 @@ public class ConnectionManager {
}
private class PingFailed implements SimpleTimer.TimedEvent {
private ByteArray _ba;
private Long _id;
private PingNotifier _notifier;
public PingFailed(ByteArray ba, PingNotifier notifier) {
_ba = ba;
public PingFailed(Long id, PingNotifier notifier) {
_id = id;
_notifier = notifier;
}
public void timeReached() {
boolean removed = false;
synchronized (_pendingPings) {
Object o = _pendingPings.remove(_ba);
Object o = _pendingPings.remove(_id);
if (o != null)
removed = true;
}
@ -411,11 +403,10 @@ public class ConnectionManager {
public boolean pongReceived() { return _ponged; }
}
void receivePong(byte pingId[]) {
ByteArray ba = new ByteArray(pingId);
void receivePong(long pingId) {
PingRequest req = null;
synchronized (_pendingPings) {
req = (PingRequest)_pendingPings.remove(ba);
req = (PingRequest)_pendingPings.remove(new Long(pingId));
}
if (req != null)
req.pong();

View File

@ -13,6 +13,8 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
private int _receiveWindow;
private int _profile;
private int _rtt;
private int _rttDev;
private int _rto;
private int _trend[];
private int _resendDelay;
private int _sendAckDelay;
@ -52,6 +54,8 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
public static final String PROP_SLOW_START_GROWTH_RATE_FACTOR = "i2p.streaming.slowStartGrowthRateFactor";
private static final int TREND_COUNT = 3;
static final int INITIAL_WINDOW_SIZE = 4;
static final int DEFAULT_MAX_SENDS = 8;
public ConnectionOptions() {
super();
@ -68,6 +72,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
public ConnectionOptions(ConnectionOptions opts) {
super(opts);
if (opts != null) {
setMaxWindowSize(opts.getMaxWindowSize());
setConnectDelay(opts.getConnectDelay());
setProfile(opts.getProfile());
setRTT(opts.getRTT());
@ -80,7 +85,6 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
setInactivityTimeout(opts.getInactivityTimeout());
setInactivityAction(opts.getInactivityAction());
setInboundBufferSize(opts.getInboundBufferSize());
setMaxWindowSize(opts.getMaxWindowSize());
setCongestionAvoidanceGrowthRateFactor(opts.getCongestionAvoidanceGrowthRateFactor());
setSlowStartGrowthRateFactor(opts.getSlowStartGrowthRateFactor());
}
@ -90,6 +94,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
super.init(opts);
_trend = new int[TREND_COUNT];
setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK));
setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, 4*1024));
@ -97,22 +102,23 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000));
setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 500));
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, 1));
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 10));
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, INITIAL_WINDOW_SIZE));
setMaxResends(getInt(opts, PROP_MAX_RESENDS, DEFAULT_MAX_SENDS));
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 2*60*1000));
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
setInboundBufferSize(getMaxMessageSize() * (Connection.MAX_WINDOW_SIZE + 2));
setCongestionAvoidanceGrowthRateFactor(getInt(opts, PROP_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR, 1));
setSlowStartGrowthRateFactor(getInt(opts, PROP_SLOW_START_GROWTH_RATE_FACTOR, 1));
setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DISCONNECT_TIMEOUT));
setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
}
public void setProperties(Properties opts) {
super.setProperties(opts);
if (opts == null) return;
if (opts.containsKey(PROP_MAX_WINDOW_SIZE))
setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
if (opts.containsKey(PROP_CONNECT_DELAY))
setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
if (opts.containsKey(PROP_PROFILE))
@ -124,17 +130,17 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
if (opts.containsKey(PROP_INITIAL_RECEIVE_WINDOW))
setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
if (opts.containsKey(PROP_INITIAL_RESEND_DELAY))
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 500));
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000));
if (opts.containsKey(PROP_INITIAL_ACK_DELAY))
setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 500));
if (opts.containsKey(PROP_INITIAL_WINDOW_SIZE))
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, 1));
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, INITIAL_WINDOW_SIZE));
if (opts.containsKey(PROP_MAX_RESENDS))
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 10));
setMaxResends(getInt(opts, PROP_MAX_RESENDS, DEFAULT_MAX_SENDS));
if (opts.containsKey(PROP_WRITE_TIMEOUT))
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
if (opts.containsKey(PROP_INACTIVITY_TIMEOUT))
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 2*60*1000));
if (opts.containsKey(PROP_INACTIVITY_ACTION))
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
setInboundBufferSize(getMaxMessageSize() * (Connection.MAX_WINDOW_SIZE + 2));
@ -145,8 +151,6 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
if (opts.containsKey(PROP_CONNECT_TIMEOUT))
setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DISCONNECT_TIMEOUT));
if (opts.containsKey(PROP_MAX_WINDOW_SIZE))
setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
}
/**
@ -191,6 +195,10 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
*/
public int getRTT() { return _rtt; }
public void setRTT(int ms) {
if (_rto == 0) {
_rttDev = ms;
_rto = (int)Connection.MAX_RESEND_DELAY;
}
synchronized (_trend) {
_trend[0] = _trend[1];
_trend[1] = _trend[2];
@ -201,10 +209,12 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
else
_trend[2] = 0;
}
_rtt = ms;
if (_rtt > 60*1000)
_rtt = 60*1000;
}
public int getRTO() { return _rto; }
/**
* If we have 3 consecutive rtt increases, we are trending upwards (1), or if we have
@ -225,7 +235,15 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
private static final double RTT_DAMPENING = 0.9;
public void updateRTT(int measuredValue) {
setRTT((int)(RTT_DAMPENING*_rtt + (1-RTT_DAMPENING)*measuredValue));
_rttDev = _rttDev + (int)(0.25d*(Math.abs(measuredValue-_rtt)-_rttDev));
int smoothed = (int)(RTT_DAMPENING*_rtt + (1-RTT_DAMPENING)*measuredValue);
_rto = smoothed + (_rttDev<<2);
if (_rto < Connection.MIN_RESEND_DELAY)
_rto = (int)Connection.MIN_RESEND_DELAY;
else if (_rto > Connection.MAX_RESEND_DELAY)
_rto = (int)Connection.MAX_RESEND_DELAY;
setRTT(smoothed);
}
/** How long after sending a packet will we wait before resending? */

View File

@ -96,16 +96,16 @@ public class ConnectionPacketHandler {
boolean allowAck = true;
if ( (!packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) &&
( (packet.getSendStreamId() == null) ||
(packet.getReceiveStreamId() == null) ||
(DataHelper.eq(packet.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) ||
(DataHelper.eq(packet.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) ) )
( (packet.getSendStreamId() <= 0) ||
(packet.getReceiveStreamId() <= 0) ) )
allowAck = false;
if (allowAck)
if (allowAck) {
isNew = con.getInputStream().messageReceived(packet.getSequenceNum(), packet.getPayload());
else
isNew = con.getInputStream().messageReceived(con.getInputStream().getHighestReadyBockId(), null);
} else {
con.getInputStream().notifyActivity();
isNew = false;
}
if ( (packet.getSequenceNum() == 0) && (packet.getPayloadSize() > 0) ) {
if (_log.shouldLog(Log.DEBUG))
@ -160,9 +160,7 @@ public class ConnectionPacketHandler {
}
}
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE) &&
((packet.getSendStreamId() == null) ||
DataHelper.eq(packet.getSendStreamId(), Packet.STREAM_ID_UNKNOWN) ) ) {
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE) && (packet.getSendStreamId() <= 0) ) {
// don't honor the ACK 0 in SYN packets received when the other side
// has obviously not seen our messages
} else {
@ -170,10 +168,16 @@ public class ConnectionPacketHandler {
}
con.eventOccurred();
if (fastAck) {
if (con.getLastSendTime() + 2000 < _context.clock().now()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fast ack for dup " + packet);
con.ackImmediately();
if (!isNew) {
// if we're congested (fastAck) but this is also a new packet,
// we've already scheduled an ack above, so there is no need to schedule
// a fast ack (we can wait a few ms)
} else {
if (con.getLastSendTime() + 2000 < _context.clock().now()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fast ack for dup " + packet);
con.ackImmediately();
}
}
}
@ -187,6 +191,7 @@ public class ConnectionPacketHandler {
}
private boolean ack(Connection con, long ackThrough, long nacks[], Packet packet, boolean isNew, boolean choke) {
if (ackThrough < 0) return false;
//if ( (nacks != null) && (nacks.length > 0) )
// con.getOptions().setRTT(con.getOptions().getRTT() + nacks.length*1000);
@ -196,8 +201,8 @@ public class ConnectionPacketHandler {
// could actually be acking data (this fixes the buggered up ack of packet 0 problem).
// this is called after packet verification, which places the stream IDs as necessary if
// the SYN verifies (so if we're acking w/out stream IDs, no SYN has been received yet)
if ( (packet != null) && (packet.getSendStreamId() != null) && (packet.getReceiveStreamId() != null) &&
(con != null) && (con.getSendStreamId() != null) && (con.getReceiveStreamId() != null) &&
if ( (packet != null) && (packet.getSendStreamId() > 0) && (packet.getReceiveStreamId() > 0) &&
(con != null) && (con.getSendStreamId() > 0) && (con.getReceiveStreamId() > 0) &&
(!DataHelper.eq(packet.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
(!DataHelper.eq(packet.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
(!DataHelper.eq(con.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
@ -315,6 +320,12 @@ public class ConnectionPacketHandler {
return congested;
}
/**
* If we don't know the send stream id yet (we're just creating a connection), allow
* the first three packets to come in. The first of those should be the SYN, of course...
*/
private static final int MAX_INITIAL_PACKETS = ConnectionOptions.INITIAL_WINDOW_SIZE;
/**
* Make sure this packet is ok and that we can continue processing its data.
*
@ -328,14 +339,14 @@ public class ConnectionPacketHandler {
} else {
verifySignature(packet, con);
if (con.getSendStreamId() == null) {
if (con.getSendStreamId() <= 0) {
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
con.setSendStreamId(packet.getReceiveStreamId());
con.setRemotePeer(packet.getOptionalFrom());
return true;
} else {
// neither RST nor SYN and we dont have the stream id yet?
if (packet.getSequenceNum() <= 2) {
if (packet.getSequenceNum() < MAX_INITIAL_PACKETS) {
return true;
} else {
if (_log.shouldLog(Log.ERROR))

View File

@ -44,6 +44,7 @@ public class MessageHandler implements I2PSessionListener {
_log.warn("Error receiving the message", ise);
return;
}
if (data == null) return;
Packet packet = new Packet();
try {
packet.readPacket(data, 0, data.length);

View File

@ -193,6 +193,8 @@ public class MessageInputStream extends InputStream {
}
}
public void notifyActivity() { synchronized (_dataLock) { _dataLock.notifyAll(); } }
/**
* A new message has arrived - toss it on the appropriate queue (moving
* previously pending messages to the ready queue if it fills the gap, etc).
@ -435,7 +437,9 @@ public class MessageInputStream extends InputStream {
*
*/
void streamErrorOccurred(IOException ioe) {
_streamError = ioe;
if (_streamError == null)
_streamError = ioe;
_locallyClosed = true;
synchronized (_dataLock) {
_dataLock.notifyAll();
}

View File

@ -312,11 +312,16 @@ public class MessageOutputStream extends OutputStream {
/** nonblocking close */
public void closeInternal() {
_closed = true;
_streamError = new IOException("Closed internally");
if (_streamError == null)
_streamError = new IOException("Closed internally");
clearData(true);
}
private void clearData(boolean shouldFlush) {
ByteArray ba = null;
synchronized (_dataLock) {
// flush any data, but don't wait for it
if (_dataReceiver != null)
if ( (_dataReceiver != null) && (_valid > 0) && shouldFlush)
_dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
@ -345,7 +350,9 @@ public class MessageOutputStream extends OutputStream {
}
void streamErrorOccurred(IOException ioe) {
_streamError = ioe;
if (_streamError == null)
_streamError = ioe;
clearData(false);
}
/**

View File

@ -10,6 +10,7 @@ import net.i2p.data.Destination;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Contain a single packet transferred as part of a streaming connection.
@ -51,8 +52,8 @@ import net.i2p.util.ByteCache;
*
*/
public class Packet {
private byte _sendStreamId[];
private byte _receiveStreamId[];
private long _sendStreamId;
private long _receiveStreamId;
private long _sequenceNum;
private long _ackThrough;
private long _nacks[];
@ -64,7 +65,6 @@ public class Packet {
private Destination _optionFrom;
private int _optionDelay;
private int _optionMaxSize;
private ByteCache _cache;
/**
* The receiveStreamId will be set to this when the packet doesn't know
@ -72,7 +72,9 @@ public class Packet {
* synchronize packet)
*
*/
public static final byte STREAM_ID_UNKNOWN[] = new byte[] { 0x00, 0x00, 0x00, 0x00 };
public static final long STREAM_ID_UNKNOWN = 0l;
public static final long MAX_STREAM_ID = 0xffffffffl;
/**
* This packet is creating a new socket connection (if the receiveStreamId
@ -135,43 +137,36 @@ public class Packet {
* ping reply (if receiveStreamId is set).
*/
public static final int FLAG_ECHO = (1 << 9);
/**
* If set, this packet doesn't really want to ack anything
*/
public static final int FLAG_NO_ACK = (1 << 10);
public static final int DEFAULT_MAX_SIZE = 32*1024;
private static final int MAX_DELAY_REQUEST = 65535;
public Packet() {
_cache = ByteCache.getInstance(128, MAX_PAYLOAD_SIZE);
}
public Packet() { }
/** what stream is this packet a part of? */
public byte[] getSendStreamId() {
if ( (_sendStreamId == null) || (DataHelper.eq(_sendStreamId, STREAM_ID_UNKNOWN)) )
return null;
else
return _sendStreamId;
}
public void setSendStreamId(byte[] id) {
private boolean _sendStreamIdSet = false;
/** what stream do we send data to the peer on? */
public long getSendStreamId() { return _sendStreamId; }
public void setSendStreamId(long id) {
if (_sendStreamIdSet) throw new RuntimeException("Send stream ID already set [" + _sendStreamId + ", " + id + "]");
_sendStreamIdSet = true;
_sendStreamId = id;
if ( (id != null) && (DataHelper.eq(id, STREAM_ID_UNKNOWN)) )
_sendStreamId = null;
}
private boolean _receiveStreamIdSet = false;
/**
* Stream that replies should be sent on. if the
* connection is still being built, this should be
* null.
*
* stream the replies should be sent on. this should be 0 if the
* connection is still being built.
*/
public byte[] getReceiveStreamId() {
if ( (_receiveStreamId == null) || (DataHelper.eq(_receiveStreamId, STREAM_ID_UNKNOWN)) )
return null;
else
return _receiveStreamId;
}
public void setReceiveStreamId(byte[] id) {
public long getReceiveStreamId() { return _receiveStreamId; }
public void setReceiveStreamId(long id) {
if (_receiveStreamIdSet) throw new RuntimeException("Receive stream ID already set [" + _receiveStreamId + ", " + id + "]");
_receiveStreamIdSet = true;
_receiveStreamId = id;
if ( (id != null) && (DataHelper.eq(id, STREAM_ID_UNKNOWN)) )
_receiveStreamId = null;
}
/** 0-indexed sequence number for this Packet in the sendStream */
@ -181,11 +176,21 @@ public class Packet {
/**
* The highest packet sequence number that received
* on the receiveStreamId. This field is ignored on the initial
* connection packet (where receiveStreamId is the unknown id).
* connection packet (where receiveStreamId is the unknown id) or
* if FLAG_NO_ACK is set.
*
*/
public long getAckThrough() { return _ackThrough; }
public void setAckThrough(long id) { _ackThrough = id; }
public long getAckThrough() {
if (isFlagSet(FLAG_NO_ACK))
return -1;
else
return _ackThrough;
}
public void setAckThrough(long id) {
if (id < 0)
setFlag(FLAG_NO_ACK);
_ackThrough = id;
}
/**
* List of packet sequence numbers below the getAckThrough() value
@ -209,8 +214,6 @@ public class Packet {
/** get the actual payload of the message. may be null */
public ByteArray getPayload() { return _payload; }
public void setPayload(ByteArray payload) {
//if ( (_payload != null) && (_payload != payload) )
// _cache.release(_payload);
_payload = payload;
if ( (payload != null) && (payload.getValid() > MAX_PAYLOAD_SIZE) )
throw new IllegalArgumentException("Too large payload: " + payload.getValid());
@ -219,15 +222,11 @@ public class Packet {
return (_payload == null ? 0 : _payload.getValid());
}
public void releasePayload() {
//if (_payload != null)
// _cache.release(_payload);
_payload = null;
//_payload = null;
}
public ByteArray acquirePayload() {
ByteArray old = _payload;
_payload = new ByteArray(new byte[Packet.MAX_PAYLOAD_SIZE]); //_cache.acquire();
//if (old != null)
// _cache.release(old);
_payload = new ByteArray(new byte[Packet.MAX_PAYLOAD_SIZE]);
return _payload;
}
@ -240,6 +239,7 @@ public class Packet {
else
_flags &= ~flag;
}
public void setFlags(int flags) { _flags = flags; }
/** the signature on the packet (only included if the flag for it is set) */
public Signature getOptionalSignature() { return _optionSignature; }
@ -263,7 +263,6 @@ public class Packet {
*/
public int getOptionalDelay() { return _optionDelay; }
public void setOptionalDelay(int delayMs) {
setFlag(FLAG_DELAY_REQUESTED, delayMs > 0);
if (delayMs > MAX_DELAY_REQUEST)
_optionDelay = MAX_DELAY_REQUEST;
else if (delayMs < 0)
@ -297,15 +296,9 @@ public class Packet {
*/
private int writePacket(byte buffer[], int offset, boolean includeSig) throws IllegalStateException {
int cur = offset;
if ( (_sendStreamId != null) && (_sendStreamId.length == 4) )
System.arraycopy(_sendStreamId, 0, buffer, cur, _sendStreamId.length);
else
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
DataHelper.toLong(buffer, cur, 4, (_sendStreamId >= 0 ? _sendStreamId : STREAM_ID_UNKNOWN));
cur += 4;
if ( (_receiveStreamId != null) && (_receiveStreamId.length == 4) )
System.arraycopy(_receiveStreamId, 0, buffer, cur, _receiveStreamId.length);
else
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
DataHelper.toLong(buffer, cur, 4, (_receiveStreamId >= 0 ? _receiveStreamId : STREAM_ID_UNKNOWN));
cur += 4;
DataHelper.toLong(buffer, cur, 4, _sequenceNum > 0 ? _sequenceNum : 0);
cur += 4;
@ -383,7 +376,7 @@ public class Packet {
size += 4; // sequenceNum
size += 4; // ackThrough
if (_nacks != null) {
size++; // nacks length
size++; // nacks length
size += 4 * _nacks.length;
} else {
size++; // nacks length
@ -425,32 +418,31 @@ public class Packet {
if (length < 22) // min header size
throw new IllegalArgumentException("Too small: len=" + buffer.length);
int cur = offset;
_sendStreamId = new byte[4];
System.arraycopy(buffer, cur, _sendStreamId, 0, 4);
setSendStreamId(DataHelper.fromLong(buffer, cur, 4));
cur += 4;
_receiveStreamId = new byte[4];
System.arraycopy(buffer, cur, _receiveStreamId, 0, 4);
setReceiveStreamId(DataHelper.fromLong(buffer, cur, 4));
cur += 4;
_sequenceNum = DataHelper.fromLong(buffer, cur, 4);
setSequenceNum(DataHelper.fromLong(buffer, cur, 4));
cur += 4;
_ackThrough = DataHelper.fromLong(buffer, cur, 4);
setAckThrough(DataHelper.fromLong(buffer, cur, 4));
cur += 4;
int numNacks = (int)DataHelper.fromLong(buffer, cur, 1);
cur++;
if (length < 22 + numNacks*4)
throw new IllegalArgumentException("Too small with " + numNacks + " nacks: " + length);
if (numNacks > 0) {
_nacks = new long[numNacks];
long nacks[] = new long[numNacks];
for (int i = 0; i < numNacks; i++) {
_nacks[i] = DataHelper.fromLong(buffer, cur, 4);
nacks[i] = DataHelper.fromLong(buffer, cur, 4);
cur += 4;
}
setNacks(nacks);
} else {
_nacks = null;
setNacks(null);
}
_resendDelay = (int)DataHelper.fromLong(buffer, cur, 1);
setResendDelay((int)DataHelper.fromLong(buffer, cur, 1));
cur++;
_flags = (int)DataHelper.fromLong(buffer, cur, 2);
setFlags((int)DataHelper.fromLong(buffer, cur, 2));
cur += 2;
int optionSize = (int)DataHelper.fromLong(buffer, cur, 2);
@ -466,33 +458,36 @@ public class Packet {
throw new IllegalArgumentException("length: " + length + " offset: " + offset + " begin: " + payloadBegin);
// skip ahead to the payload
_payload = new ByteArray(new byte[payloadSize]); //_cache.acquire();
System.arraycopy(buffer, payloadBegin, _payload.getData(), 0, payloadSize);
_payload.setValid(payloadSize);
_payload.setOffset(0);
//_payload = new ByteArray(new byte[payloadSize]);
_payload = new ByteArray(buffer, payloadBegin, payloadSize);
//System.arraycopy(buffer, payloadBegin, _payload.getData(), 0, payloadSize);
//_payload.setValid(payloadSize);
//_payload.setOffset(0);
// ok now lets go back and deal with the options
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
_optionDelay = (int)DataHelper.fromLong(buffer, cur, 2);
setOptionalDelay((int)DataHelper.fromLong(buffer, cur, 2));
cur += 2;
}
if (isFlagSet(FLAG_FROM_INCLUDED)) {
_optionFrom = new Destination();
Destination optionFrom = new Destination();
try {
cur += _optionFrom.readBytes(buffer, cur);
cur += optionFrom.readBytes(buffer, cur);
setOptionalFrom(optionFrom);
} catch (DataFormatException dfe) {
throw new IllegalArgumentException("Bad from field: " + dfe.getMessage());
}
}
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
_optionMaxSize = (int)DataHelper.fromLong(buffer, cur, 2);
setOptionalMaxSize((int)DataHelper.fromLong(buffer, cur, 2));
cur += 2;
}
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
_optionSignature = new Signature();
Signature optionSignature = new Signature();
byte buf[] = new byte[Signature.SIGNATURE_BYTES];
System.arraycopy(buffer, cur, buf, 0, Signature.SIGNATURE_BYTES);
_optionSignature.setData(buf);
optionSignature.setData(buf);
setOptionalSignature(optionSignature);
cur += Signature.SIGNATURE_BYTES;
}
}
@ -518,7 +513,12 @@ public class Packet {
}
boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, from.getSigningPublicKey());
if (!ok) {
ctx.logManager().getLog(Packet.class).error("Signature failed on " + toString(), new Exception("moo"));
Log l = ctx.logManager().getLog(Packet.class);
l.error("Signature failed on " + toString(), new Exception("moo"));
if (false) {
l.error(Base64.encode(buffer, 0, size));
l.error("Signature: " + Base64.encode(_optionSignature.getData()));
}
}
return ok;
}
@ -533,6 +533,12 @@ public class Packet {
setFlag(FLAG_SIGNATURE_INCLUDED);
int size = writePacket(buffer, offset, false);
_optionSignature = ctx.dsa().sign(buffer, offset, size, key);
if (false) {
Log l = ctx.logManager().getLog(Packet.class);
l.error("Signing: " + toString());
l.error(Base64.encode(buffer, 0, size));
l.error("Signature: " + Base64.encode(_optionSignature.getData()));
}
// jump into the signed data and inject the signature where we
// previously placed a bunch of zeroes
int signatureOffset = offset
@ -566,7 +572,7 @@ public class Packet {
else
buf.append('\t');
buf.append(toFlagString());
buf.append(" ACK ").append(_ackThrough);
buf.append(" ACK ").append(getAckThrough());
if (_nacks != null) {
buf.append(" NACK");
for (int i = 0; i < _nacks.length; i++) {
@ -578,11 +584,8 @@ public class Packet {
return buf;
}
static final String toId(byte id[]) {
if (id == null)
return Base64.encode(STREAM_ID_UNKNOWN);
else
return Base64.encode(id);
static final String toId(long id) {
return Base64.encode(DataHelper.toLong(4, id));
}
private final String toFlagString() {

View File

@ -22,19 +22,26 @@ public class PacketHandler {
private I2PAppContext _context;
private Log _log;
private int _lastDelay;
private int _dropped;
public PacketHandler(I2PAppContext ctx, ConnectionManager mgr) {
_manager = mgr;
_context = ctx;
_dropped = 0;
_log = ctx.logManager().getLog(PacketHandler.class);
_lastDelay = _context.random().nextInt(30*1000);
}
private boolean choke(Packet packet) {
if (false) {
// artificial choke: 2% random drop and a 0-30s
private boolean choke(Packet packet) {
if (true) return true;
//if ( (_dropped == 0) && true ) { //&& (_manager.getSent() <= 0) ) {
// _dropped++;
// return false;
//}
if (true) {
// artificial choke: 2% random drop and a 0-5s
// random tiered delay from 0-30s
if (_context.random().nextInt(100) >= 95) {
if (_context.random().nextInt(100) >= 98) {
displayPacket(packet, "DROP", null);
return false;
} else {
@ -42,7 +49,7 @@ public class PacketHandler {
/*
int delay = _context.random().nextInt(5*1000);
*/
int delay = _context.random().nextInt(6*1000);
int delay = _context.random().nextInt(1*1000);
int delayFactor = _context.random().nextInt(100);
if (delayFactor > 80) {
if (delayFactor > 98)
@ -90,14 +97,12 @@ public class PacketHandler {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("packet received: " + packet);
byte sendId[] = packet.getSendStreamId();
if (!isNonZero(sendId))
sendId = null;
long sendId = packet.getSendStreamId();
Connection con = (sendId != null ? _manager.getConnectionByInboundId(sendId) : null);
Connection con = (sendId > 0 ? _manager.getConnectionByInboundId(sendId) : null);
if (con != null) {
receiveKnownCon(con, packet);
displayPacket(packet, "RECV", "wsize " + con.getOptions().getWindowSize());
displayPacket(packet, "RECV", "wsize " + con.getOptions().getWindowSize() + " rto " + con.getOptions().getRTO());
} else {
receiveUnknownCon(packet, sendId);
displayPacket(packet, "UNKN", null);
@ -120,9 +125,9 @@ public class PacketHandler {
private void receiveKnownCon(Connection con, Packet packet) {
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
if (packet.getSendStreamId() != null) {
if (packet.getSendStreamId() > 0) {
receivePing(packet);
} else if (packet.getReceiveStreamId() != null) {
} else if (packet.getReceiveStreamId() > 0) {
receivePong(packet);
} else {
if (_log.shouldLog(Log.WARN))
@ -155,17 +160,17 @@ public class PacketHandler {
_log.warn("Received forged reset for " + con, ie);
}
} else {
if ( (con.getSendStreamId() == null) ||
if ( (con.getSendStreamId() <= 0) ||
(DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) ) {
byte oldId[] =con.getSendStreamId();
long oldId =con.getSendStreamId();
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) // con fully established, w00t
con.setSendStreamId(packet.getReceiveStreamId());
try {
con.getPacketHandler().receivePacket(packet, con);
} catch (I2PException ie) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received forged packet for " + con + ": " + packet, ie);
if (_log.shouldLog(Log.ERROR))
_log.error("Received forged packet for " + con + "/" + oldId + ": " + packet, ie);
con.setSendStreamId(oldId);
}
} else if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
@ -207,11 +212,11 @@ public class PacketHandler {
_manager.getPacketQueue().enqueue(reply);
}
private void receiveUnknownCon(Packet packet, byte sendId[]) {
private void receiveUnknownCon(Packet packet, long sendId) {
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
if (packet.getSendStreamId() != null) {
if (packet.getSendStreamId() > 0) {
receivePing(packet);
} else if (packet.getReceiveStreamId() != null) {
} else if (packet.getReceiveStreamId() > 0) {
receivePong(packet);
} else {
if (_log.shouldLog(Log.WARN))
@ -221,7 +226,7 @@ public class PacketHandler {
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received on an unknown stream (and not an ECHO): " + packet);
if (sendId == null) {
if (sendId <= 0) {
Connection con = _manager.getConnectionByOutboundId(packet.getReceiveStreamId());
if (con != null) {
if (con.getAckedPackets() <= 0) {
@ -250,7 +255,7 @@ public class PacketHandler {
}
_log.warn("Packet belongs to no other cons: " + packet + " connections: "
+ buf.toString() + " sendId: "
+ (sendId != null ? Base64.encode(sendId) : " unknown"));
+ (sendId > 0 ? Packet.toId(sendId) : " unknown"));
}
packet.releasePayload();
}
@ -282,25 +287,7 @@ public class PacketHandler {
_manager.receivePong(packet.getReceiveStreamId());
}
private static final boolean isValidMatch(byte conStreamId[], byte packetStreamId[]) {
if ( (conStreamId == null) || (packetStreamId == null) ||
(conStreamId.length != packetStreamId.length) )
return false;
boolean nonZeroFound = false;
for (int i = 0; i < conStreamId.length; i++) {
if (conStreamId[i] != packetStreamId[i]) return false;
if (conStreamId[i] != 0x0) nonZeroFound = true;
}
return nonZeroFound;
}
private static final boolean isNonZero(byte[] b) {
boolean nonZeroFound = false;
for (int i = 0; b != null && i < b.length; i++) {
if (b[i] != 0x0)
nonZeroFound = true;
}
return nonZeroFound;
private static final boolean isValidMatch(long conStreamId, long packetStreamId) {
return ( (conStreamId == packetStreamId) && (conStreamId != 0) );
}
}

View File

@ -5,7 +5,6 @@ import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
@ -27,7 +26,6 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
private long _ackOn;
private long _cancelledOn;
private SimpleTimer.TimedEvent _resendEvent;
private ByteCache _cache = ByteCache.getInstance(128, MAX_PAYLOAD_SIZE);
public PacketLocal(I2PAppContext ctx, Destination to) {
this(ctx, to, null);
@ -71,8 +69,11 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
public void prepare() {
if (_connection != null)
_connection.getInputStream().updateAcks(this);
if (_numSends > 0) // so we can debug to differentiate resends
if (_numSends > 0) {
// so we can debug to differentiate resends
setOptionalDelay(_numSends * 1000);
setFlag(FLAG_DELAY_REQUESTED);
}
}
public long getCreatedOn() { return _createdOn; }

View File

@ -125,7 +125,7 @@ class PacketQueue {
_log.debug(msg);
}
Connection c = packet.getConnection();
String suffix = (c != null ? "wsize " + c.getOptions().getWindowSize() : null);
String suffix = (c != null ? "wsize " + c.getOptions().getWindowSize() + " rto " + c.getOptions().getRTO() : null);
_connectionManager.getPacketHandler().displayPacket(packet, "SEND", suffix);
}

View File

@ -41,7 +41,7 @@ class SchedulerClosed extends SchedulerImpl {
(!con.getResetReceived()) &&
(timeSinceClose < Connection.DISCONNECT_TIMEOUT);
boolean conTimeout = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
con.getSendStreamId() == null &&
con.getSendStreamId() <= 0 &&
con.getLifetime() < Connection.DISCONNECT_TIMEOUT;
return (ok || conTimeout);
}

View File

@ -36,7 +36,7 @@ class SchedulerDead extends SchedulerImpl {
boolean nothingLeftToDo = (con.getDisconnectScheduledOn() > 0) &&
(timeSinceClose >= Connection.DISCONNECT_TIMEOUT);
boolean timedOut = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
con.getSendStreamId() == null &&
con.getSendStreamId() <= 0 &&
con.getLifetime() >= Connection.DISCONNECT_TIMEOUT;
return nothingLeftToDo || timedOut;
}

View File

@ -31,7 +31,7 @@ class SchedulerPreconnect extends SchedulerImpl {
public boolean accept(Connection con) {
return (con != null) &&
(con.getSendStreamId() == null) &&
(con.getSendStreamId() <= 0) &&
(con.getLastSendId() < 0);
}

View File

@ -19,7 +19,7 @@ class SchedulerReceived extends SchedulerImpl {
public boolean accept(Connection con) {
return (con != null) &&
(con.getLastSendId() < 0) &&
(con.getSendStreamId() != null);
(con.getSendStreamId() > 0);
}
public void eventOccurred(Connection con) {

4
apps/susidns/readme.txt Normal file
View File

@ -0,0 +1,4 @@
The src/ dir contains susidns 0.13 retrieved from http://susi.i2p/ on 2005/09/15
The contents are released under GPL. Please see http://susi.i2p/ for more info
The paths in the src/build.xml were updated to reference jars in the i2p
source tree.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,17 @@
<?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">
<web-app>
<display-name>susidns</display-name>
<!-- precompiled servlets -->
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="susidns" default="all" basedir=".">
<property name="jetty" value="../../jetty/" />
<property name="project" value="susidns" />
<property name="src" value="java/src" />
<property name="bin" value="./WEB-INF/classes" />
<property name="lib" value="${jetty}/jettylib" />
<property name="tmp" value="./tmp" />
<property name="jsp" value="./jsp" />
<path id="cp">
<pathelement path="${classpath}" />
<pathelement location="${bin}" />
<pathelement location="${lib}/javax.servlet.jar"/>
<pathelement location="${lib}/org.mortbay.jetty.jar"/>
<pathelement location="WEB-INF/lib/jstl.jar" />
<pathelement location="WEB-INF/lib/standard.jar" />
<pathelement location="${lib}/jasper-compiler.jar" />
<pathelement location="${lib}/jasper-runtime.jar" />
<pathelement location="${lib}/javax.servlet.jar" />
<pathelement location="${lib}/commons-logging.jar" />
<pathelement location="${lib}/commons-el.jar" />
<pathelement location="${lib}/ant.jar" />
<pathelement location="../../../core/java/build/i2p.jar" />
</path>
<target name="compile">
<mkdir dir="${bin}" />
<javac debug="true" deprecation="on" source="1.3" target="1.3"
classpathref="cp" destdir="${bin}" srcdir="${src}" includes="**/*.java" />
</target>
<target name="precompilejsp">
<delete file="WEB-INF/web-fragment.xml" />
<delete file="WEB-INF/web-out.xml" />
<mkdir dir="${tmp}" />
<java classname="org.apache.jasper.JspC" fork="true" classpathref="cp">
<arg value="-d" />
<arg value="WEB-INF/classes" />
<arg value="-v" />
<arg value="-p" />
<arg value="i2p.susi.dns.jsp" />
<arg value="-webinc" />
<arg value="WEB-INF/web-fragment.xml" />
<arg value="-webapp" />
<arg value="./jsp" />
</java>
<javac debug="true" deprecation="on" source="1.3" target="1.3"
destdir="${bin}" srcdir="./WEB-INF/classes" includes="**/*.java" classpathref="cp">
</javac>
<copy file="WEB-INF/web-template.xml" tofile="WEB-INF/web-out.xml" />
<loadfile property="jspc.web.fragment" srcfile="WEB-INF/web-fragment.xml" />
<replace file="WEB-INF//web-out.xml">
<replacefilter token="&lt;!-- precompiled servlets --&gt;" value="${jspc.web.fragment}" />
</replace>
</target>
<target name="all" depends="compile,precompilejsp,war"/>
<target name="war">
<war destfile="${project}.war" webxml="WEB-INF/web-out.xml">
<fileset dir=".">
<include name="WEB-INF/**/*.class"/>
<include name="WEB-INF/lib/*.jar"/>
<include name="${src}/**/*.java"/>
<include name="jsp/*.jsp"/>
<include name="images/*.png"/>
<include name="css.css"/>
<include name="index.html"/>
<include name="build.xml"/>
<include name="WEB-INF/web-template.xml"/>
<include name="WEB-INF/web-out.xml"/>
<include name="WEB-INF/classes/${project}.properties"/>
</fileset>
</war>
</target>
<target name="clean">
<delete file="susidns.war" />
<delete>
<fileset dir="." includes="**/*.class" />
<fileset dir="." includes="tmp" />
</delete>
</target>
<target name="distclean" depends="clean" />
</project>

94
apps/susidns/src/css.css Normal file
View File

@ -0,0 +1,94 @@
p {
font-family:Verdana,Tahoma,Arial,Helvetica;
color:black;
line-height:12pt;
margin-left:5mm;
margin-right:5mm;
font-size:10pt;
}
span.addrhlpr {
font-size:7pt;
}
h3 {
font-family:Verdana,Tahoma,Arial,Helvetica;
color:black;
font-size:12pt;
letter-spacing:2pt;
line-height:18pt;
font-weight:bold;
}
body {
background-color: white;
color:black;
}
a {
color:#327BBF;
text-decoration:none;
}
a:hover {
text-decoration:underline;
}
th {
font-family:Verdana,Tahoma,Arial,Helvetica;
color:black;
line-height:12pt;
margin-left:5mm;
margin-right:5mm;
font-size:10pt;
}
td {
font-family:Verdana,Tahoma,Arial,Helvetica;
color:black;
line-height:12pt;
margin-left:5mm;
margin-right:5mm;
font-size:10pt;
vertical-align:center;
}
li {
font-family:Verdana,Tahoma,Arial,Helvetica;
color:black;
line-height:12pt;
margin-left:5mm;
margin-right:5mm;
font-size:10pt;
}
tr.list1 {
background-color:#E0E0E0;
}
tr.list0 {
background-color:white;
}
p.messages {
background-color:#92CAFF;
color:#327BBF;
color:black;
border-style:dotted;
padding-top: 5mm;
padding-right: 5mm;
padding-bottom: 5mm;
padding-left: 5mm;
}
#help {
border-style:dotted;
padding-top: 5mm;
padding-right: 5mm;
padding-bottom: 5mm;
padding-left: 5mm;
}
p.footer {
font-size:7pt;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="refresh" content="0;url=index.jsp" />
<title>susidns</title>
</head>
<body>
<a href="index.jsp">Enter</a>
</body>
</html>

View File

@ -0,0 +1,61 @@
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.1 $
*/
package i2p.susi.dns;
public class AddressBean
{
private String name, destination;
public AddressBean()
{
}
public AddressBean(String name, String destination)
{
this.name = name;
this.destination = destination;
}
public String getDestination()
{
return destination;
}
public void setDestination(String destination)
{
this.destination = destination;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}

View File

@ -0,0 +1,44 @@
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.2 $
*/
package i2p.susi.dns;
import java.util.Comparator;
public class AddressByNameSorter implements Comparator
{
public int compare(Object arg0, Object arg1)
{
AddressBean a = (AddressBean)arg0;
AddressBean b = (AddressBean)arg1;
if( a == null )
return 1;
if( b == null )
return -1;
return a.getName().compareToIgnoreCase(b.getName());
}
}

View File

@ -0,0 +1,262 @@
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.7 $
*/
package i2p.susi.dns;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;
public class AddressbookBean
{
private String book, action, serial, lastSerial, filter, search, hostname, destination;
private Properties properties, addressbook;
private int trClass;
private LinkedList deletionMarks;
private static Comparator sorter;
static {
sorter = new AddressByNameSorter();
}
public String getSearch() {
return search;
}
public void setSearch(String search) {
this.search = search;
}
public boolean isHasFilter()
{
return filter != null && filter.length() > 0;
}
public void setTrClass(int trClass) {
this.trClass = trClass;
}
public int getTrClass() {
trClass = 1 - trClass;
return trClass;
}
public boolean isIsEmpty()
{
return ! isNotEmpty();
}
public boolean isNotEmpty()
{
return addressbook != null && addressbook.size() > 0;
}
public AddressbookBean()
{
properties = new Properties();
deletionMarks = new LinkedList();
}
private long configLastLoaded = 0;
private void loadConfig()
{
long currentTime = System.currentTimeMillis();
if( properties.size() > 0 && currentTime - configLastLoaded < 10000 )
return;
try {
properties.clear();
properties.load( new FileInputStream( ConfigBean.configFileName ) );
configLastLoaded = currentTime;
}
catch (Exception e) {
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
}
}
public String getFileName()
{
loadConfig();
String filename = properties.getProperty( getBook() + "_addressbook" );
return ConfigBean.addressbookPrefix + filename;
}
private Object[] entries;
public Object[] getEntries()
{
return entries;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getBook()
{
if( book == null || ( book.compareToIgnoreCase( "master" ) != 0 &&
book.compareToIgnoreCase( "router" ) != 0 ) &&
book.compareToIgnoreCase( "published" ) != 0 )
book = "master";
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getSerial() {
lastSerial = "" + Math.random();
action = null;
return lastSerial;
}
public void setSerial(String serial) {
this.serial = serial;
}
public String getMessages()
{
loadConfig();
String message = "";
if( action != null ) {
if( lastSerial != null && serial != null && serial.compareTo( lastSerial ) == 0 ) {
boolean changed = false;
if( action.compareToIgnoreCase( "add") == 0 ) {
if( addressbook != null && hostname != null && destination != null ) {
addressbook.put( hostname, destination );
changed = true;
message += "Destination added.<br/>";
}
}
if( action.compareToIgnoreCase( "delete" ) == 0 ) {
Iterator it = deletionMarks.iterator();
int deleted = 0;
while( it.hasNext() ) {
String name = (String)it.next();
addressbook.remove( name );
changed = true;
deleted++;
}
if( changed ) {
message += "" + deleted + " destination(s) deleted.<br/>";
}
}
if( changed ) {
try {
save();
message += "Addressbook saved.<br/>";
} catch (Exception e) {
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
message += "ERROR: Could not write addressbook file.<br/>";
}
}
}
else {
message += "Invalid nonce. Are you being spoofed?";
}
}
action = null;
addressbook = new Properties();
try {
addressbook.load( new FileInputStream( getFileName() ) );
LinkedList list = new LinkedList();
Enumeration e = addressbook.keys();
while( e.hasMoreElements() ) {
String name = (String)e.nextElement();
String destination = addressbook.getProperty( name );
if( filter != null && filter.length() > 0 ) {
if( filter.compareTo( "0-9" ) == 0 ) {
char first = name.charAt(0);
if( first < '0' || first > '9' )
continue;
}
else if( ! name.toLowerCase().startsWith( filter.toLowerCase() ) ) {
continue;
}
}
if( search != null && search.length() > 0 ) {
if( name.indexOf( search ) == -1 ) {
continue;
}
}
list.addLast( new AddressBean( name, destination ) );
}
Object array[] = list.toArray();
Arrays.sort( array, sorter );
entries = array;
}
catch (Exception e) {
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
}
if( message.length() > 0 )
message = "<p class=\"messages\">" + message + "</p>";
return message;
}
private void save() throws IOException
{
String filename = properties.getProperty( getBook() + "_addressbook" );
addressbook.store( new FileOutputStream( ConfigBean.addressbookPrefix + filename ), null );
}
public String getFilter() {
return filter;
}
public boolean isMaster()
{
return getBook().compareToIgnoreCase( "master" ) == 0;
}
public boolean isRouter()
{
return getBook().compareToIgnoreCase( "router" ) == 0;
}
public void setFilter(String filter) {
if( filter != null && ( filter.length() == 0 || filter.compareToIgnoreCase( "none" ) == 0 ) ) {
filter = null;
search = null;
}
this.filter = filter;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getHostname() {
return hostname;
}
public void setResetDeletionMarks( String dummy ) {
deletionMarks.clear();
}
public void setMarkedForDeletion( String name ) {
deletionMarks.addLast( name );
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
}

View File

@ -0,0 +1,157 @@
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.3 $
*/
package i2p.susi.dns;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
public class ConfigBean implements Serializable {
/*
* as this is not provided as constant in addressbook, we define it here
*/
public static String addressbookPrefix = "addressbook/";
public static String configFileName = addressbookPrefix + "config.txt";
private String action, config;
private String serial, lastSerial;
private boolean saved;
public static String getConfigFileName() {
return configFileName;
}
public String getfileName() {
return getConfigFileName();
}
public boolean isSaved() {
return saved;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getConfig()
{
if( config != null )
return config;
reload();
return config;
}
private void reload()
{
File file = new File( configFileName );
if( file != null && file.isFile() ) {
StringBuffer buf = new StringBuffer();
try {
BufferedReader br = new BufferedReader( new FileReader( file ) );
String line;
while( ( line = br.readLine() ) != null ) {
buf.append( line );
buf.append( "\n" );
}
config = buf.toString();
saved = true;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void save()
{
File file = new File( configFileName );
try {
PrintWriter out = new PrintWriter( new FileOutputStream( file ) );
out.print( config );
out.flush();
out.close();
saved = true;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void setConfig(String config) {
this.config = config;
this.saved = false;
/*
* as this is a property file we need a newline at the end of the last line!
*/
if( ! this.config.endsWith( "\n" ) ) {
this.config += "\n";
}
}
public String getMessages() {
String message = "";
if( action != null ) {
if( lastSerial != null && serial != null && serial.compareTo( lastSerial ) == 0 ) {
if( action.compareToIgnoreCase( "save") == 0 ) {
save();
message = "Configuration saved.";
}
else if( action.compareToIgnoreCase( "reload") == 0 ) {
reload();
message = "Configuration reloaded.";
}
}
else {
message = "Invalid nonce. Are you being spoofed?";
}
}
if( message.length() > 0 )
message = "<p class=\"messages\">" + message + "</p>";
return message;
}
public String getSerial()
{
lastSerial = "" + Math.random();
action = null;
return lastSerial;
}
public void setSerial(String serial ) {
this.serial = serial;
}
}

View File

@ -0,0 +1,56 @@
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.1 $
*/
package i2p.susi.dns;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
public class Debug
{
private static Log _log;
private static I2PAppContext _context;
static
{
try {
_context = I2PAppContext.getGlobalContext(); // new I2PAppContext();
_log = _context.logManager().getLog(Debug.class);
}
catch( NoClassDefFoundError e ) {
_context = null;
_log = null;
}
}
public static void debug( String msg )
{
if( _log != null ) {
_log.debug( msg );
}
else {
System.err.println( "DEBUG: [susidns] " + msg );
}
}
}

View File

@ -0,0 +1,171 @@
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.3 $
*/
package i2p.susi.dns;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;
public class SubscriptionsBean
{
private String action, fileName, content, serial, lastSerial;
private boolean saved;
Properties properties;
public SubscriptionsBean()
{
properties = new Properties();
}
private long configLastLoaded = 0;
private void loadConfig()
{
long currentTime = System.currentTimeMillis();
if( properties.size() > 0 && currentTime - configLastLoaded < 10000 )
return;
try {
properties.clear();
properties.load( new FileInputStream( ConfigBean.configFileName ) );
configLastLoaded = currentTime;
}
catch (Exception e) {
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
}
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getFileName()
{
loadConfig();
fileName = ConfigBean.addressbookPrefix + properties.getProperty( "subscriptions", "subscriptions.txt" );
return fileName;
}
private void reload()
{
File file = new File( getFileName() );
if( file != null && file.isFile() ) {
StringBuffer buf = new StringBuffer();
try {
BufferedReader br = new BufferedReader( new FileReader( file ) );
String line;
while( ( line = br.readLine() ) != null ) {
buf.append( line );
buf.append( "\n" );
}
content = buf.toString();
saved = true;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void save()
{
File file = new File( getFileName() );
try {
PrintWriter out = new PrintWriter( new FileOutputStream( file ) );
out.print( content );
out.flush();
out.close();
saved = true;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getMessages() {
String message = "";
if( action != null ) {
if( lastSerial != null && serial != null && serial.compareTo( lastSerial ) == 0 ) {
if( action.compareToIgnoreCase( "save") == 0 ) {
save();
message = "Subscriptions saved.";
}
else if( action.compareToIgnoreCase( "reload") == 0 ) {
reload();
message = "Subscriptions reloaded.";
}
}
else {
message = "Invalid nonce. Are you being spoofed?";
}
}
if( message.length() > 0 )
message = "<p class=\"messages\">" + message + "</p>";
return message;
}
public String getSerial()
{
lastSerial = "" + Math.random();
action = null;
return lastSerial;
}
public void setSerial(String serial ) {
this.serial = serial;
}
public void setContent(String content) {
this.content = content;
this.saved = false;
/*
* as this is a property file we need a newline at the end of the last line!
*/
if( ! this.content.endsWith( "\n" ) ) {
this.content += "\n";
}
}
public String getContent()
{
if( content != null )
return content;
reload();
return content;
}
}

View File

@ -0,0 +1,39 @@
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.4 $
*/
package i2p.susi.dns;
public class VersionBean {
private static String version = "0.4";
private static String url = "http://susi.i2p/?i2paddresshelper=T2DU1KAz3meB0B53U8Y06-I7vHR7XmC0qXAJfLW6b-1L1FVKoySRZz4xazHAwyv2xtRpvKrv6ukLm1tThEW0zQWtZPtX8G6KkzMibD8t7IS~4yw-9VkBtUydyYfsX08AK3v~-egSW8HCXTdyIJVtrETJb337VDUHW-7D4L1JLbwSH4if2ooks6yFTrljK5aVMi-16dZOVvmoyJc3jBqSdK6kraO4gW5-vHTmbLwL498p9nug1KOg1DqgN2GeU5X1QlVrlpFb~IIfdP~O8NT7u-LAjW3jSJsMbLDHMSYTIhC7xmJIiBoi-qk8p6TLynAmvJ7HRvbx4N1EB-uJHyD16wsZkkHyEOfmXbj0ZqLyKEGb3thPwCz-M9v~c2Qt3WbwjXJAtHpjlHkdJ4Fg91cX2oak~JoapnPf6Syw8hko5syf6VVoCYLnrrYyM8oGl8mLclHkj~VCidQNqMSM74IhrHfK6HmRikqtZBexb5M6wfMTTqBvaHURdD21GOpFKYBUAAAA";
public String getVersion() {
return version;
}
public String getUrl() {
return url;
}
}

View File

@ -0,0 +1,168 @@
<%
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.1 $
*/
%>
<%@ page contentType="text/html"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application" />
<jsp:useBean id="book" class="i2p.susi.dns.AddressbookBean" scope="session" />
<jsp:setProperty name="book" property="*" />
<jsp:setProperty name="book" property="resetDeletionMarks" value="1"/>
<c:forEach items="${paramValues.checked}" var="checked">
<jsp:setProperty name="book" property="markedForDeletion" value="${checked}"/>
</c:forEach>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>${book.book} addressbook - susidns v${version.version}</title>
<link rel="stylesheet" type="text/css" href="css.css">
</head>
<body>
<div id="logo">
<img src="images/logo.png" alt="susidns logo" border="0"/>
</div>
<div id="navi">
<p>addressbooks
<a href="addressbook.jsp?book=master">master</a> |
<a href="addressbook.jsp?book=router">router</a> |
<a href="addressbook.jsp?book=published">published</a> *
<a href="subscriptions.jsp">subscriptions</a> *
<a href="config.jsp">configuration</a>
</p>
</div>
<div id="headline">
<h3>${book.book} addressbook at ${book.fileName}</h3>
</div>
<div id="messages">${book.messages}</div>
<div id="filter">
<p>Filter: <a href="addressbook.jsp?filter=a">a</a>
<a href="addressbook.jsp?filter=b">b</a>
<a href="addressbook.jsp?filter=c">c</a>
<a href="addressbook.jsp?filter=d">d</a>
<a href="addressbook.jsp?filter=e">e</a>
<a href="addressbook.jsp?filter=f">f</a>
<a href="addressbook.jsp?filter=g">g</a>
<a href="addressbook.jsp?filter=h">h</a>
<a href="addressbook.jsp?filter=i">i</a>
<a href="addressbook.jsp?filter=j">j</a>
<a href="addressbook.jsp?filter=k">k</a>
<a href="addressbook.jsp?filter=l">l</a>
<a href="addressbook.jsp?filter=m">m</a>
<a href="addressbook.jsp?filter=n">n</a>
<a href="addressbook.jsp?filter=o">o</a>
<a href="addressbook.jsp?filter=p">p</a>
<a href="addressbook.jsp?filter=q">q</a>
<a href="addressbook.jsp?filter=r">r</a>
<a href="addressbook.jsp?filter=s">s</a>
<a href="addressbook.jsp?filter=t">t</a>
<a href="addressbook.jsp?filter=u">u</a>
<a href="addressbook.jsp?filter=v">v</a>
<a href="addressbook.jsp?filter=w">w</a>
<a href="addressbook.jsp?filter=x">x</a>
<a href="addressbook.jsp?filter=y">y</a>
<a href="addressbook.jsp?filter=z">z</a>
<a href="addressbook.jsp?filter=0-9">0-9</a>
<a href="addressbook.jsp?filter=none">all</a></p>
<c:if test="${book.hasFilter}">
<p>Current filter: ${book.filter}</p>
</c:if>
</div>
<form method="POST" action="addressbook.jsp">
<div id="search">
<table><tr>
<td class="search">Search: <input type="text" name="search" value="${book.search}" size="20" /></td>
<td class="search"><input type="image" src="images/search.png" name="submitsearch" value="search" alt="Search" /></td>
</tr>
</table>
</div>
</form>
<form method="POST" action="addressbook.jsp">
<input type="hidden" name="serial" value="${book.serial}"/>
<c:if test="${book.notEmpty}">
<div id="book">
<jsp:setProperty name="book" property="trClass" value="0" />
<table class="book" cellspacing="0" cellpadding="5">
<tr class="head">
<c:if test="${book.master || book.router}">
<th>&nbsp;</th>
</c:if>
<th>Name</th>
<th>Destination</th>
</tr>
<c:forEach items="${book.entries}" var="addr">
<tr class="list${book.trClass}">
<c:if test="${book.master || book.router}">
<td class="checkbox"><input type="checkbox" name="checked" value="${addr.name}" alt="Mark for deletion"></td>
</c:if>
<td class="names"><a href="http://${addr.name}/">${addr.name}</a> -
<span class="addrhlpr"><a href="http://${addr.name}/?i2paddresshelper=${addr.destination}">(addrhlpr)</a></span>
</td>
<td class="destinations"><input type="text" name="dest_${addr.name}" value="${addr.destination}" size="20"></td>
</tr>
</c:forEach>
</table>
</div>
<c:if test="${book.master || book.router}">
<div id="buttons">
<p class="buttons"><input type="image" name="action" value="delete" src="images/delete.png" alt="Delete checked" />
</p>
</div>
</c:if>
</c:if>
<c:if test="${book.isEmpty}">
<div id="book">
<p class="book">The ${book.book} addressbook is empty.</p>
</div>
</c:if>
<div id="add">
<p class="add">
<h3>Add new destination:</h3>
Hostname: <input type="text" name="hostname" value="" size="20"> Destination: <input type="text" name="destination" value="" size="20"><br/>
<input type="image" name="action" value="add" src="images/add.png" alt="Add destination" />
</p>
</div>
</form>
<div id="footer">
<p class="footer">susidns v${version.version} &copy; <a href="${version.url}">susi</a> 2005</p>
</div>
</body>
</html>

View File

@ -0,0 +1,96 @@
<%
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.14 $
*/
%>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application"/>
<jsp:useBean id="cfg" class="i2p.susi.dns.ConfigBean" scope="session"/>
<jsp:setProperty name="cfg" property="*" />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>configuration - susidns v${version.version}</title>
<link rel="stylesheet" type="text/css" href="css.css">
</head>
<body>
<div id="logo">
<img src="images/logo.png" alt="susidns logo" border="0"/>
</div>
<div id="navi">
<p>
addressbooks
<a href="addressbook.jsp?book=master">master</a> |
<a href="addressbook.jsp?book=router">router</a> |
<a href="addressbook.jsp?book=published">published</a> *
<a href="subscriptions.jsp">subscriptions</a> *
<a href="config.jsp">configuration</a>
</p>
</div>
<div id="headline">
<h3>${cfg.fileName}</h3>
</div>
<div id="messages">${cfg.messages}</div>
<form method="POST" action="config.jsp">
<div id="config">
<input type="hidden" name="serial" value="${cfg.serial}" />
<textarea name="config" rows="10" cols="80">${cfg.config}</textarea>
</div>
<div id="buttons">
<input type="image" src="images/save.png" name="action" value="save" alt="Save Config"/>
<input type="image" src="images/reload.png" name="action" value="reload" alt="Reload Config"/>
</div>
</form>
<div id="help">
<h3>Hints</h3>
<ol>
<li>All file or directory paths here are relative to the addressbooks working directory, which normally
is located at $I2P/addressbook/.</li>
<li>If you want to manually add lines to an addressbook, add them to the master addressbook. The router
addressbook and the published addressbook are overwritten by the addressbook application.</li>
<li><b>Important:</b>When you publish your addressbook, <b>ALL</b> destinations appear there, even those
from your master addressbook. Unfortunately the master addressbook points to your userhosts.txt, which was
used for private destinations before. So if you want to keep the destinations in your userhosts.txt secret,
please change the master addressbook to a different file before turning on addressbook publishing.</li>
</ol>
<h3>Options</h3>
<ul>
<li><b>subscriptions</b> - file containing the list of subscriptions URLs (no need to change)</li>
<li><b>update_delay</b> - update interval in hours (no need to change)</li>
<li><b>published_addressbook</b> - your public hosts.txt file (choose a path within your webserver document root)</li>
<li><b>router_addressbook</b> - your hosts.txt (no need to change)</li>
<li><b>master_addressbook</b> - your personal addressbook, it gets never overwritten by the addressbook</li>
<li><b>proxy_port</b> - http port for your eepProxy (no need to change)</li>
<li><b>proxy_host</b> - hostname for your eepProxy (no need to change)</li>
<li><b>should_publish</b> - true/false whether to write the published addressbook</li>
<li><b>etags</b> - file containing the etags header from the fetched subscription URLs (no need to change)</li>
<li><b>last_modified</b> - file containing the modification timestamp for each fetched subscription URL (no need to change)</li>
<li><b>log</b> - file to log activity to (change to /dev/null if you like)</li>
</ul>
</div>
<div id="footer">
<p class="footer">susidns v${version.version} &copy; <a href="${version.url}">susi</a> 2005 </p>
</div>
</body>
</html>

View File

@ -0,0 +1,80 @@
<%
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.1 $
*/
%>
<%@ page contentType="text/html"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application" />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>introduction - susidns v${version.version}</title>
<link rel="stylesheet" type="text/css" href="css.css">
</head>
<body>
<div id="logo">
<img src="images/logo.png" alt="susidns logo" border="0"/>
</div>
<div id="navi">
<p>addressbooks
<a href="addressbook.jsp?book=master">master</a> |
<a href="addressbook.jsp?book=router">router</a> |
<a href="addressbook.jsp?book=published">published</a> *
<a href="subscriptions.jsp">subscriptions</a> *
<a href="config.jsp">configuration</a>
</p>
</div>
<div id="content">
<h3>Huh? what addressbook?</h3>
<p>
The addressbook application is part of your i2p installation. It regularly updates your hosts.txt file
from distributed sources. It keeps your hosts.txt up to date, so it automatically contains all new
eepsites announced on <a href="http://orion.i2p">orion</a>
or in the <a href="http://forum.i2p/viewforum.php?f=16">forum</a>.
</p>
<p>
(To speak the truth: In its default configuration the addressbook does not poll
orion, but dev.i2p only. Subscribing to <a href="http://orion.i2p">orion</a> is an easy task,
just add <a href="http://orion.i2p/hosts.txt">http://orion.i2p/hosts.txt</a> to your <a href="subscriptions.jsp">subscriptions</a> file.)
</p>
<p>If you have questions about naming in i2p, there is an excellent <a href="http://forum.i2p.net/viewtopic.php?t=134">introduction</a>
from duck in the forum.</p>
<h3>How does the addressbook work?</h3>
<p>The addressbook application regularly (normally once per hour) polls your subscriptions and merges their content
into your so called router addressbook (normally your plain hosts.txt). Then it merges your so called master addressbook (normally
your userhosts.txt) into the router addressbook as well. If configured the router addressbook is now written to the so published addressbook,
which is a publicly available copy of your hosts.txt somewhere in your eepsites document root. (Yes, this means that, with activated publication,
your once private keys from userhosts.txt now are publicly available for everybody.)
</p>
<p><img src="images/how.png" border="0" alt="addressbook working scheme"/></p>
</div>
<div id="footer">
<p class="footer">susidns v${version.version} &copy; <a href="${version.url}">susi</a> 2005</p>
</div>
</body>
</html>

View File

@ -0,0 +1,78 @@
<%
/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* 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
*
* $Revision: 1.7 $
*/
%>
<%@ page contentType="text/html"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application" />
<jsp:useBean id="subs" class="i2p.susi.dns.SubscriptionsBean" scope="session" />
<jsp:setProperty name="subs" property="*" />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>subscriptions - susidns v${version.version}</title>
<link rel="stylesheet" type="text/css" href="css.css">
</head>
<body>
<div id="logo">
<img src="images/logo.png" alt="susidns logo" border="0"/>
</div>
<div id="navi">
<p>addressbooks
<a href="addressbook.jsp?book=master">master</a> |
<a href="addressbook.jsp?book=router">router</a> |
<a href="addressbook.jsp?book=published">published</a> *
<a href="subscriptions.jsp">subscriptions</a> *
<a href="config.jsp">configuration</a>
</p>
</div>
<div id="headline">
<h3>${subs.fileName}</h3>
</div>
<div id="messages">${subs.messages}</div>
<form method="POST" action="subscriptions.jsp">
<div id="content">
<input type="hidden" name="serial" value="${subs.serial}" />
<textarea name="content" rows="10" cols="80">${subs.content}</textarea>
</div>
<div id="buttons">
<input type="image" src="images/save.png" name="action" value="save" alt="Save Subscriptions" />
<input type="image" src="images/reload.png" name="action" value="reload" alt="Reload Subscriptions" />
</div>
</form>
<div id="help">
<h3>Explanation</h3>
<p class="help">
The subscription file contains a list of (i2p) URLs. The addressbook application
regularly (once per hour) checks this list for new eepsites. Those URLs simply contain the published hosts.txt
file of other people. Default subscription is the hosts.txt from dev.i2p. The most
popular collaboration site for eepsite is orion.i2p. So its a good idea to add http://orion.i2p/hosts.txt
as a 2nd subscription.
</p>
</div>
<div id="footer">
<p class="footer">susidns v${version.version} &copy; <a href="${version.url}">susi</a> 2005</p>
</div>
</body>
</html>

View File

@ -14,7 +14,7 @@
srcdir="./src"
debug="true" deprecation="on" source="1.3" target="1.3"
destdir="./build/obj"
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar" />
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar" />
</target>
<target name="jar" depends="builddep, compile">
<jar destfile="./build/syndie.jar" basedir="./build/obj" includes="**/*.class">
@ -46,6 +46,7 @@
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
<pathelement location="../../jetty/jettylib/commons-el.jar" />
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="../../jetty/jettylib/ant.jar" />
<pathelement location="build/obj" />
<pathelement location="../../../core/java/build/i2p.jar" />
@ -67,6 +68,7 @@
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
<pathelement location="../../jetty/jettylib/commons-el.jar" />
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="build/obj" />
<pathelement location="../../../core/java/build/i2p.jar" />
</classpath>

View File

@ -30,6 +30,7 @@ public class Archive {
private Map _blogInfo;
private ArchiveIndex _index;
private EntryExtractor _extractor;
private String _defaultSelector;
public static final String METADATA_FILE = "meta.snm";
public static final String INDEX_FILE = "archive.txt";
@ -50,6 +51,8 @@ public class Archive {
_blogInfo = new HashMap();
_index = null;
_extractor = new EntryExtractor(ctx);
_defaultSelector = ctx.getProperty("syndie.defaultSelector");
if (_defaultSelector == null) _defaultSelector = "";
reloadInfo();
}
@ -63,7 +66,13 @@ public class Archive {
BlogInfo bi = new BlogInfo();
try {
bi.load(new FileInputStream(meta));
info.add(bi);
if (bi.verify(_context)) {
info.add(bi);
} else {
System.err.println("Invalid blog (but we're storing it anyway): " + bi);
new Exception("foo").printStackTrace();
info.add(bi);
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
@ -79,8 +88,17 @@ public class Archive {
}
}
}
public String getDefaultSelector() { return _defaultSelector; }
public void setDefaultSelector(String sel) {
if (sel == null)
_defaultSelector = "";
else
_defaultSelector = sel;
}
public BlogInfo getBlogInfo(BlogURI uri) {
if (uri == null) return null;
synchronized (_blogInfo) {
return (BlogInfo)_blogInfo.get(uri.getKeyHash());
}
@ -90,14 +108,21 @@ public class Archive {
return (BlogInfo)_blogInfo.get(key);
}
}
public void storeBlogInfo(BlogInfo info) {
public boolean storeBlogInfo(BlogInfo info) {
if (!info.verify(_context)) {
System.err.println("Not storing the invalid blog " + info);
return;
new Exception("foo!").printStackTrace();
return false;
}
boolean isNew = true;
synchronized (_blogInfo) {
_blogInfo.put(info.getKey().calculateHash(), info);
BlogInfo old = (BlogInfo)_blogInfo.get(info.getKey().calculateHash());
if ( (old == null) || (old.getEdition() < info.getEdition()) )
_blogInfo.put(info.getKey().calculateHash(), info);
else
isNew = false;
}
if (!isNew) return true; // valid entry, but not stored, since its old
try {
File blogDir = new File(_rootDir, info.getKey().calculateHash().toBase64());
blogDir.mkdirs();
@ -106,8 +131,10 @@ public class Archive {
info.write(out);
out.close();
System.out.println("Blog info written to " + blogFile.getPath());
return true;
} catch (IOException ioe) {
ioe.printStackTrace();
return false;
}
}
@ -142,13 +169,17 @@ public class Archive {
for (int j = 0; j < entries.length; j++) {
try {
File entryDir = getEntryDir(entries[j]);
if (!entryDir.exists()) {
EntryContainer entry = null;
if (entryDir.exists())
entry = getCachedEntry(entryDir);
if ( (entry == null) || (!entryDir.exists()) ) {
if (!extractEntry(entries[j], entryDir, info)) {
System.err.println("Entry " + entries[j].getPath() + " is not valid");
new Exception("foo!!").printStackTrace();
continue;
}
entry = getCachedEntry(entryDir);
}
EntryContainer entry = getCachedEntry(entryDir);
String tags[] = entry.getTags();
for (int t = 0; t < tags.length; t++) {
if (!rv.contains(tags[t])) {
@ -183,7 +214,20 @@ public class Archive {
}
private EntryContainer getCachedEntry(File entryDir) {
return new CachedEntry(entryDir);
try {
CachedEntry ce = new CachedEntry(entryDir);
if (ce.isValid())
return ce;
return null;
} catch (IOException ioe) {
ioe.printStackTrace();
}
File files[] = entryDir.listFiles();
for (int i = 0; i < files.length; i++)
files[i].delete();
entryDir.delete();
return null;
}
public EntryContainer getEntry(BlogURI uri) { return getEntry(uri, null); }
@ -214,13 +258,16 @@ public class Archive {
if (blogKey == null) {
// no key, cache.
File entryDir = getEntryDir(entries[i]);
if (!entryDir.exists()) {
if (entryDir.exists())
entry = getCachedEntry(entryDir);
if ((entry == null) || !entryDir.exists()) {
if (!extractEntry(entries[i], entryDir, info)) {
System.err.println("Entry " + entries[i].getPath() + " is not valid");
new Exception("foo!!!!").printStackTrace();
continue;
}
entry = getCachedEntry(entryDir);
}
entry = getCachedEntry(entryDir);
} else {
// we have an explicit key - no caching
entry = new EntryContainer();
@ -228,6 +275,7 @@ public class Archive {
boolean ok = entry.verifySignature(_context, info);
if (!ok) {
System.err.println("Keyed entry " + entries[i].getPath() + " is not valid");
new Exception("foo!!!!!!").printStackTrace();
continue;
}
@ -262,7 +310,16 @@ public class Archive {
}
public boolean storeEntry(EntryContainer container) {
if (container == null) return false;
BlogURI uri = container.getURI();
if (uri == null) return false;
File blogDir = new File(_rootDir, uri.getKeyHash().toBase64());
blogDir.mkdirs();
File entryFile = new File(blogDir, getEntryFilename(uri.getEntryId()));
if (entryFile.exists()) return true;
BlogInfo info = getBlogInfo(uri);
if (info == null) {
System.out.println("no blog metadata for the uri " + uri);
@ -274,13 +331,10 @@ public class Archive {
} else {
//System.out.println("Signature is valid: " + container.getSignature() + " for info " + info);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
container.write(baos, true);
File blogDir = new File(_rootDir, uri.getKeyHash().toBase64());
blogDir.mkdirs();
byte data[] = baos.toByteArray();
File entryFile = new File(blogDir, getEntryFilename(uri.getEntryId()));
FileOutputStream out = new FileOutputStream(entryFile);
out.write(data);
out.close();
@ -294,7 +348,7 @@ public class Archive {
public static String getEntryFilename(long entryId) { return entryId + ".snd"; }
private static SimpleDateFormat _dateFmt = new SimpleDateFormat("yyyyMMdd");
private static SimpleDateFormat _dateFmt = new SimpleDateFormat("yyyyMMdd", Locale.UK);
public static String getIndexName(long entryId, int numBytes) {
try {
synchronized (_dateFmt) {
@ -364,8 +418,8 @@ public class Archive {
reloadInfo();
_index = ArchiveIndexer.index(_context, this);
try {
PrintWriter out = new PrintWriter(new FileWriter(new File(_rootDir, INDEX_FILE)));
out.println(_index.toString());
FileOutputStream out = new FileOutputStream(new File(_rootDir, INDEX_FILE));
out.write(DataHelper.getUTF8(_index.toString()));
out.flush();
} catch (IOException ioe) {
ioe.printStackTrace();

View File

@ -6,6 +6,7 @@ import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.data.*;
import net.i2p.syndie.data.*;
import net.i2p.syndie.sml.*;
/**
* Dig through the archive to build an index
@ -15,7 +16,7 @@ class ArchiveIndexer {
private static final int RECENT_ENTRY_COUNT = 10;
public static ArchiveIndex index(I2PAppContext ctx, Archive source) {
LocalArchiveIndex rv = new LocalArchiveIndex();
LocalArchiveIndex rv = new LocalArchiveIndex(ctx);
rv.setGeneratedOn(ctx.clock().now());
File rootDir = source.getArchiveDir();
@ -23,7 +24,7 @@ class ArchiveIndexer {
File headerFile = new File(rootDir, Archive.HEADER_FILE);
if (headerFile.exists()) {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(headerFile)));
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(headerFile), "UTF-8"));
String line = null;
while ( (line = in.readLine()) != null) {
StringTokenizer tok = new StringTokenizer(line, ":");
@ -53,6 +54,8 @@ class ArchiveIndexer {
long totalSize = 0;
int newBlogs = 0;
SMLParser parser = new SMLParser(ctx);
for (int i = 0; i < blogs.size(); i++) {
BlogInfo cur = (BlogInfo)blogs.get(i);
Hash key = cur.getKey().calculateHash();
@ -76,6 +79,7 @@ class ArchiveIndexer {
for (int t = 0; t < entryTags.length; t++) {
if (!tags.containsKey(entryTags[t])) {
tags.put(entryTags[t], new TreeMap());
//System.err.println("New tag [" + entryTags[t] + "]");
}
Map entriesByTag = (Map)tags.get(entryTags[t]);
entriesByTag.put(new Long(0-entry.getURI().getEntryId()), entry);
@ -86,6 +90,16 @@ class ArchiveIndexer {
newEntries++;
newSize += entry.getCompleteSize();
}
HeaderReceiver rec = new HeaderReceiver();
parser.parse(entry.getEntry().getText(), rec);
String reply = rec.getHeader(HTMLRenderer.HEADER_IN_REPLY_TO);
if (reply != null) {
BlogURI parent = new BlogURI(reply.trim());
if ( (parent.getKeyHash() != null) && (parent.getEntryId() >= 0) )
rv.addReply(parent, entry.getURI());
else
System.err.println("Parent of " + entry.getURI() + " is not valid: [" + reply.trim() + "]");
}
}
long lowestEntryId = -1;
@ -134,4 +148,43 @@ class ArchiveIndexer {
return rv;
}
private static class HeaderReceiver implements SMLParser.EventReceiver {
private Properties _headers;
public HeaderReceiver() { _headers = null; }
public String getHeader(String name) { return (_headers != null ? _headers.getProperty(name) : null); }
public void receiveHeader(String header, String value) {
if (_headers == null) _headers = new Properties();
_headers.setProperty(header, value);
}
public void receiveAddress(String name, String schema, String protocol, String location, String anchorText) {}
public void receiveArchive(String name, String description, String locationSchema, String location, String postingKey, String anchorText) {}
public void receiveAttachment(int id, String anchorText) {}
public void receiveBegin() {}
public void receiveBlog(String name, String blogKeyHash, String blogPath, long blogEntryId, List blogArchiveLocations, String anchorText) {}
public void receiveBold(String text) {}
public void receiveCode(String text, String codeLocationSchema, String codeLocation) {}
public void receiveCut(String summaryText) {}
public void receiveEnd() {}
public void receiveGT() {}
public void receiveH1(String text) {}
public void receiveH2(String text) {}
public void receiveH3(String text) {}
public void receiveH4(String text) {}
public void receiveH5(String text) {}
public void receiveHR() {}
public void receiveHeaderEnd() {}
public void receiveImage(String alternateText, int attachmentId) {}
public void receiveItalic(String text) {}
public void receiveLT() {}
public void receiveLeftBracket() {}
public void receiveLink(String schema, String location, String text) {}
public void receiveNewline() {}
public void receivePlain(String text) {}
public void receivePre(String text) {}
public void receiveQuote(String text, String whoQuoted, String quoteLocationSchema, String quoteLocation) {}
public void receiveRightBracket() {}
public void receiveUnderline(String text) {}
}
}

View File

@ -1,8 +1,11 @@
package net.i2p.syndie;
import java.io.*;
import java.text.*;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.PetName;
import net.i2p.client.naming.PetNameDB;
import net.i2p.data.*;
import net.i2p.syndie.data.*;
import net.i2p.syndie.sml.*;
@ -18,39 +21,87 @@ public class BlogManager {
private File _archiveDir;
private File _userDir;
private File _cacheDir;
private File _tempDir;
private File _rootDir;
private Archive _archive;
static {
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
String rootDir = I2PAppContext.getGlobalContext().getProperty("syndie.rootDir");
if (rootDir == null)
rootDir = System.getProperty("user.home");
rootDir = rootDir + File.separatorChar + ".syndie";
if (false) {
if (rootDir == null)
rootDir = System.getProperty("user.home");
rootDir = rootDir + File.separatorChar + ".syndie";
} else {
if (rootDir == null)
rootDir = "./syndie";
}
_instance = new BlogManager(I2PAppContext.getGlobalContext(), rootDir);
}
public static BlogManager instance() { return _instance; }
public BlogManager(I2PAppContext ctx, String rootDir) {
_context = ctx;
File root = new File(rootDir);
root.mkdirs();
_blogKeyDir = new File(root, "blogkeys");
_privKeyDir = new File(root, "privkeys");
_rootDir = new File(rootDir);
_rootDir.mkdirs();
readConfig();
_blogKeyDir = new File(_rootDir, "blogkeys");
_privKeyDir = new File(_rootDir, "privkeys");
String archiveDir = _context.getProperty("syndie.archiveDir");
if (archiveDir != null)
_archiveDir = new File(archiveDir);
else
_archiveDir = new File(root, "archive");
_userDir = new File(root, "users");
_cacheDir = new File(root, "cache");
_archiveDir = new File(_rootDir, "archive");
_userDir = new File(_rootDir, "users");
_cacheDir = new File(_rootDir, "cache");
_tempDir = new File(_rootDir, "temp");
_blogKeyDir.mkdirs();
_privKeyDir.mkdirs();
_archiveDir.mkdirs();
_cacheDir.mkdirs();
_userDir.mkdirs();
_tempDir.mkdirs();
_archive = new Archive(ctx, _archiveDir.getAbsolutePath(), _cacheDir.getAbsolutePath());
_archive.regenerateIndex();
}
private File getConfigFile() { return new File(_rootDir, "syndie.config"); }
private void readConfig() {
File config = getConfigFile();
if (config.exists()) {
try {
Properties p = new Properties();
DataHelper.loadProps(p, config);
for (Iterator iter = p.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
System.setProperty(key, p.getProperty(key));
System.out.println("Read config prop [" + key + "] = [" + p.getProperty(key) + "]");
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
} else {
System.out.println("Config doesn't exist: " + config.getPath());
}
}
public void writeConfig() {
File config = new File(_rootDir, "syndie.config");
FileOutputStream out = null;
try {
out = new FileOutputStream(config);
for (Iterator iter = _context.getPropertyNames().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
if (name.startsWith("syndie."))
out.write(DataHelper.getUTF8(name + '=' + _context.getProperty(name) + '\n'));
}
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
}
public BlogInfo createBlog(String name, String description, String contactURL, String archives[]) {
return createBlog(name, null, description, contactURL, archives);
}
@ -92,7 +143,30 @@ public class BlogManager {
return info;
}
public boolean updateMetadata(User user, Hash blog, Properties opts) {
if (!user.getAuthenticated()) return false;
BlogInfo oldInfo = getArchive().getBlogInfo(blog);
if (oldInfo == null) return false;
if (!user.getBlog().equals(oldInfo.getKey().calculateHash())) return false;
int oldEdition = 0;
try {
String ed = oldInfo.getProperty("Edition");
if (ed != null)
oldEdition = Integer.parseInt(ed);
} catch (NumberFormatException nfe) {}
opts.setProperty("Edition", oldEdition + 1 + "");
BlogInfo info = new BlogInfo(oldInfo.getKey(), oldInfo.getPosters(), opts);
SigningPrivateKey key = getMyPrivateKey(oldInfo);
info.sign(_context, key);
getArchive().storeBlogInfo(info);
user.setLastMetaEntry(oldEdition+1);
saveUser(user);
return true;
}
public Archive getArchive() { return _archive; }
public File getTempDir() { return _tempDir; }
public File getRootDir() { return _rootDir; }
public List listMyBlogs() {
File files[] = _privKeyDir.listFiles();
@ -135,14 +209,18 @@ public class BlogManager {
}
public String login(User user, String login, String pass) {
File userFile = new File(_userDir, Base64.encode(_context.sha().calculateHash(login.getBytes()).getData()));
if ( (login == null) || (pass == null) ) return "<span class=\"b_loginMsgErr\">Login not specified</span>";
Hash userHash = _context.sha().calculateHash(DataHelper.getUTF8(login));
Hash passHash = _context.sha().calculateHash(DataHelper.getUTF8(pass));
File userFile = new File(_userDir, Base64.encode(userHash.getData()));
System.out.println("Attempting to login to " + login + " w/ pass = " + pass
+ ": file = " + userFile.getAbsolutePath() + " passHash = "
+ Base64.encode(_context.sha().calculateHash(pass.getBytes()).getData()));
+ Base64.encode(passHash.getData()));
if (userFile.exists()) {
try {
Properties props = new Properties();
BufferedReader in = new BufferedReader(new FileReader(userFile));
FileInputStream fin = new FileInputStream(userFile);
BufferedReader in = new BufferedReader(new InputStreamReader(fin, "UTF-8"));
String line = null;
while ( (line = in.readLine()) != null) {
int split = line.indexOf('=');
@ -154,66 +232,133 @@ public class BlogManager {
return user.login(login, pass, props);
} catch (IOException ioe) {
ioe.printStackTrace();
return "Error logging in - corrupt userfile";
return "<span class=\"b_loginMsgErr\">Error logging in - corrupt userfile</span>";
}
} else {
return "User does not exist";
return "<span class=\"b_loginMsgErr\">User does not exist</span>";
}
}
/** hash of the password required to register and create a new blog (null means no password required) */
public String getRegistrationPassword() {
public String getRegistrationPasswordHash() {
String pass = _context.getProperty("syndie.registrationPassword");
if ( (pass == null) || (pass.trim().length() <= 0) ) return null;
return pass;
}
/** Password required to access the remote syndication functinoality (null means no password required) */
public String getRemotePasswordHash() {
String pass = _context.getProperty("syndie.remotePassword");
System.out.println("Remote password? [" + pass + "]");
if ( (pass == null) || (pass.trim().length() <= 0) ) return null;
return pass;
}
public String getAdminPasswordHash() {
String pass = _context.getProperty("syndie.adminPassword");
if ( (pass == null) || (pass.trim().length() <= 0) ) return "";
return pass;
}
public boolean isConfigured() {
File cfg = getConfigFile();
return (cfg.exists());
}
/**
* If true, this syndie instance is meant for just one local user, so we don't need
* to password protect registration, remote.jsp, or admin.jsp
*
*/
public boolean isSingleUser() {
String isSingle = _context.getProperty("syndie.singleUser");
return ( (isSingle != null) && (Boolean.valueOf(isSingle).booleanValue()) );
}
public String getDefaultProxyHost() { return _context.getProperty("syndie.defaultProxyHost", ""); }
public String getDefaultProxyPort() { return _context.getProperty("syndie.defaultProxyPort", ""); }
public boolean authorizeAdmin(String pass) {
if (isSingleUser()) return true;
String admin = getAdminPasswordHash();
if ( (admin == null) || (admin.trim().length() <= 0) )
return false;
String hash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(pass.trim())).getData());
return (hash.equals(admin));
}
public boolean authorizeRemote(String pass) {
if (isSingleUser()) return true;
String rem = getRemotePasswordHash();
if ( (rem == null) || (rem.trim().length() <= 0) )
return false;
String hash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(pass.trim())).getData());
return (hash.equals(rem));
}
public boolean authorizeRemote(User user) {
if (isSingleUser()) return true;
return (user.getAuthenticated() && user.getAllowAccessRemote());
}
public void configure(String registrationPassword, String remotePassword, String adminPass, String defaultSelector,
String defaultProxyHost, int defaultProxyPort, boolean isSingleUser, Properties opts) {
File cfg = getConfigFile();
Writer out = null;
try {
out = new OutputStreamWriter(new FileOutputStream(cfg), "UTF-8");
if (registrationPassword != null)
out.write("syndie.registrationPassword="+Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(registrationPassword.trim())).getData()) + "\n");
if (remotePassword != null)
out.write("syndie.remotePassword="+Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(remotePassword.trim())).getData()) + "\n");
if (adminPass != null)
out.write("syndie.adminPassword="+Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(adminPass.trim())).getData()) + "\n");
if (defaultSelector != null)
out.write("syndie.defaultSelector="+defaultSelector.trim() + "\n");
if (defaultProxyHost != null)
out.write("syndie.defaultProxyHost="+defaultProxyHost.trim() + "\n");
if (defaultProxyPort > 0)
out.write("syndie.defaultProxyPort="+defaultProxyPort + "\n");
out.write("syndie.singleUser=" + isSingleUser + "\n");
if (opts != null) {
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = opts.getProperty(key);
out.write(key.trim() + "=" + val.trim() + "\n");
}
}
_archive.setDefaultSelector(defaultSelector);
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
readConfig();
}
}
public String authorizeRemoteAccess(User user, String password) {
if (!user.getAuthenticated()) return "<span class=\"b_remoteMsgErr\">Not logged in</span>";
String remPass = getRemotePasswordHash();
if (remPass == null)
return "<span class=\"b_remoteMsgErr\">Remote access password not configured - please <a href=\"admin.jsp\">specify</a> a remote " +
"archive password</span>";
if (authorizeRemote(password)) {
user.setAllowAccessRemote(true);
saveUser(user);
return "<span class=\"b_remoteMsgOk\">Remote access authorized</span>";
} else {
return "<span class=\"b_remoteMsgErr\">Remote access denied</span>";
}
}
public void saveUser(User user) {
if (!user.getAuthenticated()) return;
String userHash = Base64.encode(_context.sha().calculateHash(user.getUsername().getBytes()).getData());
String userHash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(user.getUsername())).getData());
File userFile = new File(_userDir, userHash);
FileWriter out = null;
FileOutputStream out = null;
try {
out = new FileWriter(userFile);
out.write("password=" + user.getHashedPassword() + "\n");
out.write("blog=" + user.getBlog().toBase64() + "\n");
out.write("lastid=" + user.getMostRecentEntry() + "\n");
out.write("lastmetaedition=" + user.getLastMetaEntry() + "\n");
out.write("lastlogin=" + user.getLastLogin() + "\n");
out.write("addressbook=" + user.getAddressbookLocation() + "\n");
out.write("showimages=" + user.getShowImages() + "\n");
out.write("showexpanded=" + user.getShowExpanded() + "\n");
StringBuffer buf = new StringBuffer();
buf.append("groups=");
Map groups = user.getBlogGroups();
for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
List selectors = (List)groups.get(name);
buf.append(name).append(':');
for (int i = 0; i < selectors.size(); i++) {
buf.append(selectors.get(i));
if (i + 1 < selectors.size())
buf.append(",");
}
if (iter.hasNext())
buf.append(' ');
}
buf.append('\n');
out.write(buf.toString());
// shitlist=hash,hash,hash
List shitlistedBlogs = user.getShitlistedBlogs();
if (shitlistedBlogs.size() > 0) {
buf.setLength(0);
buf.append("shitlistedblogs=");
for (int i = 0; i < shitlistedBlogs.size(); i++) {
Hash blog = (Hash)shitlistedBlogs.get(i);
buf.append(blog.toBase64());
if (i + 1 < shitlistedBlogs.size())
buf.append(',');
}
buf.append('\n');
out.write(buf.toString());
}
out = new FileOutputStream(userFile);
out.write(DataHelper.getUTF8(user.export()));
user.getPetNameDB().store(user.getAddressbookLocation());
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
@ -221,31 +366,38 @@ public class BlogManager {
}
}
public String register(User user, String login, String password, String registrationPassword, String blogName, String blogDescription, String contactURL) {
String hashedRegistrationPassword = getRegistrationPassword();
if (hashedRegistrationPassword != null) {
if (!hashedRegistrationPassword.equals(Base64.encode(_context.sha().calculateHash(registrationPassword.getBytes()).getData())))
return "Invalid registration password";
System.err.println("Register [" + login + "] pass [" + password + "] name [" + blogName + "] descr [" + blogDescription + "] contact [" + contactURL + "] regPass [" + registrationPassword + "]");
String hashedRegistrationPassword = getRegistrationPasswordHash();
if ( (hashedRegistrationPassword != null) && (!isSingleUser()) ) {
try {
if (!hashedRegistrationPassword.equals(Base64.encode(_context.sha().calculateHash(registrationPassword.getBytes("UTF-8")).getData())))
return "<span class=\"b_regMsgErr\">Invalid registration password</span>";
} catch (UnsupportedEncodingException uee) {
return "<span class=\"b_regMsgErr\">Error registering</span>";
}
}
String userHash = Base64.encode(_context.sha().calculateHash(login.getBytes()).getData());
String userHash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(login)).getData());
File userFile = new File(_userDir, userHash);
if (userFile.exists()) {
return "Cannot register the login " + login + ": it already exists";
return "<span class=\"b_regMsgErr\">Cannot register the login " + login + ": it already exists</span>";
} else {
BlogInfo info = createBlog(blogName, blogDescription, contactURL, null);
String hashedPassword = Base64.encode(_context.sha().calculateHash(password.getBytes()).getData());
FileWriter out = null;
String hashedPassword = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(password)).getData());
FileOutputStream out = null;
try {
out = new FileWriter(userFile);
out.write("password=" + hashedPassword + "\n");
out.write("blog=" + Base64.encode(info.getKey().calculateHash().getData()) + "\n");
out.write("lastid=-1\n");
out.write("lastmetaedition=0\n");
out.write("addressbook=userhosts-"+userHash + ".txt\n");
out.write("showimages=false\n");
out.write("showexpanded=false\n");
out = new FileOutputStream(userFile);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
bw.write("password=" + hashedPassword + "\n");
bw.write("blog=" + Base64.encode(info.getKey().calculateHash().getData()) + "\n");
bw.write("lastid=-1\n");
bw.write("lastmetaedition=0\n");
bw.write("addressbook=userhosts-"+userHash + ".txt\n");
bw.write("showimages=false\n");
bw.write("showexpanded=false\n");
bw.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
return "Internal error registering - " + ioe.getMessage();
return "<span class=\"b_regMsgErr\">Internal error registering - " + ioe.getMessage() + "</span>";
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
@ -254,6 +406,29 @@ public class BlogManager {
return loginResult;
}
}
public String exportHosts(User user) {
if (!user.getAuthenticated() || !user.getAllowAccessRemote())
return "<span class=\"b_addrMsgErr\">Not authorized to export the hosts</span>";
PetNameDB userDb = user.getPetNameDB();
PetNameDB routerDb = _context.petnameDb();
// horribly inefficient...
for (Iterator names = userDb.getNames().iterator(); names.hasNext();) {
PetName pn = userDb.get((String)names.next());
if (pn == null) continue;
Destination existing = _context.namingService().lookup(pn.getName());
if (existing == null && pn.getNetwork().equalsIgnoreCase("i2p")) {
routerDb.set(pn.getName(), pn);
try {
routerDb.store();
} catch (IOException ioe) {
ioe.printStackTrace();
return "<span class=\"b_addrMsgErr\">Error exporting the hosts: " + ioe.getMessage() + "</span>";
}
}
}
return "<span class=\"b_addrMsgOk\">Hosts exported</span>";
}
public BlogURI createBlogEntry(User user, String subject, String tags, String entryHeaders, String sml) {
return createBlogEntry(user, subject, tags, entryHeaders, sml, null, null, null);
@ -289,7 +464,7 @@ public class BlogManager {
raw.append('\n');
if ( (entryHeaders != null) && (entryHeaders.trim().length() > 0) ) {
System.out.println("Entry headers: " + entryHeaders);
BufferedReader userHeaders = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(entryHeaders.getBytes())));
BufferedReader userHeaders = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(DataHelper.getUTF8(entryHeaders)), "UTF-8"));
String line = null;
while ( (line = userHeaders.readLine()) != null) {
line = line.trim();
@ -306,7 +481,7 @@ public class BlogManager {
raw.append('\n');
raw.append(sml);
EntryContainer c = new EntryContainer(uri, tagList, raw.toString().getBytes());
EntryContainer c = new EntryContainer(uri, tagList, DataHelper.getUTF8(raw));
if ((fileNames != null) && (fileStreams != null) && (fileNames.size() == fileStreams.size()) ) {
for (int i = 0; i < fileNames.size(); i++) {
String name = (String)fileNames.get(i);
@ -347,28 +522,58 @@ public class BlogManager {
}
}
public String addAddress(User user, String name, String location, String schema) {
if (!user.getAuthenticated()) return "Not logged in";
/**
* read in the syndie blog metadata file from the stream, verifying it and adding it to
* the archive if necessary
*
*/
public boolean importBlogMetadata(InputStream metadataStream) throws IOException {
try {
BlogInfo info = new BlogInfo();
info.load(metadataStream);
return _archive.storeBlogInfo(info);
} catch (IOException ioe) {
ioe.printStackTrace();
return false;
}
}
/**
* read in the syndie entry file from the stream, verifying it and adding it to
* the archive if necessary
*
*/
public boolean importBlogEntry(InputStream entryStream) throws IOException {
try {
EntryContainer c = new EntryContainer();
c.load(entryStream);
return _archive.storeEntry(c);
} catch (IOException ioe) {
ioe.printStackTrace();
return false;
}
}
public String addAddress(User user, String name, String protocol, String location, String schema) {
if (!user.getAuthenticated()) return "<span class=\"b_addrMsgErr\">Not logged in</span>";
boolean ok = validateAddressName(name);
if (!ok) return "Invalid name: " + HTMLRenderer.sanitizeString(name);
if (!ok) return "<span class=\"b_addrMsgErr\">Invalid name: " + HTMLRenderer.sanitizeString(name) + "</span>";
ok = validateAddressLocation(location);
if (!ok) return "Invalid location: " + HTMLRenderer.sanitizeString(location);
if (!validateAddressSchema(schema)) return "Unsupported schema: " + HTMLRenderer.sanitizeString(schema);
if (!ok) return "<span class=\"b_addrMsgErr\">Invalid location: " + HTMLRenderer.sanitizeString(location) + "</span>";
if (!validateAddressSchema(schema)) return "<span class=\"b_addrMsgErr\">Unsupported schema: " + HTMLRenderer.sanitizeString(schema) + "</span>";
// no need to quote user/location further, as they've been sanitized
FileWriter out = null;
try {
File userHostsFile = new File(user.getAddressbookLocation());
Properties knownHosts = getKnownHosts(user, true);
if (knownHosts.containsKey(name)) return "Name is already in use";
PetNameDB names = user.getPetNameDB();
if (names.exists(name))
return "<span class=\"b_addrMsgErr\">Name is already in use</span>";
PetName pn = new PetName(name, schema, protocol, location);
names.set(name, pn);
out = new FileWriter(userHostsFile, true);
out.write(name + "=" + location + '\n');
return "Address " + name + " written to your hosts file (" + userHostsFile.getName() + ")";
try {
names.store(user.getAddressbookLocation());
return "<span class=\"b_addrMsgOk\">Address " + name + " written to your addressbook</span>";
} catch (IOException ioe) {
return "Error writing out host entry: " + ioe.getMessage();
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
return "<span class=\"b_addrMsgErr\">Error writing out the name: " + ioe.getMessage() + "</span>";
}
}
@ -392,7 +597,7 @@ public class BlogManager {
}
private boolean validateAddressName(String name) {
if ( (name == null) || (name.trim().length() <= 0) || (!name.endsWith(".i2p")) ) return false;
if ( (name == null) || (name.trim().length() <= 0) ) return false;
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (!Character.isLetterOrDigit(c) && ('.' != c) && ('-' != c) && ('_' != c) )
@ -403,30 +608,40 @@ public class BlogManager {
private boolean validateAddressLocation(String location) {
if ( (location == null) || (location.trim().length() <= 0) ) return false;
try {
Destination d = new Destination(location);
return (d.getPublicKey() != null);
} catch (DataFormatException dfe) {
dfe.printStackTrace();
return false;
if (false) {
try {
Destination d = new Destination(location);
return (d.getPublicKey() != null);
} catch (DataFormatException dfe) {
dfe.printStackTrace();
return false;
}
} else {
// not everything is an i2p destination...
return true;
}
}
private boolean validateAddressSchema(String schema) {
if ( (schema == null) || (schema.trim().length() <= 0) ) return false;
return "eep".equals(schema) || "i2p".equals(schema);
if (true) {
return true;
} else {
return "eep".equals(schema) || "i2p".equals(schema);
}
}
private final GregorianCalendar _cal = new GregorianCalendar();
private long getDayBegin(long now) {
synchronized (_cal) {
_cal.setTimeInMillis(now);
_cal.set(Calendar.MILLISECOND, 0);
_cal.set(Calendar.SECOND, 0);
_cal.set(Calendar.MINUTE, 0);
_cal.set(Calendar.HOUR, 0);
_cal.set(Calendar.HOUR_OF_DAY, 0);
return _cal.getTimeInMillis();
private final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd", Locale.UK);
private final long getDayBegin(long now) {
synchronized (_dateFormat) {
try {
String str = _dateFormat.format(new Date(now));
return _dateFormat.parse(str).getTime();
} catch (ParseException pe) {
pe.printStackTrace();
// wtf
return -1;
}
}
}
}

View File

@ -111,12 +111,12 @@ public class CLI {
entryKey = new SessionKey(Base64.decode(args[5]));
EntryContainer entry = mgr.getArchive().getEntry(new BlogURI(new Hash(Base64.decode(args[2])), id), entryKey);
if (entry != null) {
HTMLRenderer renderer = new HTMLRenderer();
HTMLRenderer renderer = new HTMLRenderer(I2PAppContext.getGlobalContext());
boolean summaryOnly = "true".equalsIgnoreCase(args[5]);
boolean showImages = "true".equalsIgnoreCase(args[6]);
try {
File f = File.createTempFile("syndie", ".html");
Writer out = new FileWriter(f);
Writer out = new OutputStreamWriter(new FileOutputStream(f), "UTF-8");
renderer.render(null, mgr.getArchive(), entry, out, summaryOnly, showImages);
out.flush();
out.close();

View File

@ -21,13 +21,17 @@ class CachedEntry extends EntryContainer {
private Entry _entry;
private Attachment _attachments[];
public CachedEntry(File entryDir) {
public CachedEntry(File entryDir) throws IOException {
_entryDir = entryDir;
importMeta();
_entry = new CachedEntryDetails();
_attachments = null;
}
public boolean isValid() {
return (_entry != null) && (_blog != null);
}
// always available, loaded from meta
public int getFormat() { return _format; }
public BlogURI getURI() { return _blog; }
@ -70,7 +74,7 @@ class CachedEntry extends EntryContainer {
}
// now the actual lazy loading code
private void importMeta() {
private void importMeta() throws IOException {
Properties meta = readProps(new File(_entryDir, EntryExtractor.META));
_format = getInt(meta, "format");
_size = getInt(meta, "size");
@ -78,8 +82,14 @@ class CachedEntry extends EntryContainer {
}
private Properties importHeaders() {
if (_headers == null)
_headers = readProps(new File(_entryDir, EntryExtractor.HEADERS));
if (_headers == null) {
try {
_headers = readProps(new File(_entryDir, EntryExtractor.HEADERS));
} catch (IOException ioe) {
ioe.printStackTrace();
_headers = new Properties();
}
}
return _headers;
}
@ -103,19 +113,17 @@ class CachedEntry extends EntryContainer {
return;
}
private static Properties readProps(File propsFile) {
private static Properties readProps(File propsFile) throws IOException {
Properties rv = new Properties();
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(propsFile));
in = new BufferedReader(new InputStreamReader(new FileInputStream(propsFile), "UTF-8"));
String line = null;
while ( (line = in.readLine()) != null) {
int split = line.indexOf('=');
if ( (split <= 0) || (split >= line.length()) ) continue;
rv.setProperty(line.substring(0, split).trim(), line.substring(split+1).trim());
}
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
@ -152,7 +160,7 @@ class CachedEntry extends EntryContainer {
in = new FileInputStream(f);
int read = DataHelper.read(in, buf);
if (read != buf.length) throw new IOException("read: " + read + " file size: " + buf.length + " for " + f.getPath());
_text = new String(buf);
_text = DataHelper.getUTF8(buf);
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
@ -222,15 +230,20 @@ class CachedEntry extends EntryContainer {
private void importAttachmentHeaders() {
if (_attachmentHeaders == null) {
Properties props = readProps(_metaFile);
String sz = (String)props.remove(EntryExtractor.ATTACHMENT_DATA_SIZE);
if (sz != null) {
try {
_dataSize = Integer.parseInt(sz);
} catch (NumberFormatException nfe) {}
try {
Properties props = readProps(_metaFile);
String sz = (String)props.remove(EntryExtractor.ATTACHMENT_DATA_SIZE);
if (sz != null) {
try {
_dataSize = Integer.parseInt(sz);
} catch (NumberFormatException nfe) {}
}
_attachmentHeaders = props;
} catch (IOException ioe) {
ioe.printStackTrace();
_attachmentHeaders = new Properties();
}
_attachmentHeaders = props;
}
}
}

View File

@ -65,36 +65,36 @@ public class EntryExtractor {
}
}
private void extractHeaders(EntryContainer entry, File entryDir) throws IOException {
FileWriter out = null;
FileOutputStream out = null;
try {
out = new FileWriter(new File(entryDir, HEADERS));
out = new FileOutputStream(new File(entryDir, HEADERS));
Map headers = entry.getHeaders();
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
String k = (String)iter.next();
String v = (String)headers.get(k);
out.write(k.trim() + '=' + v.trim() + '\n');
out.write(DataHelper.getUTF8(k.trim() + '=' + v.trim() + '\n'));
}
} finally {
out.close();
}
}
private void extractMeta(EntryContainer entry, File entryDir) throws IOException {
FileWriter out = null;
FileOutputStream out = null;
try {
out = new FileWriter(new File(entryDir, META));
out.write("format=" + entry.getFormat() + '\n');
out.write("size=" + entry.getCompleteSize() + '\n');
out.write("blog=" + entry.getURI().getKeyHash().toBase64() + '\n');
out.write("entry=" + entry.getURI().getEntryId() + '\n');
out = new FileOutputStream(new File(entryDir, META));
out.write(DataHelper.getUTF8("format=" + entry.getFormat() + '\n'));
out.write(DataHelper.getUTF8("size=" + entry.getCompleteSize() + '\n'));
out.write(DataHelper.getUTF8("blog=" + entry.getURI().getKeyHash().toBase64() + '\n'));
out.write(DataHelper.getUTF8("entry=" + entry.getURI().getEntryId() + '\n'));
} finally {
out.close();
}
}
private void extractEntry(EntryContainer entry, File entryDir) throws IOException {
FileWriter out = null;
FileOutputStream out = null;
try {
out = new FileWriter(new File(entryDir, ENTRY));
out.write(entry.getEntry().getText());
out = new FileOutputStream(new File(entryDir, ENTRY));
out.write(DataHelper.getUTF8(entry.getEntry().getText()));
} finally {
out.close();
}
@ -115,16 +115,16 @@ public class EntryExtractor {
}
}
private void extractAttachmentMetadata(int num, Attachment attachment, File entryDir) throws IOException {
FileWriter out = null;
FileOutputStream out = null;
try {
out = new FileWriter(new File(entryDir, ATTACHMENT_PREFIX + num + ATTACHMENT_META_SUFFIX));
out = new FileOutputStream(new File(entryDir, ATTACHMENT_PREFIX + num + ATTACHMENT_META_SUFFIX));
Map meta = attachment.getMeta();
for (Iterator iter = meta.keySet().iterator(); iter.hasNext(); ) {
String k = (String)iter.next();
String v = (String)meta.get(k);
out.write(k + '=' + v + '\n');
out.write(DataHelper.getUTF8(k + '=' + v + '\n'));
}
out.write(ATTACHMENT_DATA_SIZE + '=' + attachment.getDataLength());
out.write(DataHelper.getUTF8(ATTACHMENT_DATA_SIZE + '=' + attachment.getDataLength()));
} finally {
out.close();
}

View File

@ -1,7 +1,10 @@
package net.i2p.syndie;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.PetNameDB;
import net.i2p.data.*;
/**
@ -24,9 +27,18 @@ public class User {
private String _addressbookLocation;
private boolean _showImagesByDefault;
private boolean _showExpandedByDefault;
private String _defaultSelector;
private long _lastLogin;
private long _lastMetaEntry;
private boolean _allowAccessRemote;
private boolean _authenticated;
private String _eepProxyHost;
private int _eepProxyPort;
private String _webProxyHost;
private int _webProxyPort;
private String _torProxyHost;
private int _torProxyPort;
private PetNameDB _petnames;
public User() {
_context = I2PAppContext.getGlobalContext();
@ -40,11 +52,20 @@ public class User {
_mostRecentEntry = -1;
_blogGroups = new HashMap();
_shitlistedBlogs = new ArrayList();
_defaultSelector = null;
_addressbookLocation = "userhosts.txt";
_showImagesByDefault = false;
_showImagesByDefault = true;
_showExpandedByDefault = false;
_allowAccessRemote = false;
_eepProxyHost = null;
_webProxyHost = null;
_torProxyHost = null;
_eepProxyPort = -1;
_webProxyPort = -1;
_torProxyPort = -1;
_lastLogin = -1;
_lastMetaEntry = 0;
_petnames = new PetNameDB();
}
public boolean getAuthenticated() { return _authenticated; }
@ -60,21 +81,35 @@ public class User {
public long getLastLogin() { return _lastLogin; }
public String getHashedPassword() { return _hashedPassword; }
public long getLastMetaEntry() { return _lastMetaEntry; }
public String getDefaultSelector() { return _defaultSelector; }
public void setDefaultSelector(String sel) { _defaultSelector = sel; }
public boolean getAllowAccessRemote() { return _allowAccessRemote; }
public void setAllowAccessRemote(boolean allow) { _allowAccessRemote = true; }
public void setMostRecentEntry(long id) { _mostRecentEntry = id; }
public void setLastMetaEntry(long id) { _lastMetaEntry = id; }
public String getEepProxyHost() { return _eepProxyHost; }
public int getEepProxyPort() { return _eepProxyPort; }
public String getWebProxyHost() { return _webProxyHost; }
public int getWebProxyPort() { return _webProxyPort; }
public String getTorProxyHost() { return _torProxyHost; }
public int getTorProxyPort() { return _torProxyPort; }
public PetNameDB getPetNameDB() { return _petnames; }
public void invalidate() {
BlogManager.instance().saveUser(this);
if (_authenticated)
BlogManager.instance().saveUser(this);
init();
}
public String login(String login, String pass, Properties props) {
String expectedPass = props.getProperty("password");
String hpass = Base64.encode(_context.sha().calculateHash(pass.getBytes()).getData());
String hpass = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(pass)).getData());
if (!hpass.equals(expectedPass)) {
_authenticated = false;
return "Incorrect password";
return "<span class=\"b_loginMsgErr\">Incorrect password</span>";
}
_username = login;
@ -128,18 +163,82 @@ public class User {
}
String addr = props.getProperty("addressbook", "userhosts.txt");
if (addr != null)
if (addr != null) {
_addressbookLocation = addr;
try {
_petnames.load(addr);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
String show = props.getProperty("showimages", "false");
String show = props.getProperty("showimages", "true");
_showImagesByDefault = (show != null) && (show.equals("true"));
show = props.getProperty("showexpanded", "false");
_showExpandedByDefault = (show != null) && (show.equals("true"));
_defaultSelector = props.getProperty("defaultselector");
String allow = props.getProperty("allowaccessremote", "false");
_allowAccessRemote = (allow != null) && (allow.equals("true"));
_eepProxyPort = getInt(props.getProperty("eepproxyport"));
_webProxyPort = getInt(props.getProperty("webproxyport"));
_torProxyPort = getInt(props.getProperty("torproxyport"));
_eepProxyHost = props.getProperty("eepproxyhost");
_webProxyHost = props.getProperty("webproxyhost");
_torProxyHost = props.getProperty("torproxyhost");
_lastLogin = _context.clock().now();
_authenticated = true;
return LOGIN_OK;
}
public static final String LOGIN_OK = "Logged in";
private int getInt(String val) {
if (val == null) return -1;
try { return Integer.parseInt(val); } catch (NumberFormatException nfe) { return -1; }
}
public static final String LOGIN_OK = "<span class=\"b_loginMsgOk\">Logged in</span>";
public String export() {
StringBuffer buf = new StringBuffer(512);
buf.append("password=" + getHashedPassword() + "\n");
buf.append("blog=" + getBlog().toBase64() + "\n");
buf.append("lastid=" + getMostRecentEntry() + "\n");
buf.append("lastmetaedition=" + getLastMetaEntry() + "\n");
buf.append("lastlogin=" + getLastLogin() + "\n");
buf.append("addressbook=" + getAddressbookLocation() + "\n");
buf.append("showimages=" + getShowImages() + "\n");
buf.append("showexpanded=" + getShowExpanded() + "\n");
buf.append("defaultselector=" + getDefaultSelector() + "\n");
buf.append("allowaccessremote=" + _allowAccessRemote + "\n");
buf.append("groups=");
Map groups = getBlogGroups();
for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
List selectors = (List)groups.get(name);
buf.append(name).append(':');
for (int i = 0; i < selectors.size(); i++) {
buf.append(selectors.get(i));
if (i + 1 < selectors.size())
buf.append(",");
}
if (iter.hasNext())
buf.append(' ');
}
buf.append('\n');
// shitlist=hash,hash,hash
List shitlistedBlogs = getShitlistedBlogs();
if (shitlistedBlogs.size() > 0) {
buf.setLength(0);
buf.append("shitlistedblogs=");
for (int i = 0; i < shitlistedBlogs.size(); i++) {
Hash blog = (Hash)shitlistedBlogs.get(i);
buf.append(blog.toBase64());
if (i + 1 < shitlistedBlogs.size())
buf.append(',');
}
buf.append('\n');
}
return buf.toString();
}
}

View File

@ -7,11 +7,14 @@ import net.i2p.I2PAppContext;
import net.i2p.data.*;
import net.i2p.syndie.Archive;
import net.i2p.syndie.BlogManager;
import net.i2p.util.Log;
/**
* Simple read-only summary of an archive
*/
public class ArchiveIndex {
private I2PAppContext _context;
private Log _log;
protected String _version;
protected long _generatedOn;
protected int _allBlogs;
@ -26,16 +29,24 @@ public class ArchiveIndex {
protected List _newestBlogs;
/** list of BlogURI objects */
protected List _newestEntries;
/** parent message to a set of replies, ordered with the oldest first */
protected Map _replies;
protected Properties _headers;
public ArchiveIndex() {
this(false); //true);
this(I2PAppContext.getGlobalContext(), false);
}
public ArchiveIndex(boolean shouldLoad) {
public ArchiveIndex(I2PAppContext ctx) {
this(ctx, false); //true);
}
public ArchiveIndex(I2PAppContext ctx, boolean shouldLoad) {
_context = ctx;
_log = ctx.logManager().getLog(ArchiveIndex.class);
_blogs = new ArrayList();
_newestBlogs = new ArrayList();
_newestEntries = new ArrayList();
_headers = new Properties();
_replies = Collections.synchronizedMap(new HashMap());
_generatedOn = -1;
if (shouldLoad)
setIsLocal("true");
@ -77,6 +88,58 @@ public class ArchiveIndex {
/** get the raw entry size (including attachments) from the given blog/tag pair */
public long getBlogEntrySizeKB(int index, int entryIndex) { return ((EntrySummary)((BlogSummary)_blogs.get(index)).entries.get(entryIndex)).size; }
public boolean getEntryIsKnown(BlogURI uri) { return getEntry(uri) != null; }
public long getBlogEntrySizeKB(BlogURI uri) {
EntrySummary entry = getEntry(uri);
if (entry == null) return -1;
return entry.size;
}
private EntrySummary getEntry(BlogURI uri) {
if ( (uri == null) || (uri.getKeyHash() == null) || (uri.getEntryId() < 0) ) return null;
for (int i = 0; i < _blogs.size(); i++) {
BlogSummary summary = (BlogSummary)_blogs.get(i);
if (summary.blog.equals(uri.getKeyHash())) {
for (int j = 0; j < summary.entries.size(); j++) {
EntrySummary entry = (EntrySummary)summary.entries.get(j);
if (entry.entry.equals(uri))
return entry;
}
}
}
return null;
}
public Set getBlogEntryTags(BlogURI uri) {
Set tags = new HashSet();
if ( (uri == null) || (uri.getKeyHash() == null) || (uri.getEntryId() < 0) ) return tags;
for (int i = 0; i < _blogs.size(); i++) {
BlogSummary summary = (BlogSummary)_blogs.get(i);
if (summary.blog.equals(uri.getKeyHash())) {
for (int j = 0; j < summary.entries.size(); j++) {
EntrySummary entry = (EntrySummary)summary.entries.get(j);
if (entry.entry.equals(uri)) {
tags.add(summary.tag);
break;
}
}
}
}
return tags;
}
public int getBlogEntryCount(Hash blog) {
Set uris = new HashSet(64);
for (int i = 0; i < _blogs.size(); i++) {
BlogSummary summary = (BlogSummary)_blogs.get(i);
if (summary.blog.equals(blog)) {
uris.addAll(summary.entries);
//for (int j = 0; j < summary.entries.size(); j++) {
// EntrySummary entry = (EntrySummary)summary.entries.get(j);
// uris.add(entry.entry);
//}
}
}
return uris.size();
}
/** how many 'new' blogs are listed */
public int getNewestBlogCount() { return _newestBlogs.size(); }
public Hash getNewestBlog(int index) { return (Hash)_newestBlogs.get(index); }
@ -100,7 +163,13 @@ public class ArchiveIndex {
rv.add(getBlog(i));
return rv;
}
public List getReplies(BlogURI uri) {
Set replies = (Set)_replies.get(uri);
if (replies == null) return Collections.EMPTY_LIST;
synchronized (replies) {
return new ArrayList(replies);
}
}
public void setLocation(String location) {
try {
File l = new File(location);
@ -143,7 +212,7 @@ public class ArchiveIndex {
_newestBlogs = new ArrayList();
_newestEntries = new ArrayList();
_headers = new Properties();
BufferedReader in = new BufferedReader(new InputStreamReader(index));
BufferedReader in = new BufferedReader(new InputStreamReader(index, "UTF-8"));
String line = null;
line = in.readLine();
if (line == null)
@ -193,8 +262,8 @@ public class ArchiveIndex {
_newestBlogs = parseNewestBlogs(val);
else if (key.equals("NewestEntries"))
_newestEntries = parseNewestEntries(val);
else
System.err.println("Key: " + key + " val: " + val);
//else
// System.err.println("Key: " + key + " val: " + val);
}
}
@ -217,14 +286,31 @@ public class ArchiveIndex {
}
if (tag != null) {
if (!tag.equals(summary.tag)) {
System.out.println("Tag [" + summary.tag + "] does not match the requested [" + tag + "]");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tag [" + summary.tag + "] does not match the requested [" + tag + "] in " + summary.blog.toBase64());
if (false) {
StringBuffer b = new StringBuffer(tag.length()*2);
for (int j = 0; j < tag.length(); j++) {
b.append((int)tag.charAt(j));
b.append('.');
if (summary.tag.length() > j+1)
b.append((int)summary.tag.charAt(j));
else
b.append('_');
b.append(' ');
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("tag.summary: " + b.toString());
}
continue;
}
}
for (int j = 0; j < summary.entries.size(); j++) {
EntrySummary entry = (EntrySummary)summary.entries.get(j);
ordered.put(new Long(0-entry.entry.getEntryId()), entry.entry);
String k = (Long.MAX_VALUE-entry.entry.getEntryId()) + "-" + entry.entry.getKeyHash().toBase64();
ordered.put(k, entry.entry);
//System.err.println("Including match: " + k);
}
}
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); ) {
@ -264,8 +350,10 @@ public class ArchiveIndex {
if (tok.countTokens() < 4)
return;
tok.nextToken();
Hash keyHash = new Hash(Base64.decode(tok.nextToken()));
long when = getIndexDate(tok.nextToken());
String keyStr = tok.nextToken();
Hash keyHash = new Hash(Base64.decode(keyStr));
String whenStr = tok.nextToken();
long when = getIndexDate(whenStr);
String tag = tok.nextToken();
BlogSummary summary = new BlogSummary();
summary.blog = keyHash;
@ -281,7 +369,7 @@ public class ArchiveIndex {
_blogs.add(summary);
}
private SimpleDateFormat _dateFmt = new SimpleDateFormat("yyyyMMdd");
private SimpleDateFormat _dateFmt = new SimpleDateFormat("yyyyMMdd", Locale.UK);
private long getIndexDate(String yyyymmdd) {
synchronized (_dateFmt) {
try {
@ -315,6 +403,14 @@ public class ArchiveIndex {
size = kb;
entry = uri;
}
public int hashCode() {
return entry.hashCode();
}
public boolean equals(Object obj) {
if ( (obj instanceof EntrySummary) && (((EntrySummary)obj).entry.equals(entry)) )
return true;
return false;
}
}
/** export the index into an archive.txt */

View File

@ -2,6 +2,7 @@ package net.i2p.syndie.data;
import java.io.*;
import java.util.*;
import net.i2p.data.DataHelper;
/**
*
@ -85,7 +86,7 @@ public class Attachment {
for (int i = 0; i < _keys.size(); i++) {
meta.append(_keys.get(i)).append(':').append(_values.get(i)).append('\n');
}
_rawMetadata = meta.toString().getBytes();
_rawMetadata = DataHelper.getUTF8(meta);
}
private void parseMeta() {
@ -96,10 +97,10 @@ public class Attachment {
int valBegin = -1;
for (int i = 0; i < _rawMetadata.length; i++) {
if (_rawMetadata[i] == ':') {
key = new String(_rawMetadata, keyBegin, i - keyBegin);
key = DataHelper.getUTF8(_rawMetadata, keyBegin, i - keyBegin);
valBegin = i + 1;
} else if (_rawMetadata[i] == '\n') {
val = new String(_rawMetadata, valBegin, i - valBegin);
val = DataHelper.getUTF8(_rawMetadata, valBegin, i - valBegin);
_keys.add(key);
_values.add(val);
keyBegin = i + 1;

View File

@ -4,6 +4,7 @@ import java.io.*;
import java.util.*;
import net.i2p.data.*;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Blog metadata. Formatted as: <pre>
@ -13,6 +14,7 @@ import net.i2p.I2PAppContext;
* Required keys:
* Owner: base64 of their signing public key
* Signature: base64 of the DSA signature of the rest of the ordered metadata
* Edition: base10 unique identifier for this metadata (higher clobbers lower)
*
* Optional keys:
* Posters: comma delimited list of base64 signing public keys that
@ -53,18 +55,27 @@ public class BlogInfo {
public static final String SIGNATURE = "Signature";
public static final String NAME = "Name";
public static final String DESCRIPTION = "Description";
public static final String EDITION = "Edition";
public void load(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
List names = new ArrayList();
List vals = new ArrayList();
String line = null;
while ( (line = reader.readLine()) != null) {
if (log.shouldLog(Log.DEBUG))
log.debug("Read info line [" + line + "]");
line = line.trim();
int len = line.length();
int split = line.indexOf(':');
if ( (len <= 0) || (split <= 0) || (split >= len - 2) )
if ( (len <= 0) || (split <= 0) ) {
continue;
} else if (split >= len - 1) {
names.add(line.substring(0, split).trim());
vals.add("");
continue;
}
String key = line.substring(0, split).trim();
String val = line.substring(split+1).trim();
@ -76,6 +87,7 @@ public class BlogInfo {
for (int i = 0; i < _optionNames.length; i++) {
_optionNames[i] = (String)names.get(i);
_optionValues[i] = (String)vals.get(i);
//System.out.println("Loaded info: [" + _optionNames[i] + "] = [" + _optionValues[i] + "]");
}
String keyStr = getProperty(OWNER_KEY);
@ -102,13 +114,22 @@ public class BlogInfo {
if ( (includeRealSignature) || (!SIGNATURE.equals(_optionNames[i])) )
buf.append(_optionNames[i]).append(':').append(_optionValues[i]).append('\n');
}
out.write(buf.toString().getBytes());
String s = buf.toString();
out.write(s.getBytes("UTF-8"));
}
public String getProperty(String name) {
for (int i = 0; i < _optionNames.length; i++) {
if (_optionNames[i].equals(name))
return _optionValues[i];
if (_optionNames[i].equals(name)) {
String val = _optionValues[i];
//System.out.println("getProperty[" + name + "] = [" + val + "] [sz=" + val.length() +"]");
//for (int j = 0; j < val.length(); j++) {
// char c = (char)val.charAt(j);
// if (c != (c & 0x7F))
// System.out.println("char " + j + ": " + (int)c);
//}
return val;
}
}
return null;
}
@ -133,6 +154,18 @@ public class BlogInfo {
_optionValues = values;
}
public int getEdition() {
String e = getProperty(EDITION);
if (e != null) {
try {
return Integer.parseInt(e);
} catch (NumberFormatException nfe) {
return 0;
}
}
return 0;
}
public String[] getProperties() { return _optionNames; }
public SigningPublicKey[] getPosters() { return _posters; }
@ -151,7 +184,9 @@ public class BlogInfo {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
write(out, false);
return ctx.dsa().verifySignature(_signature, out.toByteArray(), _key);
out.close();
byte data[] = out.toByteArray();
return ctx.dsa().verifySignature(_signature, data, _key);
} catch (IOException ioe) {
return false;
}
@ -192,4 +227,54 @@ public class BlogInfo {
}
return buf.toString();
}
private static final String TEST_STRING = "\u20AC\u00DF\u6771\u10400\u00F6";
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
if (true) {
try {
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
SigningPublicKey pub = (SigningPublicKey)keys[0];
SigningPrivateKey priv = (SigningPrivateKey)keys[1];
Properties opts = new Properties();
opts.setProperty("Name", TEST_STRING);
opts.setProperty("Description", TEST_STRING);
opts.setProperty("Edition", "0");
opts.setProperty("ContactURL", TEST_STRING);
String nameOrig = opts.getProperty("Name");
BlogInfo info = new BlogInfo(pub, null, opts);
info.sign(ctx, priv);
boolean ok = info.verify(ctx);
System.err.println("sign&verify: " + ok);
FileOutputStream o = new FileOutputStream("bloginfo-test.dat");
info.write(o, true);
o.close();
FileInputStream i = new FileInputStream("bloginfo-test.dat");
byte buf[] = new byte[4096];
int sz = DataHelper.read(i, buf);
BlogInfo read = new BlogInfo();
read.load(new ByteArrayInputStream(buf, 0, sz));
ok = read.verify(ctx);
System.err.println("write to disk, verify read: " + ok);
System.err.println("Data: " + Base64.encode(buf, 0, sz));
System.err.println("Str : " + new String(buf, 0, sz));
System.err.println("Name ok? " + read.getProperty("Name").equals(TEST_STRING));
System.err.println("Desc ok? " + read.getProperty("Description").equals(TEST_STRING));
System.err.println("Name ok? " + read.getProperty("ContactURL").equals(TEST_STRING));
} catch (Exception e) { e.printStackTrace(); }
} else {
try {
FileInputStream in = new FileInputStream(args[0]);
BlogInfo info = new BlogInfo();
info.load(in);
boolean ok = info.verify(I2PAppContext.getGlobalContext());
System.out.println("OK? " + ok + " :" + info);
} catch (Exception e) { e.printStackTrace(); }
}
}
}

View File

@ -31,6 +31,19 @@ public class BlogURI {
_entryId = -1;
}
}
} else if (uri.startsWith("entry://")) {
int off = "entry://".length();
_blogHash = new Hash(Base64.decode(uri.substring(off, off+44))); // 44 chars == base64(32 bytes)
int entryStart = uri.indexOf('/', off+1);
if (entryStart < 0) {
_entryId = -1;
} else {
try {
_entryId = Long.parseLong(uri.substring(entryStart+1).trim());
} catch (NumberFormatException nfe) {
_entryId = -1;
}
}
} else {
_blogHash = null;
_entryId = -1;
@ -61,7 +74,10 @@ public class BlogURI {
DataHelper.eq(_blogHash, ((BlogURI)obj)._blogHash);
}
public int hashCode() {
return (int)_entryId;
int rv = (int)_entryId;
if (_blogHash != null)
rv += _blogHash.hashCode();
return rv;
}
public static void main(String args[]) {
@ -69,6 +85,8 @@ public class BlogURI {
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=");
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/");
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/123456789");
test("entry://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/");
test("entry://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/123456789");
}
private static void test(String uri) {
BlogURI u = new BlogURI(uri);

View File

@ -0,0 +1,86 @@
package net.i2p.syndie.data;
import java.io.*;
import java.util.*;
import net.i2p.data.*;
import net.i2p.I2PAppContext;
/**
* Create a new blog metadata & set of entries using some crazy UTF8 encoded chars,
* then make sure they're always valid. These blogs & entries can then be fed into
* jetty/syndie/etc to see how and where they are getting b0rked.
*/
public class EncodingTestGenerator {
public EncodingTestGenerator() {}
public static final String TEST_STRING = "\u20AC\u00DF\u6771\u10400\u00F6";
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
try {
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
SigningPublicKey pub = (SigningPublicKey)keys[0];
SigningPrivateKey priv = (SigningPrivateKey)keys[1];
Properties opts = new Properties();
opts.setProperty("Name", TEST_STRING);
opts.setProperty("Description", TEST_STRING);
opts.setProperty("Edition", "0");
opts.setProperty("ContactURL", TEST_STRING);
String nameOrig = opts.getProperty("Name");
BlogInfo info = new BlogInfo(pub, null, opts);
info.sign(ctx, priv);
boolean ok = info.verify(ctx);
System.err.println("sign&verify: " + ok);
FileOutputStream o = new FileOutputStream("encodedMeta.dat");
info.write(o, true);
o.close();
FileInputStream i = new FileInputStream("encodedMeta.dat");
byte buf[] = new byte[4096];
int sz = DataHelper.read(i, buf);
BlogInfo read = new BlogInfo();
read.load(new ByteArrayInputStream(buf, 0, sz));
ok = read.verify(ctx);
System.err.println("write to disk, verify read: " + ok);
System.err.println("Name ok? " + read.getProperty("Name").equals(TEST_STRING));
System.err.println("Desc ok? " + read.getProperty("Description").equals(TEST_STRING));
System.err.println("Name ok? " + read.getProperty("ContactURL").equals(TEST_STRING));
// ok now lets create some entries
BlogURI uri = new BlogURI(read.getKey().calculateHash(), 0);
String tags[] = new String[4];
for (int j = 0; j < tags.length; j++)
tags[j] = TEST_STRING + "_" + j;
StringBuffer smlOrig = new StringBuffer(512);
smlOrig.append("Subject: ").append(TEST_STRING).append("\n\n");
smlOrig.append("Hi with ").append(TEST_STRING);
EntryContainer container = new EntryContainer(uri, tags, DataHelper.getUTF8(smlOrig));
container.seal(ctx, priv, null);
ok = container.verifySignature(ctx, read);
System.err.println("Sealed and verified entry: " + ok);
FileOutputStream fos = new FileOutputStream("encodedEntry.dat");
container.write(fos, true);
fos.close();
System.out.println("Written to " + new File("encodedEntry.dat").getAbsolutePath());
FileInputStream fis = new FileInputStream("encodedEntry.dat");
EntryContainer read2 = new EntryContainer();
read2.load(fis);
ok = read2.verifySignature(ctx, read);
System.out.println("Read ok? " + ok);
read2.parseRawData(ctx);
String tagsRead[] = read2.getTags();
for (int j = 0; j < tagsRead.length; j++) {
if (!tags[j].equals(tagsRead[j]))
System.err.println("Tag error [" + j + "]: read = [" + tagsRead[j] + "] want [" + tags[j] + "]");
else
System.err.println("Tag ok [" + j + "]");
}
String readText = read2.getEntry().getText();
ok = readText.equals(smlOrig.toString());
System.err.println("SML text ok? " + ok);
} catch (Exception e) { e.printStackTrace(); }
}
}

View File

@ -6,6 +6,7 @@ import java.util.zip.*;
import net.i2p.data.*;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Securely wrap up an entry and any attachments. Container format:<pre>
@ -58,7 +59,7 @@ public class EntryContainer {
public EntryContainer(BlogURI uri, String tags[], byte smlData[]) {
this();
_entryURI = uri;
_entryData = new Entry(new String(smlData));
_entryData = new Entry(DataHelper.getUTF8(smlData));
setHeader(HEADER_BLOGKEY, Base64.encode(uri.getKeyHash().getData()));
StringBuffer buf = new StringBuffer();
for (int i = 0; tags != null && i < tags.length; i++)
@ -71,8 +72,35 @@ public class EntryContainer {
public int getFormat() { return _format; }
private String readLine(InputStream in) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
int i = 0;
while (true) {
int c = in.read();
if ( (c == (int)'\n') || (c == (int)'\r') ) {
break;
} else if (c == -1) {
if (i == 0)
return null;
else
break;
} else {
baos.write(c);
}
i++;
}
return DataHelper.getUTF8(baos.toByteArray());
//BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"), 1);
//String line = r.readLine();
//return line;
}
public void load(InputStream source) throws IOException {
String fmt = DataHelper.readLine(source).trim();
String line = readLine(source);
if (line == null) throw new IOException("No format line in the entry");
//System.err.println("read container format line [" + line + "]");
String fmt = line.trim();
if (FORMAT_ZIP_UNENCRYPTED_STR.equals(fmt)) {
_format = FORMAT_ZIP_UNENCRYPTED;
} else if (FORMAT_ZIP_ENCRYPTED_STR.equals(fmt)) {
@ -81,35 +109,51 @@ public class EntryContainer {
throw new IOException("Unsupported entry format: " + fmt);
}
String line = null;
while ( (line = DataHelper.readLine(source)) != null) {
while ( (line = readLine(source)) != null) {
//System.err.println("read container header line [" + line + "]");
line = line.trim();
int len = line.length();
if (len <= 0)
break;
int split = line.indexOf(':');
if ( (split <= 0) || (split >= len - 2) )
if (split <= 0) {
throw new IOException("Invalid format of the syndie entry: line=" + line);
String key = line.substring(0, split);
String val = line.substring(split+1);
_rawKeys.add(key);
_rawValues.add(val);
} else if (split >= len - 2) {
// foo:\n
String key = line.substring(0, split);
_rawKeys.add(key);
_rawValues.add("");
} else {
String key = line.substring(0, split);
String val = line.substring(split+1);
_rawKeys.add(key);
_rawValues.add(val);
}
}
parseHeaders();
String sigStr = DataHelper.readLine(source);
String sigStr = readLine(source);
//System.err.println("read container signature line [" + line + "]");
if ( (sigStr == null) || (sigStr.indexOf("Signature:") == -1) )
throw new IOException("No signature line");
sigStr = sigStr.substring("Signature:".length()+1).trim();
_signature = new Signature(Base64.decode(sigStr));
//System.out.println("Sig: " + _signature.toBase64());
line = DataHelper.readLine(source).trim();
line = readLine(source);
//System.err.println("read container size line [" + line + "]");
if (line == null)
throw new IOException("No size line");
line = line.trim();
int dataSize = -1;
try {
int index = line.indexOf("Size:");
if (index == 0)
dataSize = Integer.parseInt(line.substring("Size:".length()+1).trim());
else
throw new IOException("Invalid size line");
} catch (NumberFormatException nfe) {
throw new IOException("Invalid entry size: " + line);
}
@ -123,7 +167,9 @@ public class EntryContainer {
}
public void seal(I2PAppContext ctx, SigningPrivateKey signingKey, SessionKey entryKey) throws IOException {
System.out.println("Sealing " + _entryURI);
Log l = ctx.logManager().getLog(getClass());
if (l.shouldLog(Log.DEBUG))
l.debug("Sealing " + _entryURI);
if (entryKey == null)
_format = FORMAT_ZIP_UNENCRYPTED;
else
@ -157,7 +203,7 @@ public class EntryContainer {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(baos);
ZipEntry ze = new ZipEntry(ZIP_ENTRY);
byte data[] = _entryData.getText().getBytes();
byte data[] = DataHelper.getUTF8(_entryData.getText());
ze.setTime(0);
out.putNextEntry(ze);
out.write(data);
@ -214,7 +260,7 @@ public class EntryContainer {
String name = entry.getName();
if (ZIP_ENTRY.equals(name)) {
_entryData = new Entry(new String(entryData));
_entryData = new Entry(DataHelper.getUTF8(entryData));
} else if (name.startsWith(ZIP_ATTACHMENT_PREFIX)) {
attachments.put(name, (Object)entryData);
} else if (name.startsWith(ZIP_ATTACHMENT_META_PREFIX)) {
@ -229,17 +275,21 @@ public class EntryContainer {
for (int i = 0; i < attachments.size(); i++) {
byte data[] = (byte[])attachments.get(ZIP_ATTACHMENT_PREFIX + i + ZIP_ATTACHMENT_SUFFIX);
byte metadata[] = (byte[])attachmentMeta.get(ZIP_ATTACHMENT_META_PREFIX + i + ZIP_ATTACHMENT_META_SUFFIX);
if ( (data != null) && (metadata != null) )
if ( (data != null) && (metadata != null) ) {
_attachments[i] = new Attachment(data, metadata);
else
System.out.println("Unable to get " + i + ": " + data + "/" + metadata);
} else {
Log l = ctx.logManager().getLog(getClass());
if (l.shouldLog(Log.WARN))
l.warn("Unable to get " + i + ": " + data + "/" + metadata);
}
}
//System.out.println("Attachments: " + _attachments.length + "/" + attachments.size() + ": " + attachments);
}
public BlogURI getURI() { return _entryURI; }
private static final String NO_TAGS[] = new String[0];
public static final String NO_TAGS_TAG = "[none]";
private static final String NO_TAGS[] = new String[] { NO_TAGS_TAG };
public String[] getTags() {
String tags = getHeader(HEADER_BLOGTAGS);
if ( (tags == null) || (tags.trim().length() <= 0) ) {
@ -303,8 +353,9 @@ public class EntryContainer {
String keyHash = getHeader(HEADER_BLOGKEY);
String idVal = getHeader(HEADER_ENTRYID);
if (keyHash == null)
if (keyHash == null) {
throw new IOException("Missing " + HEADER_BLOGKEY + " header");
}
long entryId = -1;
if ( (idVal != null) && (idVal.length() > 0) ) {
@ -377,7 +428,7 @@ public class EntryContainer {
String str = buf.toString();
//System.out.println("Writing raw: \n[" + str + "] / " + I2PAppContext.getGlobalContext().sha().calculateHash(str.getBytes()) + ", raw data: " + I2PAppContext.getGlobalContext().sha().calculateHash(_rawData).toBase64() + "\n");
out.write(str.getBytes());
out.write(DataHelper.getUTF8(str));
out.write(_rawData);
}

View File

@ -1,16 +1,19 @@
package net.i2p.syndie.data;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.data.*;
import net.i2p.syndie.Archive;
import net.i2p.util.Log;
/**
* writable archive index (most are readonly)
*/
public class LocalArchiveIndex extends ArchiveIndex {
public LocalArchiveIndex() {
super(false);
private Log _log;
public LocalArchiveIndex(I2PAppContext ctx) {
super(ctx, false);
_log = ctx.logManager().getLog(getClass());
}
public void setGeneratedOn(long when) { _generatedOn = when; }
@ -46,7 +49,8 @@ public class LocalArchiveIndex extends ArchiveIndex {
if (summary.blog.equals(key) && (summary.tag.equals(tag)) ) {
long entryId = Archive.getEntryIdFromIndexName(entry);
int kb = Archive.getSizeFromIndexName(entry);
System.out.println("Adding entry " + entryId + ", size=" + kb + "KB [" + entry + "]");
if (_log.shouldLog(Log.INFO))
_log.info("Adding entry " + entryId + ", size=" + kb + "KB [" + entry + "]");
EntrySummary entrySummary = new EntrySummary(new BlogURI(key, entryId), kb);
for (int j = 0; j < summary.entries.size(); j++) {
EntrySummary cur = (EntrySummary)summary.entries.get(j);
@ -67,4 +71,36 @@ public class LocalArchiveIndex extends ArchiveIndex {
if (!_newestEntries.contains(entry))
_newestEntries.add(entry);
}
public void addReply(BlogURI parent, BlogURI reply) {
Set replies = (Set)_replies.get(parent);
if (replies == null) {
replies = Collections.synchronizedSet(new TreeSet(BlogURIComparator.HIGHEST_ID_FIRST));
_replies.put(parent, replies);
}
replies.add(reply);
//System.err.println("Adding reply to " + parent + " from child " + reply + " (# replies: " + replies.size() + ")");
}
private static class BlogURIComparator implements Comparator {
public static final BlogURIComparator HIGHEST_ID_FIRST = new BlogURIComparator(true);
public static final BlogURIComparator HIGHEST_ID_LAST = new BlogURIComparator(false);
private boolean _highestFirst;
public BlogURIComparator(boolean highestFirst) {
_highestFirst = highestFirst;
}
public int compare(Object lhs, Object rhs) {
if ( (lhs == null) || !(lhs instanceof BlogURI) ) return 1;
if ( (rhs == null) || !(rhs instanceof BlogURI) ) return -1;
BlogURI l = (BlogURI)lhs;
BlogURI r = (BlogURI)rhs;
if (l.getEntryId() > r.getEntryId())
return (_highestFirst ? 1 : -1);
else if (l.getEntryId() < r.getEntryId())
return (_highestFirst ? -1 : 1);
else
return DataHelper.compareTo(l.getKeyHash().getData(), r.getKeyHash().getData());
}
}
}

View File

@ -0,0 +1,81 @@
package net.i2p.syndie.data;
import java.io.*;
import java.text.*;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.data.*;
import net.i2p.syndie.Archive;
import net.i2p.syndie.BlogManager;
/**
* Simple read-only summary of an archive, proxied to the BlogManager's instance
*/
public class TransparentArchiveIndex extends ArchiveIndex {
public TransparentArchiveIndex() { super(I2PAppContext.getGlobalContext(), false); }
private static ArchiveIndex index() { return BlogManager.instance().getArchive().getIndex(); }
public String getVersion() { return index().getVersion(); }
public Properties getHeaders() { return index().getHeaders(); }
public int getAllBlogs() { return index().getAllBlogs(); }
public int getNewBlogs() { return index().getNewBlogs(); }
public int getAllEntries() { return index().getAllEntries(); }
public int getNewEntries() { return index().getNewEntries(); }
public long getTotalSize() { return index().getTotalSize(); }
public long getNewSize() { return index().getNewSize(); }
public long getGeneratedOn() { return index().getGeneratedOn(); }
public String getNewSizeStr() { return index().getNewSizeStr(); }
public String getTotalSizeStr() { return index().getTotalSizeStr(); }
/** how many blogs/tags are indexed */
public int getIndexBlogs() { return index().getIndexBlogs(); }
/** get the blog used for the given blog/tag pair */
public Hash getBlog(int index) { return index().getBlog(index); }
/** get the tag used for the given blog/tag pair */
public String getBlogTag(int index) { return index().getBlogTag(index); }
/** get the highest entry ID for the given blog/tag pair */
public long getBlogLastUpdated(int index) { return index().getBlogLastUpdated(index); }
/** get the entry count for the given blog/tag pair */
public int getBlogEntryCount(int index) { return index().getBlogEntryCount(index); }
/** get the entry from the given blog/tag pair */
public BlogURI getBlogEntry(int index, int entryIndex) { return index().getBlogEntry(index, entryIndex); }
/** get the raw entry size (including attachments) from the given blog/tag pair */
public long getBlogEntrySizeKB(int index, int entryIndex) { return index().getBlogEntrySizeKB(index, entryIndex); }
public boolean getEntryIsKnown(BlogURI uri) { return index().getEntryIsKnown(uri); }
public long getBlogEntrySizeKB(BlogURI uri) { return index().getBlogEntrySizeKB(uri); }
public Set getBlogEntryTags(BlogURI uri) { return index().getBlogEntryTags(uri); }
/** how many 'new' blogs are listed */
public int getNewestBlogCount() { return index().getNewestBlogCount(); }
public Hash getNewestBlog(int index) { return index().getNewestBlog(index); }
/** how many 'new' entries are listed */
public int getNewestBlogEntryCount() { return index().getNewestBlogEntryCount(); }
public BlogURI getNewestBlogEntry(int index) { return index().getNewestBlogEntry(index); }
/** list of locally known tags (String) under the given blog */
public List getBlogTags(Hash blog) { return index().getBlogTags(blog); }
/** list of unique blogs locally known (set of Hash) */
public Set getUniqueBlogs() { return index().getUniqueBlogs(); }
public void setLocation(String location) { return; }
public void setIsLocal(String val) { return; }
public void load(File location) throws IOException { return; }
/** load up the index from an archive.txt */
public void load(InputStream index) throws IOException { return; }
/**
* Dig through the index for BlogURIs matching the given criteria, ordering the results by
* their own entryIds.
*
* @param out where to store the matches
* @param blog if set, what blog key must the entries be under
* @param tag if set, what tag must the entry be in
*
*/
public void selectMatchesOrderByEntryId(List out, Hash blog, String tag) {
index().selectMatchesOrderByEntryId(out, blog, tag);
}
/** export the index into an archive.txt */
public String toString() { return index().toString(); }
}

View File

@ -1,52 +1,108 @@
package net.i2p.syndie.sml;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
*
*/
public class EventReceiverImpl implements SMLParser.EventReceiver {
private I2PAppContext _context;
private Log _log;
public EventReceiverImpl(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(EventReceiverImpl.class);
}
public void receiveHeader(String header, String value) {
System.out.println("Receive header [" + header + "] = [" + value + "]");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive header [" + header + "] = [" + value + "]");
}
public void receiveLink(String schema, String location, String text) {
System.out.println("Receive link [" + schema + "]/[" + location+ "]/[" + text + "]");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive link [" + schema + "]/[" + location+ "]/[" + text + "]");
}
public void receiveBlog(String name, String blogKeyHash, String blogPath, long blogEntryId,
List blogArchiveLocations, String anchorText) {
System.out.println("Receive blog [" + name + "]/[" + blogKeyHash + "]/[" + blogPath
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive blog [" + name + "]/[" + blogKeyHash + "]/[" + blogPath
+ "]/[" + blogEntryId + "]/[" + blogArchiveLocations + "]/[" + anchorText + "]");
}
public void receiveArchive(String name, String description, String locationSchema, String location,
String postingKey, String anchorText) {
System.out.println("Receive archive [" + name + "]/[" + description + "]/[" + locationSchema
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive archive [" + name + "]/[" + description + "]/[" + locationSchema
+ "]/[" + location + "]/[" + postingKey + "]/[" + anchorText + "]");
}
public void receiveImage(String alternateText, int attachmentId) {
System.out.println("Receive image [" + alternateText + "]/[" + attachmentId + "]");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive image [" + alternateText + "]/[" + attachmentId + "]");
}
public void receiveAddress(String name, String schema, String location, String anchorText) {
System.out.println("Receive address [" + name + "]/[" + schema + "]/[" + location + "]/[" + anchorText+ "]");
public void receiveAddress(String name, String schema, String protocol, String location, String anchorText) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive address [" + name + "]/[" + schema + "]/[" + location + "]/[" + anchorText+ "]");
}
public void receiveBold(String text) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive bold [" + text+ "]");
}
public void receiveItalic(String text) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive italic [" + text+ "]");
}
public void receiveUnderline(String text) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive underline [" + text+ "]");
}
public void receiveBold(String text) { System.out.println("Receive bold [" + text+ "]"); }
public void receiveItalic(String text) { System.out.println("Receive italic [" + text+ "]"); }
public void receiveUnderline(String text) { System.out.println("Receive underline [" + text+ "]"); }
public void receiveQuote(String text, String whoQuoted, String quoteLocationSchema, String quoteLocation) {
System.out.println("Receive quote [" + text + "]/[" + whoQuoted + "]/[" + quoteLocationSchema + "]/[" + quoteLocation + "]");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive quote [" + text + "]/[" + whoQuoted + "]/[" + quoteLocationSchema + "]/[" + quoteLocation + "]");
}
public void receiveCode(String text, String codeLocationSchema, String codeLocation) {
System.out.println("Receive code [" + text+ "]/[" + codeLocationSchema + "]/[" + codeLocation + "]");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive code [" + text+ "]/[" + codeLocationSchema + "]/[" + codeLocation + "]");
}
public void receiveCut(String summaryText) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive cut [" + summaryText + "]");
}
public void receivePlain(String text) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive plain [" + text + "]");
}
public void receiveNewline() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive NL");
}
public void receiveLT() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive LT");
}
public void receiveGT() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive GT");
}
public void receiveBegin() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive begin");
}
public void receiveEnd() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive end");
}
public void receiveHeaderEnd() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive header end");
}
public void receiveLeftBracket() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive [");
}
public void receiveRightBracket() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive ]");
}
public void receiveCut(String summaryText) { System.out.println("Receive cut [" + summaryText + "]"); }
public void receivePlain(String text) { System.out.println("Receive plain [" + text + "]"); }
public void receiveNewline() { System.out.println("Receive NL"); }
public void receiveLT() { System.out.println("Receive LT"); }
public void receiveGT() { System.out.println("Receive GT"); }
public void receiveBegin() { System.out.println("Receive begin"); }
public void receiveEnd() { System.out.println("Receive end"); }
public void receiveHeaderEnd() { System.out.println("Receive header end"); }
public void receiveLeftBracket() { System.out.println("Receive ["); }
public void receiveRightBracket() { System.out.println("Receive ]"); }
public void receiveH1(String text) {}
public void receiveH2(String text) {}

View File

@ -0,0 +1,147 @@
package net.i2p.syndie.sml;
import java.io.*;
import java.text.*;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.data.*;
import net.i2p.syndie.*;
import net.i2p.syndie.data.*;
import net.i2p.syndie.web.*;
/**
*
*/
public class HTMLPreviewRenderer extends HTMLRenderer {
private List _filenames;
private List _fileTypes;
private List _files;
public HTMLPreviewRenderer(I2PAppContext ctx, List filenames, List fileTypes, List files) {
super(ctx);
_filenames = filenames;
_fileTypes = fileTypes;
_files = files;
}
protected String getAttachmentURLBase() { return "viewtempattachment.jsp"; }
protected String getAttachmentURL(int id) {
return getAttachmentURLBase() + "?" +
ArchiveViewerBean.PARAM_ATTACHMENT + "=" + id;
}
public void receiveAttachment(int id, String anchorText) {
if (!continueBody()) { return; }
if ( (id < 0) || (_files == null) || (id >= _files.size()) ) {
_bodyBuffer.append(sanitizeString(anchorText));
} else {
File f = (File)_files.get(id);
String name = (String)_filenames.get(id);
String type = (String)_fileTypes.get(id);
_bodyBuffer.append("<a ").append(getClass("attachmentView")).append(" href=\"").append(getAttachmentURL(id)).append("\">");
_bodyBuffer.append(sanitizeString(anchorText)).append("</a>");
_bodyBuffer.append(getSpan("attachmentSummary")).append(" (");
_bodyBuffer.append(getSpan("attachmentSummarySize")).append(f.length()/1024).append("KB</span>, ");
_bodyBuffer.append(getSpan("attachmentSummaryName")).append(" \"").append(sanitizeString(name)).append("\"</span>, ");
_bodyBuffer.append(getSpan("attachmentSummaryType")).append(sanitizeString(type)).append("</span>)</span>");
}
}
public void receiveEnd() {
_postBodyBuffer.append("</td></tr>\n");
_postBodyBuffer.append("<tr ").append(getClass("summDetail")).append(" >\n");
_postBodyBuffer.append("<form action=\"").append(getAttachmentURLBase()).append("\">\n");
_postBodyBuffer.append("<td colspan=\"2\" valign=\"top\" align=\"left\" ").append(getClass("summDetail")).append("> \n");
if (_files.size() > 0) {
_postBodyBuffer.append(getSpan("summDetailAttachment")).append("Attachments:</span> ");
_postBodyBuffer.append("<select ").append(getClass("summDetailAttachmentId")).append(" name=\"").append(ArchiveViewerBean.PARAM_ATTACHMENT).append("\">\n");
for (int i = 0; i < _files.size(); i++) {
_postBodyBuffer.append("<option value=\"").append(i).append("\">");
File f = (File)_files.get(i);
String name = (String)_filenames.get(i);
String type = (String)_fileTypes.get(i);
_postBodyBuffer.append(sanitizeString(name));
_postBodyBuffer.append(" (").append(f.length()/1024).append("KB");
_postBodyBuffer.append(", type ").append(sanitizeString(type)).append(")</option>\n");
}
_postBodyBuffer.append("</select>\n");
_postBodyBuffer.append("<input ").append(getClass("summDetailAttachmentDl")).append(" type=\"submit\" value=\"Download\" name=\"Download\" /><br />\n");
}
if (_blogs.size() > 0) {
_postBodyBuffer.append(getSpan("summDetailBlog")).append("Blog references:</span> ");
for (int i = 0; i < _blogs.size(); i++) {
Blog b = (Blog)_blogs.get(i);
boolean expanded = (_user != null ? _user.getShowExpanded() : false);
boolean images = (_user != null ? _user.getShowImages() : false);
_postBodyBuffer.append("<a ").append(getClass("summDetailBlogLink")).append(" href=\"");
_postBodyBuffer.append(getPageURL(new Hash(Base64.decode(b.hash)), b.tag, b.entryId, -1, -1, expanded, images));
_postBodyBuffer.append("\">").append(sanitizeString(b.name)).append("</a> ");
}
_postBodyBuffer.append("<br />\n");
}
if (_links.size() > 0) {
_postBodyBuffer.append(getSpan("summDetailExternal")).append("External links:</span> ");
for (int i = 0; i < _links.size(); i++) {
Link l = (Link)_links.get(i);
_postBodyBuffer.append("<a ").append(getClass("summDetailExternalLink")).append(" href=\"externallink.jsp?");
if (l.schema != null)
_postBodyBuffer.append("schema=").append(sanitizeURL(l.schema)).append('&');
if (l.location != null)
_postBodyBuffer.append("location=").append(sanitizeURL(l.location)).append('&');
_postBodyBuffer.append("\">").append(sanitizeString(l.location));
_postBodyBuffer.append(getSpan("summDetailExternalNet")).append(" (").append(sanitizeString(l.schema)).append(")</span></a> ");
}
_postBodyBuffer.append("<br />\n");
}
if (_addresses.size() > 0) {
_postBodyBuffer.append(getSpan("summDetailAddr")).append("Addresses:</span>");
for (int i = 0; i < _addresses.size(); i++) {
Address a = (Address)_addresses.get(i);
String knownName = null;
if (_user != null)
knownName = _user.getPetNameDB().getNameByLocation(a.location);
if (knownName != null) {
_postBodyBuffer.append(' ').append(getSpan("summDetailAddrKnown"));
_postBodyBuffer.append(sanitizeString(knownName)).append("</span>");
} else {
_postBodyBuffer.append(" <a ").append(getClass("summDetailAddrLink")).append(" href=\"addresses.jsp?");
if (a.schema != null)
_postBodyBuffer.append("network=").append(sanitizeTagParam(a.schema)).append('&');
if (a.location != null)
_postBodyBuffer.append("location=").append(sanitizeTagParam(a.location)).append('&');
if (a.name != null)
_postBodyBuffer.append("name=").append(sanitizeTagParam(a.name)).append('&');
if (a.protocol != null)
_postBodyBuffer.append("protocol=").append(sanitizeTagParam(a.protocol)).append('&');
_postBodyBuffer.append("\">").append(sanitizeString(a.name)).append("</a>");
}
}
_postBodyBuffer.append("<br />\n");
}
if (_archives.size() > 0) {
_postBodyBuffer.append(getSpan("summDetailArchive")).append("Archives:</span>");
for (int i = 0; i < _archives.size(); i++) {
ArchiveRef a = (ArchiveRef)_archives.get(i);
_postBodyBuffer.append(" <a ").append(getClass("summDetailArchiveLink")).append(" href=\"").append(getArchiveURL(null, new SafeURL(a.locationSchema + "://" + a.location)));
_postBodyBuffer.append("\">").append(sanitizeString(a.name)).append("</a>");
if (a.description != null)
_postBodyBuffer.append(": ").append(getSpan("summDetailArchiveDesc")).append(sanitizeString(a.description)).append("</span>");
if (null == _user.getPetNameDB().getNameByLocation(a.location)) {
_postBodyBuffer.append(" <a ").append(getClass("summDetailArchiveBookmark")).append(" href=\"");
_postBodyBuffer.append(getBookmarkURL(a.name, a.location, a.locationSchema, "syndiearchive"));
_postBodyBuffer.append("\">bookmark</a>");
}
}
_postBodyBuffer.append("<br />\n");
}
_postBodyBuffer.append("</td>\n</form>\n</tr>\n");
_postBodyBuffer.append("</table>\n");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,310 @@
package net.i2p.syndie.sml;
import java.io.*;
import java.util.*;
import java.text.SimpleDateFormat;
import net.i2p.I2PAppContext;
import net.i2p.data.*;
import net.i2p.syndie.*;
import net.i2p.syndie.data.*;
/**
*
*/
public class RSSRenderer extends HTMLRenderer {
public RSSRenderer(I2PAppContext ctx) {
super(ctx);
}
public void render(User user, Archive archive, EntryContainer entry, String urlPrefix, Writer out) throws IOException {
if (entry == null) return;
prepare(user, archive, entry, entry.getEntry().getText(), out, true, false);
out.write(" <item>\n");
out.write(" <title>" + sanitizeXML(sanitizeString((String)_headers.get(HEADER_SUBJECT))) + "</title>\n");
out.write(" <link>" + urlPrefix + sanitizeXML(getEntryURL()) + "</link>\n");
out.write(" <guid isPermalink=\"false\">" + urlPrefix + entry.getURI().toString() + "</guid>\n");
out.write(" <pubDate>" + getRFC822Date(entry.getURI().getEntryId()) + "</pubDate>\n");
String author = user.getPetNameDB().getNameByLocation(entry.getURI().getKeyHash().toBase64());
if (author == null) {
BlogInfo info = archive.getBlogInfo(entry.getURI());
if (info != null)
author = info.getProperty(BlogInfo.NAME);
}
if (author == null)
author = entry.getURI().getKeyHash().toBase64();
out.write(" <author>" + sanitizeXML(sanitizeString(author)) + "@syndie.invalid</author>\n");
String tags[] = entry.getTags();
if (tags != null)
for (int i = 0; i < tags.length; i++)
out.write(" <category>" + sanitizeXML(sanitizeString(tags[i])) + "</category>\n");
out.write(" <description>" + sanitizeXML(_bodyBuffer.toString()) + "</description>\n");
//renderEnclosures(user, entry, urlPrefix, out);
out.write(" </item>\n");
}
public void receiveBold(String text) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(text));
}
public void receiveItalic(String text) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(text));
}
public void receiveUnderline(String text) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(text));
}
public void receiveHR() {
if (!continueBody()) { return; }
}
public void receiveH1(String body) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(body));
}
public void receiveH2(String body) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(body));
}
public void receiveH3(String body) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(body));
}
public void receiveH4(String body) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(body));
}
public void receiveH5(String body) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(body));
}
public void receivePre(String body) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(body));
}
public void receiveQuote(String text, String whoQuoted, String quoteLocationSchema, String quoteLocation) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(text));
}
public void receiveCode(String text, String codeLocationSchema, String codeLocation) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(text));
}
public void receiveImage(String alternateText, int attachmentId) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(alternateText));
}
public void receiveCut(String summaryText) {
if (!continueBody()) { return; }
_cutReached = true;
if (_cutBody) {
if ( (summaryText != null) && (summaryText.length() > 0) )
_bodyBuffer.append(sanitizeString(summaryText));
else
_bodyBuffer.append("more inside...");
} else {
if (summaryText != null)
_bodyBuffer.append(sanitizeString(summaryText));
}
}
/** are we either before the cut or rendering without cutting? */
protected boolean continueBody() {
boolean rv = ( (!_cutReached) && (_bodyBuffer.length() <= _cutSize) ) || (!_cutBody);
//if (!rv)
// System.out.println("rv: " + rv + " Cut reached: " + _cutReached + " bodyBufferSize: " + _bodyBuffer.length() + " cutBody? " + _cutBody);
if (!rv && !_cutReached) {
// exceeded the allowed size
_bodyBuffer.append("more inside...");
_cutReached = true;
}
return rv;
}
public void receiveNewline() {
if (!continueBody()) { return; }
if (true || (_lastNewlineAt >= _bodyBuffer.length()))
_bodyBuffer.append("\n");
else
_lastNewlineAt = _bodyBuffer.length();
}
public void receiveBlog(String name, String hash, String tag, long entryId, List locations, String description) {
byte blogData[] = Base64.decode(hash);
if ( (blogData == null) || (blogData.length != Hash.HASH_LENGTH) )
return;
Blog b = new Blog();
b.name = name;
b.hash = hash;
b.tag = tag;
b.entryId = entryId;
b.locations = locations;
if (!_blogs.contains(b))
_blogs.add(b);
if (!continueBody()) { return; }
if (hash == null) return;
Hash blog = new Hash(blogData);
if ( (description != null) && (description.trim().length() > 0) ) {
_bodyBuffer.append(sanitizeString(description));
} else if ( (name != null) && (name.trim().length() > 0) ) {
_bodyBuffer.append(sanitizeString(name));
} else {
_bodyBuffer.append("[view entry]");
}
}
public void receiveArchive(String name, String description, String locationSchema, String location,
String postingKey, String anchorText) {
ArchiveRef a = new ArchiveRef();
a.name = name;
a.description = description;
a.locationSchema = locationSchema;
a.location = location;
if (!_archives.contains(a))
_archives.add(a);
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(anchorText));
}
public void receiveLink(String schema, String location, String text) {
Link l = new Link();
l.schema = schema;
l.location = location;
if (!_links.contains(l))
_links.add(l);
if (!continueBody()) { return; }
if ( (schema == null) || (location == null) ) return;
_bodyBuffer.append(sanitizeString(text));
}
public void receiveAddress(String name, String schema, String protocol, String location, String anchorText) {
Address a = new Address();
a.name = name;
a.schema = schema;
a.location = location;
a.protocol = protocol;
if (!_addresses.contains(a))
_addresses.add(a);
if (!continueBody()) { return; }
if ( (schema == null) || (location == null) ) return;
String knownName = null;
if (_user != null)
knownName = _user.getPetNameDB().getNameByLocation(location);
if (knownName != null) {
_bodyBuffer.append(sanitizeString(anchorText));
} else {
_bodyBuffer.append(sanitizeString(anchorText));
}
}
public void receiveAttachment(int id, String anchorText) {
if (!continueBody()) { return; }
_bodyBuffer.append(sanitizeString(anchorText));
}
// Mon, 03 Jun 2005 13:04:11 +0000
private static final SimpleDateFormat _rfc822Date = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
private static final String getRFC822Date(long when) {
synchronized (_rfc822Date) {
return _rfc822Date.format(new Date(when));
}
}
private void renderEnclosures(User user, EntryContainer entry, String urlPrefix, Writer out) throws IOException {
if (entry.getAttachments() != null) {
for (int i = 0; i < _entry.getAttachments().length; i++) {
Attachment a = _entry.getAttachments()[i];
out.write(" <enclosure url=\"" + urlPrefix + sanitizeXML(getAttachmentURL(i))
+ "\" length=\"" + a.getDataLength()
+ "\" type=\"" + sanitizeTagParam(a.getMimeType()) + "\" syndietype=\"attachment\" />\n");
}
}
if (_blogs.size() > 0) {
for (int i = 0; i < _blogs.size(); i++) {
Blog b = (Blog)_blogs.get(i);
out.write(" <enclosure url=\"" + urlPrefix +
sanitizeXML(getPageURL(new Hash(Base64.decode(b.hash)), b.tag, b.entryId,
-1, -1, (_user != null ? _user.getShowExpanded() : false),
(_user != null ? _user.getShowImages() : false)))
+ "\" length=\"1\" type=\"text/html\" syndietype=\"blog\" />\n");
}
}
if (_links.size() > 0) {
for (int i = 0; i < _links.size(); i++) {
Link l = (Link)_links.get(i);
StringBuffer url = new StringBuffer(128);
url.append("externallink.jsp?schema=");
url.append(sanitizeURL(l.schema)).append("&location=");
url.append(sanitizeURL(l.location));
out.write(" <enclosure url=\"" + urlPrefix + sanitizeXML(url) + "\" length=\"1\" type=\"text/html\" syndietype=\"link\" />\n");
}
}
if (_addresses.size() > 0) {
for (int i = 0; i < _addresses.size(); i++) {
Address a = (Address)_addresses.get(i);
String knownName = null;
if (_user != null)
knownName = _user.getPetNameDB().getNameByLocation(a.location);
if (knownName == null) {
StringBuffer url = new StringBuffer(128);
url.append("addresses.jsp?network=");
url.append(sanitizeTagParam(a.schema)).append("&location=");
url.append(sanitizeTagParam(a.location)).append("&name=");
url.append(sanitizeTagParam(a.name)).append("&protocol=");
url.append(sanitizeTagParam(a.protocol));
out.write(" <enclosure url=\"" + urlPrefix + sanitizeXML(url) + "\" length=\"1\" type=\"text/html\" syndietype=\"address\" />\n");
}
}
}
if (_archives.size() > 0) {
for (int i = 0; i < _archives.size(); i++) {
ArchiveRef a = (ArchiveRef)_archives.get(i);
String url = getArchiveURL(null, new SafeURL(a.locationSchema + "://" + a.location));
out.write(" <enclosure url=\"" + urlPrefix + sanitizeXML(url) + "\" length=\"1\" type=\"text/html\" syndietype=\"archive\" />\n");
}
}
if (_entry != null) {
List replies = _archive.getIndex().getReplies(_entry.getURI());
if ( (replies != null) && (replies.size() > 0) ) {
for (int i = 0; i < replies.size(); i++) {
BlogURI reply = (BlogURI)replies.get(i);
String url = getPageURL(reply.getKeyHash(), null, reply.getEntryId(), -1, -1, true, _user.getShowImages());
out.write(" <enclosure url=\"" + urlPrefix + sanitizeXML(url) + "\" length=\"1\" type=\"text/html\" syndietype=\"reply\" />\n");
}
}
}
String inReplyTo = (String)_headers.get(HEADER_IN_REPLY_TO);
if ( (inReplyTo != null) && (inReplyTo.trim().length() > 0) ) {
String url = getPageURL(sanitizeTagParam(inReplyTo));
out.write(" <enclosure url=\"" + urlPrefix + sanitizeXML(url) + "\" length=\"1\" type=\"text/html\" syndietype=\"parent\" />\n");
}
}
public void receiveHeaderEnd() {}
public void receiveEnd() {}
public static void main(String args[]) {
test("");
test("&");
test("a&");
test("&a");
test("a&a");
test("aa&aa");
}
private static final void test(String str) {
StringBuffer t = new StringBuffer(str);
String sanitized = sanitizeXML(t);
System.out.println("[" + str + "] --> [" + sanitized + "]");
}
}

View File

@ -3,6 +3,8 @@ package net.i2p.syndie.sml;
import java.lang.String;
import java.util.*;
import net.i2p.syndie.data.*;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Parse out the SML from the text, firing off info to the receiver whenever certain
@ -12,6 +14,7 @@ import net.i2p.syndie.data.*;
*
*/
public class SMLParser {
private Log _log;
private static final char TAG_BEGIN = '[';
private static final char TAG_END = ']';
private static final char LT = '<';
@ -23,6 +26,10 @@ public class SMLParser {
private static final char NL = '\n';
private static final char CR = '\n';
private static final char LF = '\f';
public SMLParser(I2PAppContext ctx) {
_log = ctx.logManager().getLog(SMLParser.class);
}
public void parse(String rawSML, EventReceiver receiver) {
receiver.receiveBegin();
@ -196,6 +203,7 @@ public class SMLParser {
private static final String T_HR = "hr";
private static final String T_PRE = "pre";
private static final String T_ATTACHMENT = "attachment";
private static final String T_ARCHIVE = "archive";
private static final String P_ATTACHMENT = "attachment";
private static final String P_WHO_QUOTED = "author";
@ -210,7 +218,13 @@ public class SMLParser {
private static final String P_ADDRESS_NAME = "name";
private static final String P_ADDRESS_LOCATION = "location";
private static final String P_ADDRESS_SCHEMA = "schema";
private static final String P_ADDRESS_PROTOCOL = "proto";
private static final String P_ATTACHMENT_ID = "id";
private static final String P_ARCHIVE_NAME = "name";
private static final String P_ARCHIVE_DESCRIPTION = "description";
private static final String P_ARCHIVE_LOCATION_SCHEMA = "schema";
private static final String P_ARCHIVE_LOCATION = "location";
private static final String P_ARCHIVE_POSTING_KEY = "postingkey";
private void parseTag(String tagName, Map attr, String body, EventReceiver receiver) {
tagName = tagName.toLowerCase();
@ -241,10 +255,14 @@ public class SMLParser {
}
receiver.receiveBlog(getString(P_BLOG_NAME, attr), getString(P_BLOG_HASH, attr), getString(P_BLOG_TAG, attr),
getLong(P_BLOG_ENTRY, attr), locations, body);
} else if (T_ARCHIVE.equals(tagName)) {
receiver.receiveArchive(getString(P_ARCHIVE_NAME, attr), getString(P_ARCHIVE_DESCRIPTION, attr),
getString(P_ARCHIVE_LOCATION_SCHEMA, attr), getString(P_ARCHIVE_LOCATION, attr),
getString(P_ARCHIVE_POSTING_KEY, attr), body);
} else if (T_LINK.equals(tagName)) {
receiver.receiveLink(getString(P_LINK_SCHEMA, attr), getString(P_LINK_LOCATION, attr), body);
} else if (T_ADDRESS.equals(tagName)) {
receiver.receiveAddress(getString(P_ADDRESS_NAME, attr), getString(P_ADDRESS_SCHEMA, attr), getString(P_ADDRESS_LOCATION, attr), body);
receiver.receiveAddress(getString(P_ADDRESS_NAME, attr), getString(P_ADDRESS_SCHEMA, attr), getString(P_ADDRESS_PROTOCOL, attr), getString(P_ADDRESS_LOCATION, attr), body);
} else if (T_H1.equals(tagName)) {
receiver.receiveH1(body);
} else if (T_H2.equals(tagName)) {
@ -262,7 +280,8 @@ public class SMLParser {
} else if (T_ATTACHMENT.equals(tagName)) {
receiver.receiveAttachment((int)getLong(P_ATTACHMENT_ID, attr), body);
} else {
System.out.println("need to learn how to parse the tag [" + tagName + "]");
if (_log.shouldLog(Log.WARN))
_log.warn("need to learn how to parse the tag [" + tagName + "]");
}
}
@ -371,7 +390,7 @@ public class SMLParser {
public void receiveArchive(String name, String description, String locationSchema, String location,
String postingKey, String anchorText);
public void receiveImage(String alternateText, int attachmentId);
public void receiveAddress(String name, String schema, String location, String anchorText);
public void receiveAddress(String name, String schema, String protocol, String location, String anchorText);
public void receiveAttachment(int id, String anchorText);
public void receiveBold(String text);
public void receiveItalic(String text);
@ -426,7 +445,8 @@ public class SMLParser {
test("A: B\n\n[b]This[/b] is [i]special[/i][cut]why?[/cut][u]because I say so[/u].\neven if you dont care");
}
private static void test(String rawSML) {
SMLParser parser = new SMLParser();
parser.parse(rawSML, new EventReceiverImpl());
I2PAppContext ctx = I2PAppContext.getGlobalContext();
SMLParser parser = new SMLParser(ctx);
parser.parse(rawSML, new EventReceiverImpl(ctx));
}
}

View File

@ -0,0 +1,190 @@
package net.i2p.syndie.web;
import java.io.*;
import java.util.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import net.i2p.I2PAppContext;
import net.i2p.data.*;
import net.i2p.syndie.*;
import net.i2p.syndie.data.*;
/**
*
*/
public class ArchiveServlet extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String path = req.getPathInfo();
if ( (path == null) || (path.trim().length() <= 1) ) {
renderRootIndex(resp);
return;
} else if (path.endsWith(Archive.INDEX_FILE)) {
renderSummary(resp);
} else if (path.indexOf("export.zip") != -1) {
ExportServlet.export(req, resp);
} else {
String blog = getBlog(path);
if (path.endsWith(Archive.METADATA_FILE)) {
renderMetadata(blog, resp);
} else if (path.endsWith(".snd")) {
renderEntry(blog, getEntry(path), resp);
} else {
renderBlogIndex(blog, resp);
}
}
}
private String getBlog(String path) {
//System.err.println("Blog: [" + path + "]");
int start = 0;
int end = -1;
int len = path.length();
for (int i = 0; i < len; i++) {
if (path.charAt(i) != '/') {
start = i;
break;
}
}
for (int j = start + 1; j < len; j++) {
if (path.charAt(j) == '/') {
end = j;
break;
}
}
if (end < 0) end = len;
String rv = path.substring(start, end);
//System.err.println("Blog: [" + path + "] rv: [" + rv + "]");
return rv;
}
private long getEntry(String path) {
int start = path.lastIndexOf('/');
if (start < 0) return -1;
if (!(path.endsWith(".snd"))) return -1;
String rv = path.substring(start+1, path.length()-".snd".length());
//System.err.println("Entry: [" + path + "] rv: [" + rv + "]");
try {
return Long.parseLong(rv);
} catch (NumberFormatException nfe) {
return -1;
}
}
private void renderRootIndex(HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//resp.setCharacterEncoding("UTF-8");
OutputStream out = resp.getOutputStream();
out.write(DataHelper.getUTF8("<a href=\"archive.txt\">archive.txt</a><br />\n"));
ArchiveIndex index = BlogManager.instance().getArchive().getIndex();
Set blogs = index.getUniqueBlogs();
for (Iterator iter = blogs.iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
String s = blog.toBase64();
out.write(DataHelper.getUTF8("<a href=\"" + s + "/\">" + s + "</a><br />\n"));
}
out.close();
}
public static final String HEADER_EXPORT_CAPABLE = "X-Syndie-Export-Capable";
private void renderSummary(HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/plain;charset=utf-8");
//resp.setCharacterEncoding("UTF-8");
ArchiveIndex index = BlogManager.instance().getArchive().getIndex();
byte[] indexUTF8 = DataHelper.getUTF8(index.toString());
resp.setHeader(HEADER_EXPORT_CAPABLE, "true");
Hash hash = I2PAppContext.getGlobalContext().sha().calculateHash(indexUTF8);
resp.setHeader("ETag", "\"" + hash.toBase64() + "\"");
OutputStream out = resp.getOutputStream();
out.write(indexUTF8);
out.close();
}
private void renderMetadata(String blog, HttpServletResponse resp) throws ServletException, IOException {
byte b[] = Base64.decode(blog);
if ( (b == null) || (b.length != Hash.HASH_LENGTH) ) {
resp.sendError(404, "Invalid blog requested");
return;
}
Hash h = new Hash(b);
BlogInfo info = BlogManager.instance().getArchive().getBlogInfo(h);
if (info == null) {
resp.sendError(404, "Blog does not exist");
return;
}
resp.setContentType("application/x-syndie-meta");
OutputStream out = resp.getOutputStream();
info.write(out);
out.close();
}
private void renderBlogIndex(String blog, HttpServletResponse resp) throws ServletException, IOException {
byte b[] = Base64.decode(blog);
if ( (b == null) || (b.length != Hash.HASH_LENGTH) ) {
resp.sendError(404, "Invalid blog requested");
return;
}
Hash h = new Hash(b);
BlogInfo info = BlogManager.instance().getArchive().getBlogInfo(h);
if (info == null) {
resp.sendError(404, "Blog does not exist");
return;
}
resp.setContentType("text/html;charset=utf-8");
//resp.setCharacterEncoding("UTF-8");
OutputStream out = resp.getOutputStream();
out.write(DataHelper.getUTF8("<a href=\"..\">..</a><br />\n"));
out.write(DataHelper.getUTF8("<a href=\"" + Archive.METADATA_FILE + "\">" + Archive.METADATA_FILE + "</a><br />\n"));
List entries = new ArrayList(64);
BlogManager.instance().getArchive().getIndex().selectMatchesOrderByEntryId(entries, h, null);
for (int i = 0; i < entries.size(); i++) {
BlogURI entry = (BlogURI)entries.get(i);
out.write(DataHelper.getUTF8("<a href=\"" + entry.getEntryId() + ".snd\">" + entry.getEntryId() + ".snd</a><br />\n"));
}
out.close();
}
private void renderEntry(String blog, long entryId, HttpServletResponse resp) throws ServletException, IOException {
byte b[] = Base64.decode(blog);
if ( (b == null) || (b.length != Hash.HASH_LENGTH) ) {
resp.sendError(404, "Invalid blog requested");
return;
}
Hash h = new Hash(b);
BlogInfo info = BlogManager.instance().getArchive().getBlogInfo(h);
if (info == null) {
resp.sendError(404, "Blog does not exist");
return;
}
File root = BlogManager.instance().getArchive().getArchiveDir();
File blogDir = new File(root, blog);
if (!blogDir.exists()) {
resp.sendError(404, "Blog does not exist");
return;
}
File entry = new File(blogDir, entryId + ".snd");
if (!entry.exists()) {
resp.sendError(404, "Entry does not exist");
return;
}
resp.setContentType("application/x-syndie-post");
dump(entry, resp);
}
private void dump(File source, HttpServletResponse resp) throws ServletException, IOException {
FileInputStream in = new FileInputStream(source);
OutputStream out = resp.getOutputStream();
byte buf[] = new byte[1024];
int read = 0;
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
out.close();
in.close();
}
}

View File

@ -4,6 +4,8 @@ import java.io.*;
import java.text.*;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.PetName;
import net.i2p.client.naming.PetNameDB;
import net.i2p.data.*;
import net.i2p.syndie.*;
import net.i2p.syndie.data.*;
@ -20,25 +22,6 @@ public class ArchiveViewerBean {
else
return HTMLRenderer.sanitizeString(info.getProperty("Name"));
}
public static String getEntryTitle(String keyHash, long entryId) {
String name = getBlogName(keyHash);
return getEntryTitleDate(name, entryId);
}
private static final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd");
private static final String getEntryTitleDate(String blogName, long when) {
synchronized (_dateFormat) {
try {
String str = _dateFormat.format(new Date(when));
long dayBegin = _dateFormat.parse(str).getTime();
return blogName + ":<br /> <i>" + str + "-" + (when - dayBegin) + "</i>";
} catch (ParseException pe) {
pe.printStackTrace();
// wtf
return "unknown";
}
}
}
/** base64 encoded hash of the blog's public key, or null for no filtering by blog */
public static final String PARAM_BLOG = "blog";
@ -79,70 +62,157 @@ public class ArchiveViewerBean {
public static final String SEL_BLOGTAG = "blogtag://";
public static final String SEL_ENTRY = "entry://";
public static final String SEL_GROUP = "group://";
/** submit field for the selector form */
public static final String PARAM_SELECTOR_ACTION = "action";
public static final String SEL_ACTION_SET_AS_DEFAULT = "Set as default";
public static void renderBlogSelector(User user, Map parameters, Writer out) throws IOException {
out.write("<select name=\"");
String sel = getString(parameters, PARAM_SELECTOR);
String action = getString(parameters, PARAM_SELECTOR_ACTION);
if ( (sel != null) && (action != null) && (SEL_ACTION_SET_AS_DEFAULT.equals(action)) ) {
user.setDefaultSelector(HTMLRenderer.sanitizeString(sel, false));
BlogManager.instance().saveUser(user);
}
out.write("<select class=\"b_selector\" name=\"");
out.write(PARAM_SELECTOR);
out.write("\">");
out.write("<option value=\"");
out.write(getDefaultSelector(user, parameters));
out.write("\">Default blog filter</option>\n");
out.write("<option value=\"");
out.write(SEL_ALL);
out.write("\">All posts from all blogs</option>\n");
Map groups = null;
List groups = null;
if (user != null)
groups = user.getBlogGroups();
groups = user.getPetNameDB().getGroups();
if (groups != null) {
for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
out.write("<option value=\"group://" + Base64.encode(name.getBytes()) + "\">" +
for (int i = 0; i < groups.size(); i++) {
String name = (String)groups.get(i);
out.write("<option value=\"group://" + Base64.encode(DataHelper.getUTF8(name)) + "\">" +
"Group: " + HTMLRenderer.sanitizeString(name) + "</option>\n");
}
}
Archive archive = BlogManager.instance().getArchive();
ArchiveIndex index = archive.getIndex();
List allTags = new ArrayList();
// perhaps sort this by name (even though it isnt unique...)
Set blogs = index.getUniqueBlogs();
for (Iterator iter = blogs.iterator(); iter.hasNext(); ) {
Hash cur = (Hash)iter.next();
for (int i = 0; i < index.getNewestBlogCount(); i++) {
Hash cur = index.getNewestBlog(i);
String knownName = user.getPetNameDB().getNameByLocation(cur.toBase64());
PetName pn = null;
if (knownName != null) {
pn = user.getPetNameDB().get(knownName);
knownName = pn.getName();
}
if ( (pn != null) && (pn.isMember("Ignore")) )
continue;
String blog = Base64.encode(cur.getData());
out.write("<option value=\"blog://");
out.write(blog);
out.write("\">");
out.write("<option value=\"blog://" + blog + "\">");
out.write("New blog: ");
BlogInfo info = archive.getBlogInfo(cur);
String name = info.getProperty(BlogInfo.NAME);
String name = knownName;
if ( (name == null) && (info != null) )
name = info.getProperty(BlogInfo.NAME);
if (name != null)
name = HTMLRenderer.sanitizeString(name);
else
name = Base64.encode(cur.getData());
out.write(name);
out.write("- all posts</option>\n");
out.write("</option>\n");
}
////List allTags = new ArrayList();
// perhaps sort this by name (even though it isnt unique...)
Set blogs = index.getUniqueBlogs();
for (Iterator iter = blogs.iterator(); iter.hasNext(); ) {
Hash cur = (Hash)iter.next();
String knownName = user.getPetNameDB().getNameByLocation(cur.toBase64());
PetName pn = null;
if (knownName != null) {
pn = user.getPetNameDB().get(knownName);
knownName = pn.getName();
}
if ( (pn != null) && (pn.isMember("Ignore")) )
continue;
String blog = Base64.encode(cur.getData());
out.write("<option value=\"blog://");
out.write(blog);
out.write("\">");
BlogInfo info = archive.getBlogInfo(cur);
String name = knownName;
if ( (name == null) && (info != null) )
name = info.getProperty(BlogInfo.NAME);
if (name != null)
name = HTMLRenderer.sanitizeString(name);
else
name = Base64.encode(cur.getData());
out.write(name);
if (info != null) {
int howMany = index.getBlogEntryCount(info.getKey().calculateHash());
if (howMany == 1)
out.write(" [1 post]");
else
out.write(" [" + howMany + " posts]");
}
out.write("</option>\n");
/*
List tags = index.getBlogTags(cur);
for (int j = 0; j < tags.size(); j++) {
String tag = (String)tags.get(j);
if (false) {
StringBuffer b = new StringBuffer(tag.length()*2);
for (int k = 0; k < tag.length(); k++) {
b.append((int)tag.charAt(k));
b.append(' ');
}
System.out.println("tag in select: " + tag + ": " + b.toString());
}
if (!allTags.contains(tag))
allTags.add(tag);
out.write("<option value=\"blogtag://");
out.write(blog);
out.write("/");
out.write(Base64.encode(tag));
byte utf8tag[] = DataHelper.getUTF8(tag);
String encoded = Base64.encode(utf8tag);
if (false) {
byte utf8dec[] = Base64.decode(encoded);
String travel = DataHelper.getUTF8(utf8dec);
StringBuffer b = new StringBuffer();
for (int k = 0; k < travel.length(); k++) {
b.append((int)travel.charAt(k));
b.append(' ');
}
b.append(" encoded into: ");
for (int k = 0; k < encoded.length(); k++) {
b.append((int)encoded.charAt(k));
b.append(' ');
}
System.out.println("UTF8(unbase64(base64(UTF8(tag)))) == tag: " + b.toString());
}
out.write(encoded);
out.write("\">");
out.write(name);
out.write("- posts with the tag &quot;");
out.write(tag);
out.write("&quot;</option>\n");
}
*/
}
/*
for (int i = 0; i < allTags.size(); i++) {
String tag = (String)allTags.get(i);
out.write("<option value=\"tag://");
out.write(Base64.encode(tag));
out.write(Base64.encode(DataHelper.getUTF8(tag)));
out.write("\">Posts in any blog with the tag &quot;");
out.write(tag);
out.write("&quot;</option>\n");
}
*/
out.write("</select>");
int numPerPage = getInt(parameters, PARAM_NUM_PER_PAGE, 5);
@ -157,23 +227,42 @@ public class ArchiveViewerBean {
}
public static void renderBlogs(User user, Map parameters, Writer out) throws IOException {
private static String getDefaultSelector(User user, Map parameters) {
if ( (user == null) || (user.getDefaultSelector() == null) )
return BlogManager.instance().getArchive().getDefaultSelector();
else
return user.getDefaultSelector();
}
public static void renderBlogs(User user, Map parameters, Writer out, String afterPagination) throws IOException {
String blogStr = getString(parameters, PARAM_BLOG);
Hash blog = null;
if (blogStr != null) blog = new Hash(Base64.decode(blogStr));
if ( (blog != null) && (blog.getData() == null) ) blog = null;
String tag = getString(parameters, PARAM_TAG);
if (tag != null) tag = new String(Base64.decode(tag));
if (tag != null) tag = DataHelper.getUTF8(Base64.decode(tag));
long entryId = -1;
if (blogStr != null) {
if (blog != null) {
String entryIdStr = getString(parameters, PARAM_ENTRY);
try {
entryId = Long.parseLong(entryIdStr);
} catch (NumberFormatException nfe) {}
}
String group = getString(parameters, PARAM_GROUP);
if (group != null) group = new String(Base64.decode(group));
if (group != null) group = DataHelper.getUTF8(Base64.decode(group));
String sel = getString(parameters, PARAM_SELECTOR);
if (getString(parameters, "action") != null) {
tag = null;
blog = null;
sel = null;
group = null;
}
if ( (sel == null) && (blog == null) && (group == null) && (tag == null) )
sel = getDefaultSelector(user, parameters);
if (sel != null) {
Selector s = new Selector(sel);
blog = s.blog;
@ -188,7 +277,7 @@ public class ArchiveViewerBean {
boolean showImages = getBool(parameters, PARAM_SHOW_IMAGES, (user != null ? user.getShowImages() : false));
boolean regenerateIndex = getBool(parameters, PARAM_REGENERATE_INDEX, false);
try {
renderBlogs(user, blog, tag, entryId, group, numPerPage, pageNum, expandEntries, showImages, regenerateIndex, out);
renderBlogs(user, blog, tag, entryId, group, numPerPage, pageNum, expandEntries, showImages, regenerateIndex, sel, out, afterPagination);
} catch (IOException ioe) {
ioe.printStackTrace();
throw ioe;
@ -210,47 +299,93 @@ public class ArchiveViewerBean {
if (selector != null) {
if (selector.startsWith(SEL_BLOG)) {
String blogStr = selector.substring(SEL_BLOG.length());
System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "]");
blog = new Hash(Base64.decode(blogStr));
//System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "]");
byte h[] = Base64.decode(blogStr);
if (h != null)
blog = new Hash(h);
//else
// System.out.println("blog string does not decode properly: [" + blogStr + "]");
} else if (selector.startsWith(SEL_BLOGTAG)) {
int tagStart = selector.lastIndexOf('/');
String blogStr = selector.substring(SEL_BLOGTAG.length(), tagStart);
blog = new Hash(Base64.decode(blogStr));
if (blog.getData() == null) {
System.out.println("Blog string [" + blogStr + "] does not decode");
blog = null;
return;
}
tag = selector.substring(tagStart+1);
if (tag != null) tag = new String(Base64.decode(tag));
System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "] tag: [" + tag + "]");
String origTag = tag;
byte rawDecode[] = null;
if (tag != null) {
rawDecode = Base64.decode(tag);
tag = DataHelper.getUTF8(rawDecode);
}
//System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "] tag: [" + tag + "]");
if (false && tag != null) {
StringBuffer b = new StringBuffer(tag.length()*2);
for (int j = 0; j < tag.length(); j++) {
b.append((int)tag.charAt(j));
if (rawDecode.length > j)
b.append('.').append((int)rawDecode[j]);
b.append(' ');
}
b.append("encoded as ");
for (int j = 0; j < origTag.length(); j++) {
b.append((int)origTag.charAt(j)).append(' ');
}
//System.out.println("selected tag: " + b.toString());
}
} else if (selector.startsWith(SEL_TAG)) {
tag = selector.substring(SEL_TAG.length());
if (tag != null) tag = new String(Base64.decode(tag));
System.out.println("Selector [" + selector + "] tag: [" + tag + "]");
byte rawDecode[] = null;
if (tag != null) {
rawDecode = Base64.decode(tag);
tag = DataHelper.getUTF8(rawDecode);
}
//System.out.println("Selector [" + selector + "] tag: [" + tag + "]");
if (false && tag != null) {
StringBuffer b = new StringBuffer(tag.length()*2);
for (int j = 0; j < tag.length(); j++) {
b.append((int)tag.charAt(j));
if (rawDecode.length > j)
b.append('.').append((int)rawDecode[j]);
b.append(' ');
}
//System.out.println("selected tag: " + b.toString());
}
} else if (selector.startsWith(SEL_ENTRY)) {
int entryStart = selector.lastIndexOf('/');
String blogStr = selector.substring(SEL_ENTRY.length(), entryStart);
String blogStr = blogStr = selector.substring(SEL_ENTRY.length(), entryStart);
String entryStr = selector.substring(entryStart+1);
try {
entry = Long.parseLong(entryStr);
blog = new Hash(Base64.decode(blogStr));
System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "] entry: [" + entry + "]");
Hash h = new Hash(Base64.decode(blogStr));
if (h.getData() != null)
blog = h;
//else
// System.out.println("Blog does not decode [" + blogStr + "]");
//System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "] entry: [" + entry + "]");
} catch (NumberFormatException nfe) {}
} else if (selector.startsWith(SEL_GROUP)) {
group = new String(Base64.decode(selector.substring(SEL_GROUP.length())));
System.out.println("Selector [" + selector + "] group: [" + group + "]");
group = DataHelper.getUTF8(Base64.decode(selector.substring(SEL_GROUP.length())));
//System.out.println("Selector [" + selector + "] group: [" + group + "]");
}
}
}
}
public static void renderBlogs(User user, Hash blog, String tag, long entryId, String group, int numPerPage, int pageNum,
boolean expandEntries, boolean showImages, boolean regenerateIndex, Writer out) throws IOException {
private static void renderBlogs(User user, Hash blog, String tag, long entryId, String group, int numPerPage, int pageNum,
boolean expandEntries, boolean showImages, boolean regenerateIndex, String selector, Writer out, String afterPagination) throws IOException {
Archive archive = BlogManager.instance().getArchive();
if (regenerateIndex)
archive.regenerateIndex();
ArchiveIndex index = archive.getIndex();
List entries = pickEntryURIs(user, index, blog, tag, entryId, group);
System.out.println("Searching for " + blog + "/" + tag + "/" + entryId + "/" + pageNum + "/" + numPerPage + "/" + group);
System.out.println("Entry URIs: " + entries);
//System.out.println("Searching for " + blog + "/" + tag + "/" + entryId + "/" + pageNum + "/" + numPerPage + "/" + group);
//System.out.println("Entry URIs: " + entries);
HTMLRenderer renderer = new HTMLRenderer();
HTMLRenderer renderer = new HTMLRenderer(I2PAppContext.getGlobalContext());
int start = pageNum * numPerPage;
int end = start + numPerPage;
int pages = 1;
@ -268,26 +403,33 @@ public class ArchiveViewerBean {
pages = entries.size() / numPerPage;
if (numPerPage * pages < entries.size())
pages++;
out.write("<i>");
if (pageNum > 0) {
String prevURL = HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum-1, expandEntries, showImages);
System.out.println("prevURL: " + prevURL);
out.write(" <a href=\"" + prevURL + "\">&lt;&lt;</a>");
String prevURL = null;
if ( (selector == null) || (selector.trim().length() <= 0) )
prevURL = HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum-1, expandEntries, showImages);
else
prevURL = HTMLRenderer.getPageURL(user, selector, numPerPage, pageNum-1);
//System.out.println("prevURL: " + prevURL);
out.write(" <a class=\"b_selectorPrevMore\" href=\"" + prevURL + "\">&lt;&lt;</a>");
} else {
out.write(" &lt;&lt; ");
out.write(" <span class=\"b_selectorPrevNone\">&lt;&lt;</span> ");
}
out.write("Page " + (pageNum+1) + " of " + pages);
out.write("<span class=\"b_selectorPage\">Page " + (pageNum+1) + " of " + pages + "</span>");
if (pageNum + 1 < pages) {
String nextURL = HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum+1, expandEntries, showImages);
System.out.println("nextURL: " + nextURL);
out.write(" <a href=\"" + nextURL + "\">&gt;&gt;</a>");
String nextURL = null;
if ( (selector == null) || (selector.trim().length() <= 0) )
nextURL = HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum+1, expandEntries, showImages);
else
nextURL = HTMLRenderer.getPageURL(user, selector, numPerPage, pageNum+1);
//System.out.println("nextURL: " + nextURL);
out.write(" <a class=\"b_selectorNextMore\" href=\"" + nextURL + "\">&gt;&gt;</a>");
} else {
out.write(" &gt;&gt;");
out.write(" <span class=\"b_selectorNextNone\">&gt;&gt;</span>");
}
out.write("</i>");
}
}
/*
out.write(" <i>");
if (showImages)
@ -305,9 +447,13 @@ public class ArchiveViewerBean {
"\">Expand details</a>");
out.write("</i>");
*/
if (afterPagination != null)
out.write(afterPagination);
if (entries.size() <= 0) end = -1;
System.out.println("Entries.size: " + entries.size() + " start=" + start + " end=" + end);
//System.out.println("Entries.size: " + entries.size() + " start=" + start + " end=" + end);
for (int i = start; i < end; i++) {
BlogURI uri = (BlogURI)entries.get(i);
EntryContainer c = archive.getEntry(uri);
@ -323,7 +469,9 @@ public class ArchiveViewerBean {
}
}
private static List pickEntryURIs(User user, ArchiveIndex index, Hash blog, String tag, long entryId, String group) {
public static List pickEntryURIs(User user, ArchiveIndex index, Hash blog, String tag, long entryId, String group) {
if ( (blog != null) && ( (blog.getData() == null) || (blog.getData().length != Hash.HASH_LENGTH) ) )
blog = null;
List rv = new ArrayList(16);
if ( (blog != null) && (entryId >= 0) ) {
rv.add(new BlogURI(blog, entryId));
@ -333,7 +481,7 @@ public class ArchiveViewerBean {
if ( (group != null) && (user != null) ) {
List selectors = (List)user.getBlogGroups().get(group);
if (selectors != null) {
System.out.println("Selectors for group " + group + ": " + selectors);
//System.out.println("Selectors for group " + group + ": " + selectors);
for (int i = 0; i < selectors.size(); i++) {
String sel = (String)selectors.get(i);
Selector s = new Selector(sel);
@ -342,14 +490,58 @@ public class ArchiveViewerBean {
else
index.selectMatchesOrderByEntryId(rv, s.blog, s.tag);
}
return rv;
}
PetNameDB db = user.getPetNameDB();
for (Iterator iter = db.getNames().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
PetName pn = db.get(name);
if ("syndie".equals(pn.getNetwork()) && "syndieblog".equals(pn.getProtocol()) && pn.isMember(group)) {
byte pnLoc[] = Base64.decode(pn.getLocation());
if (pnLoc != null) {
Hash pnHash = new Hash(pnLoc);
index.selectMatchesOrderByEntryId(rv, pnHash, null);
}
}
}
sort(rv);
if (rv.size() > 0)
return rv;
}
index.selectMatchesOrderByEntryId(rv, blog, tag);
filterIgnored(user, rv);
return rv;
}
private static final String getString(Map parameters, String param) {
private static void filterIgnored(User user, List uris) {
for (int i = 0; i < uris.size(); i++) {
BlogURI uri = (BlogURI)uris.get(i);
Hash k = uri.getKeyHash();
if (k == null) continue;
String pname = user.getPetNameDB().getNameByLocation(k.toBase64());
if (pname != null) {
PetName pn = user.getPetNameDB().get(pname);
if ( (pn != null) && (pn.isMember("Ignore")) ) {
uris.remove(i);
i--;
}
}
}
}
private static void sort(List uris) {
TreeMap ordered = new TreeMap();
while (uris.size() > 0) {
BlogURI uri = (BlogURI)uris.remove(0);
int off = 0;
while (ordered.containsKey(new Long(0 - off - uri.getEntryId())))
off++;
ordered.put(new Long(0-off-uri.getEntryId()), uri);
}
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); )
uris.add(iter.next());
}
public static final String getString(Map parameters, String param) {
if ( (parameters == null) || (parameters.get(param) == null) )
return null;
Object vals = parameters.get(param);
@ -369,6 +561,24 @@ public class ArchiveViewerBean {
return null;
}
}
public static final String[] getStrings(Map parameters, String param) {
if ( (parameters == null) || (parameters.get(param) == null) )
return null;
Object vals = parameters.get(param);
if (vals.getClass().isArray()) {
return (String[])vals;
} else if (vals instanceof Collection) {
Collection c = (Collection)vals;
if (c.size() <= 0) return null;
String rv[] = new String[c.size()];
int i = 0;
for (Iterator iter = c.iterator(); iter.hasNext(); i++)
rv[i] = (String)iter.next();
return rv;
} else {
return null;
}
}
private static final int getInt(Map param, String key, int defaultVal) {
String val = getString(param, key);
@ -410,6 +620,17 @@ public class ArchiveViewerBean {
return "application/octet-stream";
}
public static final boolean getAttachmentShouldShowInline(Map parameters) {
Attachment a = getAttachment(parameters);
if (a == null)
return true;
String mime = a.getMimeType();
if ( (mime != null) && ((mime.startsWith("image/") || mime.startsWith("text/plain"))) )
return true;
else
return false;
}
public static final int getAttachmentContentLength(Map parameters) {
Attachment a = getAttachment(parameters);
if (a != null)
@ -418,6 +639,14 @@ public class ArchiveViewerBean {
return -1;
}
public static final String getAttachmentFilename(Map parameters) {
Attachment a = getAttachment(parameters);
if (a != null)
return a.getName();
else
return "attachment.dat";
}
private static final Attachment getAttachment(Map parameters) {
String blogStr = getString(parameters, PARAM_BLOG);
Hash blog = null;
@ -440,10 +669,63 @@ public class ArchiveViewerBean {
}
private static void renderInvalidAttachment(Map parameters, OutputStream out) throws IOException {
out.write("<b>No such entry, or no such attachment</b>".getBytes());
out.write(DataHelper.getUTF8("<span class=\"b_msgErr\">No such entry, or no such attachment</span>"));
}
public static void renderMetadata(Map parameters, Writer out) throws IOException {
private static String getURL(String uri, Map parameters) {
StringBuffer rv = new StringBuffer(128);
rv.append(uri);
rv.append('?');
if (parameters != null) {
for (Iterator iter = parameters.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String vals[] = getStrings(parameters, key);
// we are already looking at the page with the given parameters, no need to further sanitize
if ( (key != null) && (vals != null) )
for (int i = 0; i < vals.length; i++)
rv.append(key).append('=').append(vals[i]).append('&');
}
}
return rv.toString();
}
private static void updateMetadata(User viewer, Map parameters, Writer out) throws IOException {
if ( (viewer == null) || (!viewer.getAuthenticated()) )
return;
String blogStr = getString(parameters, PARAM_BLOG);
if (blogStr != null) {
Hash blog = new Hash(Base64.decode(blogStr));
Archive archive = BlogManager.instance().getArchive();
BlogInfo info = archive.getBlogInfo(blog);
if (info != null) {
boolean isUser = viewer.getBlog().equals(info.getKey().calculateHash());
if (!isUser)
return;
Properties toSave = new Properties();
String existing[] = info.getProperties();
for (int i = 0; i < existing.length; i++) {
String newVal = getString(parameters, existing[i]);
if ( (newVal != null) && (newVal.length() > 0) )
toSave.setProperty(existing[i], newVal.trim());
else
toSave.setProperty(existing[i], info.getProperty(existing[i]));
}
boolean saved = BlogManager.instance().updateMetadata(viewer, blog, toSave);
if (saved)
out.write("<p><em class=\"b_msgOk\">Blog metadata saved</em></p>\n");
else
out.write("<p><em class=\"b_msgErr\">Blog metadata could not be saved</em></p>\n");
}
}
}
/**
* @param currentURI URI of the with current page without any parameters tacked on
*/
public static void renderMetadata(User viewer, String currentURI, Map parameters, Writer out) throws IOException {
if (parameters.get("action") != null) {
updateMetadata(viewer, parameters, out);
}
String blogStr = getString(parameters, PARAM_BLOG);
if (blogStr != null) {
Hash blog = new Hash(Base64.decode(blogStr));
@ -453,44 +735,59 @@ public class ArchiveViewerBean {
out.write("Blog " + blog.toBase64() + " does not exist");
return;
}
boolean isUser = ( (viewer != null) && (viewer.getAuthenticated()) && (viewer.getBlog().equals(info.getKey().calculateHash())) );
String props[] = info.getProperties();
out.write("<table border=\"0\">");
if (isUser) {
out.write("<form action=\"" + getURL(currentURI, parameters) + "\" method=\"GET\">\n");
out.write("<input type=\"hidden\" name=\"submit_blog\" value=\"" + blog.toBase64() + "\" />\n");
}
out.write("<table class=\"b_meta\" border=\"0\">");
for (int i = 0; i < props.length; i++) {
if (props[i].equals(BlogInfo.OWNER_KEY)) {
out.write("<tr><td><b>Blog:</b></td><td>");
out.write("<tr class=\"b_metaBlog\"><td class=\"b_metaBlog\"><span class=\"b_metaBlog\">Blog:</span></td>");
String blogURL = HTMLRenderer.getPageURL(blog, null, -1, -1, -1, false, false);
out.write("<a href=\"" + blogURL + "\">" + Base64.encode(blog.getData()) + "</td></tr>\n");
out.write("<td class=\"b_metaBlog\"><a class=\"b_metaBlog\" href=\"" + blogURL + "\">" + Base64.encode(blog.getData()) + "</td></tr>\n");
} else if (props[i].equals(BlogInfo.SIGNATURE)) {
continue;
} else if (props[i].equals(BlogInfo.POSTERS)) {
SigningPublicKey keys[] = info.getPosters();
if ( (keys != null) && (keys.length > 0) ) {
out.write("<tr><td><b>Allowed authors:</b></td><td>");
out.write("<tr class=\"b_metaAuthor\"><td class=\"b_metaAuthor\"><span class=\"b_metaAuthor\">Allowed authors:</span></td>");
out.write("<td class=\"b_metaAuthor\">");
for (int j = 0; j < keys.length; j++) {
out.write(keys[j].calculateHash().toBase64());
out.write("<span class=\"b_metaAuthor\">" + keys[j].calculateHash().toBase64() + "</span>");
if (j + 1 < keys.length)
out.write("<br />\n");
}
out.write("</td></tr>\n");
}
} else {
out.write("<tr><td>" + HTMLRenderer.sanitizeString(props[i]) + ":</td><td>" +
HTMLRenderer.sanitizeString(info.getProperty(props[i])) + "</td></tr>\n");
String field = HTMLRenderer.sanitizeString(props[i]);
String val = HTMLRenderer.sanitizeString(info.getProperty(props[i]));
out.write("<tr class=\"b_metaField\"><td class=\"b_metaField\"><span class=\"b_metaField\">" + field
+ ":</span></td><td class=\"b_metaValue\"><span class=\"b_metaValue\">" + val + "</span></td></tr>\n");
if (isUser && (!field.equals("Edition")))
out.write("<tr class=\"b_metaField\"><td>&nbsp;</td><td class=\"b_metaValue\"><input type=\"text\" name=\""
+ HTMLRenderer.sanitizeTagParam(props[i]) + "\" value=\""
+ HTMLRenderer.sanitizeTagParam(info.getProperty(props[i])) + "\" size=\"40\" ></td></tr>");
}
}
List tags = BlogManager.instance().getArchive().getIndex().getBlogTags(blog);
if ( (tags != null) && (tags.size() > 0) ) {
out.write("<tr><td>Known tags:</td><td>");
out.write("<tr class=\"b_metaTags\"><td class=\"b_metaTags\"><span class=\"b_metaTags\">Known tags:</span></td><td class=\"b_metaTags\">");
for (int i = 0; i < tags.size(); i++) {
String tag = (String)tags.get(i);
out.write("<a href=\"" + HTMLRenderer.getPageURL(blog, tag, -1, -1, -1, false, false) + "\">" +
out.write("<a class=\"b_metaTag\" href=\"" + HTMLRenderer.getPageURL(blog, tag, -1, -1, -1, false, false) + "\">" +
HTMLRenderer.sanitizeString(tag) + "</a> ");
}
out.write("</td></tr>");
}
if (isUser)
out.write("<tr class=\"b_metaField\"><td colspan=\"2\" class=\"b_metaField\"><input type=\"submit\" name=\"action\" value=\"Save changes\" class=\"b_metaSave\" /></td></tr>\n");
out.write("</table>");
} else {
out.write("Blog not specified");
out.write("<span class=\"b_metaMsgErr\">Blog not specified</span>");
}
}
}

View File

@ -0,0 +1,183 @@
package net.i2p.syndie.web;
import java.io.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import net.i2p.data.*;
import net.i2p.syndie.*;
import net.i2p.syndie.data.*;
/**
* Dump out a whole series of blog metadata and entries as a zip stream. All metadata
* is written before any entries, so it can be processed in order safely.
*
* HTTP parameters:
* = meta (multiple values): base64 hash of the blog for which metadata is requested
* = entry (multiple values): blog URI of an entry being requested
*/
public class ExportServlet extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
export(req, resp);
}
public static void export(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doExport(req, resp);
} catch (ServletException se) {
se.printStackTrace();
throw se;
} catch (IOException ioe) {
ioe.printStackTrace();
throw ioe;
}
}
private static void doExport(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String meta[] = null;
String entries[] = null;
String type = req.getHeader("Content-Type");
if ( (type == null) || (type.indexOf("boundary") == -1) ) {
// it has to be POSTed with the request, name=value pairs. the export servlet doesn't allow any
// free form fields, so no worry about newlines, so lets parse 'er up
List metaList = new ArrayList();
List entryList = new ArrayList();
StringBuffer key = new StringBuffer();
StringBuffer val = null;
String lenStr = req.getHeader("Content-length");
int len = -1;
if (lenStr != null)
try { len = Integer.valueOf(lenStr).intValue(); } catch (NumberFormatException nfe) {}
int read = 0;
int c = 0;
InputStream in = req.getInputStream();
while ( (len == -1) || (read < len) ){
c = in.read();
if ( (c == '=') && (val == null) ) {
val = new StringBuffer(128);
} else if ( (c == -1) || (c == '&') ) {
String k = (key == null ? "" : key.toString());
String v = (val == null ? "" : val.toString());
if ("meta".equals(k))
metaList.add(v.trim());
else if ("entry".equals(k))
entryList.add(v.trim());
key.setLength(0);
val = null;
// no newlines in the export servlet
if (c == -1)
break;
} else {
if (val == null)
key.append((char)c);
else
val.append((char)c);
}
read++;
}
if (metaList != null) {
meta = new String[metaList.size()];
for (int i = 0; i < metaList.size(); i++)
meta[i] = (String)metaList.get(i);
}
if (entryList != null) {
entries = new String[entryList.size()];
for (int i = 0; i < entryList.size(); i++)
entries[i] = (String)entryList.get(i);
}
} else {
meta = req.getParameterValues("meta");
entries = req.getParameterValues("entry");
}
resp.setContentType("application/x-syndie-zip");
resp.setStatus(200);
OutputStream out = resp.getOutputStream();
if (false) {
StringBuffer bbuf = new StringBuffer(1024);
bbuf.append("meta: ");
if (meta != null)
for (int i = 0; i < meta.length; i++)
bbuf.append(meta[i]).append(", ");
bbuf.append("entries: ");
if (entries != null)
for (int i = 0; i < entries.length; i++)
bbuf.append(entries[i]).append(", ");
System.out.println(bbuf.toString());
}
ZipOutputStream zo = null;
if ( (meta != null) && (entries != null) && (meta.length + entries.length > 0) )
zo = new ZipOutputStream(out);
List metaFiles = getMetaFiles(meta);
ZipEntry ze = null;
byte buf[] = new byte[1024];
int read = -1;
for (int i = 0; metaFiles != null && i < metaFiles.size(); i++) {
ze = new ZipEntry("meta" + i);
ze.setTime(0);
zo.putNextEntry(ze);
FileInputStream in = new FileInputStream((File)metaFiles.get(i));
while ( (read = in.read(buf)) != -1)
zo.write(buf, 0, read);
zo.closeEntry();
}
List entryFiles = getEntryFiles(entries);
for (int i = 0; entryFiles != null && i < entryFiles.size(); i++) {
ze = new ZipEntry("entry" + i);
ze.setTime(0);
zo.putNextEntry(ze);
FileInputStream in = new FileInputStream((File)entryFiles.get(i));
while ( (read = in.read(buf)) != -1)
zo.write(buf, 0, read);
zo.closeEntry();
}
if (zo != null) {
zo.finish();
zo.close();
}
}
private static List getMetaFiles(String blogHashes[]) {
if ( (blogHashes == null) || (blogHashes.length <= 0) ) return null;
File dir = BlogManager.instance().getArchive().getArchiveDir();
List rv = new ArrayList(blogHashes.length);
for (int i = 0; i < blogHashes.length; i++) {
byte hv[] = Base64.decode(blogHashes[i]);
if ( (hv == null) || (hv.length != Hash.HASH_LENGTH) )
continue;
File blogDir = new File(dir, blogHashes[i]);
File metaFile = new File(blogDir, Archive.METADATA_FILE);
if (metaFile.exists())
rv.add(metaFile);
}
return rv;
}
private static List getEntryFiles(String blogURIs[]) {
if ( (blogURIs == null) || (blogURIs.length <= 0) ) return null;
File dir = BlogManager.instance().getArchive().getArchiveDir();
List rv = new ArrayList(blogURIs.length);
for (int i = 0; i < blogURIs.length; i++) {
BlogURI uri = new BlogURI(blogURIs[i]);
if (uri.getEntryId() < 0)
continue;
File blogDir = new File(dir, uri.getKeyHash().toBase64());
File entryFile = new File(blogDir, uri.getEntryId() + ".snd");
if (entryFile.exists())
rv.add(entryFile);
}
return rv;
}
}

View File

@ -0,0 +1,175 @@
package net.i2p.syndie.web;
import java.io.*;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.PetName;
import net.i2p.syndie.*;
import net.i2p.syndie.data.BlogURI;
import net.i2p.syndie.sml.HTMLPreviewRenderer;
import net.i2p.util.Log;
/**
*
*/
public class PostBean {
private I2PAppContext _context;
private Log _log;
private User _user;
private String _subject;
private String _tags;
private String _headers;
private String _text;
private String _archive;
private List _filenames;
private List _fileStreams;
private List _localFiles;
private List _fileTypes;
private boolean _previewed;
public PostBean() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(PostBean.class);
reinitialize();
}
public void reinitialize() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Reinitializing " + (_text != null ? "(with " + _text.length() + " bytes of sml!)" : ""));
_user = null;
_subject = null;
_tags = null;
_text = null;
_headers = null;
_archive = null;
_filenames = new ArrayList();
_fileStreams = new ArrayList();
_fileTypes = new ArrayList();
if (_localFiles != null)
for (int i = 0; i < _localFiles.size(); i++)
((File)_localFiles.get(i)).delete();
_localFiles = new ArrayList();
_previewed = false;
}
public User getUser() { return _user; }
public String getSubject() { return (_subject != null ? _subject : ""); }
public String getTags() { return (_tags != null ? _tags : ""); }
public String getText() { return (_text != null ? _text : ""); }
public String getHeaders() { return (_headers != null ? _headers : ""); }
public void setUser(User user) { _user = user; }
public void setSubject(String subject) { _subject = subject; }
public void setTags(String tags) { _tags = tags; }
public void setText(String text) { _text = text; }
public void setHeaders(String headers) { _headers = headers; }
public void setArchive(String archive) { _archive = archive; }
public String getContentType(int id) {
if ( (id >= 0) && (id < _fileTypes.size()) )
return (String)_fileTypes.get(id);
return "application/octet-stream";
}
public void writeAttachmentData(int id, OutputStream out) throws IOException {
FileInputStream in = new FileInputStream((File)_localFiles.get(id));
byte buf[] = new byte[1024];
int read = 0;
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
out.close();
}
public void addAttachment(String filename, InputStream fileStream, String mimeType) {
_filenames.add(filename);
_fileStreams.add(fileStream);
_fileTypes.add(mimeType);
}
public int getAttachmentCount() { return (_filenames != null ? _filenames.size() : 0); }
public BlogURI postEntry() throws IOException {
if (!_previewed) return null;
List localStreams = new ArrayList(_localFiles.size());
for (int i = 0; i < _localFiles.size(); i++) {
File f = (File)_localFiles.get(i);
localStreams.add(new FileInputStream(f));
}
BlogURI uri = BlogManager.instance().createBlogEntry(_user, _subject, _tags, _headers, _text,
_filenames, localStreams, _fileTypes);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Posted the entry " + uri.toString() + " (archive = " + _archive + ")");
if ( (uri != null) && BlogManager.instance().authorizeRemote(_user) ) {
PetName pn = _user.getPetNameDB().get(_archive);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Archive to petname? " + pn + " (protocol: " + (pn != null ? pn.getProtocol() : "") + ")");
if ( (pn != null) && ("syndiearchive".equals(pn.getProtocol())) ) {
RemoteArchiveBean r = new RemoteArchiveBean();
Map params = new HashMap();
params.put("localentry", new String[] { uri.toString() });
String proxyHost = BlogManager.instance().getDefaultProxyHost();
String port = BlogManager.instance().getDefaultProxyPort();
int proxyPort = 4444;
try { proxyPort = Integer.parseInt(port); } catch (NumberFormatException nfe) {}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Posting the entry " + uri.toString() + " to " + pn.getLocation());
r.postSelectedEntries(_user, params, proxyHost, proxyPort, pn.getLocation());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Post status: " + r.getStatus());
}
}
return uri;
}
public void renderPreview(Writer out) throws IOException {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Subject: " + _subject);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Text: " + _text);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Headers: " + _headers);
// cache all the _fileStreams into temporary files, storing those files in _localFiles
// then render the page accordingly with an HTMLRenderer, altered to use a different
// 'view attachment'
cacheAttachments();
String smlContent = renderSMLContent();
HTMLPreviewRenderer r = new HTMLPreviewRenderer(_context, _filenames, _fileTypes, _localFiles);
r.render(_user, BlogManager.instance().getArchive(), null, smlContent, out, false, true);
_previewed = true;
}
private String renderSMLContent() {
StringBuffer raw = new StringBuffer();
raw.append("Subject: ").append(_subject).append('\n');
raw.append("Tags: ");
StringTokenizer tok = new StringTokenizer(_tags, " \t\n");
while (tok.hasMoreTokens())
raw.append(tok.nextToken()).append('\t');
raw.append('\n');
raw.append(_headers.trim());
raw.append("\n\n");
raw.append(_text.trim());
return raw.toString();
}
private void cacheAttachments() throws IOException {
File postCacheDir = new File(BlogManager.instance().getTempDir(), _user.getBlog().toBase64());
if (!postCacheDir.exists())
postCacheDir.mkdirs();
for (int i = 0; i < _fileStreams.size(); i++) {
InputStream in = (InputStream)_fileStreams.get(i);
File f = File.createTempFile("attachment", ".dat", postCacheDir);
FileOutputStream o = new FileOutputStream(f);
byte buf[] = new byte[1024];
int read = 0;
while ( (read = in.read(buf)) != -1)
o.write(buf, 0, read);
o.close();
in.close();
_localFiles.add(f);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Caching attachment " + i + " temporarily in "
+ f.getAbsolutePath() + " w/ " + f.length() + "bytes");
}
_fileStreams.clear();
}
}

View File

@ -0,0 +1,95 @@
package net.i2p.syndie.web;
import java.io.*;
import java.util.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import net.i2p.I2PAppContext;
import net.i2p.data.*;
import net.i2p.syndie.*;
import net.i2p.syndie.data.*;
import net.i2p.syndie.sml.*;
/**
*
*/
public class RSSServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("application/rss+xml");
User user = (User)req.getSession().getAttribute("user");
if (user == null) {
String login = req.getParameter("login");
String pass = req.getParameter("password");
user = new User();
BlogManager.instance().login(user, login, pass); // ignore failures - user will just be unauthorized
if (!user.getAuthenticated())
user.invalidate();
}
String selector = req.getParameter("selector");
if ( (selector == null) || (selector.length() <= 0) ) {
selector = getDefaultSelector(user);
}
ArchiveViewerBean.Selector sel = new ArchiveViewerBean.Selector(selector);
Archive archive = BlogManager.instance().getArchive();
ArchiveIndex index = archive.getIndex();
List entries = ArchiveViewerBean.pickEntryURIs(user, index, sel.blog, sel.tag, sel.entry, sel.group);
StringBuffer cur = new StringBuffer();
cur.append(req.getScheme());
cur.append("://");
cur.append(req.getServerName());
if (req.getServerPort() != 80)
cur.append(':').append(req.getServerPort());
cur.append(req.getContextPath()).append('/');
String urlPrefix = cur.toString();
Writer out = resp.getWriter();
out.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
out.write("<rss version=\"2.0\">\n");
out.write(" <channel>\n");
out.write(" <title>Syndie feed</title>\n");
out.write(" <link>" + urlPrefix + HTMLRenderer.sanitizeXML(HTMLRenderer.getPageURL(sel.blog, sel.tag, sel.entry, sel.group, 5, 0, false, false)) +"</link>\n");
out.write(" <description>Summary of the latest Syndie posts</description>\n");
out.write(" <generator>Syndie</generator>\n");
int count = 10;
String wanted = req.getParameter("wanted");
if (wanted != null) {
try {
count = Integer.parseInt(wanted);
} catch (NumberFormatException nfe) {
count = 10;
}
}
if (count < 0) count = 10;
if (count > 100) count = 100;
RSSRenderer r = new RSSRenderer(I2PAppContext.getGlobalContext());
for (int i = 0; i < count && i < entries.size(); i++) {
BlogURI uri = (BlogURI)entries.get(i);
EntryContainer entry = archive.getEntry(uri);
r.render(user, archive, entry, urlPrefix, out);
}
out.write(" </channel>\n");
out.write("</rss>\n");
out.close();
}
private static String getDefaultSelector(User user) {
if ( (user == null) || (user.getDefaultSelector() == null) )
return BlogManager.instance().getArchive().getDefaultSelector();
else
return user.getDefaultSelector();
}
}

View File

@ -0,0 +1,777 @@
package net.i2p.syndie.web;
import java.io.*;
import java.text.*;
import java.util.*;
import java.util.zip.*;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.PetNameDB;
import net.i2p.data.*;
import net.i2p.util.EepGet;
import net.i2p.util.EepGetScheduler;
import net.i2p.util.EepPost;
import net.i2p.syndie.data.*;
import net.i2p.syndie.sml.*;
import net.i2p.syndie.*;
import net.i2p.util.Log;
/**
*
*/
public class RemoteArchiveBean {
private I2PAppContext _context;
private Log _log;
private String _remoteSchema;
private String _remoteLocation;
private String _proxyHost;
private int _proxyPort;
private ArchiveIndex _remoteIndex;
private List _statusMessages;
private boolean _fetchIndexInProgress;
private boolean _exportCapable;
public RemoteArchiveBean() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(RemoteArchiveBean.class);
reinitialize();
}
public void reinitialize() {
_remoteSchema = null;
_remoteLocation = null;
_remoteIndex = null;
_fetchIndexInProgress = false;
_proxyHost = null;
_proxyPort = -1;
_exportCapable = false;
_statusMessages = new ArrayList();
}
public String getRemoteSchema() { return _remoteSchema; }
public String getRemoteLocation() { return _remoteLocation; }
public ArchiveIndex getRemoteIndex() { return _remoteIndex; }
public String getProxyHost() { return _proxyHost; }
public int getProxyPort() { return _proxyPort; }
public boolean getFetchIndexInProgress() { return _fetchIndexInProgress; }
public String getStatus() {
StringBuffer buf = new StringBuffer();
while (_statusMessages.size() > 0)
buf.append(_statusMessages.remove(0)).append("\n");
return buf.toString();
}
private boolean ignoreBlog(User user, Hash blog) {
PetNameDB db = user.getPetNameDB();
String name = db.getNameByLocation(blog.toBase64());
return ( (name != null) && (db.get(name).isMember("Ignore")) );
}
public void fetchMetadata(User user, Map parameters) {
String meta = ArchiveViewerBean.getString(parameters, "blog");
if (meta == null) return;
Set blogs = new HashSet();
if ("ALL".equals(meta)) {
Set localBlogs = BlogManager.instance().getArchive().getIndex().getUniqueBlogs();
Set remoteBlogs = _remoteIndex.getUniqueBlogs();
for (Iterator iter = remoteBlogs.iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
if (!localBlogs.contains(blog)) {
if (!ignoreBlog(user, blog))
blogs.add(blog);
}
}
} else {
byte h[] = Base64.decode(meta.trim());
if (h != null) {
Hash blog = new Hash(h);
if (!ignoreBlog(user, blog))
blogs.add(blog);
}
}
List urls = new ArrayList(blogs.size());
List tmpFiles = new ArrayList(blogs.size());
for (Iterator iter = blogs.iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
urls.add(buildMetaURL(blog));
try {
tmpFiles.add(File.createTempFile("fetchMeta", ".txt", BlogManager.instance().getTempDir()));
} catch (IOException ioe) {
_statusMessages.add("Internal error creating temporary file to fetch " + blog.toBase64() + ": " + ioe.getMessage());
}
}
for (int i = 0; i < urls.size(); i++)
_statusMessages.add("Scheduling up metadata fetches for " + HTMLRenderer.sanitizeString((String)urls.get(i)));
fetch(urls, tmpFiles, user, new MetadataStatusListener());
}
private String buildMetaURL(Hash blog) {
String loc = _remoteLocation.trim();
int root = loc.lastIndexOf('/');
return loc.substring(0, root + 1) + blog.toBase64() + "/" + Archive.METADATA_FILE;
}
public void fetchSelectedEntries(User user, Map parameters) {
String entries[] = ArchiveViewerBean.getStrings(parameters, "entry");
if ( (entries == null) || (entries.length <= 0) ) return;
List urls = new ArrayList(entries.length);
List tmpFiles = new ArrayList(entries.length);
for (int i = 0; i < entries.length; i++) {
BlogURI uri = new BlogURI(entries[i]);
if (ignoreBlog(user, uri.getKeyHash()))
continue;
urls.add(buildEntryURL(uri));
try {
tmpFiles.add(File.createTempFile("fetchBlog", ".txt", BlogManager.instance().getTempDir()));
} catch (IOException ioe) {
_statusMessages.add("Internal error creating temporary file to fetch " + HTMLRenderer.sanitizeString(entries[i]) + ": " + ioe.getMessage());
}
}
for (int i = 0; i < urls.size(); i++)
_statusMessages.add("Scheduling blog post fetching for " + HTMLRenderer.sanitizeString(entries[i]));
fetch(urls, tmpFiles, user, new BlogStatusListener());
}
public void fetchSelectedBulk(User user, Map parameters) {
String entries[] = ArchiveViewerBean.getStrings(parameters, "entry");
String action = ArchiveViewerBean.getString(parameters, "action");
if ("Fetch all new entries".equals(action)) {
ArchiveIndex localIndex = BlogManager.instance().getArchive().getIndex();
List uris = new ArrayList();
List matches = new ArrayList();
for (Iterator iter = _remoteIndex.getUniqueBlogs().iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
if (ignoreBlog(user, blog))
continue;
_remoteIndex.selectMatchesOrderByEntryId(matches, blog, null);
for (int i = 0; i < matches.size(); i++) {
BlogURI uri = (BlogURI)matches.get(i);
if (!localIndex.getEntryIsKnown(uri))
uris.add(uri);
}
matches.clear();
}
entries = new String[uris.size()];
for (int i = 0; i < uris.size(); i++)
entries[i] = ((BlogURI)uris.get(i)).toString();
}
if ( (entries == null) || (entries.length <= 0) ) return;
if (_exportCapable) {
StringBuffer url = new StringBuffer(512);
url.append(buildExportURL());
StringBuffer postData = new StringBuffer(512);
Set meta = new HashSet();
for (int i = 0; i < entries.length; i++) {
BlogURI uri = new BlogURI(entries[i]);
if (uri.getEntryId() >= 0) {
postData.append("entry=").append(uri.toString()).append('&');
meta.add(uri.getKeyHash());
_statusMessages.add("Scheduling bulk blog post fetch of " + HTMLRenderer.sanitizeString(entries[i]));
}
}
for (Iterator iter = meta.iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
postData.append("meta=").append(blog.toBase64()).append('&');
_statusMessages.add("Scheduling bulk blog metadata fetch of " + blog.toBase64());
}
try {
File tmp = File.createTempFile("fetchBulk", ".zip", BlogManager.instance().getTempDir());
boolean shouldProxy = (_proxyHost != null) && (_proxyPort > 0);
EepGet get = new EepGet(_context, shouldProxy, _proxyHost, _proxyPort, 0, tmp.getAbsolutePath(), url.toString(), postData.toString());
get.addStatusListener(new BulkFetchListener(tmp));
get.fetch();
} catch (IOException ioe) {
_statusMessages.add("Internal error creating temporary file to fetch " + HTMLRenderer.sanitizeString(url.toString()) + ": " + ioe.getMessage());
}
} else {
List urls = new ArrayList(entries.length+8);
for (int i = 0; i < entries.length; i++) {
BlogURI uri = new BlogURI(entries[i]);
if (uri.getEntryId() >= 0) {
String metaURL = buildMetaURL(uri.getKeyHash());
if (!urls.contains(metaURL)) {
urls.add(metaURL);
_statusMessages.add("Scheduling blog metadata fetch of " + HTMLRenderer.sanitizeString(entries[i]));
}
urls.add(buildEntryURL(uri));
_statusMessages.add("Scheduling blog post fetch of " + HTMLRenderer.sanitizeString(entries[i]));
}
}
List tmpFiles = new ArrayList(1);
try {
for (int i = 0; i < urls.size(); i++) {
File t = File.createTempFile("fetchBulk", ".dat", BlogManager.instance().getTempDir());
tmpFiles.add(t);
}
fetch(urls, tmpFiles, user, new BlogStatusListener());
} catch (IOException ioe) {
_statusMessages.add("Internal error creating temporary file to fetch posts: " + HTMLRenderer.sanitizeString(urls.toString()));
}
}
}
private String buildExportURL() {
String loc = _remoteLocation.trim();
int root = loc.lastIndexOf('/');
return loc.substring(0, root + 1) + "export.zip?";
}
private String buildEntryURL(BlogURI uri) {
String loc = _remoteLocation.trim();
int root = loc.lastIndexOf('/');
return loc.substring(0, root + 1) + uri.getKeyHash().toBase64() + "/" + uri.getEntryId() + ".snd";
}
public void fetchAllEntries(User user, Map parameters) {
ArchiveIndex localIndex = BlogManager.instance().getArchive().getIndex();
List uris = new ArrayList();
List entries = new ArrayList();
for (Iterator iter = _remoteIndex.getUniqueBlogs().iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
if (ignoreBlog(user, blog))
continue;
_remoteIndex.selectMatchesOrderByEntryId(entries, blog, null);
for (int i = 0; i < entries.size(); i++) {
BlogURI uri = (BlogURI)entries.get(i);
if (!localIndex.getEntryIsKnown(uri))
uris.add(uri);
}
entries.clear();
}
List urls = new ArrayList(uris.size());
List tmpFiles = new ArrayList(uris.size());
for (int i = 0; i < uris.size(); i++) {
urls.add(buildEntryURL((BlogURI)uris.get(i)));
try {
tmpFiles.add(File.createTempFile("fetchBlog", ".txt", BlogManager.instance().getTempDir()));
} catch (IOException ioe) {
_statusMessages.add("Internal error creating temporary file to fetch " + HTMLRenderer.sanitizeString(uris.get(i).toString()) + ": " + ioe.getMessage());
}
}
for (int i = 0; i < urls.size(); i++)
_statusMessages.add("Fetch all entries: " + HTMLRenderer.sanitizeString((String)urls.get(i)));
fetch(urls, tmpFiles, user, new BlogStatusListener());
}
private void fetch(List urls, List tmpFiles, User user, EepGet.StatusListener lsnr) {
EepGetScheduler scheduler = new EepGetScheduler(I2PAppContext.getGlobalContext(), urls, tmpFiles, _proxyHost, _proxyPort, lsnr);
scheduler.fetch();
}
public void fetchIndex(User user, String schema, String location, String proxyHost, String proxyPort) {
_fetchIndexInProgress = true;
_remoteIndex = null;
_remoteLocation = location;
_remoteSchema = schema;
_proxyHost = null;
_proxyPort = -1;
_exportCapable = false;
if ( (schema == null) || (schema.trim().length() <= 0) ||
(location == null) || (location.trim().length() <= 0) ) {
_statusMessages.add("Location must be specified");
_fetchIndexInProgress = false;
return;
}
if ("web".equals(schema)) {
if ( (proxyHost != null) && (proxyHost.trim().length() > 0) &&
(proxyPort != null) && (proxyPort.trim().length() > 0) ) {
_proxyHost = proxyHost;
try {
_proxyPort = Integer.parseInt(proxyPort);
} catch (NumberFormatException nfe) {
_statusMessages.add("Proxy port " + HTMLRenderer.sanitizeString(proxyPort) + " is invalid");
_fetchIndexInProgress = false;
return;
}
}
} else {
_statusMessages.add(new String("Remote schema " + HTMLRenderer.sanitizeString(schema) + " currently not supported"));
_fetchIndexInProgress = false;
return;
}
_statusMessages.add("Fetching index from " + HTMLRenderer.sanitizeString(_remoteLocation) +
(_proxyHost != null ? " via " + HTMLRenderer.sanitizeString(_proxyHost) + ":" + _proxyPort : ""));
File archiveFile = new File(BlogManager.instance().getTempDir(), user.getBlog().toBase64() + "_remoteArchive.txt");
archiveFile.delete();
Properties etags = new Properties();
try {
etags.load(new FileInputStream(new File(BlogManager.instance().getRootDir(), "etags")));
} catch (Exception exp) {
//ignore
}
EepGet eep = new EepGet(I2PAppContext.getGlobalContext(), ((_proxyHost != null) && (_proxyPort > 0)),
_proxyHost, _proxyPort, 0, archiveFile.getAbsolutePath(), location, etags.getProperty(location));
eep.addStatusListener(new IndexFetcherStatusListener(archiveFile));
eep.fetch();
if (eep.getETag() != null) {
etags.setProperty(location, eep.getETag());
}
try {
etags.store(new FileOutputStream(new File(BlogManager.instance().getRootDir(), "etags")), "etags");
} catch (Exception exp) {
//ignore
}
}
private class IndexFetcherStatusListener implements EepGet.StatusListener {
private File _archiveFile;
public IndexFetcherStatusListener(File file) {
_archiveFile = file;
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
_statusMessages.add("Attempt " + currentAttempt + " failed after " + bytesTransferred + (cause != null ? cause.getMessage() : ""));
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
_statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " successful");
_fetchIndexInProgress = false;
ArchiveIndex i = new ArchiveIndex(I2PAppContext.getGlobalContext(), false);
if (!notModified) {
try {
i.load(_archiveFile);
_statusMessages.add("Archive fetched and loaded");
_remoteIndex = i;
} catch (IOException ioe) {
_statusMessages.add("Archive is corrupt: " + ioe.getMessage());
}
}
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
_statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " failed after " + bytesTransferred);
_fetchIndexInProgress = false;
}
public void headerReceived(String url, int currentAttempt, String key, String val) {
if (ArchiveServlet.HEADER_EXPORT_CAPABLE.equals(key) && ("true".equals(val))) {
_statusMessages.add("Remote archive is bulk export capable");
_exportCapable = true;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Header received: [" + key + "] = [" + val + "]");
}
}
}
private class MetadataStatusListener implements EepGet.StatusListener {
public MetadataStatusListener() {}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
_statusMessages.add("Attempt " + currentAttempt + " failed after " + bytesTransferred + (cause != null ? cause.getMessage() : ""));
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
handleMetadata(url, outputFile);
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
_statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " failed after " + bytesTransferred);;
}
public void headerReceived(String url, int currentAttempt, String key, String val) {}
}
private void handleMetadata(String url, String outputFile) {
_statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " successful");
File info = new File(outputFile);
FileInputStream in = null;
try {
BlogInfo i = new BlogInfo();
in = new FileInputStream(info);
i.load(in);
boolean ok = BlogManager.instance().getArchive().storeBlogInfo(i);
if (ok) {
_statusMessages.add("Blog info for " + HTMLRenderer.sanitizeString(i.getProperty(BlogInfo.NAME)) + " imported");
BlogManager.instance().getArchive().reloadInfo();
} else {
_statusMessages.add("Blog info at " + HTMLRenderer.sanitizeString(url) + " was corrupt / invalid / forged");
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error handling metadata", ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
info.delete();
}
}
private class BlogStatusListener implements EepGet.StatusListener {
public BlogStatusListener() {}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
_statusMessages.add("Attempt " + currentAttempt + " failed after " + bytesTransferred + (cause != null ? cause.getMessage() : ""));
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (url.endsWith(".snm")) {
handleMetadata(url, outputFile);
return;
}
_statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " successful");
File file = new File(outputFile);
FileInputStream in = null;
try {
EntryContainer c = new EntryContainer();
in = new FileInputStream(file);
c.load(in);
BlogURI uri = c.getURI();
if ( (uri == null) || (uri.getKeyHash() == null) ) {
_statusMessages.add("Blog post at " + HTMLRenderer.sanitizeString(url) + " was corrupt - no URI");
return;
}
Archive a = BlogManager.instance().getArchive();
BlogInfo info = a.getBlogInfo(uri);
if (info == null) {
_statusMessages.add("Blog post " + uri.toString() + " cannot be imported, as we don't have their blog metadata");
return;
}
boolean ok = a.storeEntry(c);
if (!ok) {
_statusMessages.add("Blog post at " + url + ": " + uri.toString() + " has an invalid signature");
return;
} else {
_statusMessages.add("Blog post " + uri.toString() + " imported");
BlogManager.instance().getArchive().regenerateIndex();
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error importing", ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
file.delete();
}
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
_statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " failed after " + bytesTransferred);
}
public void headerReceived(String url, int currentAttempt, String key, String val) {}
}
/**
* Receive the status of a fetch for the zip containing blogs and metadata (as generated by
* the ExportServlet)
*/
private class BulkFetchListener implements EepGet.StatusListener {
private File _tmp;
public BulkFetchListener(File tmp) {
_tmp = tmp;
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
_statusMessages.add("Attempt " + currentAttempt + " failed after " + bytesTransferred + (cause != null ? cause.getMessage() : ""));
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
_statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url.substring(0, url.indexOf('?'))) + " successful, importing the data");
File file = new File(outputFile);
ZipInputStream zi = null;
try {
zi = new ZipInputStream(new FileInputStream(file));
while (true) {
ZipEntry entry = zi.getNextEntry();
if (entry == null)
break;
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
byte buf[] = new byte[1024];
int read = -1;
while ( (read = zi.read(buf)) != -1)
out.write(buf, 0, read);
if (entry.getName().startsWith("meta")) {
BlogInfo i = new BlogInfo();
i.load(new ByteArrayInputStream(out.toByteArray()));
boolean ok = BlogManager.instance().getArchive().storeBlogInfo(i);
if (ok) {
_statusMessages.add("Blog info for " + HTMLRenderer.sanitizeString(i.getProperty(BlogInfo.NAME)) + " imported");
} else {
_statusMessages.add("Blog info at " + HTMLRenderer.sanitizeString(url) + " was corrupt / invalid / forged");
}
} else if (entry.getName().startsWith("entry")) {
EntryContainer c = new EntryContainer();
c.load(new ByteArrayInputStream(out.toByteArray()));
BlogURI uri = c.getURI();
if ( (uri == null) || (uri.getKeyHash() == null) ) {
_statusMessages.add("Blog post " + HTMLRenderer.sanitizeString(entry.getName()) + " was corrupt - no URI");
continue;
}
Archive a = BlogManager.instance().getArchive();
BlogInfo info = a.getBlogInfo(uri);
if (info == null) {
_statusMessages.add("Blog post " + HTMLRenderer.sanitizeString(entry.getName()) + " cannot be imported, as we don't have their blog metadata");
continue;
}
boolean ok = a.storeEntry(c);
if (!ok) {
_statusMessages.add("Blog post " + uri.toString() + " has an invalid signature");
continue;
} else {
_statusMessages.add("Blog post " + uri.toString() + " imported");
}
}
}
BlogManager.instance().getArchive().regenerateIndex();
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.debug("Error importing", ioe);
_statusMessages.add("Error importing from " + HTMLRenderer.sanitizeString(url) + ": " + ioe.getMessage());
} finally {
if (zi != null) try { zi.close(); } catch (IOException ioe) {}
file.delete();
}
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
_statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " failed after " + bytesTransferred);
_tmp.delete();
}
public void headerReceived(String url, int currentAttempt, String key, String val) {}
}
public void postSelectedEntries(User user, Map parameters) {
postSelectedEntries(user, parameters, _proxyHost, _proxyPort, _remoteLocation);
}
public void postSelectedEntries(User user, Map parameters, String proxyHost, int proxyPort, String location) {
String entries[] = ArchiveViewerBean.getStrings(parameters, "localentry");
if ( (entries == null) || (entries.length <= 0) ) return;
List uris = new ArrayList(entries.length);
for (int i = 0; i < entries.length; i++)
uris.add(new BlogURI(entries[i]));
if ( (proxyPort > 0) && (proxyHost != null) && (proxyHost.trim().length() > 0) ) {
_proxyPort = proxyPort;
_proxyHost = proxyHost;
} else {
_proxyPort = -1;
_proxyHost = null;
}
_remoteLocation = location;
post(uris, user);
}
private void post(List blogURIs, User user) {
List files = new ArrayList(blogURIs.size()+1);
Set meta = new HashSet(4);
Map uploads = new HashMap(files.size());
String importURL = getImportURL();
_statusMessages.add("Uploading through " + HTMLRenderer.sanitizeString(importURL));
for (int i = 0; i < blogURIs.size(); i++) {
BlogURI uri = (BlogURI)blogURIs.get(i);
File blogDir = new File(BlogManager.instance().getArchive().getArchiveDir(), uri.getKeyHash().toBase64());
BlogInfo info = BlogManager.instance().getArchive().getBlogInfo(uri);
if (!meta.contains(uri.getKeyHash())) {
uploads.put("blogmeta" + meta.size(), new File(blogDir, Archive.METADATA_FILE));
meta.add(uri.getKeyHash());
_statusMessages.add("Scheduling upload of the blog metadata for " + HTMLRenderer.sanitizeString(info.getProperty(BlogInfo.NAME)));
}
uploads.put("blogpost" + i, new File(blogDir, uri.getEntryId() + ".snd"));
_statusMessages.add("Scheduling upload of " + HTMLRenderer.sanitizeString(info.getProperty(BlogInfo.NAME))
+ ": " + getEntryDate(uri.getEntryId()));
}
EepPost post = new EepPost();
post.postFiles(importURL, _proxyHost, _proxyPort, uploads, new Runnable() { public void run() { _statusMessages.add("Upload complete"); } });
}
private String getImportURL() {
String loc = _remoteLocation.trim();
int archiveRoot = loc.lastIndexOf('/');
int syndieRoot = loc.lastIndexOf('/', archiveRoot-1);
return loc.substring(0, syndieRoot + 1) + "import.jsp";
}
public void renderDeltaForm(User user, ArchiveIndex localIndex, Writer out) throws IOException {
Archive archive = BlogManager.instance().getArchive();
StringBuffer buf = new StringBuffer(512);
buf.append("<em class=\"b_remMeta\">New blogs:</em> <select class=\"b_remMeta\"name=\"blog\"><option value=\"ALL\">All</option>\n");
Set localBlogs = archive.getIndex().getUniqueBlogs();
Set remoteBlogs = _remoteIndex.getUniqueBlogs();
int newBlogs = 0;
for (Iterator iter = remoteBlogs.iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
if (ignoreBlog(user, blog))
continue;
if (!localBlogs.contains(blog)) {
buf.append("<option value=\"" + blog.toBase64() + "\">" + blog.toBase64() + "</option>\n");
newBlogs++;
}
}
if (newBlogs > 0) {
out.write(buf.toString());
out.write("</select> <input class=\"b_remMetaFetch\" type=\"submit\" name=\"action\" value=\"Fetch metadata\" /><br />\n");
}
int newEntries = 0;
int localNew = 0;
out.write("<table class=\"b_remDelta\" border=\"1\" width=\"100%\">\n");
List entries = new ArrayList();
for (Iterator iter = remoteBlogs.iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
if (ignoreBlog(user, blog))
continue;
buf.setLength(0);
int shownEntries = 0;
buf.append("<tr class=\"b_remBlog\"><td class=\"b_remBlog\" colspan=\"5\" align=\"left\" valign=\"top\">\n");
BlogInfo info = archive.getBlogInfo(blog);
if (info != null) {
buf.append("<a class=\"b_remBlog\" href=\"");
buf.append(HTMLRenderer.getPageURL(blog, null, -1, -1, -1, user.getShowExpanded(), user.getShowImages()));
buf.append("\">").append(HTMLRenderer.sanitizeString(info.getProperty(BlogInfo.NAME))).append("</a>: ");
buf.append("<span class=\"b_remBlogDesc\">").append(HTMLRenderer.sanitizeString(info.getProperty(BlogInfo.DESCRIPTION)));
buf.append("</span>\n");
} else {
buf.append("<span class=\"b_remBlog\">" + blog.toBase64() + "</span>\n");
}
buf.append("</td></tr>\n");
buf.append("<tr class=\"b_remHeader\"><td class=\"b_remHeader\">&nbsp;</td><td class=\"b_remHeader\" nowrap=\"nowrap\">");
buf.append("<em class=\"b_remHeader\">Posted on</em></td>");
buf.append("<td class=\"b_remHeader\" nowrap=\"nowrap\"><em class=\"b_remHeader\">#</em></td>");
buf.append("<td class=\"b_remHeader\" nowrap=\"nowrap\"><em class=\"b_remHeader\">Size</em></td>");
buf.append("<td class=\"b_remHeader\" width=\"90%\" nowrap=\"true\"><em class=\"b_remHeader\">Tags</em></td></tr>\n");
entries.clear();
_remoteIndex.selectMatchesOrderByEntryId(entries, blog, null);
for (int i = 0; i < entries.size(); i++) {
BlogURI uri = (BlogURI)entries.get(i);
buf.append("<tr class=\"b_remDetail\">\n");
if (!archive.getIndex().getEntryIsKnown(uri)) {
buf.append("<td class=\"b_remDetail\"><input class=\"b_remSelect\" type=\"checkbox\" name=\"entry\" value=\"" + uri.toString() + "\" /></td>\n");
newEntries++;
shownEntries++;
} else {
String page = HTMLRenderer.getPageURL(blog, null, uri.getEntryId(), -1, -1,
user.getShowExpanded(), user.getShowImages());
buf.append("<td class=\"b_remDetail\"><a class=\"b_remLocal\" href=\"" + page + "\">(local)</a></td>\n");
}
buf.append("<td class=\"b_remDetail\"><span class=\"b_remDate\">" + getDate(uri.getEntryId()) + "</span></td>\n");
buf.append("<td class=\"b_remDetail\"><span class=\"b_remNum\">" + getId(uri.getEntryId()) + "</span></td>\n");
buf.append("<td class=\"b_remDetail\"><span class=\"b_remSize\">" + _remoteIndex.getBlogEntrySizeKB(uri) + "KB</span></td>\n");
buf.append("<td class=\"b_remDetail\">");
for (Iterator titer = new TreeSet(_remoteIndex.getBlogEntryTags(uri)).iterator(); titer.hasNext(); ) {
String tag = (String)titer.next();
buf.append("<a class=\"b_remTag\" href=\"" + HTMLRenderer.getPageURL(blog, tag, -1, -1, -1, user.getShowExpanded(), user.getShowImages()) + "\">" + tag + "</a> \n");
}
buf.append("</td>\n");
buf.append("</tr>\n");
}
if (shownEntries > 0) {
out.write(buf.toString());
buf.setLength(0);
}
int remote = shownEntries;
// now for posts in known blogs that we have and they don't
entries.clear();
localIndex.selectMatchesOrderByEntryId(entries, blog, null);
buf.append("<tr class=\"b_remLocalHeader\"><td class=\"b_remLocalHeader\" colspan=\"5\"><span class=\"b_remLocalHeader\">Entries we have, but the remote Syndie doesn't:</span></td></tr>\n");
for (int i = 0; i < entries.size(); i++) {
BlogURI uri = (BlogURI)entries.get(i);
if (!_remoteIndex.getEntryIsKnown(uri)) {
buf.append("<tr class=\"b_remLocalDetail\">\n");
buf.append("<td class=\"b_remLocalDetail\"><input class=\"b_remLocalSend\" type=\"checkbox\" name=\"localentry\" value=\"" + uri.toString() + "\" /></td>\n");
shownEntries++;
newEntries++;
localNew++;
buf.append("<td class=\"b_remLocalDate\"><span class=\"b_remLocalDate\">" + getDate(uri.getEntryId()) + "</span></td>\n");
buf.append("<td class=\"b_remLocalNum\"><span class=\"b_remLocalNum\">" + getId(uri.getEntryId()) + "</span></td>\n");
buf.append("<td class=\"b_remLocalSize\"><span class=\"b_remLocalSize\">" + localIndex.getBlogEntrySizeKB(uri) + "KB</span></td>\n");
buf.append("<td class=\"b_remLocalTags\">");
for (Iterator titer = new TreeSet(localIndex.getBlogEntryTags(uri)).iterator(); titer.hasNext(); ) {
String tag = (String)titer.next();
buf.append("<a class=\"b_remLocalTag\" href=\"" + HTMLRenderer.getPageURL(blog, tag, -1, -1, -1, user.getShowExpanded(), user.getShowImages()) + "\">" + tag + "</a> \n");
}
buf.append("</td>\n");
buf.append("</tr>\n");
}
}
if (shownEntries > remote) // skip blogs we have already syndicated
out.write(buf.toString());
}
// now for posts in blogs we have and they don't
int newBefore = localNew;
buf.setLength(0);
buf.append("<tr class=\"b_remLocalHeader\"><td class=\"b_remLocalHeader\" colspan=\"5\"><span class=\"b_remLocalHeader\">Blogs the remote Syndie doesn't have</span></td></tr>\n");
for (Iterator iter = localBlogs.iterator(); iter.hasNext(); ) {
Hash blog = (Hash)iter.next();
if (remoteBlogs.contains(blog)) {
//System.err.println("Remote index has " + blog.toBase64());
continue;
} else if (ignoreBlog(user, blog)) {
continue;
}
entries.clear();
localIndex.selectMatchesOrderByEntryId(entries, blog, null);
for (int i = 0; i < entries.size(); i++) {
BlogURI uri = (BlogURI)entries.get(i);
buf.append("<tr class=\"b_remLocalDetail\">\n");
buf.append("<td class=\"b_remLocalDetail\"><input class=\"b_remLocalSend\" type=\"checkbox\" name=\"localentry\" value=\"" + uri.toString() + "\" /></td>\n");
buf.append("<td class=\"b_remLocalDate\"><span class=\"b_remLocalDate\">" + getDate(uri.getEntryId()) + "</span></td>\n");
buf.append("<td class=\"b_remLocalNum\"><span class=\"b_remLocalNum\">" + getId(uri.getEntryId()) + "</span></td>\n");
buf.append("<td class=\"b_remLocalSize\"><span class=\"b_remLocalSize\">" + localIndex.getBlogEntrySizeKB(uri) + "KB</span></td>\n");
buf.append("<td class=\"b_remLocalTags\">");
for (Iterator titer = new TreeSet(localIndex.getBlogEntryTags(uri)).iterator(); titer.hasNext(); ) {
String tag = (String)titer.next();
buf.append("<a class=\"b_remLocalTag\" href=\"" + HTMLRenderer.getPageURL(blog, tag, -1, -1, -1, user.getShowExpanded(), user.getShowImages()) + "\">" + tag + "</a> \n");
}
buf.append("</td>\n");
buf.append("</tr>\n");
localNew++;
}
}
if (localNew > newBefore)
out.write(buf.toString());
out.write("</table>\n");
if (newEntries > 0) {
out.write("<input class=\"b_remFetchSelected\" type=\"submit\" name=\"action\" value=\"Fetch selected entries\" /> \n");
out.write("<input class=\"b_remFetchAll\" type=\"submit\" name=\"action\" value=\"Fetch all new entries\" /> \n");
} else {
out.write("<span class=\"b_remNoRemotePosts\">" + HTMLRenderer.sanitizeString(_remoteLocation) + " has no new posts to offer us</span>\n");
}
if (localNew > 0) {
out.write("<input class=\"b_remPostSelected\" type=\"submit\" name=\"action\" value=\"Post selected entries\" /> \n");
}
out.write("<hr />\n");
}
private final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd", Locale.UK);
private String getDate(long when) {
synchronized (_dateFormat) {
return _dateFormat.format(new Date(when));
}
}
private final String getEntryDate(long when) {
synchronized (_dateFormat) {
try {
String str = _dateFormat.format(new Date(when));
long dayBegin = _dateFormat.parse(str).getTime();
return str + "." + (when - dayBegin);
} catch (ParseException pe) {
pe.printStackTrace();
// wtf
return "unknown";
}
}
}
private long getId(long id) {
synchronized (_dateFormat) {
try {
String str = _dateFormat.format(new Date(id));
long dayBegin = _dateFormat.parse(str).getTime();
return (id - dayBegin);
} catch (ParseException pe) {
pe.printStackTrace();
// wtf
return id;
}
}
}
}

View File

@ -1,8 +1,39 @@
<%@page import="net.i2p.syndie.web.ArchiveViewerBean, net.i2p.syndie.*" %>
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" />
<form action="index.jsp">
<b>Blogs:</b> <%ArchiveViewerBean.renderBlogSelector(user, request.getParameterMap(), out);%>
<input type="submit" value="Refresh" /></form>
<hr />
<%@page contentType="text/html; charset=UTF-8" import="net.i2p.syndie.web.ArchiveViewerBean, net.i2p.syndie.*, net.i2p.client.naming.PetName" %>
<% request.setCharacterEncoding("UTF-8"); %>
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" /><%
if (user.getAuthenticated() && (null != request.getParameter("action")) ) {
%><!-- <%=request.getParameterMap()%> --><%
String blog = request.getParameter("blog");
String group = null;
if (request.getParameter("action").equals("Bookmark blog"))
group = "Favorites";
else if (request.getParameter("action").equals("Ignore blog"))
group = "Ignore";
boolean unignore = ("Unignore blog".equals(request.getParameter("action")));
<%ArchiveViewerBean.renderBlogs(user, request.getParameterMap(), out); out.flush(); %>
String name = user.getPetNameDB().getNameByLocation(blog);
if (name == null)
name = request.getParameter("name");
if (name == null)
name = blog;
if ( (name != null) && (blog != null) && ( (group != null) || (unignore) ) ) {
PetName pn = user.getPetNameDB().get(name);
if (pn != null) {
if (unignore)
pn.removeGroup("Ignore");
else
pn.addGroup(group);
} else {
pn = new PetName(name, "syndie", "syndieblog", blog);
pn.addGroup(group);
user.getPetNameDB().set(name, pn);
}
BlogManager.instance().saveUser(user);
}
}
%><table border="0" width="100%" class="b_content">
<tr class="b_content"><form action="index.jsp"><td nowrap="nowrap">
<em class="b_selectorTitle">Blogs:</em> <span class="b_selector"><%ArchiveViewerBean.renderBlogSelector(user, request.getParameterMap(), out);%></span>
<input type="submit" value="Refresh" class="b_selectorRefresh" />
<input type="submit" name="action" value="<%=ArchiveViewerBean.SEL_ACTION_SET_AS_DEFAULT%>" class="b_selectorDefault" />
<%ArchiveViewerBean.renderBlogs(user, request.getParameterMap(), out, "</td></form></tr><tr><td align=\"left\" valign=\"top\">");%></td></tr></table>

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