Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
9a8ac980d3 | |||
728c646dac | |||
5c08658360 | |||
02328bd5d4 | |||
6bc7821f1b | |||
cf48fcd901 | |||
8ff48800d6 | |||
1a90be6701 | |||
a4729aba16 | |||
cd2441ca4a | |||
d9d3283b3e | |||
1acaef9514 | |||
0b6d22da3b | |||
38c47f9993 | |||
6e7406567b | |||
b970d198ca | |||
241b95bc65 | |||
ee39fa92ae | |||
26ad8bfd08 | |||
8d82d13d1c | |||
d9b4a5e1c4 | |||
886a05730c | |||
65dd1161ed | |||
0edade465c | |||
1e94a95a7a | |||
b7009e3a20 | |||
9eece7ab8b | |||
f5ed7ec874 | |||
071a36c348 | |||
d07780615c | |||
e157028db4 | |||
8ff2c93b0a | |||
20245a3e8e | |||
edbb803095 | |||
b2432833e3 | |||
6073e61ba4 | |||
e1e29a953b | |||
059f1cf6e7 | |||
32062dff20 | |||
7a6c0bcccd | |||
1853bbfd0e | |||
5828c358f6 | |||
017d8a61c3 | |||
cc640088c4 | |||
2016546fc1 | |||
9ed1a1ce0c | |||
8fd17dd938 | |||
51e4184ea8 | |||
df683fcf5c | |||
ca66c075bf | |||
f35827abcb | |||
290b7e9ba8 | |||
9a988e5c6d | |||
5d2343fe71 | |||
94ccfa25e6 | |||
36cee7777a | |||
2db3525fbb | |||
ea3fa57b04 | |||
66ba3f3e76 | |||
66900c2fbc | |||
96fa6adfad | |||
1227c727d0 | |||
d29211301e | |||
1b1c5a1c6b | |||
d72c0c6d61 | |||
ae250314b0 | |||
175515f629 | |||
c2482d68c8 | |||
a39186cfff | |||
22cf9e06ff | |||
758c0865dc | |||
ab14737fd1 | |||
dd0d7ba6c9 | |||
d3ccdb5970 | |||
404ec7d720 | |||
fa37064834 | |||
7cc2d97825 | |||
9ffef703b5 | |||
5239dfb768 | |||
953533c6e3 | |||
2e0f7ae4bf | |||
17e5595dd0 | |||
8350d009d4 | |||
a7225a2379 | |||
8a9094cd8e | |||
7f4394aa0f | |||
38f38398ef | |||
8317e3ce9a | |||
c1f6a14367 | |||
7e36455b05 | |||
a85aa6af31 | |||
f33303f394 | |||
ab882f8582 | |||
ce7562c547 | |||
52d8e26ffc | |||
284d7a58d6 | |||
af259e7038 | |||
0b177f317a | |||
0c43324657 | |||
eeeedee076 | |||
d1165b9f9a | |||
a6157ee8f5 | |||
2a795f3d8a | |||
7a13998082 | |||
93a8e5bfd5 | |||
6e3b85ac97 |
66
.github/workflows/sync.yaml
vendored
Normal file
66
.github/workflows/sync.yaml
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
# GitHub Actions workflow file to sync an external repository to this GitHub mirror.
|
||||
# This file was automatically generated by go-github-sync.
|
||||
#
|
||||
# The workflow does the following:
|
||||
# - Runs on a scheduled basis (and can also be triggered manually)
|
||||
# - Clones the GitHub mirror repository
|
||||
# - Fetches changes from the primary external repository
|
||||
# - Applies those changes to the mirror repository
|
||||
# - Pushes the updated content back to the GitHub mirror
|
||||
#
|
||||
# Authentication is handled by the GITHUB_TOKEN secret provided by GitHub Actions.
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate Github Actions Environment
|
||||
run: if [ "$GITHUB_ACTIONS" != "true" ]; then echo 'This script must be run in a GitHub Actions environment.'; exit 1; fi
|
||||
- name: Checkout GitHub Mirror
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Configure Git
|
||||
run: |-
|
||||
git config user.name 'GitHub Actions'
|
||||
git config user.email 'actions@github.com'
|
||||
- env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: Sync Primary Repository
|
||||
run: |-
|
||||
# Add the primary repository as a remote
|
||||
git remote add primary https://i2pgit.org/I2P_Developers/i2p.plugins.zzzot.git
|
||||
|
||||
# Fetch the latest changes from the primary repository
|
||||
git fetch primary
|
||||
|
||||
# Check if the primary branch exists in the primary repository
|
||||
if git ls-remote --heads primary master | grep -q master; then
|
||||
echo "Primary branch master found in primary repository"
|
||||
else
|
||||
echo "Error: Primary branch master not found in primary repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we're already on the mirror branch
|
||||
if git rev-parse --verify --quiet master; then
|
||||
git checkout master
|
||||
else
|
||||
# Create the mirror branch if it doesn't exist
|
||||
git checkout -b master
|
||||
fi
|
||||
|
||||
|
||||
# Force-apply all changes from primary, overriding any conflicts
|
||||
echo "Performing force sync from primary/master to master"
|
||||
git reset --hard primary/master
|
||||
|
||||
|
||||
# Push changes back to the mirror repository
|
||||
git push origin master
|
||||
name: Sync Primary Repository to GitHub Mirror
|
||||
"on":
|
||||
push: {}
|
||||
schedule:
|
||||
- cron: 0 * * * *
|
||||
workflow_dispatch: {}
|
113
CHANGES.txt
Normal file
113
CHANGES.txt
Normal file
@ -0,0 +1,113 @@
|
||||
2025-xx-xx [0.20.0] (Requires I2P 2.9.0 or higher)
|
||||
- Support UDP announces
|
||||
- Fix dup ids in jetty.xml, existing installs must fix manually,
|
||||
s/<Ref id=/<Ref refid=/g
|
||||
- Add interval to stats page
|
||||
- Add stats to I2P stats subsystem
|
||||
- Show announce URLs on stats page
|
||||
- Remove ElGamal support
|
||||
- Remove support for non-compact announce replies
|
||||
- Reduce memory usage
|
||||
- Remove seedless support
|
||||
|
||||
2024-04-07 [0.19.0]
|
||||
- Disable full scrape by default
|
||||
- Handle BiglyBT scrape URLs
|
||||
|
||||
2020-08-30 [0.18.0]
|
||||
- Enable both encryption types
|
||||
- Disable pack200
|
||||
|
||||
2019-11-19 [0.17.0]
|
||||
- Add more configuration, customization, and registration info to help page
|
||||
- Add variables for $VERSION and $SITENAME
|
||||
- Add custom CSS to docroot/tracker.css to allow customization and include
|
||||
alternative tracker-purple.css
|
||||
- Add favicon.png to docroot
|
||||
- Add custom icon for homepage
|
||||
- Add auto page reload for tracker stats (ajax with meta refresh fallback)
|
||||
- Add footer to /tracker with ZzzOT version and github link
|
||||
- Add config options to hide version footer on /tracker or change footer text
|
||||
- Tidy CHANGES.txt and add to plugin/
|
||||
- Start tunnel in background after first install
|
||||
- Changes for new installs only:
|
||||
- Change default sig type to EdDSA
|
||||
- Enhance presentation of help page
|
||||
- Modify docroot/index.html post-install to contain b32 footer
|
||||
- Only display warning about moving help.html if necessary
|
||||
- Add rewrite rules for /tracker/ and /tracker/index.html to web.xml
|
||||
- Add rewrite rules for /help and /help/ to jetty.xml
|
||||
- Replace jetty.servlet.DefaultServlet with I2PDefaultServlet in BaseContext.xml
|
||||
(requires I2P 0.9.31 or newer)
|
||||
|
||||
2017-05-21 [0.16.0]
|
||||
- Fix scrape response (requires I2P 0.9.30-8) (ticket #1994)
|
||||
|
||||
2017-03-11 [0.15.0]
|
||||
- Increase default limits, set I2CP tag options
|
||||
- Update eepsite configuration for Jetty 9 (I2P 0.9.30)
|
||||
|
||||
2015-05-28 [0.14.0]
|
||||
- Remove use of deprecated SimpleScheduler (ticket #1523)
|
||||
- Remove static structures
|
||||
- Use RandomIterator for efficiency
|
||||
|
||||
2014-11-29 [0.13.0]
|
||||
- Fix NPE on missing announce parameters
|
||||
- Increase startup delay
|
||||
|
||||
2014-11-13 [0.12.0]
|
||||
- Fix parameter decoding for scrape also
|
||||
- Add caching for info hashes and peer ids
|
||||
- Stop cleaner when plugin stops
|
||||
- Move to the ClientApp interface, remove all static refs
|
||||
- Attempt to fix crash after update
|
||||
- Add zzzot.config file to set interval
|
||||
- Don't overwrite index.html and robots.txt in the update
|
||||
- Release all resources when shut down (requires 0.9.16-6 or higher)
|
||||
|
||||
2014-11-11 [0.11.0]
|
||||
- Critical fix for announce parameter decoding, triggered by recent Jetty versions
|
||||
- Change request queueing in jetty.xml (new installs only)
|
||||
- SU3 plugin file format
|
||||
|
||||
2013-04-14 [0.10.0]
|
||||
- Updates and migration for Jetty 7 (I2P 0.9.6)
|
||||
|
||||
2013-01-25 [0.9.0]
|
||||
- Add cache-control directives
|
||||
- Set max Jetty to 6.99999
|
||||
|
||||
2012-03-10 [0.8]
|
||||
- Fix comment in jetty.xml
|
||||
|
||||
2012-03-10 [0.7]
|
||||
- Port to Jetty 6
|
||||
- Replace QForwardHandler with RewriteHandler
|
||||
- Use ${ant.home}/lib/ant.jar instead of pulling ant.jar from Jetty
|
||||
|
||||
2011-12-31 [0.6]
|
||||
- Set max jetty version 5.99999
|
||||
- Add throttle options
|
||||
- Stub out announce-to-seedless
|
||||
- Seedless fixes, untested
|
||||
|
||||
2010-07-11 [0.5]
|
||||
- Final compact response format
|
||||
|
||||
2010-07-09 [0.4]
|
||||
- Compact request/response support - may not be final format
|
||||
- Fix NPE if no ip parameter
|
||||
|
||||
2010-04-13 [0.3]
|
||||
- Verify dest
|
||||
- Add xfs check
|
||||
|
||||
2010-03-23 [0.2]
|
||||
- Cache b64 dest strings
|
||||
- Help typo fix (thx duck)
|
||||
- Lots of seedless fixes
|
||||
- Build cleanups
|
||||
|
||||
2010-03-23 [0.1]
|
||||
- Initial release
|
71
README.txt
71
README.txt
@ -1,42 +1,51 @@
|
||||
ZzzOT I2P Open Tracker Plugin
|
||||
-----------------------------
|
||||
|
||||
This is a very simple in-memory open tracker, wrapped into an I2P plugin.
|
||||
Plugin su3 binaries are available at http://stats.i2p/i2p/plugins/
|
||||
|
||||
The plugin starts a new http serer tunnel, eepsite, and Jetty server running at port 7662.
|
||||
The tracker status is available at http://127.0.0.1:7661/tracker/ .
|
||||
If other files are desired on the eepsite, they can be added at eepsite/docroot .
|
||||
The plugin starts a new http server tunnel, eepsite, and Jetty server running at
|
||||
port 7662. The tracker status is available at http://127.0.0.1:7662/tracker/
|
||||
If other files are desired on the eepsite, they can be added at eepsite/docroot
|
||||
|
||||
The open tracker code and jsps were written from scratch, but depend on some code
|
||||
in i2psnark.jar from the I2P installation for bencoding, and of course
|
||||
on other i2p libraries.
|
||||
See the license files in I2P for i2p and i2psnark licenses.
|
||||
There is also some code modified from Jetty 5.1.15.
|
||||
See LICENSES.txt for the zzzot and Jetty licenses.
|
||||
The open tracker code and jsps were written from scratch, but depend on some
|
||||
code in i2psnark.jar from the I2P installation for bencoding, and of course on
|
||||
other i2p libraries. See the license files in I2P for i2p and i2psnark licenses.
|
||||
There is also some code modified from Jetty. See LICENSES.txt for the
|
||||
zzzot and Jetty licenses.
|
||||
|
||||
I2P source must be installed and built in ../i2p.i2p to compile this package.
|
||||
|
||||
Sure, as a standalone program in its own JVM with Jetty, this would be a pig -
|
||||
you should use the C opentracker instead. But since you're already running
|
||||
the JVM and Jetty, running this in the same JVM probably doesn't hog to much more memory.
|
||||
As of release 0.19.0:
|
||||
- Full scrape is disabled by default
|
||||
|
||||
As of release 0.20.0:
|
||||
- I2P 2.9.0 or higher required to build and run
|
||||
- UDP announces are supported, see http://i2p-projekt.i2p/spec/proposals/160
|
||||
- Non-compact responses are no longer supported
|
||||
- Seedless support is removed
|
||||
- Memory usage greatly reduced
|
||||
|
||||
Valid announce URLs:
|
||||
/a
|
||||
/announce
|
||||
/announce.jsp
|
||||
/announce.php
|
||||
/tracker/a
|
||||
/tracker/announce
|
||||
/tracker/announce.jsp
|
||||
/tracker/announce.php
|
||||
/a
|
||||
/announce
|
||||
/announce.jsp
|
||||
/announce.php
|
||||
/tracker/a
|
||||
/tracker/announce
|
||||
/tracker/announce.jsp
|
||||
/tracker/announce.php
|
||||
|
||||
UDP announce URLs (default port is 6969):
|
||||
udp://yourb32string.b32.i2p:6969/
|
||||
|
||||
Valid scrape URLs:
|
||||
/scrape
|
||||
/scrape.jsp
|
||||
/scrape.php
|
||||
/tracker/scrape
|
||||
/tracker/scrape.jsp
|
||||
/tracker/scrape.php
|
||||
/scrape
|
||||
/scrape.jsp
|
||||
/scrape.php
|
||||
/tracker/scrape
|
||||
/tracker/scrape.jsp
|
||||
/tracker/scrape.php
|
||||
|
||||
The tracker also responds to seedless queries at
|
||||
/Seedless/index.jsp
|
||||
|
||||
You may use the rest of the eepsite for other purposes, for example you
|
||||
may place torrent files in eepsite/docroot/torrents.
|
||||
You may use the rest of the eepsite for other purposes; for example you
|
||||
may place torrent files in: eepsite/docroot/torrents/
|
||||
|
13
TODO.txt
13
TODO.txt
@ -1,12 +1,9 @@
|
||||
Configuration file:
|
||||
- interval
|
||||
- clean time
|
||||
- max peers in response
|
||||
- disable full scrapes
|
||||
- disable all scrapes
|
||||
- disable seedless
|
||||
|
||||
Stop the cleaner
|
||||
Remove seedless
|
||||
|
||||
Throttles:
|
||||
- full scrapes
|
||||
@ -17,3 +14,11 @@ Bans:
|
||||
|
||||
Verifier:
|
||||
- Check dest vs. b32 in header
|
||||
|
||||
Feature requests:
|
||||
|
||||
- display infohashes and status for individual torrents i.e. number of active peers/seeds, total number of downloads: http://bittorrent.org/beps/bep_0048.html
|
||||
- optional mapping of hashes to torrent names
|
||||
- optional clustering of open trackers to permit syncing
|
||||
- blacklists for hashes/torrents
|
||||
- optional password protection for site and/or admin section
|
||||
|
66
build.xml
66
build.xml
@ -1,22 +1,32 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project basedir="." default="all" name="zzzot">
|
||||
|
||||
<property file="override.properties"/>
|
||||
|
||||
<target name="all" depends="clean,plugin" />
|
||||
|
||||
<target name="war" >
|
||||
<ant dir="src" target="build" />
|
||||
</target>
|
||||
|
||||
<target name="plugin" depends="war">
|
||||
<delete file="plugin/i2ptunnel.config" />
|
||||
<target name="plugin" depends="war">
|
||||
<delete>
|
||||
<!-- in installer but not update -->
|
||||
<fileset dir="plugin/" includes="i2ptunnel.config zzzot.config eepsite/docroot/index.html eepsite/docroot/robots.txt" />
|
||||
</delete>
|
||||
<delete dir="plugin/eepsite/docroot/torrents/" />
|
||||
<!-- get version number -->
|
||||
<buildnumber file="scripts/build.number" />
|
||||
<property name="release.number" value="0.4" />
|
||||
<!-- NOTE: Change VERSION in ZzzOTController when you change this -->
|
||||
<property name="release.number" value="0.19.0" />
|
||||
|
||||
<!-- make the update xpi2p -->
|
||||
<!-- this contains everything except i2ptunnel.config -->
|
||||
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
|
||||
<copy file="README.txt" todir="plugin/" overwrite="true" />
|
||||
<copy file="CHANGES.txt" todir="plugin/" overwrite="true" />
|
||||
<copy file="scripts/tracker.css" todir="plugin/eepsite/docroot/" overwrite="true" />
|
||||
<copy file="scripts/tracker-purple.css" todir="plugin/eepsite/docroot/" overwrite="true" />
|
||||
<copy file="scripts/favicon.png" todir="plugin/eepsite/docroot/" overwrite="true" />
|
||||
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
|
||||
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
||||
<arg value="update-only=true" />
|
||||
@ -24,28 +34,43 @@
|
||||
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
||||
<arg value="version=${release.number}-b${build.number}" />
|
||||
</exec>
|
||||
<mkdir dir="plugin/lib/" />
|
||||
<exec executable="pack200" failonerror="true">
|
||||
<arg value="-g" />
|
||||
<arg value="plugin/lib/zzzot.jar.pack" />
|
||||
<arg value="-r" />
|
||||
<arg value="plugin/lib/zzzot.jar" />
|
||||
<arg value="src/build/zzzot.jar" />
|
||||
</exec>
|
||||
<mkdir dir="plugin/eepsite/webapps/" />
|
||||
<mkdir dir="plugin/eepsite/logs/" />
|
||||
<exec executable="pack200" failonerror="true">
|
||||
<arg value="-g" />
|
||||
<arg value="plugin/eepsite/webapps/tracker.war.pack" />
|
||||
<arg value="-r" />
|
||||
<arg value="plugin/eepsite/webapps/tracker.war" />
|
||||
<arg value="src/build/tracker.war.jar" />
|
||||
</exec>
|
||||
<exec executable="scripts/makeplugin.sh" failonerror="true" >
|
||||
<input message="Enter su3 signing key password:" addproperty="release.password.su3" />
|
||||
<fail message="You must enter a password." >
|
||||
<condition>
|
||||
<equals arg1="${release.password.su3}" arg2=""/>
|
||||
</condition>
|
||||
</fail>
|
||||
<!-- this will fail if no su3 keys exist, as it needs the password twice -->
|
||||
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
|
||||
<arg value="plugin" />
|
||||
</exec>
|
||||
<move file="zzzot.xpi2p" tofile="zzzot-update.xpi2p" overwrite="true" />
|
||||
<move file="zzzot.su3" tofile="zzzot-update.su3" overwrite="true" />
|
||||
|
||||
<!-- make the install xpi2p -->
|
||||
<copy file="scripts/i2ptunnel.config" todir="plugin/" overwrite="true" />
|
||||
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
|
||||
<!-- Files in installer but not update. Be sure to Add to delete fileset above and clean target below -->
|
||||
<copy file="scripts/i2ptunnel.config" todir="plugin/" overwrite="true" />
|
||||
<copy file="scripts/zzzot.config" todir="plugin/" overwrite="true" />
|
||||
<copy file="scripts/robots.txt" todir="plugin/eepsite/docroot/" overwrite="true" />
|
||||
<mkdir dir="plugin/eepsite/docroot/torrents/" />
|
||||
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
||||
<arg value="version=${release.number}-b${build.number}" />
|
||||
</exec>
|
||||
<exec executable="scripts/makeplugin.sh" failonerror="true" >
|
||||
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
|
||||
<arg value="plugin" />
|
||||
</exec>
|
||||
</target>
|
||||
@ -54,14 +79,29 @@
|
||||
|
||||
<target name="clean" >
|
||||
<ant dir="src" target="clean" />
|
||||
<defaultexcludes remove="**/*~"/>
|
||||
<delete>
|
||||
<fileset dir="." includes="*/*.~ **/*.*~ */**/*.*~ *.*~" />
|
||||
</delete>
|
||||
<delete file="plugin/i2ptunnel.config" />
|
||||
<delete file="plugin/plugin.config" />
|
||||
<delete file="plugin/lib/zzzot.jar.pack" />
|
||||
<delete file="plugin/eepsite/webapps/tracker.war.pack" />
|
||||
<delete file="plugin/zzzot.config" />
|
||||
<delete file="plugin/eepsite/docroot/index.html" />
|
||||
<delete file="plugin/eepsite/docroot/robots.txt" />
|
||||
<delete file="plugin/eepsite/docroot/tracker.css" />
|
||||
<delete file="plugin/eepsite/docroot/tracker-purple.css" />
|
||||
<delete file="plugin/eepsite/docroot/favicon.png" />
|
||||
<delete file="plugin/lib/zzzot.jar" />
|
||||
<delete file="plugin/eepsite/webapps/tracker.war" />
|
||||
<delete file="plugin/CHANGES.txt" />
|
||||
<delete file="plugin/LICENSE.txt" />
|
||||
<delete file="plugin/README.txt" />
|
||||
<delete file="zzzot.xpi2p" />
|
||||
<delete file="zzzot-update.xpi2p" />
|
||||
<delete file="zzzot.su3" />
|
||||
<delete file="zzzot-update.su3" />
|
||||
<delete file="plugin.zip" />
|
||||
<delete dir="plugin/eepsite/docroot/torrents/" />
|
||||
</target>
|
||||
|
||||
</project>
|
||||
|
@ -2,7 +2,7 @@ clientApp.0.main=net.i2p.zzzot.ZzzOTController
|
||||
clientApp.0.name=ZzzOT
|
||||
clientApp.0.args=-d $PLUGIN start
|
||||
clientApp.0.stopargs=-d $PLUGIN stop
|
||||
clientApp.0.delay=15
|
||||
clientApp.0.delay=0
|
||||
clientApp.0.startOnLoad=true
|
||||
# we also use i2p.jar and i2ptunnel.jar, they are in the standard router classpath
|
||||
clientApp.0.classpath=$PLUGIN/lib/zzzot.jar,$I2P/lib/i2psnark.jar
|
||||
|
@ -1,6 +0,0 @@
|
||||
<html><head>
|
||||
<!-- edit this file if you want to change your home page -->
|
||||
<title>zzzot</title>
|
||||
</head><body style="background-color: #000; color: #c30; font-size: 2000%;">
|
||||
<center><b>zzzot</b></center>
|
||||
</body></html>
|
23
plugin/templates/contexts/base-context.xml
Normal file
23
plugin/templates/contexts/base-context.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||
|
||||
<!--
|
||||
Configure a custom context for the eepsite.
|
||||
|
||||
This context contains only a ServletContextHandler with a default servlet
|
||||
to serve static html files and images.
|
||||
-->
|
||||
|
||||
<Configure class="org.eclipse.jetty.servlet.ServletContextHandler">
|
||||
<Set name="contextPath">/</Set>
|
||||
<Set name="resourceBase">$PLUGIN/eepsite/docroot/</Set>
|
||||
<Call name="setInitParameter">
|
||||
<Arg>cacheControl</Arg>
|
||||
<Arg>max-age=3600,public</Arg>
|
||||
</Call>
|
||||
<Call name="addServlet">
|
||||
<Arg>net.i2p.servlet.I2PDefaultServlet</Arg>
|
||||
<Arg>/</Arg>
|
||||
</Call>
|
||||
</Configure>
|
||||
|
36
plugin/templates/contexts/cgi-context.xml
Normal file
36
plugin/templates/contexts/cgi-context.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||
|
||||
<!--
|
||||
Configure a custom context for the eepsite.
|
||||
|
||||
* CGI Servlet.
|
||||
*
|
||||
* The cgi bin directory can be set with the "cgibinResourceBase" init parameter
|
||||
* or it will default to the resource base of the context.
|
||||
*
|
||||
* The "commandPrefix" init parameter may be used to set a prefix to all
|
||||
* commands passed to exec. This can be used on systems that need assistance to
|
||||
* execute a particular file type. For example on windows this can be set to
|
||||
* "perl" so that perl scripts are executed.
|
||||
*
|
||||
* The "Path" init param is passed to the exec environment as PATH. Note: Must
|
||||
* be run unpacked somewhere in the filesystem.
|
||||
*
|
||||
* Any initParameter that starts with ENV_ is used to set an environment
|
||||
* variable with the name stripped of the leading ENV_ and using the init
|
||||
* parameter value.
|
||||
-->
|
||||
|
||||
<Configure class="org.eclipse.jetty.servlet.ServletContextHandler">
|
||||
<Set name="contextPath">/cgi-bin</Set>
|
||||
<Set name="resourceBase">$PLUGIN/eepsite/cgi-bin/</Set>
|
||||
<Call name="setInitParameter">
|
||||
<Arg>Path</Arg>
|
||||
<Arg>/usr/local/bin:/bin:/usr/bin</Arg>
|
||||
</Call>
|
||||
<Call name="addServlet">
|
||||
<Arg>org.eclipse.jetty.servlets.CGI</Arg>
|
||||
<Arg>/</Arg>
|
||||
</Call>
|
||||
</Configure>
|
21
plugin/templates/etc/realm.properties
Normal file
21
plugin/templates/etc/realm.properties
Normal file
@ -0,0 +1,21 @@
|
||||
#
|
||||
# This file defines users passwords and roles for a HashUserRealm
|
||||
#
|
||||
# The format is
|
||||
# <username>: <password>[,<rolename> ...]
|
||||
#
|
||||
# Passwords may be clear text, obfuscated or checksummed. The class
|
||||
# org.mortbay.util.Password should be used to generate obfuscated
|
||||
# passwords or password checksums
|
||||
#
|
||||
# If DIGEST Authentication is used, the password must be in a recoverable
|
||||
# format, either plain text or OBF:.
|
||||
#
|
||||
jetty: MD5:164c88b302622e17050af52c89945d44,user
|
||||
admin: CRYPT:ad1ks..kc.1Ug,server-administrator,content-administrator,admin
|
||||
other: OBF:1xmk1w261u9r1w1c1xmq
|
||||
plain: plain
|
||||
user: password
|
||||
|
||||
# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password
|
||||
digest: MD5:6e120743ad67abfbc385bc2bb754e297
|
408
plugin/templates/etc/webdefault.xml
Normal file
408
plugin/templates/etc/webdefault.xml
Normal file
@ -0,0 +1,408 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
|
||||
<!-- ===================================================================== -->
|
||||
<!-- This file contains the default descriptor for web applications. -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- The intent of this descriptor is to include jetty specific or common -->
|
||||
<!-- configuration for all webapps. If a context has a webdefault.xml -->
|
||||
<!-- descriptor, it is applied before the contexts own web.xml file -->
|
||||
<!-- -->
|
||||
<!-- A context may be assigned a default descriptor by: -->
|
||||
<!-- + Calling WebApplicationContext.setDefaultsDescriptor -->
|
||||
<!-- + Passed an arg to addWebApplications -->
|
||||
<!-- -->
|
||||
<!-- This file is used both as the resource within the jetty.jar (which is -->
|
||||
<!-- used as the default if no explicit defaults descriptor is set) and it -->
|
||||
<!-- is copied to the etc directory of the Jetty distro and explicitly -->
|
||||
<!-- by the jetty.xml file. -->
|
||||
<!-- -->
|
||||
<!-- ===================================================================== -->
|
||||
<web-app
|
||||
xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
metadata-complete="true"
|
||||
version="2.5">
|
||||
|
||||
<description>
|
||||
Default web.xml file.
|
||||
This file is applied to a Web application before it's own WEB_INF/web.xml file
|
||||
</description>
|
||||
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<!-- Context params to control Session Cookies -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- UNCOMMENT TO ACTIVATE
|
||||
<context-param>
|
||||
<param-name>org.eclipse.jetty.servlet.SessionDomain</param-name>
|
||||
<param-value>127.0.0.1</param-value>
|
||||
</context-param>
|
||||
|
||||
<context-param>
|
||||
<param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
|
||||
<param-value>/</param-value>
|
||||
</context-param>
|
||||
|
||||
<context-param>
|
||||
<param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
|
||||
<param-value>-1</param-value>
|
||||
</context-param>
|
||||
-->
|
||||
|
||||
<context-param>
|
||||
<param-name>org.eclipse.jetty.webapp.NoTLDJarPattern</param-name>
|
||||
<param-value>start.jar|ant-.*\.jar|dojo-.*\.jar|jetty-.*\.jar|jsp-api-.*\.jar|junit-.*\.jar|servlet-api-.*\.jar|dnsns\.jar|rt\.jar|jsse\.jar|tools\.jar|sunpkcs11\.jar|sunjce_provider\.jar|xerces.*\.jar</param-value>
|
||||
</context-param>
|
||||
|
||||
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<!-- The default servlet. -->
|
||||
<!-- This servlet, normally mapped to /, provides the handling for static -->
|
||||
<!-- content, OPTIONS and TRACE methods for the context. -->
|
||||
<!-- The following initParameters are supported: -->
|
||||
<!-- -->
|
||||
<!-- acceptRanges If true, range requests and responses are -->
|
||||
<!-- supported -->
|
||||
<!-- -->
|
||||
<!-- dirAllowed If true, directory listings are returned if no -->
|
||||
<!-- welcome file is found. Else 403 Forbidden. -->
|
||||
<!-- -->
|
||||
<!-- welcomeServlets If true, attempt to dispatch to welcome files -->
|
||||
<!-- that are servlets, if no matching static -->
|
||||
<!-- resources can be found. -->
|
||||
<!-- -->
|
||||
<!-- redirectWelcome If true, redirect welcome file requests -->
|
||||
<!-- else use request dispatcher forwards -->
|
||||
<!-- -->
|
||||
<!-- gzip If set to true, then static content will be served-->
|
||||
<!-- as gzip content encoded if a matching resource is -->
|
||||
<!-- found ending with ".gz" -->
|
||||
<!-- -->
|
||||
<!-- resoureBase Can be set to replace the context resource base -->
|
||||
<!-- -->
|
||||
<!-- relativeResourceBase -->
|
||||
<!-- Set with a pathname relative to the base of the -->
|
||||
<!-- servlet context root. Useful for only serving -->
|
||||
<!-- static content from only specific subdirectories. -->
|
||||
<!-- -->
|
||||
<!-- useFileMappedBuffer -->
|
||||
<!-- If set to true (the default), a memory mapped -->
|
||||
<!-- file buffer will be used to serve static content -->
|
||||
<!-- when using an NIO connector. Setting this value -->
|
||||
<!-- to false means that a direct buffer will be used -->
|
||||
<!-- instead. If you are having trouble with Windows -->
|
||||
<!-- file locking, set this to false. -->
|
||||
<!-- -->
|
||||
<!-- cacheControl If set, all static content will have this value -->
|
||||
<!-- set as the cache-control header. -->
|
||||
<!-- -->
|
||||
<!-- maxCacheSize Maximum size of the static resource cache -->
|
||||
<!-- -->
|
||||
<!-- maxCachedFileSize Maximum size of any single file in the cache -->
|
||||
<!-- -->
|
||||
<!-- maxCachedFiles Maximum number of files in the cache -->
|
||||
<!-- -->
|
||||
<!-- cacheType "nio", "bio" or "both" to determine the type(s) -->
|
||||
<!-- of resource cache. A bio cached buffer may be used-->
|
||||
<!-- by nio but is not as efficient as a nio buffer. -->
|
||||
<!-- An nio cached buffer may not be used by bio. -->
|
||||
<!-- -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<servlet>
|
||||
<servlet-name>default</servlet-name>
|
||||
<servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>acceptRanges</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>dirAllowed</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>welcomeServlets</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>redirectWelcome</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>maxCacheSize</param-name>
|
||||
<param-value>256000000</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>maxCachedFileSize</param-name>
|
||||
<param-value>10000000</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>maxCachedFiles</param-name>
|
||||
<param-value>1000</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>cacheType</param-name>
|
||||
<param-value>both</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>gzip</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>useFileMappedBuffer</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>cacheControl</param-name>
|
||||
<param-value>max-age=3600,public</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>0</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
|
||||
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<!-- JSP Servlet -->
|
||||
<!-- This is the jasper JSP servlet from the jakarta project -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- The JSP page compiler and execution servlet, which is the mechanism -->
|
||||
<!-- used by Glassfish to support JSP pages. Traditionally, this servlet -->
|
||||
<!-- is mapped to URL patterh "*.jsp". This servlet supports the -->
|
||||
<!-- following initialization parameters (default values are in square -->
|
||||
<!-- brackets): -->
|
||||
<!-- -->
|
||||
<!-- checkInterval If development is false and reloading is true, -->
|
||||
<!-- background compiles are enabled. checkInterval -->
|
||||
<!-- is the time in seconds between checks to see -->
|
||||
<!-- if a JSP page needs to be recompiled. [300] -->
|
||||
<!-- -->
|
||||
<!-- compiler Which compiler Ant should use to compile JSP -->
|
||||
<!-- pages. See the Ant documenation for more -->
|
||||
<!-- information. [javac] -->
|
||||
<!-- -->
|
||||
<!-- classdebuginfo Should the class file be compiled with -->
|
||||
<!-- debugging information? [true] -->
|
||||
<!-- -->
|
||||
<!-- classpath What class path should I use while compiling -->
|
||||
<!-- generated servlets? [Created dynamically -->
|
||||
<!-- based on the current web application] -->
|
||||
<!-- Set to ? to make the container explicitly set -->
|
||||
<!-- this parameter. -->
|
||||
<!-- -->
|
||||
<!-- development Is Jasper used in development mode (will check -->
|
||||
<!-- for JSP modification on every access)? [true] -->
|
||||
<!-- -->
|
||||
<!-- enablePooling Determines whether tag handler pooling is -->
|
||||
<!-- enabled [true] -->
|
||||
<!-- -->
|
||||
<!-- fork Tell Ant to fork compiles of JSP pages so that -->
|
||||
<!-- a separate JVM is used for JSP page compiles -->
|
||||
<!-- from the one Tomcat is running in. [true] -->
|
||||
<!-- -->
|
||||
<!-- ieClassId The class-id value to be sent to Internet -->
|
||||
<!-- Explorer when using <jsp:plugin> tags. -->
|
||||
<!-- [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93] -->
|
||||
<!-- -->
|
||||
<!-- javaEncoding Java file encoding to use for generating java -->
|
||||
<!-- source files. [UTF-8] -->
|
||||
<!-- -->
|
||||
<!-- keepgenerated Should we keep the generated Java source code -->
|
||||
<!-- for each page instead of deleting it? [true] -->
|
||||
<!-- -->
|
||||
<!-- logVerbosityLevel The level of detailed messages to be produced -->
|
||||
<!-- by this servlet. Increasing levels cause the -->
|
||||
<!-- generation of more messages. Valid values are -->
|
||||
<!-- FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
|
||||
<!-- [WARNING] -->
|
||||
<!-- -->
|
||||
<!-- mappedfile Should we generate static content with one -->
|
||||
<!-- print statement per input line, to ease -->
|
||||
<!-- debugging? [false] -->
|
||||
<!-- -->
|
||||
<!-- -->
|
||||
<!-- reloading Should Jasper check for modified JSPs? [true] -->
|
||||
<!-- -->
|
||||
<!-- suppressSmap Should the generation of SMAP info for JSR45 -->
|
||||
<!-- debugging be suppressed? [false] -->
|
||||
<!-- -->
|
||||
<!-- dumpSmap Should the SMAP info for JSR45 debugging be -->
|
||||
<!-- dumped to a file? [false] -->
|
||||
<!-- False if suppressSmap is true -->
|
||||
<!-- -->
|
||||
<!-- scratchdir What scratch directory should we use when -->
|
||||
<!-- compiling JSP pages? [default work directory -->
|
||||
<!-- for the current web application] -->
|
||||
<!-- -->
|
||||
<!-- tagpoolMaxSize The maximum tag handler pool size [5] -->
|
||||
<!-- -->
|
||||
<!-- xpoweredBy Determines whether X-Powered-By response -->
|
||||
<!-- header is added by generated servlet [false] -->
|
||||
<!-- -->
|
||||
<!-- If you wish to use Jikes to compile JSP pages: -->
|
||||
<!-- Set the init parameter "compiler" to "jikes". Define -->
|
||||
<!-- the property "-Dbuild.compiler.emacs=true" when starting Jetty -->
|
||||
<!-- to cause Jikes to emit error messages in a format compatible with -->
|
||||
<!-- Jasper. -->
|
||||
<!-- If you get an error reporting that jikes can't use UTF-8 encoding, -->
|
||||
<!-- try setting the init parameter "javaEncoding" to "ISO-8859-1". -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<servlet id="jsp">
|
||||
<servlet-name>jsp</servlet-name>
|
||||
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>logVerbosityLevel</param-name>
|
||||
<param-value>DEBUG</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>fork</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>xpoweredBy</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
<!--
|
||||
<init-param>
|
||||
<param-name>classpath</param-name>
|
||||
<param-value>?</param-value>
|
||||
</init-param>
|
||||
-->
|
||||
<load-on-startup>0</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>jsp</servlet-name>
|
||||
<url-pattern>*.jsp</url-pattern>
|
||||
<url-pattern>*.jspf</url-pattern>
|
||||
<url-pattern>*.jspx</url-pattern>
|
||||
<url-pattern>*.xsp</url-pattern>
|
||||
<url-pattern>*.JSP</url-pattern>
|
||||
<url-pattern>*.JSPF</url-pattern>
|
||||
<url-pattern>*.JSPX</url-pattern>
|
||||
<url-pattern>*.XSP</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<!-- Dynamic Servlet Invoker. -->
|
||||
<!-- This servlet invokes anonymous servlets that have not been defined -->
|
||||
<!-- in the web.xml or by other means. The first element of the pathInfo -->
|
||||
<!-- of a request passed to the envoker is treated as a servlet name for -->
|
||||
<!-- an existing servlet, or as a class name of a new servlet. -->
|
||||
<!-- This servlet is normally mapped to /servlet/* -->
|
||||
<!-- This servlet support the following initParams: -->
|
||||
<!-- -->
|
||||
<!-- nonContextServlets If false, the invoker can only load -->
|
||||
<!-- servlets from the contexts classloader. -->
|
||||
<!-- This is false by default and setting this -->
|
||||
<!-- to true may have security implications. -->
|
||||
<!-- -->
|
||||
<!-- verbose If true, log dynamic loads -->
|
||||
<!-- -->
|
||||
<!-- * All other parameters are copied to the -->
|
||||
<!-- each dynamic servlet as init parameters -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Uncomment for dynamic invocation
|
||||
<servlet>
|
||||
<servlet-name>invoker</servlet-name>
|
||||
<servlet-class>org.eclipse.jetty.servlet.Invoker</servlet-class>
|
||||
<init-param>
|
||||
<param-name>verbose</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>nonContextServlets</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>dynamicParam</param-name>
|
||||
<param-value>anyValue</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>0</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
|
||||
-->
|
||||
|
||||
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<session-config>
|
||||
<session-timeout>30</session-timeout>
|
||||
</session-config>
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<!-- Default MIME mappings -->
|
||||
<!-- The default MIME mappings are provided by the mime.properties -->
|
||||
<!-- resource in the org.mortbay.jetty.jar file. Additional or modified -->
|
||||
<!-- mappings may be specified here -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- UNCOMMENT TO ACTIVATE
|
||||
<mime-mapping>
|
||||
<extension>mysuffix</extension>
|
||||
<mime-type>mymime/type</mime-type>
|
||||
</mime-mapping>
|
||||
-->
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.html</welcome-file>
|
||||
<welcome-file>index.htm</welcome-file>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<locale-encoding-mapping-list>
|
||||
<locale-encoding-mapping><locale>ar</locale><encoding>ISO-8859-6</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>be</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>bg</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>ca</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>cs</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>da</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>de</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>el</locale><encoding>ISO-8859-7</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>en</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>es</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>et</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>fi</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>fr</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>hr</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>hu</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>is</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>it</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>iw</locale><encoding>ISO-8859-8</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>ja</locale><encoding>Shift_JIS</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>ko</locale><encoding>EUC-KR</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>lt</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>lv</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>mk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>nl</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>no</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>pl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>pt</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>ro</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>ru</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>sh</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>sk</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>sl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>sq</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>sr</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>sv</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>tr</locale><encoding>ISO-8859-9</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>uk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>zh</locale><encoding>GB2312</encoding></locale-encoding-mapping>
|
||||
<locale-encoding-mapping><locale>zh_TW</locale><encoding>Big5</encoding></locale-encoding-mapping>
|
||||
</locale-encoding-mapping-list>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Disable TRACE</web-resource-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
<http-method>TRACE</http-method>
|
||||
</web-resource-collection>
|
||||
<auth-constraint/>
|
||||
</security-constraint>
|
||||
|
||||
</web-app>
|
||||
|
@ -1,56 +1,426 @@
|
||||
<html><head><title>ZzzOT Plugin Help</title></head>
|
||||
<body style="background-color: #ddd; color: #a30;">
|
||||
<h2>Welcome to the ZzzOT I2P Plugin!</h2>
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
A new eepsite tunnel and Jetty server have been started for your open tracker.
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ZZZOT OPENTRACKER | HELP</title>
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,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">
|
||||
<style type="text/css">
|
||||
html,
|
||||
body {
|
||||
min-width: 800px;
|
||||
min-height: 100%;
|
||||
line-height: 1.4;
|
||||
font-size: 14pt;
|
||||
font-family: "Droid Sans", "Open Sans", "Noto Sans", Ubuntu, "Segoe UI", "Lucida Grande", "DejaVu Sans", Helvetica, sans-serif;
|
||||
color: #ccc;
|
||||
background: #111;
|
||||
overflow-x: hidden;
|
||||
scrollbar-color: #222 #111;
|
||||
}
|
||||
|
||||
<p><a href="/tracker/index.jsp">Click here to see the current stats</a>.
|
||||
This link is also at the top of your router console when ZzzOT is running.
|
||||
@supports (background-blend-mode: overlay) {
|
||||
html, body {
|
||||
background: repeating-linear-gradient(45deg, #333, #111 2px, #111 3px),
|
||||
repeating-linear-gradient(135deg, #444, #333 2px, #222 3px) #111;
|
||||
background-blend-mode: overlay, normal;
|
||||
background-size: 100% 100%, 100% 100%;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
<p>Report bugs or add comments on
|
||||
<a href="http://zzz.i2p//forums/16">the plugin forum on zzz.i2p</a>.
|
||||
::selection,
|
||||
::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #431;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
<h3>Eepsite Key and Helpful Hints for I2P</h3>
|
||||
code::-moz-selection,
|
||||
#b64::-moz-selection,
|
||||
code::selection,
|
||||
#b64::selection {
|
||||
background: #150;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
<p>Your Base 32 address is <a href="http://$B32/">$B32</a>.
|
||||
Others may access your eepsite using this address, even if you do not publish a hostname.
|
||||
<p>Once you decide on a host name, you may
|
||||
<a href="http://127.0.0.1:7657/susidns/addressbook.jsp?book=private&destination=$B64">add the key to your local addressbook here</a>.
|
||||
<p>Your Base 64 key is: <textarea rows="1" style="height: 3em;" cols="40" readonly="readonly" wrap="off">$B64</textarea>
|
||||
<br>You will need this key to register a hostname at <a href="http://stats.i2p/i2p/addkey.html">stats.i2p</a>.
|
||||
<p>Your private key file is $PLUGIN/eepPriv.dat - back it up!!!
|
||||
<p>Your eepsite document root is $PLUGIN/eepsite/docroot,
|
||||
you may put other files there is you wish to have additional content on your eepsite.
|
||||
<p>The supported announce URLs are:
|
||||
<ul>
|
||||
<li><a href="http://$B32/a">http://$B32/a</a>
|
||||
<li><a href="http://$B32/announce">http://$B32/announce</a>
|
||||
<li><a href="http://$B32/announce.jsp">http://$B32/announce.jsp</a>
|
||||
<li><a href="http://$B32/announce.php">http://$B32/announce.php</a>
|
||||
<li><a href="http://$B32/tracker/a">http://$B32/tracker/a</a>
|
||||
<li><a href="http://$B32/tracker/announce">http://$B32/tracker/announce</a>
|
||||
<li><a href="http://$B32/tracker/announce.jsp">http://$B32/tracker/announce.jsp</a>
|
||||
<li><a href="http://$B32/tracker/announce.php">http://$B32/tracker/announce.php</a>
|
||||
</ul>
|
||||
<p>The supported scrape URLs are:
|
||||
<ul>
|
||||
<li><a href="http://$B32/scrape">http://$B32/scrape</a>
|
||||
<li><a href="http://$B32/scrape.jsp">http://$B32/scrape.jsp</a>
|
||||
<li><a href="http://$B32/scrape.php">http://$B32/scrape.php</a>
|
||||
<li><a href="http://$B32/tracker/scrape">http://$B32/tracker/scrape</a>
|
||||
<li><a href="http://$B32/tracker/scrape.jsp">http://$B32/tracker/scrape.jsp</a>
|
||||
<li><a href="http://$B32/tracker/scrape.php">http://$B32/tracker/scrape.php</a>
|
||||
</ul>
|
||||
<p>Your eepsite tunnel is configured for 2 inbound and 2 outbound tunnels, 3 hops each.
|
||||
You may change tunnel settings by editing $PLUGIN/i2ptunnel.config and restarting the plugin.
|
||||
The tunnel will not appear in <a href="http://127.0.0.1:7657/i2ptunnel/index.jsp">i2ptunnel</a>.
|
||||
If your tracker gets over 1000 peers, you will probably want to increase the number of tunnels.
|
||||
<p>The Jetty webserver port is 7662. If you must change it, edit jetty.xml, i2ptunnel.config, and plugins.config
|
||||
in the directory $PLUGIN. Then stop and restart the plugin.
|
||||
<p>This help file is $PLUGIN/eepsite/docroot/help.html, you should probably move it
|
||||
outside of the document root before you announce your eepsite as it may contain your user name.
|
||||
<p>As you probably know, an open tracker does not require torrents to be registered,
|
||||
and it does not host torrent files. You can, however, host torrent files elsewhere on
|
||||
the eepsite, for example at <a href="http://$B32/torrents/">/torrents</a>.
|
||||
p,
|
||||
li,
|
||||
code {
|
||||
text-shadow: 0 1px 1px #000;
|
||||
}
|
||||
|
||||
</body></html>
|
||||
#container {
|
||||
padding: 5% 15%;
|
||||
}
|
||||
|
||||
#panel {
|
||||
padding: 20px 40px 15px;
|
||||
font-size: 85%;
|
||||
text-align: justify;
|
||||
border: 1px solid #555;
|
||||
box-shadow: inset 0 0 0 1px #111, inset 0 0 2px 1px #444, 0 0 2px 2px #000;
|
||||
background: #181818;
|
||||
background: repeating-linear-gradient(to right, rgba(255, 255, 255, .05), rgba(0, 0, 0, .08) 2px),
|
||||
repeating-linear-gradient(to bottom, #222, #111 2px);
|
||||
background-blend-mode: overlay;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@supports not (-moz-appearance: none) {
|
||||
#panel {
|
||||
background: repeating-linear-gradient(to bottom, #222, #111 2px);
|
||||
}
|
||||
}
|
||||
|
||||
#sitename,
|
||||
#sitename:hover,
|
||||
#sitename:focus {
|
||||
margin: -8px 0 -14px;
|
||||
display: inline-block;
|
||||
font-size: 500%;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#sitename::before {
|
||||
margin-top: 4px;
|
||||
display: inline-block;
|
||||
content: "";
|
||||
width: 68px;
|
||||
height: 56px;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAABdFBMVEUAAADOzs7g4ODb29vZ2dnd3d3T09Pi4uLq6urr6+vX19fk5OTl5eXm5ubp6ene3t7h4eHk5OTn5+fo6Ojk5OTm5ubq6urp6em7u7v///8dEgrX19fg4OBSPTF/W0VsTzxKNywgFg/k5OTS0tLAv7/p6Oh3VkHv7+9iRzjNzc2EX0hyUz9aQjRnSzra2trJycn19fXGxsby8vJfRjXDw8P7+/vc3Nw/MScmGxOIYUlEMyn4+Pg6KyItIRoxJRzq6up6WUXPz8+KZEw0KCA8LSWwrau/t7FgV1JFPDe5uLe3tLKzsbC6raStopqZlZSIg4GgjICEa1zc1dHDubOgnZuyoZapmY+mk4eVioRtVEXCvbqsqaiFgH2AenZ1b2qKdWdpYV5RR0I8Miy9saqxqaWSc192ZVrUzsumo6Gypp6jmpSSg3mXemluZ2JjXVhZUk7j3tnLwLm9ubech3qZgXJ/ZVNnWE9wXE5fS0A+NjGNiYbSycJQXgkWAAAAGHRSTlMA/NAH6uD4vEk+7qymkGPbwp96b7GFNSKLdq62AAAFzUlEQVRo3qzTSW/aQBgG4DE4G82etH1HbWIUDWCRRIGAIyU9FCGB1BQkFrFeOBCIshzaZpX65wvGIeOBjMc0j82Bz2ZeZvmIss9by4G5obX15c1P5F0tbesag0ta09cXybv4qCdhY6ydH6q2GRvFHekrQfJ/NkNpe+x87fbU5b6Wt0OYvkpm90EDwKqNbHyqbL3KAGgLs00jGEgCGIx+KtNoA0guEP+WjwDW7WU99QoMONom/qxqALvKniopDyM0X2cqwIBuNq4sO4hgAaJqUQPavbgvtwzQlhRXn4HV4r7VGNIrSssDsN7uDHoM8F6moA7kd2cTzwMhr/HngavdmdWA+aDX+I1dl7tq4VY9oe6RMA+Uoi4FSql1H1VWkiaEJsav0aFI1FeCLjk/9YxLx6K224y6xptnaQUoRt1+0ZFa1Ici8IFMsZRGft+tRB2/B18M+8MxJj6OLti0ntbAMvsu0Qh1VLjqQ6Hy629pXyLKoE3bAFYWXqzTF5HXYifmzEmizCa3YZGhbggi9EViXHs4dkplQ6IOtjrRAW1DcEdf7Tu1zEuolTFk/oiLtA028ZcqXMCDYRsfK1oxpMrABuEd4WriHcrp2KXW5bhQMuQukOQbegGsdSjoUs69XfrNbcqhXMu1z8EkiocCI0E5jWGpQMeqh16ukZRPoER5hUGlzi+ZZ4DBuH7WUDRFFcq7NM07i45FTG8XrwdpFWilBE8W5cXMDl8opry1gC0youOPKSpStyK/JdaTqeAcIWeLGW5M0R6VqJgqbpAmthWwlKhvWfTNy7pJqTAZPjordH4gupIFHJsHSs6hO13cPBBFLImTTksp4GbUCovAs/iob3n4Wm2Un70CTGDJ7jLsiC6+KUhUus3Ujsyo10LI7Yj2vin6ciALyEG327gpPugnlDVlAU27mdPoiw+K6gGXsoAnMEI+AWHRSSJxrHglHsMSwGeyCSaWH499uJYFMGyRjclDdO0n4ER+jDbIOn6IuZcxP/qSGfxAgKzh55nbc8yXi7Oz8NRrIIc1MoecENv0F7C3E35TDnOjgDPuDqdj32M+7lgzbP9OvLmAf63ay2+iUBgF8AtlprU++m5PKAtpPxfdlJCQljSlk4k2oCa6cTU+omYWZhJrMotJ2r9+AhGBggjc/sTNXZwTicnV7/IUMWv8yKfx51/9Kdm64DFi3Mjv5ldvMnuMcwuOPhd0GgXd/u7b9WhWF0dMRCeyVlcaHJSOHQ5r45idAM9hyxs+9/VQGHDCzkCRgsUNp2kojHDKroBWyOM1d0EoDbhkjDALddoKLzsUBsPbcEYvgbHCaxaE9b0NR8IwVDBQHhSuS6kHYUNvyywDzcBfhdc8CAPcaWEJaAal1w+8gqwW4A3ydEwsn3PP687amEBjLgmvlu+Nu8AICtxfLa4L0Iu/Nr7m1Q4KCFXmMbA01wbcBUPTtwTt+WOiV38R3AU909fdDI7OgLnpsW558wctP38O1II/gete55YPrcyNXmiaUAaZqsvmir8bm+qGRQjN5HX0VdeIJ38wV0N6kVmCCLK8L9HPwsg2w/wPEMwSBm4t7ooaWWpEDxoLOwHeZdkqFt79sJtylBMbDAroqqpTILxvN9W4dmxqVyOM1KmWix8eNwIdJExlnb6WWWe0asrbvBNEFiOgvdA0PdNLe5NTmIDA4kqEjp5RT00zhFFiCcpAW89mqaboA9Wtw3EYmaTdoUnKQYsEIt6CKXCYdgBCmazUbabAd8Z2NBi7Llql3J/dhzi020xO9uHlpztEBskF6gKQ9tguIlyU8gYcNYHTBkSWQdXALo4cNwaowjIpCfkL3ru5TnxFQqq5HGUNABJZDgdCjgKzT4BQY/mca9iuJQeaPQK0CstvX8c2z7JvOgSgiwWfS9gXkMySXeZkSACEMiuuJlFygTNewGUcnjI+e1VJw2frVl26YF/i4FgSDESQIJ2X2Je6PK0cH31ziZWzK5bVf0D9anRc2+uqAAAAAElFTkSuQmCC) center center no-repeat;
|
||||
background-size: 56px 56px;
|
||||
opacity: 0.7;
|
||||
filter: drop-shadow(0 0 3px rgba(0,0,0,.75));
|
||||
}
|
||||
|
||||
hr.heading {
|
||||
margin: -10px 0 15px;
|
||||
height: 1px;
|
||||
color: transparent;
|
||||
border: none;
|
||||
background: repeating-linear-gradient(to right, rgba(255, 255, 255, .05), rgba(0, 0, 0, .08) 2px),
|
||||
linear-gradient(to right, #888, #666 20%, rgba(0, 0, 0, 0) 90%);
|
||||
filter: drop-shadow(0 1px 1px #000);
|
||||
animation: ease-out underline 1s forwards;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@keyframes underline {
|
||||
from {
|
||||
width: 0;
|
||||
}
|
||||
to {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#v {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
color: #ccc;
|
||||
text-shadow: 0 0 2px #181818, 0 3px 1px #000;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding: 5px 0 0;
|
||||
}
|
||||
|
||||
h2::first-letter,
|
||||
h3::first-letter {
|
||||
font-size: 114%;
|
||||
}
|
||||
|
||||
a:link,
|
||||
.urls li {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #c4ad9d;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#local {
|
||||
font-weight: normal;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #a98770;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: #e88b44;
|
||||
}
|
||||
|
||||
code,
|
||||
#b64 {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
font-family: "Droid Sans Mono", "Noto Mono", "DejaVu Sans Mono", "Lucida Console", monospace;
|
||||
font-size: 90%;
|
||||
color: #292;
|
||||
-moz-user-select: all;
|
||||
-webkit-user-select: all;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
#b64 {
|
||||
margin-bottom: -5px;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
white-space: no-wrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#b64:focus {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
overflow: normal;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.warn {
|
||||
margin-top: 18px;
|
||||
padding: 15px 18px 15px 54px;
|
||||
line-height: 1.2;
|
||||
border: 1px solid #900;
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 0 0 1px #000, inset 0 0 2px 1px #900, 0 0 2px 1px rgba(0, 0, 0, .2);
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACcAAAAhCAMAAAEaI3KMAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACbVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQECAgIDAAADAgIEBAQGBAQGBQUGBgYHAQAHBwcICAgJCQkKAQAKCgoLAQALCwsMCwsODg4PDw8QAQAQAgASAAASAwAVCwsaGhobDw8bExMbGxskHx8mJiYpAwAqKiotKysuAQAwLi4yMjI8HR08JiY9CAA/CAA/Pz9BQUFDQ0NKBwBLPDxLS0tTAwBhBABmZmZpAABpPT1tEABvEAB4RUV4WFh4a2uCBwCECACGhoaHeHiUAQCWFgCaFgCclZWkDgClVFSlb2+0lZW0tLS2FQC4BQC9vb3GGQDGxsbIVlbKWFjMFwDOCgDPfn7RFADUrKnV1dXWCgDWEgDWLR7YBATYEADYg4PZ2dnaAwPakozbEADbp6fbysrb29vezc3e1NTe3t7hDQDhIxjk5OTlb2nlcWvmCwDqwL7qysnq6urrCQDrDAPrDgbtAADtgIDv7+/wQkLwS0bxBgDxPTjzBADzhITzq6vz8/P1oJ/1qKf19fX2BAD209P51tb7AgD7HBv77+/7+/v8/Pz/AAD/DAz/amr/bm7/cnL/enr/goL/ior/k5P/lJT/mJj/m5v/o6P/q6v/s7P/t7f/uLj/u7v/vr7/w8P/xcX/yMj/y8v/09P/2Nj/29v/4+P/5OT/6en/6ur/6+v/8fH/8vL/9PT//Pz//f3////ZtbwoAAAAJXRSTlMAAwYMEhUYGyEkMDxCS1ppeISHkJacpairtMPP2Nvk5+3w8/n8UHR7egAAAp9JREFUGBmFwYlDi2EcB/BvB6lRjoxOS/1ykzPkFrmv3GdCyZEzLMRcrZdhjjG3MV4du/S2aU1puXn+Js87G/WWfD4I8W4FkMpaARgEwYAexAHQgFM1gbOPtAPThPUCQNxQyOb5w/Db2aYsKR4y62txgWglILosaDVkQxAS5p+HoK1NXhUC4qVxjccRYG/NYvZUcGQVF4uvreAMgrBeEAyTgNVlQb2QRkE9EaK+fFENhbQWToMOyO3++dPtJrQz2+lkmdnM6ZyJkJgN9+oYmzuZsbp7G2IQ0P+A+b7JZCooMJlMD8wH+oNLOaM7X8GtWlXBndedSQGSSztJRoiKekNpypcxUEh66X+RhA6i1rS0tKzpgfbI6/3q9RLaidvrbmTv3Hvj8NcoSbpEJyVpBP5IfORsfkIHm513ExEUucRuZ4xuMrt9SQR+yzhd+5kxYuxz7bkMBMQWWl+JokiiKL6yFsZCRmaziSMTZzYTuIRThmsCRwJ31XAsAQjP1ekqZFQh0+lyw5FeppSO5aVKy6AmpUFQitIs2rMwLQr/oaKdbW1t20iFbsVRyQdZCcWhG+rRF3w+33ufz3dhtBr/EpY057HH4/Ezv8fjeTwnKQxditQsbeA+5VP+xwZuqSYSXYjJ2OJyueq/HxpP2fu+1btcro0ZMegkloodDsfbH4wNo4kn2Y+3DoejmGKhED/8iM1mkxg3legNY0yy2WyHhw9AB4k5d6qrayQmm0+zmEyqqa6+k5OIvyJS8x5aLJZnzwNW0ornAU8tFsvDvNQIBEWnrzMajbdv3FS6fstoNK5Nj0ZAH9qh1+urrlQGZVJmZdCVKr1ev536gOtHRVqttvxE18q1Wm0R9QUGTth/9H/2j43HoBmbd+3u3q5N0wf/ApzdYLKMtOjLAAAAAElFTkSuQmCC) 12px center no-repeat, repeating-linear-gradient(to bottom, rgba(0, 0, 0, .5), rgba(64, 0, 0, .2) 2px), linear-gradient(to bottom, #222, #111);
|
||||
background-size: 32px auto, 100% 100%, 100% 100%;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.emphasis {
|
||||
margin-top: 4px;
|
||||
display: inline-block;
|
||||
line-height: 110%;
|
||||
color: #900;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
ul#config {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#config li {
|
||||
padding: 10px 15px 5px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#config li::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: -22px;
|
||||
background: url(data:image/png;base64,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) left top no-repeat;
|
||||
background-size: 14px 14px;
|
||||
opacity: .85;
|
||||
filter: saturate(0) brightness(1.6) drop-shadow(0 1px 1px #000);
|
||||
}
|
||||
|
||||
#config li br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.urls li {
|
||||
margin-left: -15px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.urls li::before {
|
||||
margin: 2px 0 -3px;
|
||||
width: 22px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
content: "";
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABEVBMVEUAAAAAAAAAAAAAAAAAAABDREYAAAAAAAAAAACDhYgAAABmaGuPkZRZWlyFh4oAAABkZWcAAAAAAABRUlRiY2ZPUVN4eXtydHdcXV9YWVtCQ0QxMjM2Nzh6fH6DhIcAAAAAAAB3eXt3enxoaWtlZ2p2d3pwcnWCg4Z7fH5OTlBnaWtdXmAxMTJXWFoyMjRAQEIAAABeYGJmZ2qIio2jpKfx8vOKjZDLzM7v7+6wsrWdnqCAgYSbnJ6Nj5KMjY58foFxcnXp6urh4uTOz9KztreYmp2QkJOFh4uSlJaPkJF/f4NVVljR0tS/vsC8vsCsrrKgoqSXl5uQkpN6e31ub3JsbXBdYGJaW12/v8G+vb+SlJjRk2qVAAAAMnRSTlMABAwWJ4UHQiBhEWDohWEw7ToU8e3r5dKRg3tpXz05Nw/v7ejEvrGtfHRtaGZXV0w5MLROkgwAAAE3SURBVDjLndHXcoJAFIBhlyWEGA2WJJY003tjAQ2hKQIGe4tJ3v9BsjDOeLEcLzy335nZPfOnNpzTjEnILYIY7SnNVm/g2QLgO4ra/jPHw8kNgvyDlJ9KvuJwoGcl/sUezQtJ78fO17i7X0/G7MJJ5BWeQ7thPcixC0Km2Sb3kX/WNfk4zSykzVZn6lwKsW+J7J1Yc21dXTzHziccgYlvNKzu0lHCwnnkPy7k6FBvWN+6Eazxr6Izk/ePqDP9NHJB/WCbzplInenn+gb1vIgxTgsJ/To2/V8xjxHUZxr9zxFBrzqq1dVnGOpXecst5L4B9svyolQNXagfdU54XNOvHPUF+5Ge+VqjDvcbjEvvDyHYr3DtDX27D3qKk+yJMvICyFMI56+c+aofOwKWVv0SB3GFuN9m8w950jtLubKVyAAAAABJRU5ErkJggg==) left center no-repeat;
|
||||
background-size: 16px 16px;
|
||||
filter: sepia(1) saturate(.4) drop-shadow(0 1px 1px #000);
|
||||
}
|
||||
|
||||
.external::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
background:url(data:image/png;base64,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) center top no-repeat;
|
||||
background-size: 12px 12px;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.external:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@supports (-webkit-text-stroke-width: 1px) {
|
||||
#sitename,
|
||||
#sitename:hover,
|
||||
#sitename:focus {
|
||||
background: #731;
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, .5) 2px),
|
||||
linear-gradient(to bottom, rgba(255, 96, 0, .2), rgba(0, 0, 0, .9) 100%),
|
||||
linear-gradient(to bottom, #210, #310 15%, #fff 50%, #310 80%);
|
||||
-moz-background-clip: text !important;
|
||||
-webkit-background-clip: text !important;
|
||||
background-clip: text !important;
|
||||
-moz-text-fill-color: transparent !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
text-fill-color: transparent !important;
|
||||
filter: drop-shadow(0 0 2px #000);
|
||||
-webkit-text-stroke-color: #999;
|
||||
-webkit-text-stroke-width: 0.02em;
|
||||
}
|
||||
|
||||
#sitename:hover,
|
||||
#sitename:focus {
|
||||
background: #951;
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, .3) 2px),
|
||||
linear-gradient(to bottom, rgba(255, 255, 255, .2), rgba(0, 0, 0, .7) 100%),
|
||||
linear-gradient(to bottom, #930, #d50 15%, #fff 50%, #930 80%, #000 100%, #ff0 150%);
|
||||
filter: drop-shadow(0 0 2px #b00);
|
||||
-webkit-text-stroke-color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
#help {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.external::before {
|
||||
margin-left: -2px;
|
||||
}
|
||||
|
||||
#sitename::before {
|
||||
height: 52px;
|
||||
background-size: 52px 52px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1400px) {
|
||||
#container {
|
||||
padding: 2% !important;
|
||||
}
|
||||
|
||||
#panel {
|
||||
padding: 20px 35px 10px;
|
||||
}
|
||||
|
||||
#sitename,
|
||||
#sitename:hover,
|
||||
#sitename:focus {
|
||||
margin: -14px 0;
|
||||
}
|
||||
|
||||
code:not(#b64) {
|
||||
display: inline-block;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
br + code {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
#config li br {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="help">
|
||||
<div id="container">
|
||||
<div id="panel">
|
||||
<a href="http://127.0.0.1:7662/" title="OpenTracker homepage" alt="OpenTracker homepage"><span id="sitename">ZZZOT</span></a>
|
||||
<span id="v">$VERSION</span>
|
||||
<h2>Welcome to the ZzzOT I2P Plugin!</h2>
|
||||
<hr class="heading">
|
||||
<p class="warn" id="docroot">This help file is located at: <code>$PLUGIN/eepsite/docroot/help.html</code><br><span class="emphasis"><b>You should probably move it outside of the document root before you announce your eepsite as it may contain your username.</b></span></p>
|
||||
<p>ZzzOT is a simple in-memory BitTorrent open <a href="https://en.wikipedia.org/wiki/BitTorrent_tracker" class="external" target="_blank">tracker</a>, wrapped into an I2P plugin. The software depends on several I2P libraries, in addition to <code>i2psnark.jar</code> from the I2P installation for <a href="https://en.wikipedia.org/wiki/Bencode" class="external" target="_blank">bencoding</a>. Please report bugs on <a href="http://i2pforum.i2p/" target="_blank">i2pforum.i2p</a>. New releases will be announced on <a href="http://zzz.i2p/forums/16" target="_blank">the plugin forum</a> on zzz.i2p.</p>
|
||||
<p>Source code is available (under the <a href="https://www.apache.org/licenses/LICENSE-2.0.html" class="external" target="_blank">Apache 2.0 license</a>) at <a href="http://git.idk.i2p/I2P_Developers/i2p.plugins.zzzot" target="_blank">our Gitea site</a> and on <a href="https://github.com/i2p/i2p.plugins.zzzot" class="external" target="_blank">github</a>. Note that the I2P source code must be available (and compiled) in <code>../i2p.i2p</code> in order to build ZzzOT.</p>
|
||||
<h3>Configuration & Customization</h3>
|
||||
<hr class="heading">
|
||||
<ul id="config">
|
||||
<li>A new eepsite tunnel and Jetty server have been started for your open tracker, configured for 2 inbound and 2 outbound tunnels, 3 hops each. If your tracker gets over 1000 peers, you will probably want to increase the number of tunnels.</li>
|
||||
<li>To change the tunnel settings, edit: <code>$PLUGIN/i2ptunnel.config</code> (the tunnel will not appear in the <a href="http://127.0.0.1:7657/i2ptunnelmgr" target="_blank">Tunnel Manager</a>), then stop and restart the plugin from the <a href="http://127.0.0.1:7657/configplugins" target="_blank">Plugin Manager</a>.</li>
|
||||
<li>The Jetty webserver is running on port <code>7662</code>. If you must change it, edit: <code>jetty.xml</code>, <code>i2ptunnel.config</code>, and <code>plugins.config</code> in: <code>$PLUGIN/</code>, then stop and restart the plugin.</li>
|
||||
<li>The configuration file for ZzzOT is located at: <code>$PLUGIN/zzzot.config</code>
|
||||
<li>The most important configuration for ZzzOT is the <code>interval</code>, which tells clients how long to wait between announces. The default is 1620 seconds (27 minutes).</li>
|
||||
<li>The easiest way to reduce the load on your tracker is to increase the <code>interval</code>. To do so, edit the configuration file, then stop and restart the plugin.</li>
|
||||
<li>UDP may also be enabled or disabled, and the connection lifetime changed, in the configuration file.</li>
|
||||
<li>All conguration changes require the plugin to be stoped and restarted.</li>
|
||||
<li>Live stats are available on the <a href="http://127.0.0.1:7662/tracker" target="_blank">tracker page</a> (this link is also on your router console sidebar when ZzzOT is running).</li>
|
||||
<li>To change the display name for the site (for the logo and page titles), add the line: <code>sitename=<i>mytracker</i></code> to the configuration file (substituting <i>mytracker</i> with your desired name) and then stop and restart the plugin.</li>
|
||||
<li>To change the footer text displayed on the tracker stats page, add the line: <code>footertext=<i>alternative text</i></code> to the configuration file, and then stop and restart the plugin.</li>
|
||||
<li>To hide the version footer displayed on the stats page, add the line: <code>showfooter=false</code> to the configuration file, and then stop and restart the plugin.
|
||||
<li>Your eepsite document root is: <code>$PLUGIN/eepsite/docroot/</code>. You may put other files there if you wish to host additional content on your eepsite.</li>
|
||||
<li>Your eepsite home page is located at: <code>$PLUGIN/eepsite/docroot/index.html</code>, you may edit it to add information about your tracker or links to additional content, or to change the displayed name and page title.</li>
|
||||
<li>If you wish to change the appearance of the site, edit the css file located at: <br><code>$PLUGIN/eepsite/docroot/tracker.css</code>. There's an alternative purple-themed css file in the same directory that should be renamed to <code>tracker.css</code> (having renamed the existing file) if you wish to use that instead, or deleted if you don't intend to use it.</li>
|
||||
<li>Directory listings are enabled on the server, so you don't need to create a web page to serve files; content in any folders you create in your document root will be immediately available to your visitors. The quickest method to disable listings is to place an empty <code>index.html</code> file in any folder you wish to disable.</li>
|
||||
<li>Open trackers do not require torrents to be registered, and do not host torrent files. However, you can host torrent files elsewhere on the eepsite, for example at <a href="/torrents/" target="_blank">/torrents</a>.</li>
|
||||
</ul>
|
||||
<h3>Eepsite Key & Hostname Registration</h3>
|
||||
<hr class="heading">
|
||||
<p class="warn">Your private key file is located at: <code>$PLUGIN/eepPriv.dat</code><br><span class="emphasis">Make sure you backup this file and move the copy outside of <code>$PLUGIN/</code> as it controls ownership of your B64/B32 address.</span></p>
|
||||
<p>Your Base 32 address is: <a href="http://$B32/" target="_blank">$B32</a><br>Others may access your eepsite using this address, even if you do not publish a hostname.</p>
|
||||
<p>Your Base 64 key is:<br><code id="b64" tabindex="1">$B64</code><br>Once you decide on a hostname, you may <a href="http://127.0.0.1:7657/susidns/addressbook.jsp?book=private&destination=$B64" target="_blank">add the key</a> to your local addressbook.</p>
|
||||
<p>To generate a registration authentication string for your hostname (required for <a href="http://stats.i2p/i2p/addkey.html" target="_blank">hostname registration</a> at stats.i2p), run the following from the command line:<br><code>java -jar $I2P/lib/i2p.jar privatekeyfile -a <i>your-hostname</i>.i2p $PLUGIN/eepPriv.dat</code></p>
|
||||
<h3>Tracker URLs</h3>
|
||||
<hr class="heading">
|
||||
<p>Tracker statistics:</p>
|
||||
<ul class="urls">
|
||||
<li><a href="http://$B32/tracker">http://$B32/tracker</a></li>
|
||||
<li><a href="http://$B32/tracker/">http://$B32/tracker/</a></li>
|
||||
<li><a href="http://$B32/tracker/index.html">http://$B32/tracker/index.html</a></li>
|
||||
<li><a href="http://$B32/tracker/index.jsp">http://$B32/tracker/index.jsp</a></li>
|
||||
</ul>
|
||||
<p>Supported announce URLs:</p>
|
||||
<ul class="urls">
|
||||
<li><a href="http://$B32/a">http://$B32/a</a></li>
|
||||
<li><a href="http://$B32/announce">http://$B32/announce</a></li>
|
||||
<li><a href="http://$B32/announce.jsp">http://$B32/announce.jsp</a></li>
|
||||
<li><a href="http://$B32/announce.php">http://$B32/announce.php</a></li>
|
||||
<li><a href="http://$B32/tracker/a">http://$B32/tracker/a</a></li>
|
||||
<li><a href="http://$B32/tracker/announce">http://$B32/tracker/announce</a></li>
|
||||
<li><a href="http://$B32/tracker/announce.jsp">http://$B32/tracker/announce.jsp</a></li>
|
||||
<li><a href="http://$B32/tracker/announce.php">http://$B32/tracker/announce.php</a></li>
|
||||
</ul>
|
||||
<p>UDP announce URL, if enabled (enabled by default):</p>
|
||||
<ul class="urls">
|
||||
<li><a href="udp://$B32:6969/">udp://$B32:6969/</a></li>
|
||||
</ul>
|
||||
<p>Supported scrape URLs (note that full scrapes are disabled by default):</p>
|
||||
<ul class="urls">
|
||||
<li><a href="http://$B32/scrape">http://$B32/scrape</a></li>
|
||||
<li><a href="http://$B32/scrape.jsp">http://$B32/scrape.jsp</a></li>
|
||||
<li><a href="http://$B32/scrape.php">http://$B32/scrape.php</a></li>
|
||||
<li><a href="http://$B32/tracker/scrape">http://$B32/tracker/scrape</a></li>
|
||||
<li><a href="http://$B32/tracker/scrape.jsp">http://$B32/tracker/scrape.jsp</a></li>
|
||||
<li><a href="http://$B32/tracker/scrape.php">http://$B32/tracker/scrape.php</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
25
plugin/templates/index.html
Normal file
25
plugin/templates/index.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- edit this file if you want to change your home page -->
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ZZZOT OPENTRACKER</title>
|
||||
<style type="text/css">
|
||||
body, html {min-height: 100%; font-size: 0;}
|
||||
body {background: #111;}
|
||||
body {background: repeating-linear-gradient(45deg, #444, #333 2px, #222 3px), repeating-linear-gradient(135deg, #444, #333 2px, #222 3px); background-blend-mode: multiply, normal;}
|
||||
</style>
|
||||
<link href="/tracker.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="panel">
|
||||
<a href="/tracker/" title="View OpenTracker stats"><span id="sitename">zzzot</span></a>
|
||||
<span id="footer" class="b32">$B32</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure 1.2//EN" "http://jetty.mortbay.org/configure_1_2.dtd">
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
<!-- This file configures the Jetty server. -->
|
||||
@ -8,183 +8,359 @@
|
||||
<!-- Commonly changed settings: -->
|
||||
<!-- * host: Change 127.0.0.1 to 0.0.0.0 in the addListener section -->
|
||||
<!-- to access the server directly (bypassing i2p) -->
|
||||
<!-- from other computers. The included version of Jetty has -->
|
||||
<!-- been patched to allow IPv6 addresses as well, -->
|
||||
<!-- enclosed in brackets e.g. [::1] -->
|
||||
<!-- * port: Default 7662 in the addListener section -->
|
||||
<!-- * threads: Raise MaxThreads in the addListener section -->
|
||||
<!-- from other computers. -->
|
||||
<!-- * port: Default 7662 in the addConnector section -->
|
||||
<!-- * docroot: Change the ResourceBase in the contexts/base-context.xml file -->
|
||||
<!-- to serve files from a different location. -->
|
||||
<!-- * threads: Raise maximumPoolSize in the ThreadPool section -->
|
||||
<!-- if you have a high-traffic site and get a lot of warnings. -->
|
||||
<!-- -->
|
||||
<!-- I2P uses Jetty 5.1.15. We have no plans to upgrade to Jetty 6, due to -->
|
||||
<!-- the significant changes in the API. If you need web server features not -->
|
||||
<!-- found in Jetty 5, you may install and run Jetty 6 in a different JVM, -->
|
||||
<!-- or run any other web server such as Apache. If you do run another -->
|
||||
<!-- web server instead, be sure and disable the Jetty 5 server for your -->
|
||||
<!-- eepsite on http://127.0.0.1/configclients.jsp . -->
|
||||
<!-- I2P uses Jetty 9. If you need web server features not found -->
|
||||
<!-- in I2P's Jetty 9, you may install and run Jetty 9 in a different JVM -->
|
||||
<!-- or run any other web server such as Apache. If you do run another web -->
|
||||
<!-- server instead, be sure and disable the Jetty 9 server for your -->
|
||||
<!-- eepsite on http://127.0.0.1:7657/configclients.jsp . -->
|
||||
<!-- -->
|
||||
<!-- Jetty now uses the I2P logging system rather than wrapper.log. -->
|
||||
<!-- Use the log override org.eclipse.jetty.server.Server to adjust the log level. -->
|
||||
<!-- -->
|
||||
<!-- Note that the XML encoding for this file is UTF-8. -->
|
||||
<!-- -->
|
||||
<!-- Jetty errors and warnings will appear in wrapper.log, check there -->
|
||||
<!-- to diagnose problems. -->
|
||||
<!-- ========================================================================= -->
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Jetty Server -->
|
||||
<!-- -->
|
||||
<!-- Documentation of this file format can be found at: -->
|
||||
<!-- http://docs.codehaus.org/display/JETTY/jetty.xml -->
|
||||
<!-- -->
|
||||
<!-- =============================================================== -->
|
||||
<Configure class="org.mortbay.jetty.Server">
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Request Listeners -->
|
||||
<!-- =============================================================== -->
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add and configure a HTTP listener to port 8080 -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<Call name="addListener">
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Server Thread Pool -->
|
||||
<!-- =========================================================== -->
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.SocketListener">
|
||||
|
||||
<!--
|
||||
Requests above the maxThreads + queue_size will be rejected and logged.
|
||||
ref:
|
||||
https://wiki.eclipse.org/Jetty/Howto/High_Load
|
||||
http://trac.i2p2.i2p/ticket/1395
|
||||
-->
|
||||
<New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
|
||||
<Arg type="int">20</Arg> <!-- maxThreads, overridden below -->
|
||||
<Arg type="int">3</Arg> <!-- minThreads, overridden below -->
|
||||
<Arg type="int">60000</Arg> <!-- maxIdleTimeMs, overridden below -->
|
||||
<Arg>
|
||||
<New class="org.mortbay.util.InetAddrPort">
|
||||
<New class="java.util.concurrent.LinkedBlockingQueue">
|
||||
<Arg type="int">50</Arg>
|
||||
</New>
|
||||
</Arg>
|
||||
<Set name="minThreads">4</Set>
|
||||
<Set name="maxThreads">20</Set>
|
||||
<Set name="idleTimeout">60000</Set>
|
||||
<Set name="daemon">true</Set>
|
||||
<Set name="name">Zzzot Jetty</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
|
||||
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Set connectors -->
|
||||
<!-- =========================================================== -->
|
||||
<!-- One of each type! -->
|
||||
<!-- =========================================================== -->
|
||||
|
||||
<!-- Use this connector for many frequently idle connections
|
||||
and for threadless continuations.
|
||||
Not recommended on Java 5 - comment this out, and uncomment the
|
||||
SocketConnector below.
|
||||
Do not use for gij or JamVM - comment this out, and uncomment the
|
||||
SocketConnector below.
|
||||
-->
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Arg><Ref refid="Server" /></Arg>
|
||||
<Arg type="int">1</Arg> <!-- number of acceptors -->
|
||||
<Arg type="int">0</Arg> <!-- default number of selectors -->
|
||||
<Arg>
|
||||
<Array type="org.eclipse.jetty.server.ConnectionFactory"> <!-- varargs so we need an array -->
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.server.HttpConfiguration">
|
||||
<Set name="sendServerVersion">false</Set>
|
||||
<Set name="sendDateHeader">true</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</New>
|
||||
</Item>
|
||||
</Array>
|
||||
</Arg>
|
||||
<Set name="host">127.0.0.1</Set>
|
||||
<Set name="port">7662</Set>
|
||||
<Set name="idleTimeout">600000</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Set up global session ID manager -->
|
||||
<!-- =========================================================== -->
|
||||
<!--
|
||||
<Set name="sessionIdManager">
|
||||
<New class="org.eclipse.jetty.server.session.HashSessionIdManager">
|
||||
<Set name="workerName">node1</Set>
|
||||
</New>
|
||||
</Set>
|
||||
-->
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Set handler Collection Structure -->
|
||||
<!-- -->
|
||||
<!-- We use a RewriteHandler at the top level so that we may -->
|
||||
<!-- convert any top-level URLs for the tracker down into the -->
|
||||
<!-- tracker servlet, without interfering with anything else -->
|
||||
<!-- that is hosted on the same eepsite. -->
|
||||
<!-- =========================================================== -->
|
||||
<Set name="handler">
|
||||
<New id="Rewrite" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
|
||||
<Set name="rewriteRequestURI">true</Set>
|
||||
<Set name="rewritePathInfo">false</Set>
|
||||
<Set name="originalPathAttribute">requestedPath</Set>
|
||||
<Set name="rules">
|
||||
<Array type="org.eclipse.jetty.rewrite.handler.Rule">
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/help</Set>
|
||||
<Set name="replacement">/help.html</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/help/</Set>
|
||||
<Set name="replacement">/help.html</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/a</Set>
|
||||
<Set name="replacement">/tracker/announce.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/announce</Set>
|
||||
<Set name="replacement">/tracker/announce.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/announce.jsp</Set>
|
||||
<Set name="replacement">/tracker/announce.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/announce.php</Set>
|
||||
<Set name="replacement">/tracker/announce.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/scrape</Set>
|
||||
<Set name="replacement">/tracker/scrape.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/scrape.jsp</Set>
|
||||
<Set name="replacement">/tracker/scrape.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/scrape.php</Set>
|
||||
<Set name="replacement">/tracker/scrape.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<!-- BiglyBT -->
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/a/scrape</Set>
|
||||
<Set name="replacement">/tracker/scrape.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/Seedless</Set>
|
||||
<Set name="replacement">/tracker/seedless.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/Seedless/</Set>
|
||||
<Set name="replacement">/tracker/seedless.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/Seedless/index.jsp</Set>
|
||||
<Set name="replacement">/tracker/seedless.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/Seedless/seedless</Set>
|
||||
<Set name="replacement">/tracker/seedless.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
</Array>
|
||||
</Set>
|
||||
<Set name="handler">
|
||||
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
|
||||
<Set name="handlers">
|
||||
<Array type="org.eclipse.jetty.server.Handler">
|
||||
<Item>
|
||||
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
|
||||
</Item>
|
||||
<Item>
|
||||
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
|
||||
</Item>
|
||||
<Item>
|
||||
<New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
|
||||
</Item>
|
||||
</Array>
|
||||
</Set>
|
||||
</New>
|
||||
</Set>
|
||||
</New>
|
||||
</Set>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Create the deployment manager -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- The deplyment manager handles the lifecycle of deploying web -->
|
||||
<!-- applications. Apps are provided by instances of the -->
|
||||
<!-- AppProvider interface. Typically these are provided by -->
|
||||
<!-- one or more of: -->
|
||||
<!-- jetty-webapps.xml - monitors webapps for wars and dirs -->
|
||||
<!-- jetty-contexts.xml - monitors contexts for context xml -->
|
||||
<!-- jetty-templates.xml - monitors contexts and templates -->
|
||||
<!-- =============================================================== -->
|
||||
<Call name="addBean">
|
||||
<Arg>
|
||||
<New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
|
||||
<Set name="contexts">
|
||||
<Ref refid="Contexts" />
|
||||
</Set>
|
||||
<Call name="setContextAttribute">
|
||||
<Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
|
||||
<Arg>.*/.*jsp-api-[^/]*\.jar$|.*/.*jsp-[^/]*\.jar$|.*/.*taglibs[^/]*\.jar$</Arg>
|
||||
</Call>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Configure the context deployer -->
|
||||
<!-- A context deployer will deploy contexts described in -->
|
||||
<!-- configuration files discovered in a directory. -->
|
||||
<!-- The configuration directory can be scanned for hot -->
|
||||
<!-- deployments at the configured scanInterval. -->
|
||||
<!-- -->
|
||||
<!-- This deployer is configured to deploy contexts configured -->
|
||||
<!-- in the $JETTY_HOME/contexts directory -->
|
||||
<!-- -->
|
||||
<!-- =========================================================== -->
|
||||
<Ref refid="DeploymentManager">
|
||||
<Call name="addAppProvider">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
|
||||
<Set name="monitoredDirName">$PLUGIN/contexts</Set>
|
||||
<Set name="scanInterval">120</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
<Set name="MinThreads">3</Set>
|
||||
<Set name="MaxThreads">10</Set>
|
||||
<Set name="MaxIdleTimeMs">60000</Set>
|
||||
<Set name="LowResourcePersistTimeMs">1000</Set>
|
||||
<Set name="ConfidentialPort">8443</Set>
|
||||
<Set name="IntegralPort">8443</Set>
|
||||
<Set name="PoolName">main</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</Call>
|
||||
</Ref>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Configure the webapp deployer. -->
|
||||
<!-- A webapp deployer will deploy standard webapps discovered -->
|
||||
<!-- in a directory at startup, without the need for additional -->
|
||||
<!-- configuration files. It does not support hot deploy or -->
|
||||
<!-- non standard contexts (see ContextDeployer above). -->
|
||||
<!-- -->
|
||||
<!-- This deployer is configured to deploy webapps from the -->
|
||||
<!-- $JETTY_HOME/webapps directory -->
|
||||
<!-- -->
|
||||
<!-- Normally only one type of deployer need be used. -->
|
||||
<!-- -->
|
||||
<!-- =========================================================== -->
|
||||
<Ref refid="DeploymentManager">
|
||||
<Call id="webappprovider" name="addAppProvider">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
|
||||
<Set name="monitoredDirName">$PLUGIN/eepsite/webapps</Set>
|
||||
<Set name="parentLoaderPriority">false</Set>
|
||||
<Set name="extractWars">false</Set>
|
||||
<Set name="defaultsDescriptor">$PLUGIN/etc/webdefault.xml</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</Ref>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Contexts -->
|
||||
<!-- =============================================================== -->
|
||||
<!-- =========================================================== -->
|
||||
<!-- Configure Authentication Realms -->
|
||||
<!-- Realms may be configured for the entire server here, or -->
|
||||
<!-- they can be configured for a specific web app in a context -->
|
||||
<!-- configuration (see $(jetty.home)/contexts/test.xml for an -->
|
||||
<!-- example). -->
|
||||
<!-- =========================================================== -->
|
||||
<!-- UNCOMMENT TO ACTIVATE
|
||||
<Set name="UserRealms">
|
||||
<Array type="org.eclipse.jetty.security.LoginService">
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.security.HashLoginService">
|
||||
<Set name="name">Test Realm</Set>
|
||||
<Set name="config">$PLUGIN/etc/realm.properties</Set>
|
||||
<Set name="refreshInterval">0</Set>
|
||||
</New>
|
||||
</Item>
|
||||
</Array>
|
||||
</Set>
|
||||
-->
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Configure Request Log -->
|
||||
<!-- Request logs may be configured for the entire server here, -->
|
||||
<!-- or they can be configured for a specific web app in a -->
|
||||
<!-- contexts configuration (see $(jetty.home)/contexts/test.xml -->
|
||||
<!-- for an example). -->
|
||||
<!-- =========================================================== -->
|
||||
<Ref refid="RequestLog">
|
||||
<Set name="requestLog">
|
||||
<New id="RequestLogImpl" class="net.i2p.jetty.I2PRequestLog">
|
||||
<Set name="filename">$PLUGIN/eepsite/logs/yyyy_mm_dd.request.log</Set>
|
||||
<Set name="filenameDateFormat">yyyy_MM_dd</Set>
|
||||
<Set name="retainDays">90</Set>
|
||||
<Set name="append">true</Set>
|
||||
<Set name="extended">false</Set>
|
||||
<Set name="logCookies">false</Set>
|
||||
<Set name="LogTimeZone">GMT</Set>
|
||||
</New>
|
||||
</Set>
|
||||
</Ref>
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a all web application within the webapps directory. -->
|
||||
<!-- + No virtual host specified -->
|
||||
<!-- + Look in the webapps directory relative to jetty.home or . -->
|
||||
<!-- + Use the default webdefault.xml in jetty's install -->
|
||||
<!-- + Upack the war file -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<Set name="rootWebApp">root</Set>
|
||||
<Call name="addWebApplications">
|
||||
<Arg></Arg>
|
||||
<Arg>$PLUGIN/eepsite/webapps/</Arg>
|
||||
<Arg></Arg>
|
||||
<Arg type="boolean">true</Arg>
|
||||
</Call>
|
||||
|
||||
<Call name="addContext">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.HttpContext">
|
||||
<Set name="contextPath">/</Set>
|
||||
<Set name="resourceBase">$PLUGIN/eepsite/docroot</Set>
|
||||
<Call name="addHandler">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.handler.ResourceHandler">
|
||||
<Set name="redirectWelcome">FALSE</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
<!-- This custom handler is like Jetty's ForwardHandler,
|
||||
- but it passes CGI query parameters through.
|
||||
- Note that it is somewhat misnamed - it does NOT
|
||||
- return a 301/302, it handles the request directly.
|
||||
-->
|
||||
<Call name="addHandler">
|
||||
<Arg>
|
||||
<New class="net.i2p.zzzot.QForwardHandler">
|
||||
<Call name="setHandleQueries">
|
||||
<Arg type="boolean">true</Arg>
|
||||
</Call>
|
||||
<!-- Forward announce requests to /tracker/announce.jsp -->
|
||||
<Call name="addForward">
|
||||
<Arg>/a</Arg>
|
||||
<Arg>/tracker/announce.jsp</Arg>
|
||||
</Call>
|
||||
<Call name="addForward">
|
||||
<Arg>/announce</Arg>
|
||||
<Arg>/tracker/announce.jsp</Arg>
|
||||
</Call>
|
||||
<Call name="addForward">
|
||||
<Arg>/announce.jsp</Arg>
|
||||
<Arg>/tracker/announce.jsp</Arg>
|
||||
</Call>
|
||||
<Call name="addForward">
|
||||
<Arg>/announce.php</Arg>
|
||||
<Arg>/tracker/announce.jsp</Arg>
|
||||
</Call>
|
||||
<!-- Forward scrape requests to /tracker/scrape.jsp -->
|
||||
<Call name="addForward">
|
||||
<Arg>/scrape</Arg>
|
||||
<Arg>/tracker/scrape.jsp</Arg>
|
||||
</Call>
|
||||
<Call name="addForward">
|
||||
<Arg>/scrape.jsp</Arg>
|
||||
<Arg>/tracker/scrape.jsp</Arg>
|
||||
</Call>
|
||||
<Call name="addForward">
|
||||
<Arg>/scrape.php</Arg>
|
||||
<Arg>/tracker/scrape.jsp</Arg>
|
||||
</Call>
|
||||
<!-- Forward Seedless requests to /tracker/seedless.jsp -->
|
||||
<Call name="addForward">
|
||||
<Arg>/Seedless</Arg>
|
||||
<Arg>/tracker/seedless.jsp</Arg>
|
||||
</Call>
|
||||
<Call name="addForward">
|
||||
<Arg>/Seedless/</Arg>
|
||||
<Arg>/tracker/seedless.jsp</Arg>
|
||||
</Call>
|
||||
<Call name="addForward">
|
||||
<Arg>/Seedless/index.jsp</Arg>
|
||||
<Arg>/tracker/seedless.jsp</Arg>
|
||||
</Call>
|
||||
<Call name="addForward">
|
||||
<Arg>/Seedless/seedless</Arg>
|
||||
<Arg>/tracker/seedless.jsp</Arg>
|
||||
</Call>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
<Call name="addContext">
|
||||
<Arg>/cgi-bin/*</Arg>
|
||||
<Set name="ResourceBase">$PLUGIN/eepsite/cgi-bin</Set>
|
||||
<Call name="addServlet">
|
||||
<Arg>Common Gateway Interface</Arg>
|
||||
<Arg>/</Arg>
|
||||
<Arg>org.mortbay.servlet.CGI</Arg>
|
||||
<Put name="Path">/usr/local/bin:/usr/ucb:/bin:/usr/bin</Put>
|
||||
</Call>
|
||||
</Call>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Request Log -->
|
||||
<!-- =============================================================== -->
|
||||
<Set name="RequestLog">
|
||||
<New class="org.mortbay.http.I2PRequestLog">
|
||||
<Arg>$PLUGIN/eepsite/logs/yyyy_mm_dd.request.log</Arg>
|
||||
<Set name="retainDays">30</Set>
|
||||
<Set name="append">true</Set>
|
||||
<Set name="extended">false</Set>
|
||||
<Set name="buffered">false</Set>
|
||||
<Set name="LogTimeZone">GMT</Set>
|
||||
</New>
|
||||
</Set>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Other Server Options -->
|
||||
<!-- =============================================================== -->
|
||||
<Set name="requestsPerGC">2000</Set>
|
||||
<Set name="statsOn">false</Set>
|
||||
<!-- =========================================================== -->
|
||||
<!-- extra options -->
|
||||
<!-- =========================================================== -->
|
||||
<Set name="stopAtShutdown">true</Set>
|
||||
<Set name="stopTimeout">1000</Set>
|
||||
|
||||
</Configure>
|
||||
|
BIN
scripts/favicon.png
Normal file
BIN
scripts/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -2,22 +2,31 @@ tunnel.0.description=ZzzOT
|
||||
tunnel.0.i2cpHost=127.0.0.1
|
||||
tunnel.0.i2cpPort=7654
|
||||
tunnel.0.name=zzzot
|
||||
tunnel.0.option.crypto.lowTagThreshold=4
|
||||
tunnel.0.option.crypto.tagsToSend=10
|
||||
tunnel.0.option.i2cp.destination.sigType=7
|
||||
tunnel.0.option.i2cp.enableAccessList=false
|
||||
tunnel.0.option.i2cp.encryptLeaseSet=false
|
||||
tunnel.0.option.i2cp.leaseSetEncType=4
|
||||
tunnel.0.option.i2cp.reduceIdleTime=1200000
|
||||
tunnel.0.option.i2cp.reduceOnIdle=true
|
||||
tunnel.0.option.i2cp.reduceQuantity=1
|
||||
tunnel.0.option.i2p.streaming.connectDelay=0
|
||||
tunnel.0.option.i2p.streaming.maxConcurrentStreams=80
|
||||
tunnel.0.option.i2p.streaming.maxConnsPerHour=300
|
||||
tunnel.0.option.i2p.streaming.maxConnsPerMinute=20
|
||||
tunnel.0.option.i2p.streaming.maxTotalConnsPerHour=50000
|
||||
tunnel.0.option.i2p.streaming.maxTotalConnsPerMinute=2000
|
||||
tunnel.0.option.inbound.backupQuantity=0
|
||||
tunnel.0.option.inbound.length=3
|
||||
tunnel.0.option.inbound.lengthVariance=0
|
||||
tunnel.0.option.inbound.nickname=ZzzOT
|
||||
tunnel.0.option.inbound.quantity=2
|
||||
tunnel.0.option.inbound.quantity=3
|
||||
tunnel.0.option.outbound.backupQuantity=0
|
||||
tunnel.0.option.outbound.length=3
|
||||
tunnel.0.option.outbound.lengthVariance=0
|
||||
tunnel.0.option.outbound.nickname=ZzzOT
|
||||
tunnel.0.option.outbound.quantity=2
|
||||
tunnel.0.option.outbound.quantity=3
|
||||
tunnel.0.privKeyFile=plugins/zzzot/eepPriv.dat
|
||||
tunnel.0.startOnLoad=true
|
||||
tunnel.0.targetHost=127.0.0.1
|
||||
|
@ -5,58 +5,93 @@
|
||||
# usage: makeplugin.sh plugindir
|
||||
#
|
||||
# zzz 2010-02
|
||||
# zzz 2014-08 added support for su3 files
|
||||
#
|
||||
|
||||
if [ -z "$I2P" -a -d "$PWD/../i2p.i2p/pkg-temp" ]; then
|
||||
export I2P=../i2p.i2p/pkg-temp
|
||||
fi
|
||||
|
||||
if [ ! -d "$I2P" ]; then
|
||||
echo "Can't locate your I2P installation. Please add a environment variable named I2P with the path to the folder as value"
|
||||
echo "On OSX this solved with running: export I2P=/Applications/i2p if default install directory is used."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PUBKEYDIR=$HOME/.i2p-plugin-keys
|
||||
PUBKEYFILE=$PUBKEYDIR/plugin-public-signing.key
|
||||
PRIVKEYFILE=$PUBKEYDIR/plugin-private-signing.key
|
||||
B64KEYFILE=$PUBKEYDIR/plugin-public-signing.txt
|
||||
export I2P=../i2p/pkg-temp
|
||||
PUBKEYSTORE=$PUBKEYDIR/plugin-su3-public-signing.crt
|
||||
PRIVKEYSTORE=$PUBKEYDIR/plugin-su3-keystore.ks
|
||||
KEYTYPE=RSA_SHA512_4096
|
||||
|
||||
PLUGINDIR=${1:-plugin}
|
||||
|
||||
PC=plugin.config
|
||||
PCT=${PC}.tmp
|
||||
|
||||
if [ ! -f $PRIVKEYFILE ]
|
||||
then
|
||||
mkdir -p $PUBKEYDIR
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate keygen $PUBKEYFILE $PRIVKEYFILE || exit 1
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.data.Base64 encode $PUBKEYFILE $B64KEYFILE || exit 1
|
||||
rm -rf logs/
|
||||
chmod 444 $PUBKEYFILE $B64KEYFILE
|
||||
chmod 400 $PRIVKEYFILE
|
||||
echo "Created new keys: $PUBKEYFILE $PRIVKEYFILE"
|
||||
fi
|
||||
|
||||
rm -f plugin.zip
|
||||
if [ ! -d $PLUGINDIR ]
|
||||
then
|
||||
echo "You must have a $PLUGINDIR directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OPWD=$PWD
|
||||
cd $PLUGINDIR
|
||||
|
||||
if [ ! -f $PC ]
|
||||
if [ ! -f $PLUGINDIR/$PC ]
|
||||
then
|
||||
echo "You must have a $PC file"
|
||||
echo "You must have a $PLUGINDIR/$PC file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep -q '^signer=' $PC
|
||||
SIGNER=`grep '^signer=' $PLUGINDIR/$PC`
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a signer in $PC"
|
||||
echo 'For example signer=joe@mail.i2p'
|
||||
echo "You must have a plugin name in $PC"
|
||||
echo 'For example name=foo'
|
||||
exit 1
|
||||
fi
|
||||
SIGNER=`echo $SIGNER | cut -f 2 -d '='`
|
||||
|
||||
if [ ! -f $PRIVKEYFILE ]
|
||||
then
|
||||
echo "Creating new XPI2P DSA keys"
|
||||
mkdir -p $PUBKEYDIR || exit 1
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate keygen $PUBKEYFILE $PRIVKEYFILE || exit 1
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.data.Base64 encode $PUBKEYFILE $B64KEYFILE || exit 1
|
||||
rm -rf logs/
|
||||
chmod 444 $PUBKEYFILE $B64KEYFILE
|
||||
chmod 400 $PRIVKEYFILE
|
||||
echo "Created new XPI2P keys: $PUBKEYFILE $PRIVKEYFILE"
|
||||
fi
|
||||
|
||||
if [ ! -f $PRIVKEYSTORE ]
|
||||
then
|
||||
echo "Creating new SU3 $KEYTYPE keys for $SIGNER"
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File keygen -t $KEYTYPE $PUBKEYSTORE $PRIVKEYSTORE $SIGNER || exit 1
|
||||
echo '*** Save your password in a safe place!!! ***'
|
||||
rm -rf logs/
|
||||
# copy to the router dir so verify will work
|
||||
CDIR=$I2P/certificates/plugin
|
||||
mkdir -p $CDIR || exit 1
|
||||
CFILE=$CDIR/`echo $SIGNER | sed s/@/_at_/`.crt
|
||||
cp $PUBKEYSTORE $CFILE
|
||||
chmod 444 $PUBKEYSTORE
|
||||
chmod 400 $PRIVKEYSTORE
|
||||
chmod 644 $CFILE
|
||||
echo "Created new SU3 keys: $PUBKEYSTORE $PRIVKEYSTORE"
|
||||
echo "Copied public key to $CFILE for testing"
|
||||
fi
|
||||
|
||||
rm -f plugin.zip
|
||||
|
||||
OPWD=$PWD
|
||||
cd $PLUGINDIR
|
||||
|
||||
grep -q '^name=' $PC
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a plugin name in $PC"
|
||||
echo 'For example name=foo'
|
||||
echo 'For example name=foo'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -64,7 +99,7 @@ grep -q '^version=' $PC
|
||||
if [ "$?" -ne "0" ]
|
||||
then
|
||||
echo "You must have a version in $PC"
|
||||
echo 'For example version=0.1.2'
|
||||
echo 'For example version=0.1.2'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -72,33 +107,41 @@ fi
|
||||
grep -v '^date=' $PC > $PCT
|
||||
DATE=`date '+%s000'`
|
||||
echo "date=$DATE" >> $PCT
|
||||
mv $PCT $PC
|
||||
mv $PCT $PC || exit 1
|
||||
|
||||
# add our Base64 key
|
||||
grep -v '^key=' $PC > $PCT
|
||||
B64KEY=`cat $B64KEYFILE`
|
||||
echo "key=$B64KEY" >> $PCT || exit 1
|
||||
mv $PCT $PC
|
||||
mv $PCT $PC || exit 1
|
||||
|
||||
# zip it
|
||||
zip -r $OPWD/plugin.zip * -x \*.jar || exit 1
|
||||
zip -r $OPWD/plugin.zip * || exit 1
|
||||
|
||||
# get the version and use it for the sud header
|
||||
VERSION=`grep '^version=' $PC | cut -f 2 -d '='`
|
||||
# get the name and use it for the file name
|
||||
NAME=`grep '^name=' $PC | cut -f 2 -d '='`
|
||||
XPI2P=${NAME}.xpi2p
|
||||
SU3=${NAME}.su3
|
||||
cd $OPWD
|
||||
|
||||
# sign it
|
||||
echo 'Signing. ...'
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign plugin.zip $XPI2P $PRIVKEYFILE $VERSION || exit 1
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File sign -c PLUGIN -t $KEYTYPE plugin.zip $SU3 $PRIVKEYSTORE $VERSION $SIGNER || exit 1
|
||||
rm -f plugin.zip
|
||||
|
||||
# verify
|
||||
echo 'Verifying. ...'
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion $XPI2P || exit 1
|
||||
java -cp $I2P/lib/i2p.jar -Drouter.trustedUpdateKeys=$B64KEY net.i2p.crypto.TrustedUpdate verifysig $XPI2P || exit 1
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File showversion $SU3 || exit 1
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File verifysig -k $PUBKEYSTORE $SU3 || exit 1
|
||||
rm -rf logs/
|
||||
|
||||
echo -n 'Plugin created: '
|
||||
echo 'Plugin files created: '
|
||||
wc -c $XPI2P
|
||||
wc -c $SU3
|
||||
|
||||
exit 0
|
||||
|
@ -1,8 +1,14 @@
|
||||
name=zzzot
|
||||
signer=zzz-plugin@mail.i2p
|
||||
consoleLinkName=ZzzOT
|
||||
consoleLinkURL=http://127.0.0.1:7662/tracker/index.jsp
|
||||
description=Open tracker
|
||||
consoleLinkURL=http://127.0.0.1:7662/tracker/
|
||||
consoleLinkTooltip=BitTorrent OpenTracker
|
||||
icon-code=iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABm1BMVEUAAAAZGRkZGRkaGhoZGRkZGRkZGRkZGRkYGBgZGRkYGBgZGRkYGBgZGRkZGRkXFxcYGBgZGRkZGRkZGRkZGRkAAAAZGRnziRYTExPzfhbzbhbzdxbzgxbzlRYLCwvzjhbzcxbMzMzzkBb~~~~~~Pl0dHRdXV3GxsYnJyfe3t7OycbBwcGcnJyHh4cjEgP39~f96dSurq5~f397e3tra2tlZWU7OzseHh7haRSGSAsOBwGpqKeWlpbgpnRUVFRNTU00NDTxmynPbRJwNgpXLwdEJQY4IAUVCwLy8vL83sK7u7uysrL7yqf6v5Tdr4H4t3H4rGz2pEvnjUnqnkQvLy~zii30gyjxdCLqdhXLexK9bhGQTQ1-QAtlLgn-9u~Pxr7Uvqr71KbYuJz6yZn5v4T3pVv3s1rkmVr1nzr1kzLyjiPzex~pbBXJYxLJXRK~XxG4aBCYRQ5iNQlMKgctGgT7-~vas4zhnGj2qlNERETlhRTefxTedRTVfxOsZhC0YBC4UhCpWw9wQgr4sH~kqF7sizVpSyfWXxOZVw56Yx8pAAAAFXRSTlMAjPQOr8vV7eVwaVtPNyUa27WCwoZvol0xAAAEx0lEQVRYw72XZ1PbQBCGXQEnVMNJAjtyjpPcbWxsMB2SQOg1tCT0EiA9kN578rOzd4eQZUuO8iXPjGfkmdv3dm-v7DqsaKht8XhrBKDG6~HXNjj-iTpnlVBClbPOrrXb18htJElmSNK5hs9tx9zp4saCXOgOq6qaH-qWkCQzEZf~rxKXqvnU0c6pCUKIohDKREKNckeqL1U0r~cyc6kzRpTJg8OTILBysji~nyEkpnKJ5npr-8suZp7sUHbvXTFybX6SvEhKVMJ12creSaeXUx1bB6utJqzsKx0pthZOc3sPnX4kpsw-uNozmr5xtZzVGRIboU54zOyb6PQ5kjkWxbdphNBd0YTFDFGpE03m88v9ZD8Ew04R8Fw0I7RLsrKZD05qH1EWqKePEUNz~GGPIY4FMkUVnCXrT-2nlMNWyhhirLM~66MIpZ-0FnGoJKjCZUP-XWCfJTx3txHnNvt3hoBxQ0rvKf2g4CreD83ggErmg4xRxPkUBE7ZZ1~QwALJgQveov0L9iNkpo3Rg855Bn82-Odmm5FZMgIKF7vaXQ0BxDIil7-rCWzo39eDRsRMDIKo1k6WHxxIkcV2TloTGG3~iDjP20s5VlJ6JtywglLHTIDzCHHo2mtfvYEyZjskWEfugg8cSCqrAc4NdEGf9tFTLvBgKwku-JhAFazAi93QOeOojLGQCQfUhUZ2~7EUnoicp6icL6IJ95VOcKGOb2I5NqklaBOZkN589ritlJmYDMvII4iSBW2f9iErxt63GlgkURZDAzigkmtXOHeQNaNXjLAYGhy1sASJjCZ7iiqwbnRhcgoup1pHiyCgiVktMMsI-N428G0C0UVogl1E5kWOdQQmV8xrIkhwsXgFqZu8DnG-oop8CBVzTLolOJI1gjxEVq5yxlFFjBdtgIRlocYBSciTVf0cVOTs7qOAjqLCTnCwLLZzepENbn79-JYP39IEwttBThrZ5IwNx2FNIM7z8gvZ5jMMX8NDIEAXcRgHGbYi0O~LFfwGFpGmcRnznTyGbMJv7Ft4mabRAxsJL7VSxpFtrsPwIww3QhM9zSjxir1AyD70sRpMwFZuYYdpYI~vY~uIwE5SpoepAQS64iLw-98E7rMsQvnXKEgFvAQubSLb9MHwI1yQhKrzKy37EhblJrJNLwzfmz6~0upYDJDIPmSTsQ-0aGIRsOKzkVYGg8H3yB7jX4KUOSzzCPjD0rm99hnZ4sbDNspaPAcO-PSnLTLYa-ckbmjP7FxEpk-bXt7k8c-~GffeCV1wC-fBAX~x8z7dX3HmJ-uGl3Eny5734gIjilXruAMlzOEoLzA0vDSVeBhZ8DRk5DtLYXNpkZWKdFsJiAZu4RQrssrKvGTCQsH4si7FB1iZZ1JoDuBhq9OvcxRPskLTtNRN4Zz5~aMzx~wXPBbFdhj3Ryt58GMHd-nFtpkP0SzOSWX3TxtnbRBPR1m5X6nhyEciaonEO17UDG5H8rzhqNjyyHIO44FhuViAbt6X8Uhn5ZYHqG8WmER4GuN-dWhEYq4Ull7txXG2izdd3nobbZ8sF8LJBGZE4JcY6CrYavsAt98lcA0kLb8ZCofDw8sSor4DLqfbVutbZd76Nvrc~6P51tt~v97-t1i3~38A57d5M52iLPcAAAAASUVORK5CYII=
|
||||
description=BitTorrent OpenTracker
|
||||
author=zzz
|
||||
updateURL=http://stats.i2p/i2p/plugins/zzzot-update.xpi2p
|
||||
updateURL.su3=http://stats.i2p/i2p/plugins/zzzot-update.su3
|
||||
websiteURL=http://zzz.i2p/forums/16
|
||||
license=Apache 2.0
|
||||
min-jetty-version=9
|
||||
min-i2p-version=2.9.0
|
||||
|
235
scripts/tracker-purple.css
Normal file
235
scripts/tracker-purple.css
Normal file
@ -0,0 +1,235 @@
|
||||
/* ZZZOT OpenTracker theme (purrrrple) */
|
||||
/* Author: dr|z3d 2019 */
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100%;
|
||||
color: #fef;
|
||||
background: #111;
|
||||
font-size: 14pt;
|
||||
font-family: "Droid Sans", "Open Sans", "Noto Sans", Ubuntu, "Segoe UI", "Lucida Grande", Verdana, Helvetica, sans-serif;
|
||||
|
||||
}
|
||||
|
||||
@supports (background-blend-mode: overlay) {
|
||||
html, body {
|
||||
background: repeating-linear-gradient(45deg, #313, #000 2px, #000 3px), repeating-linear-gradient(135deg, #414, #313 2px, #212 3px);
|
||||
background-blend-mode: overlay, normal;
|
||||
background-size: 100% 100%, 100% 100%;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
#container {
|
||||
padding: 2%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: table;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#panel {
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
border: 1px solid #535;
|
||||
box-shadow: inset 0 0 0 1px #111, inset 0 0 2px 1px #444, 0 0 2px 2px #000;
|
||||
background: #180618;
|
||||
background: repeating-linear-gradient(to right, rgba(255, 200, 255, .05), rgba(0, 0, 0, .08) 2px), repeating-linear-gradient(to bottom, #212, #101 2px); /* purple */
|
||||
background-blend-mode: overlay;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Droid Sans Mono", "Noto Mono", "DejaVu Sans Mono", "Lucida Console", monospace;
|
||||
}
|
||||
|
||||
#sitename, #sitename:hover, #sitename:focus {
|
||||
margin: 0 auto;
|
||||
font-size: 10em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
line-height: 1;
|
||||
letter-spacing: .05em;
|
||||
transition: ease background .3s;
|
||||
color: #731;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#stats #sitename, #stats #sitename:hover, #stats #sitename:focus {
|
||||
font-size: 8em;
|
||||
}
|
||||
|
||||
#sitename:hover, #sitename:focus {
|
||||
color: #951;
|
||||
}
|
||||
|
||||
@supports (-webkit-text-stroke-width: 1px) {
|
||||
#sitename, #sitename:hover, #sitename:focus {
|
||||
background: #731;
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, .5) 2px), linear-gradient(to bottom, rgba(255, 96, 0, .2), rgba(0, 0, 0, .9) 100%), linear-gradient(to bottom, #414, #313 15%, #fff 50%, #313 80%);
|
||||
-moz-background-clip: text !important;
|
||||
-webkit-background-clip: text !important;
|
||||
background-clip: text !important;
|
||||
-moz-text-fill-color: transparent !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
text-fill-color: transparent !important;
|
||||
filter: hue-rotate(0) drop-shadow(0 0 2px #181818) drop-shadow(0 3px 0.01em #000);
|
||||
-webkit-text-stroke-color: #fef;
|
||||
-webkit-text-stroke-width: 0.02em;
|
||||
animation: ease-in-out spinwash 120s alternate infinite;
|
||||
mix-blend-mode: soft-light;
|
||||
}
|
||||
|
||||
#sitename:hover, #sitename:focus {
|
||||
background: #951;
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, .3) 2px), linear-gradient(to bottom, rgba(255, 255, 255, .2), rgba(0, 0, 0, .95)), linear-gradient(to bottom, #d59, #a39 15%, #fff 50%, #839 70%, #000);
|
||||
filter: drop-shadow(0 0 0.02em #f00);
|
||||
-webkit-text-stroke-color: #fef;
|
||||
opacity: .5;
|
||||
mix-blend-mode: normal;
|
||||
background-blend-mode: overlay;
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinwash {
|
||||
from {
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, .3) 2px), linear-gradient(45deg, rgba(64,16,64,.1), rgba(16,0,16,.2)), linear-gradient(to bottom, rgba(255, 255, 255, .2), rgba(0, 0, 0, .7) 100%), linear-gradient(to bottom, #930, #d50 15%, #fff 50%, #930 80%, #000 100%, #ff0 150%);
|
||||
filter: sepia(0) hue-rotate(0) drop-shadow(0 0 2px #181818) drop-shadow(0 3px 0.01em #000);
|
||||
}
|
||||
to {
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, .3) 2px), linear-gradient(45deg, rgba(16,0,16,.1), rgba(64,16,64,.2)), linear-gradient(to bottom, rgba(255, 255, 255, .2), rgba(0, 0, 0, .7) 100%), linear-gradient(to bottom, #930, #d50 15%, #fff 50%, #930 80%, #000 100%, #ff0 150%);
|
||||
filter: sepia(0) hue-rotate(1440deg) drop-shadow(0 0 2px #181818) drop-shadow(0 3px 0.01em #000);
|
||||
}
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
color: #f60;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
color: #f90;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 20px 10px;
|
||||
height: 1px;
|
||||
color: transparent;
|
||||
border: none;
|
||||
background: #555;
|
||||
background: linear-gradient(to right, rgba(0, 0, 0, 0), #535, rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
::selection,
|
||||
::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #515;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#totals {
|
||||
padding-top: 6px;
|
||||
line-height: 140%;
|
||||
text-shadow: 0 1px 1px #000;
|
||||
}
|
||||
|
||||
#footer {
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
font-size: 11pt;
|
||||
font-weight: bold;
|
||||
border-top: 1px solid #535;
|
||||
box-shadow: inset 0 0 0 1px #000, inset 0 0 2px 1px #333;
|
||||
background: rgba(16, 0, 16, .5);
|
||||
-moz-user-select: all;
|
||||
-webkit-user-select: all;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
#footer.version {
|
||||
font-size: 9.5pt;
|
||||
}
|
||||
|
||||
#footer.version a {
|
||||
margin: 0 1px 0 2px;
|
||||
}
|
||||
|
||||
#footer.version::before {
|
||||
margin-left: -10px;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
margin-top: 1px;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
background: url(/favicon.png) left top no-repeat;
|
||||
background-size: 16px 16px;
|
||||
filter: hue-rotate(-110deg) saturate(.4) brightness(1.4);
|
||||
}
|
||||
|
||||
#initializing::before {
|
||||
margin-left: -20px;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 24px;
|
||||
vertical-align: text-bottom;
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABRhJREFUeNq0Vd1vk1UYP+f9bruPrmu70W1lrLKJzE0o+BEFt4yLRZJpgl4YZ6IXhiuT/Slcqlw6QyIauNEYIhHlAgMrJDjUDQejdF3Xj7Xv24/3+xyf01LWOpArTvLr6XnOOc/X7znPiyml6HkOgf28+cXDxwLMcYgDsBljHAS8AeJjgDFAGMAByoAk4Ddw8GdAihKCmLMcz7N7s7CXu/JpOCE8xbAXMC0KaGYwLMeG9yj7ujuEPkXmAiDnDZNYasUx1jeNqVTWOG3Z6CLIzwK2AfNgaOGRniNPMtADB+ZiEXl8Yn9nTOD4AcMgnXoVydUy4R4FKXtFQT482tU98YIvcvtuuf+fhzUW6RXiuvMfn4zEv/4hnXicIhA209MlYDrz6rg/Gu33BlWViLbtPDW/lQpBoojlw2Pdw8Fu0Xd9uRh9/8TAIU0jiDjODgcuLCBvEqY0fnQyEOzrUXybmzZqFgDsMUWI53F97boU2Tat71sWQrUaRn0BJXRqJhJKpy3U3S3AGXfHAAGCQEn0xeFOPyj3bm5a9ctMsSxjkteqhdRWNV8qWyxFnL9TUgb7fIFgl7fXNCnPzqbTO5FacMxtjQBSJMsSHz4w4vekUnrdQ6Zc8WDnxp+5B4WSsQqCH+HoHRZQxrQnMrnKh71+5fgro6GYoVOptdwtC7cbcGy7JzbUIWsli9drdqOMfDxZupNNbavGZSi975nBJl9w+VdYD23l7COel/sktWi0cWOZHNPZxkHXUL9Pym2V4T+lmMNORrdT2Xx5GRTVlZt6DYJohM7z/DwsFk4eG4kn10u7yDcNuisCiSOE08t19/OixOfXN0oaHPqdUoKqmlo/LCkeNs06jrMwNx2Lq0WoXWl3dVHi/CcCWDimqVpGTW2sOVRUa1XXJduu20KeriNBFHMs3xcu/Z14RpdItEbw+cXLq/Fn9RVK6GkovwSk7EhTBpyIHIdfOjDY6WvKIgM96NK1da2Vg/hHHxz6XwPfnL/FPAoxkiVFqZMNqQKjJNDhk4dtjLtY9cGwq7arurajtUaAdIugm8vpXYoPj0fQt98tMa/PQBP8yTZNVCmVkCBJjGz2+o+HwsFxmxd4m7oFxmFZtwnotNo4qDoUGbzYpnxyfxCdO3ctAX6dgeWiY1moJTUICvdUj987HR7slZPZSg7xHPbKAtpIFyzISksEEGqFYKQL8mMFPkVAEJTlD/i8+az6FqSC9fTbjArAQSD6HdgbfW3qoLyypRead30y56aSOdOxnWJbBJrLI13cMaBDKyEqlV6fjY9m7m323l/deLuU19iLIv5gF9k3OoD7R/aQv7JmfhvuIpFHHTKP0itrulHVs5BSs63ZlVHDQCwgoZTmINMhaAMyks+4/N7IQDg+ujfcIXH17FQsUs5UnOLVjF0wHZDBPVngkJHZqKVXH5SAl2Tz5TcMQD41JKJQQEQ3Fy/c6j06OYijwyGd8cLyojKw7ui2MAQKMLwyoM0jQO9JrldTN/7IU1bGlFqo1QDr3RUsoNTZxQRsLG5dvT7lGytMipMTewzFK0MwT/7egvOKUbP1pdv56spaEu5eBu815O44gtmrFN/9aulR42eluAiyAJD6GZbl9+ThaB8fGwlQf49CPR7WGFys69u4VNxy1+7dN9eTa9Q0meJfADX2uWsO4/wnDQPC3JdxmEOsztlHm8lYGcI8COdOAI4DooBOFjAgC1gBXAVcg3znucabQE808DzHvwIMALq5vhJjsCXwAAAAAElFTkSuQmCC") center center no-repeat;
|
||||
background-size: 16px 16px;
|
||||
filter: hue-rotate(90deg) saturate(.6);
|
||||
animation: spin linear 3s forwards infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0)
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 600px) {
|
||||
#container {
|
||||
padding: 1%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
html, body {
|
||||
font-size: 12pt;
|
||||
}
|
||||
#container {
|
||||
padding: 1%;
|
||||
}
|
||||
#sitename, #sitename:hover, #sitename:focus {
|
||||
font-size: 8em;
|
||||
}
|
||||
#stats #sitename, #stats #sitename:hover, #stas #sitename:focus {
|
||||
font-size: 6em;
|
||||
}
|
||||
#footer.b32 {
|
||||
font-size: 10pt;
|
||||
}
|
||||
#footer.version {
|
||||
font-size: 8.5pt;
|
||||
}
|
||||
#initializing::before {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
247
scripts/tracker.css
Normal file
247
scripts/tracker.css
Normal file
@ -0,0 +1,247 @@
|
||||
/* ZZZOT OpenTracker theme */
|
||||
/* Author: dr|z3d 2019 */
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100%;
|
||||
color: #bbb;
|
||||
background: #111;
|
||||
font-size: 14pt;
|
||||
font-family: "Droid Sans", "Open Sans", "Noto Sans", Ubuntu, "Segoe UI", "Lucida Grande", Verdana, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@supports (background-blend-mode: overlay) {
|
||||
html, body {
|
||||
background: repeating-linear-gradient(45deg, #333, #111 2px, #111 3px),
|
||||
repeating-linear-gradient(135deg, #444, #333 2px, #222 3px);
|
||||
background-blend-mode: overlay, normal;
|
||||
background-size: 100% 100%, 100% 100%;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
#container {
|
||||
padding: 2%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: table;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#panel {
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
border: 1px solid #555;
|
||||
box-shadow: inset 0 0 0 1px #111, inset 0 0 2px 1px #444, 0 0 2px 2px #000;
|
||||
background: #181818;
|
||||
background: repeating-linear-gradient(to right, rgba(255, 255, 255, .05), rgba(0, 0, 0, .08) 2px) center center / 2px 100%,
|
||||
repeating-linear-gradient(to bottom, #222, #111 2px) center center / 100% 2px;
|
||||
background-blend-mode: overlay;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Droid Sans Mono", "Noto Mono", "DejaVu Sans Mono", "Lucida Console", monospace;
|
||||
}
|
||||
|
||||
#sitename, #sitename:hover, #sitename:focus {
|
||||
margin: 0 auto;
|
||||
font-size: 10em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
line-height: 1;
|
||||
letter-spacing: .05em;
|
||||
transition: ease background .3s;
|
||||
color: #731;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#stats #sitename, #stats #sitename:hover, #stats #sitename:focus {
|
||||
font-size: 8em;
|
||||
}
|
||||
|
||||
#sitename:hover, #sitename:focus {
|
||||
color: #951;
|
||||
}
|
||||
|
||||
@supports (-webkit-text-stroke-width: 1px) {
|
||||
#sitename, #sitename:hover, #sitename:focus {
|
||||
background: #731;
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, .5) 2px),
|
||||
linear-gradient(to bottom, rgba(255, 96, 0, .2), rgba(0, 0, 0, .9) 100%),
|
||||
linear-gradient(to bottom, #210, #310 15%, #fff 50%, #310 80%);
|
||||
-moz-background-clip: text !important;
|
||||
-webkit-background-clip: text !important;
|
||||
background-clip: text !important;
|
||||
-moz-text-fill-color: transparent !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
text-fill-color: transparent !important;
|
||||
filter: drop-shadow(0 0 2px #000);
|
||||
-webkit-text-stroke-color: #999;
|
||||
-webkit-text-stroke-width: 0.02em;
|
||||
animation: ease-in-out spinwash 60s 15s forwards infinite;
|
||||
}
|
||||
|
||||
#sitename:hover, #sitename:focus {
|
||||
background: #951;
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, .3) 2px),
|
||||
linear-gradient(to bottom, rgba(255, 255, 255, .2), rgba(0, 0, 0, .7) 100%),
|
||||
linear-gradient(to bottom, #930, #d50 15%, #fff 50%, #930 80%, #000 100%, #ff0 150%);
|
||||
filter: drop-shadow(0 0 2px #b00);
|
||||
-webkit-text-stroke-color: #bbb;
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinwash {
|
||||
from {
|
||||
filter: hue-rotate(0) drop-shadow(0 0 2px #000);
|
||||
}
|
||||
to {
|
||||
filter: hue-rotate(360deg) drop-shadow(0 0 2px #000);
|
||||
}
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
font-weight: bold;
|
||||
color: #c4ad9d;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
color: #e88b44;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #f60;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 20px 10px;
|
||||
height: 1px;
|
||||
color: transparent;
|
||||
border: none;
|
||||
background: #555;
|
||||
background: linear-gradient(to right, rgba(0,0,0,0) 35%, rgba(255,255,255,.3), rgba(0,0,0,0) 65%),
|
||||
linear-gradient(to right, rgba(0, 0, 0, 0), #605555, rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
::selection,
|
||||
::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #431;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#totals {
|
||||
padding-top: 6px;
|
||||
line-height: 140%;
|
||||
text-shadow: 0 1px 1px #000;
|
||||
}
|
||||
|
||||
#footer {
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
font-size: 11pt;
|
||||
font-weight: bold;
|
||||
border-top: 1px solid #555;
|
||||
box-shadow: inset 0 0 0 1px #000, inset 0 0 2px 1px #333;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
-moz-user-select: all;
|
||||
-webkit-user-select: all;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
#footer.version {
|
||||
padding: 4px;
|
||||
line-height: 19px;
|
||||
font-size: 9.5pt;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#footer.version a {
|
||||
margin: 0 1px 0 2px;
|
||||
}
|
||||
|
||||
#footer.version::before {
|
||||
margin-left: -10px;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
margin-top: 1px;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
background: url(/favicon.png) left top no-repeat;
|
||||
background-size: 16px 16px;
|
||||
mix-blend-mode: luminosity;
|
||||
}
|
||||
|
||||
#initializing::before {
|
||||
margin-left: -20px;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 24px;
|
||||
vertical-align: text-bottom;
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABRhJREFUeNq0Vd1vk1UYP+f9bruPrmu70W1lrLKJzE0o+BEFt4yLRZJpgl4YZ6IXhiuT/Slcqlw6QyIauNEYIhHlAgMrJDjUDQejdF3Xj7Xv24/3+xyf01LWOpArTvLr6XnOOc/X7znPiyml6HkOgf28+cXDxwLMcYgDsBljHAS8AeJjgDFAGMAByoAk4Ddw8GdAihKCmLMcz7N7s7CXu/JpOCE8xbAXMC0KaGYwLMeG9yj7ujuEPkXmAiDnDZNYasUx1jeNqVTWOG3Z6CLIzwK2AfNgaOGRniNPMtADB+ZiEXl8Yn9nTOD4AcMgnXoVydUy4R4FKXtFQT482tU98YIvcvtuuf+fhzUW6RXiuvMfn4zEv/4hnXicIhA209MlYDrz6rg/Gu33BlWViLbtPDW/lQpBoojlw2Pdw8Fu0Xd9uRh9/8TAIU0jiDjODgcuLCBvEqY0fnQyEOzrUXybmzZqFgDsMUWI53F97boU2Tat71sWQrUaRn0BJXRqJhJKpy3U3S3AGXfHAAGCQEn0xeFOPyj3bm5a9ctMsSxjkteqhdRWNV8qWyxFnL9TUgb7fIFgl7fXNCnPzqbTO5FacMxtjQBSJMsSHz4w4vekUnrdQ6Zc8WDnxp+5B4WSsQqCH+HoHRZQxrQnMrnKh71+5fgro6GYoVOptdwtC7cbcGy7JzbUIWsli9drdqOMfDxZupNNbavGZSi975nBJl9w+VdYD23l7COel/sktWi0cWOZHNPZxkHXUL9Pym2V4T+lmMNORrdT2Xx5GRTVlZt6DYJohM7z/DwsFk4eG4kn10u7yDcNuisCiSOE08t19/OixOfXN0oaHPqdUoKqmlo/LCkeNs06jrMwNx2Lq0WoXWl3dVHi/CcCWDimqVpGTW2sOVRUa1XXJduu20KeriNBFHMs3xcu/Z14RpdItEbw+cXLq/Fn9RVK6GkovwSk7EhTBpyIHIdfOjDY6WvKIgM96NK1da2Vg/hHHxz6XwPfnL/FPAoxkiVFqZMNqQKjJNDhk4dtjLtY9cGwq7arurajtUaAdIugm8vpXYoPj0fQt98tMa/PQBP8yTZNVCmVkCBJjGz2+o+HwsFxmxd4m7oFxmFZtwnotNo4qDoUGbzYpnxyfxCdO3ctAX6dgeWiY1moJTUICvdUj987HR7slZPZSg7xHPbKAtpIFyzISksEEGqFYKQL8mMFPkVAEJTlD/i8+az6FqSC9fTbjArAQSD6HdgbfW3qoLyypRead30y56aSOdOxnWJbBJrLI13cMaBDKyEqlV6fjY9m7m323l/deLuU19iLIv5gF9k3OoD7R/aQv7JmfhvuIpFHHTKP0itrulHVs5BSs63ZlVHDQCwgoZTmINMhaAMyks+4/N7IQDg+ujfcIXH17FQsUs5UnOLVjF0wHZDBPVngkJHZqKVXH5SAl2Tz5TcMQD41JKJQQEQ3Fy/c6j06OYijwyGd8cLyojKw7ui2MAQKMLwyoM0jQO9JrldTN/7IU1bGlFqo1QDr3RUsoNTZxQRsLG5dvT7lGytMipMTewzFK0MwT/7egvOKUbP1pdv56spaEu5eBu815O44gtmrFN/9aulR42eluAiyAJD6GZbl9+ThaB8fGwlQf49CPR7WGFys69u4VNxy1+7dN9eTa9Q0meJfADX2uWsO4/wnDQPC3JdxmEOsztlHm8lYGcI8COdOAI4DooBOFjAgC1gBXAVcg3znucabQE808DzHvwIMALq5vhJjsCXwAAAAAElFTkSuQmCC") center center no-repeat;
|
||||
background-size: 16px 16px;
|
||||
filter: hue-rotate(180deg) saturate(.6);
|
||||
animation: spin linear 3s forwards infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0)
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 600px) {
|
||||
#container {
|
||||
padding: 1%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
html, body {
|
||||
font-size: 12pt;
|
||||
}
|
||||
#container {
|
||||
padding: 1%;
|
||||
}
|
||||
#sitename, #sitename:hover, #sitename:focus {
|
||||
font-size: 8em;
|
||||
}
|
||||
#stats #sitename, #stats #sitename:hover, #stas #sitename:focus {
|
||||
font-size: 6em;
|
||||
}
|
||||
#footer.b32 {
|
||||
font-size: 10pt;
|
||||
}
|
||||
#footer.version {
|
||||
margin-top: 0;
|
||||
line-height: 18px;
|
||||
font-size: 8.5pt;
|
||||
}
|
||||
#initializing::before {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
25
scripts/zzzot.config
Normal file
25
scripts/zzzot.config
Normal file
@ -0,0 +1,25 @@
|
||||
#
|
||||
# All changes require plugin restart
|
||||
#
|
||||
# announce interval in seconds
|
||||
# minimum 900 (15 minutes), maximum 21600 (6 hours)
|
||||
interval=1620
|
||||
#
|
||||
# Enable UDP announces
|
||||
# default false
|
||||
udp=false
|
||||
#
|
||||
# UDP connection lifetime in seconds
|
||||
# minimum 60 (1 minute), maximum 21600 (6 hours)
|
||||
lifetime=1200
|
||||
#
|
||||
#
|
||||
# UDP announce port
|
||||
# default 6969
|
||||
port=6969
|
||||
#
|
||||
showfoooter=true
|
||||
#footerText=your html text here
|
||||
#
|
||||
# default false as of 0.19.0
|
||||
#allowFullScrape=false
|
@ -3,18 +3,18 @@
|
||||
<property name="i2pbase" value="../../i2p.i2p"/>
|
||||
<property name="i2plib" value="${i2pbase}/build"/>
|
||||
<property name="jettylib" value="${i2pbase}/apps/jetty/jettylib"/>
|
||||
<property name="war" value="../plugin/eepsite/webapps/blog"/>
|
||||
<property name="lib" value="${war}/WEB-INF/lib"/>
|
||||
<path id="cp">
|
||||
<pathelement path="${java.class.path}" />
|
||||
<pathelement location="${i2plib}/i2p.jar" />
|
||||
<pathelement location="${i2plib}/i2ptunnel.jar" />
|
||||
<pathelement location="${i2plib}/i2psnark.jar" />
|
||||
<pathelement location="${jettylib}/ant.jar"/>
|
||||
<pathelement location="${i2plib}/mstreaming.jar" />
|
||||
<pathelement location="${ant.home}/lib/ant.jar"/>
|
||||
<pathelement location="${jettylib}/org.mortbay.jetty.jar"/>
|
||||
<pathelement location="${jettylib}/jasper-compiler.jar" />
|
||||
<pathelement location="${jettylib}/jasper-runtime.jar" />
|
||||
<pathelement location="${jettylib}/javax.servlet.jar" />
|
||||
<pathelement location="${jettylib}/jetty-util.jar" />
|
||||
<pathelement location="${jettylib}/jetty-xml.jar" />
|
||||
<pathelement location="${jettylib}/commons-logging.jar" />
|
||||
<pathelement location="${jettylib}/commons-el.jar" />
|
||||
</path>
|
||||
@ -25,15 +25,17 @@
|
||||
</target>
|
||||
|
||||
<property name="javac.compilerargs" value="" />
|
||||
<property name="javac.version" value="1.8" />
|
||||
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./java"
|
||||
debug="true" deprecation="on" source="1.5" target="1.5"
|
||||
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
|
||||
destdir="./build/obj"
|
||||
classpath="${i2plib}/i2p.jar:${i2plib}/i2ptunnel.jar:${i2plib}/i2psnark.jar:${i2plib}/systray.jar:${i2plib}/org.mortbay.jetty.jar" >
|
||||
includeAntRuntime="false"
|
||||
classpath="${i2plib}/i2p.jar:${i2plib}/i2ptunnel.jar:${i2plib}/i2psnark.jar:${i2plib}/mstreaming.jar:${i2plib}/systray.jar:${jettylib}/org.mortbay.jetty.jar:${jettylib}/jetty-util.jar:${jettylib}/jetty-xml.jar" >
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
</javac>
|
||||
</target>
|
||||
@ -60,17 +62,21 @@
|
||||
<arg value="build/web-fragment.xml" />
|
||||
<arg value="-webapp" />
|
||||
<arg value="jsp/" />
|
||||
<arg value="-die" />
|
||||
</java>
|
||||
|
||||
<javac
|
||||
debug="true"
|
||||
deprecation="on"
|
||||
source="1.5" target="1.5"
|
||||
source="${javac.version}" target="${javac.version}"
|
||||
destdir="build/war/WEB-INF/classes"
|
||||
srcdir="./build/jspjava"
|
||||
includes="**/*.java"
|
||||
includeAntRuntime="false"
|
||||
classpathref="jspcp"
|
||||
failonerror="true" />
|
||||
failonerror="true" >
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
</javac>
|
||||
|
||||
<copy file="jsp/WEB-INF/web.xml" tofile="build/web.xml" />
|
||||
<loadfile property="jspc.web.fragment" srcfile="build/web-fragment.xml" />
|
||||
@ -81,7 +87,6 @@
|
||||
</target>
|
||||
|
||||
<target name="war" depends="precompilejsp">
|
||||
<copy file="jsp/index.html" todir="build/war" />
|
||||
<war destfile="build/tracker.war.jar" webxml="build/web.xml">
|
||||
<fileset dir="build/war" />
|
||||
</war>
|
||||
|
@ -16,22 +16,20 @@ package net.i2p.zzzot;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
|
||||
/**
|
||||
* A 20-byte SHA1 info hash
|
||||
*/
|
||||
public class InfoHash extends ByteArray {
|
||||
public class InfoHash extends SimpleDataStructure {
|
||||
|
||||
public InfoHash(String data) throws UnsupportedEncodingException {
|
||||
this(data.getBytes("ISO-8859-1"));
|
||||
}
|
||||
public static final int LENGTH = 20;
|
||||
|
||||
public InfoHash(byte[] data) {
|
||||
super(data);
|
||||
if (data.length != 20)
|
||||
throw new IllegalArgumentException("Bad infohash length: " + data.length);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return LENGTH;
|
||||
}
|
||||
}
|
||||
|
@ -16,22 +16,20 @@ package net.i2p.zzzot;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
|
||||
/**
|
||||
* A 20-byte peer ID
|
||||
*/
|
||||
public class PID extends ByteArray {
|
||||
public class PID extends SimpleDataStructure {
|
||||
|
||||
public PID(String data) throws UnsupportedEncodingException {
|
||||
this(data.getBytes("ISO-8859-1"));
|
||||
}
|
||||
public static final int LENGTH = 20;
|
||||
|
||||
public PID(byte[] data) {
|
||||
super(data);
|
||||
if (data.length != 20)
|
||||
throw new IllegalArgumentException("Bad peer ID length: " + data.length);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return LENGTH;
|
||||
}
|
||||
}
|
||||
|
@ -16,48 +16,31 @@ package net.i2p.zzzot;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/*
|
||||
* A single peer for a single torrent.
|
||||
* Save a couple stats, and implements
|
||||
* a Map so we can BEncode it
|
||||
* So it's like PeerID but in reverse - we make a Map from the
|
||||
* data. PeerID makes the data from a Map.
|
||||
* Save a couple stats. We no longer support non-compact
|
||||
* announces, so this is no longer a Map that can be BEncoded.
|
||||
* See announce.jsp.
|
||||
*/
|
||||
public class Peer extends HashMap<String, Object> {
|
||||
public class Peer {
|
||||
|
||||
private final Hash hash;
|
||||
private long lastSeen;
|
||||
private long bytesLeft;
|
||||
private static final ConcurrentHashMap<String, String> destCache = new ConcurrentHashMap();
|
||||
private static final Integer PORT = Integer.valueOf(6881);
|
||||
private static final long CLEAN_TIME = 3*60*60*1000;
|
||||
|
||||
static {
|
||||
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
|
||||
}
|
||||
|
||||
public Peer(byte[] id, Destination address) {
|
||||
super(3);
|
||||
if (id.length != 20)
|
||||
throw new IllegalArgumentException("Bad peer ID length: " + id.length);
|
||||
put("peer id", id);
|
||||
put("port", PORT);
|
||||
// cache the 520-byte address strings
|
||||
String dest = address.toBase64() + ".i2p";
|
||||
String oldDest = destCache.putIfAbsent(dest, dest);
|
||||
if (oldDest != null)
|
||||
dest = oldDest;
|
||||
put("ip", dest);
|
||||
hash = address.calculateHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public Peer(byte[] id, Hash h) {
|
||||
hash = h;
|
||||
}
|
||||
|
||||
public void setLeft(long l) {
|
||||
@ -73,20 +56,10 @@ public class Peer extends HashMap<String, Object> {
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
/** convert b64.i2p to a Hash, then to a binary string */
|
||||
/* or should we just store it in the constructor? cache it? */
|
||||
public String getHash() {
|
||||
String ip = (String) get("ip");
|
||||
byte[] b = Base64.decode(ip.substring(0, ip.length() - 4));
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(b);
|
||||
try {
|
||||
return new String(h.getData(), "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) { return null; }
|
||||
}
|
||||
|
||||
private static class Cleaner implements SimpleTimer.TimedEvent {
|
||||
public void timeReached() {
|
||||
destCache.clear();
|
||||
}
|
||||
/**
|
||||
* @since 0.20
|
||||
*/
|
||||
public byte[] getHashBytes() {
|
||||
return hash.getData();
|
||||
}
|
||||
}
|
||||
|
@ -1,158 +0,0 @@
|
||||
// ========================================================================
|
||||
// $Id: ForwardHandler.java,v 1.16 2005/08/13 00:01:26 gregwilkins Exp $
|
||||
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========================================================================
|
||||
|
||||
package net.i2p.zzzot;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mortbay.http.HttpException;
|
||||
import org.mortbay.http.HttpMessage;
|
||||
import org.mortbay.http.HttpRequest;
|
||||
import org.mortbay.http.HttpResponse;
|
||||
import org.mortbay.http.PathMap;
|
||||
import org.mortbay.http.handler.AbstractHttpHandler;
|
||||
import org.mortbay.util.URI;
|
||||
import org.mortbay.util.UrlEncoded;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Forward Request Handler.
|
||||
* Forwards a request to a new URI. Experimental - use with caution.
|
||||
* @version $Revision: 1.16 $
|
||||
* @author Greg Wilkins (gregw)
|
||||
*
|
||||
* Just like ForwardHandler but forwards query parameters too
|
||||
* And took out the dependency on apache logging
|
||||
* @author zzz
|
||||
*/
|
||||
public class QForwardHandler extends AbstractHttpHandler
|
||||
{
|
||||
PathMap _forward = new PathMap();
|
||||
String _root;
|
||||
boolean _handleQueries = false;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Constructor.
|
||||
*/
|
||||
public QForwardHandler()
|
||||
{}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Constructor.
|
||||
* @param rootForward
|
||||
*/
|
||||
public QForwardHandler(String rootForward)
|
||||
{
|
||||
_root=rootForward;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Add a forward mapping.
|
||||
* @param pathSpecInContext The path to forward from
|
||||
* @param newPath The path to forward to.
|
||||
*/
|
||||
public void addForward(String pathSpecInContext,
|
||||
String newPath)
|
||||
{
|
||||
_forward.put(pathSpecInContext,newPath);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Add a forward mapping for root path.
|
||||
* This allows a forward for exactly / which is the default
|
||||
* path in a pathSpec.
|
||||
* @param newPath The path to forward to.
|
||||
*/
|
||||
public void setRootForward(String newPath)
|
||||
{
|
||||
_root=newPath;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set the Handler up to cope with forwards to paths that contain query
|
||||
* elements (e.g. "/blah"->"/foo?a=b").
|
||||
* AND (I2P) pass params through (e.g. "/blah?c=d? -> "/foo?c=d? ).
|
||||
* @param b
|
||||
*/
|
||||
public void setHandleQueries(boolean b)
|
||||
{
|
||||
_handleQueries = b;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void handle(String pathInContext,
|
||||
String pathParams,
|
||||
HttpRequest request,
|
||||
HttpResponse response)
|
||||
throws HttpException, IOException
|
||||
{
|
||||
String newPath=null;
|
||||
String query=null;
|
||||
if (_root!=null && ("/".equals(pathInContext) || pathInContext.startsWith("/;")))
|
||||
newPath=_root;
|
||||
else
|
||||
{
|
||||
Map.Entry entry = _forward.getMatch(pathInContext);
|
||||
if (entry!=null)
|
||||
{
|
||||
String match = (String)entry.getValue();
|
||||
if (_handleQueries)
|
||||
{
|
||||
int hook = match.indexOf('?');
|
||||
if (hook != -1){
|
||||
query = match.substring(hook+1);
|
||||
match = match.substring(0, hook);
|
||||
}
|
||||
}
|
||||
String info=PathMap.pathInfo((String)entry.getKey(),pathInContext);
|
||||
newPath=info==null?match:(URI.addPaths(match,info));
|
||||
}
|
||||
}
|
||||
|
||||
if (newPath!=null)
|
||||
{
|
||||
// this is the new part for i2p
|
||||
// setPath() changes the request URI and loses the parameters
|
||||
// so save them and add them back
|
||||
Map saved = null;
|
||||
if (_handleQueries){
|
||||
request.setCharacterEncoding("ISO-8859-1", false);
|
||||
saved = request.getParameters();
|
||||
}
|
||||
|
||||
int last=request.setState(HttpMessage.__MSG_EDITABLE);
|
||||
String context=getHttpContext().getContextPath();
|
||||
if (context.length()==1)
|
||||
request.setPath(newPath);
|
||||
else
|
||||
request.setPath(URI.addPaths(context,newPath));
|
||||
if (_handleQueries && query != null){
|
||||
// add forwarded to query string to parameters
|
||||
UrlEncoded.decodeTo(query, request.getParameters());
|
||||
}
|
||||
|
||||
// this is the new part for i2p
|
||||
if (_handleQueries){
|
||||
// add them back
|
||||
request.getParameters().putAll(saved);
|
||||
}
|
||||
|
||||
request.setState(last);
|
||||
getHttpContext().getHttpServer().service(request,response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
225
src/java/net/i2p/zzzot/RandomIterator.java
Normal file
225
src/java/net/i2p/zzzot/RandomIterator.java
Normal file
@ -0,0 +1,225 @@
|
||||
package net.i2p.zzzot;
|
||||
|
||||
/*
|
||||
* Modified from:
|
||||
* http://www.lockergnome.com/awarberg/2007/04/22/random-iterator-in-java/
|
||||
* No license, free to use
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* Copied from net.i2p.router.util
|
||||
*
|
||||
* This is some Java code I wrote for a school project to save some time when iterating in
|
||||
* random order over a part of list (until some condition becomes true):
|
||||
*
|
||||
* Here is a sample on how to use the code:
|
||||
*
|
||||
<pre>
|
||||
for(Iterator<Object> iter = new RandomIterator<Object>(myObjList); iter.hasNext();){
|
||||
Object o = iter.next();
|
||||
if(someCondition(o) )
|
||||
return o; // iteration stopped early
|
||||
}
|
||||
</pre>
|
||||
*
|
||||
* I wrote it to replace a Collection.shuffle call and this code gave us an overall increase in program execution speed of about 25%.
|
||||
* As the javadoc description says, you are better off calling Collection.shuffle if you need to iterate over the entire list. But if you may stop early this class can save you some time, as it did in our case.
|
||||
*
|
||||
* Provides a random iteration over the given list.
|
||||
*
|
||||
* This effect can be achieved by using Collections.shuffle,
|
||||
* which shuffles the entire collection in linear time.
|
||||
*
|
||||
* If the iteration process may end before all items
|
||||
* are processed, this class may give a speed increase
|
||||
* because the shuffling process is performed as items are requested
|
||||
* rather than in the beginning.
|
||||
*
|
||||
* I2P changes:
|
||||
*<pre>
|
||||
* - Use BitSet instead of boolean[]
|
||||
* - Use I2P RandomSource
|
||||
* - Done check in next(), throw NSEE
|
||||
* - Ensure lower and upper bounds are always clear
|
||||
* - Replace unbounded loop in next(). It is now O(N) time, but now
|
||||
* the iterator will tend to "clump" results and thus is not truly random.
|
||||
* *** This class is not recommended for small Lists,
|
||||
* *** or for iterating through a large portion of a List.
|
||||
* *** Use Collections.shuffle() instead.
|
||||
* - Add test code
|
||||
*</pre>
|
||||
*
|
||||
* @since zzzot 0.14.0
|
||||
*/
|
||||
public class RandomIterator<E> implements Iterator<E> {
|
||||
/**
|
||||
* Mapping indicating which items were served (by index).
|
||||
* if served[i] then the item with index i in the list
|
||||
* has already been served.
|
||||
*
|
||||
* Note it is possible to save memory here by using
|
||||
* BitSet rather than a boolean array, however it will
|
||||
* increase the running time slightly.
|
||||
*/
|
||||
private final BitSet served;
|
||||
|
||||
/** The amount of items served so far */
|
||||
private int servedCount = 0;
|
||||
private final List<E> list;
|
||||
private final int LIST_SIZE;
|
||||
|
||||
/**
|
||||
* The random number generator has a great influence
|
||||
* on the running time of this iterator.
|
||||
*
|
||||
* See, for instance,
|
||||
* <a href="http://www.qbrundage.com/michaelb/pubs/essays/random_number_generation" title="http://www.qbrundage.com/michaelb/pubs/essays/random_number_generation" target="_blank">http://www.qbrundage.com/michaelb/pubs/e…</a>
|
||||
* for some implementations, which are faster than java.util.Random.
|
||||
*/
|
||||
private final Random rand = RandomSource.getInstance();
|
||||
|
||||
/** Used to narrow the range to take random indexes from */
|
||||
private int lower, upper;
|
||||
|
||||
private static final boolean hasAndroidBug;
|
||||
static {
|
||||
if (SystemVersion.isAndroid()) {
|
||||
// only present on Gingerbread (API 11), but set if version check failed also
|
||||
int ver = SystemVersion.getAndroidVersion();
|
||||
hasAndroidBug = ver == 11 || ver == 0;
|
||||
if (hasAndroidBug)
|
||||
testAndroid();
|
||||
} else {
|
||||
hasAndroidBug = false;
|
||||
}
|
||||
}
|
||||
|
||||
public RandomIterator(List<E> list){
|
||||
this.list = list;
|
||||
LIST_SIZE = list.size();
|
||||
served = new BitSet(LIST_SIZE);
|
||||
upper = LIST_SIZE - 1;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return servedCount < LIST_SIZE;
|
||||
}
|
||||
|
||||
public E next() {
|
||||
if (!hasNext())
|
||||
throw new NoSuchElementException();
|
||||
int range = upper - lower + 1;
|
||||
|
||||
// This has unbounded behavior, even with lower/upper
|
||||
//int index;
|
||||
//do {
|
||||
// index = lower + rand.nextInt(range);
|
||||
//} while (served.get(index));
|
||||
|
||||
// This tends to "clump" results, escpecially toward the end of the iteration.
|
||||
// It also tends to leave the first and last few elements until the end.
|
||||
int start = lower + rand.nextInt(range);
|
||||
int index;
|
||||
if ((start % 2) == 0) // coin flip
|
||||
index = served.nextClearBit(start);
|
||||
else
|
||||
index = previousClearBit(start);
|
||||
if (index < 0)
|
||||
throw new NoSuchElementException("shouldn't happen");
|
||||
servedCount++;
|
||||
served.set(index);
|
||||
|
||||
// check if the range from which random values
|
||||
// are taken can be reduced
|
||||
// I2P - ensure lower and upper are always clear
|
||||
if (hasNext()) {
|
||||
if (index == lower)
|
||||
// workaround for Android ICS bug - see below
|
||||
lower = hasAndroidBug ? nextClearBit(index) : served.nextClearBit(index);
|
||||
else if (index == upper)
|
||||
upper = previousClearBit(index - 1);
|
||||
}
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
/** just like nextClearBit() */
|
||||
private int previousClearBit(int n) {
|
||||
for (int i = n; i >= lower; i--) {
|
||||
if (!served.get(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround for bug in Android (ICS only?)
|
||||
* http://code.google.com/p/android/issues/detail?id=31036
|
||||
* @since 0.9.2
|
||||
*/
|
||||
private int nextClearBit(int n) {
|
||||
for (int i = n; i <= upper; i++) {
|
||||
if (!served.get(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
testAndroid();
|
||||
test(0);
|
||||
test(1);
|
||||
test(2);
|
||||
test(1000);
|
||||
}
|
||||
|
||||
private static void test(int n) {
|
||||
System.out.println("testing with " + n);
|
||||
List<Integer> l = new ArrayList<Integer>(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
l.add(Integer.valueOf(i));
|
||||
}
|
||||
for (Iterator<Integer> iter = new RandomIterator<Integer>(l); iter.hasNext(); ) {
|
||||
System.out.println(iter.next().toString());
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Test case from android ticket above
|
||||
* @since 0.9.2
|
||||
*/
|
||||
private static void testAndroid() {
|
||||
System.out.println("Checking for Android BitSet bug");
|
||||
BitSet theBitSet = new BitSet(864);
|
||||
for (int exp =0; exp < 864; exp++) {
|
||||
int act = theBitSet.nextClearBit(0);
|
||||
if (exp != act) {
|
||||
System.err.println(String.format("Test failed for: exp=%d, act=%d", exp, act));
|
||||
System.err.println("Android BitSet bug detected, workaround implemented!");
|
||||
return;
|
||||
}
|
||||
theBitSet.set(exp);
|
||||
}
|
||||
System.err.println("Android BitSet bug NOT detected, no workaround needed!");
|
||||
}
|
||||
}
|
@ -17,14 +17,36 @@ package net.i2p.zzzot;
|
||||
*/
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.SDSCache;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
|
||||
/**
|
||||
* All the torrents
|
||||
*/
|
||||
public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
|
||||
|
||||
public Torrents() {
|
||||
private static final int CACHE_SIZE = 2048;
|
||||
private final SDSCache<InfoHash> _hashCache;
|
||||
private final SDSCache<PID> _pidCache;
|
||||
private final Integer _interval;
|
||||
private final int _udpLifetime;
|
||||
private final AtomicInteger _announces = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* @param interval in seconds
|
||||
* @param udpInterval in seconds
|
||||
*/
|
||||
public Torrents(int interval, int udpLifetime) {
|
||||
super();
|
||||
_hashCache = new SDSCache<InfoHash>(InfoHash.class, InfoHash.LENGTH, CACHE_SIZE);
|
||||
_pidCache = new SDSCache<PID>(PID.class, PID.LENGTH, CACHE_SIZE);
|
||||
_interval = Integer.valueOf(interval);
|
||||
_udpLifetime = udpLifetime;
|
||||
}
|
||||
|
||||
public int countPeers() {
|
||||
@ -34,4 +56,91 @@ public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return in seconds
|
||||
* @since 0.12.0
|
||||
*/
|
||||
public Integer getInterval() {
|
||||
return _interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return in seconds
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public int getUDPLifetime() {
|
||||
return _udpLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull from cache or return new
|
||||
*
|
||||
* @throws IllegalArgumentException if data is not the correct number of bytes
|
||||
* @since 0.12.0
|
||||
*/
|
||||
public InfoHash createInfoHash(String data) throws IllegalArgumentException {
|
||||
byte[] d = DataHelper.getASCII(data);
|
||||
if (d.length != InfoHash.LENGTH)
|
||||
throw new IllegalArgumentException("bad infohash length " + d.length);
|
||||
return _hashCache.get(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull from cache or return new
|
||||
*
|
||||
* @throws IllegalArgumentException if data is not the correct number of bytes
|
||||
* @since 0.12.0
|
||||
*/
|
||||
public PID createPID(String data) throws IllegalArgumentException {
|
||||
byte[] d = DataHelper.getASCII(data);
|
||||
if (d.length != PID.LENGTH)
|
||||
throw new IllegalArgumentException("bad peer id length " + d.length);
|
||||
return _pidCache.get(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called for every announce except for event = STOPPED.
|
||||
* Hook it here to keep an announce counter.
|
||||
*
|
||||
* @since 0.20.0
|
||||
*/
|
||||
@Override
|
||||
public Peers putIfAbsent(InfoHash ih, Peers p) {
|
||||
_announces.incrementAndGet();
|
||||
return super.putIfAbsent(ih, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of announces since the last call.
|
||||
* Resets the counter to zero.
|
||||
*
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public int getAnnounces() {
|
||||
return _announces.getAndSet(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.12.0
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
clearCaches();
|
||||
_announces.set(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.12.0
|
||||
*/
|
||||
private void clearCaches() {
|
||||
// not available until 0.9.17
|
||||
if (VersionComparator.comp(CoreVersion.VERSION, "0.9.17") >= 0) {
|
||||
try {
|
||||
_hashCache.clear();
|
||||
_pidCache.clear();
|
||||
} catch (Throwable t) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
571
src/java/net/i2p/zzzot/UDPHandler.java
Normal file
571
src/java/net/i2p/zzzot/UDPHandler.java
Normal file
@ -0,0 +1,571 @@
|
||||
package net.i2p.zzzot;
|
||||
/*
|
||||
* Copyright 2022 zzz (zzz@mail.i2p)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionMuxedListener;
|
||||
import net.i2p.client.datagram.Datagram2;
|
||||
import net.i2p.client.datagram.Datagram3;
|
||||
import net.i2p.crypto.SipHashInline;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
* Hook into the session and handle UDP announces
|
||||
* Ref: Proposal 160, BEP 15
|
||||
*
|
||||
* @since 0.19.0
|
||||
*/
|
||||
public class UDPHandler implements I2PSessionMuxedListener {
|
||||
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
private final I2PTunnel _tunnel;
|
||||
private final ZzzOT _zzzot;
|
||||
private final Cleaner _cleaner;
|
||||
private final long sipk0, sipk1;
|
||||
private final Map<Hash, Destination> _destCache;
|
||||
private final AtomicInteger _announces = new AtomicInteger();
|
||||
private volatile boolean _running;
|
||||
private ThreadPoolExecutor _executor;
|
||||
/** how long to wait before dropping an idle thread */
|
||||
private static final long HANDLER_KEEPALIVE_MS = 2*60*1000;
|
||||
|
||||
// The listen port.
|
||||
public final int PORT;
|
||||
private static final long MAGIC = 0x41727101980L;
|
||||
private static final int ACTION_CONNECT = 0;
|
||||
private static final int ACTION_ANNOUNCE = 1;
|
||||
private static final int ACTION_SCRAPE = 2;
|
||||
private static final int ACTION_ERROR = 3;
|
||||
private static final int MAX_RESPONSES = 25;
|
||||
private static final int EVENT_NONE = 0;
|
||||
private static final int EVENT_COMPLETED = 1;
|
||||
private static final int EVENT_STARTED = 2;
|
||||
private static final int EVENT_STOPPED = 3;
|
||||
// keep it short, we should have the leaseset,
|
||||
// if a new ratchet session was created
|
||||
private final long LOOKUP_TIMEOUT = 2000;
|
||||
private final long CLEAN_TIME;
|
||||
private final long STAT_TIME = 2*60*1000;
|
||||
private static final byte[] INVALID = DataHelper.getUTF8("Invalid connection ID");
|
||||
private static final byte[] PROTOCOL = DataHelper.getUTF8("Bad protocol");
|
||||
private static final byte[] SCRAPE = DataHelper.getUTF8("Scrape unsupported");
|
||||
|
||||
public UDPHandler(I2PAppContext ctx, I2PTunnel tunnel, ZzzOT zzzot, int port) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(UDPHandler.class);
|
||||
_tunnel = tunnel;
|
||||
_zzzot = zzzot;
|
||||
CLEAN_TIME = (zzzot.getTorrents().getUDPLifetime() + 60) * 1000;
|
||||
PORT = port;
|
||||
_cleaner = new Cleaner();
|
||||
sipk0 = ctx.random().nextLong();
|
||||
sipk1 = ctx.random().nextLong();
|
||||
// the highest-traffic zzzot is running about 3000 announces/minute,
|
||||
// give us enough to respond to the first announce after the connection
|
||||
_destCache = new LHMCache<Hash, Destination>(1024);
|
||||
}
|
||||
|
||||
public synchronized void start() {
|
||||
_running = true;
|
||||
_executor = new CustomThreadPoolExecutor();
|
||||
_executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
|
||||
(new I2PAppThread(new Waiter(), "ZzzOT UDP startup", true)).start();
|
||||
long[] r = new long[] { 5*60*1000 };
|
||||
_context.statManager().createRequiredRateStat("plugin.zzzot.announces.udp", "UDP announces per minute", "Plugins", r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
_running = false;
|
||||
_executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
|
||||
_executor.shutdownNow();
|
||||
_executor = null;
|
||||
_cleaner.cancel();
|
||||
_context.statManager().removeRateStat("plugin.zzzot.announces.udp");
|
||||
_announces.set(0);
|
||||
}
|
||||
|
||||
private class Waiter implements Runnable {
|
||||
public void run() {
|
||||
while (_running) {
|
||||
// requires I2P 0.9.53 (1.7.0)
|
||||
List<I2PSession> sessions = _tunnel.getSessions();
|
||||
if (sessions.isEmpty()) {
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) { break; }
|
||||
continue;
|
||||
}
|
||||
I2PSession session = sessions.get(0);
|
||||
session.addMuxedSessionListener(UDPHandler.this, I2PSession.PROTO_DATAGRAM2, PORT);
|
||||
session.addMuxedSessionListener(UDPHandler.this, I2PSession.PROTO_DATAGRAM3, PORT);
|
||||
_cleaner.schedule(STAT_TIME);
|
||||
if (_log.shouldInfo())
|
||||
_log.info("got session");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// begin listener methods ///
|
||||
|
||||
public void messageAvailable(I2PSession sess, int id, long size) {
|
||||
throw new IllegalStateException("muxed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int id, long size, int proto, int fromPort, int toPort) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got " + size + " bytes, proto: " + proto + " from port: " + fromPort + " to port: " + toPort);
|
||||
try {
|
||||
// receive message
|
||||
byte[] msg = session.receiveMessage(id);
|
||||
if (proto == I2PSession.PROTO_DATAGRAM2) {
|
||||
// load datagram into it
|
||||
Datagram2 dg = Datagram2.load(_context, session, msg);
|
||||
handle(session, dg.getSender(), null, fromPort, dg.getPayload());
|
||||
} else if (proto == I2PSession.PROTO_DATAGRAM3) {
|
||||
Datagram3 dg = Datagram3.load(_context, session, msg);
|
||||
handle(session, null, dg.getSender(), fromPort, dg.getPayload());
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping message with unknown protocol " + proto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error receiving datagram", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession arg0, int arg1) {}
|
||||
|
||||
public void disconnected(I2PSession arg0) {
|
||||
_cleaner.cancel();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession arg0, String arg1, Throwable arg2) {
|
||||
_log.error(arg1, arg2);
|
||||
}
|
||||
|
||||
/// end listener methods ///
|
||||
|
||||
/**
|
||||
* One of from or fromHash non-null
|
||||
* @param from non-null for connect request
|
||||
* @param fromHash non-null for announce request
|
||||
*/
|
||||
private void handle(I2PSession session, Destination from, Hash fromHash, int fromPort, byte[] data) {
|
||||
int sz = data.length;
|
||||
if (sz < 16) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping short msg length " + sz);
|
||||
return;
|
||||
}
|
||||
long connID = DataHelper.fromLong8(data, 0);
|
||||
int action = (int) DataHelper.fromLong(data, 8, 4);
|
||||
if (action == ACTION_CONNECT) {
|
||||
if (connID != MAGIC) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping bad connect magic " + connID);
|
||||
return;
|
||||
}
|
||||
if (from == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping dg3 connect");
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
sendError(session, fromHash, fromPort, transID, PROTOCOL);
|
||||
return;
|
||||
}
|
||||
handleConnect(session, from, fromPort, data);
|
||||
} else if (action == ACTION_ANNOUNCE) {
|
||||
if (fromHash == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping dg2 announce");
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
sendError(session, from, fromPort, transID, PROTOCOL);
|
||||
return;
|
||||
}
|
||||
handleAnnounce(session, connID, fromHash, fromPort, data);
|
||||
} else if (action == ACTION_SCRAPE) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("got unsupported scrape");
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
if (from != null)
|
||||
sendError(session, from, fromPort, transID, SCRAPE);
|
||||
else
|
||||
sendError(session, fromHash, fromPort, transID, SCRAPE);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping bad action " + action);
|
||||
// TODO send error?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from non-null
|
||||
*/
|
||||
private void handleConnect(I2PSession session, Destination from, int fromPort, byte[] data) {
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
long connID = generateCID(from.calculateHash());
|
||||
byte[] resp = new byte[18];
|
||||
DataHelper.toLong(resp, 4, 4, transID);
|
||||
DataHelper.toLong8(resp, 8, connID);
|
||||
// Addition to BEP 15
|
||||
DataHelper.toLong(resp, 16, 2, _zzzot.getTorrents().getUDPLifetime());
|
||||
try {
|
||||
session.sendMessage(from, resp, I2PSession.PROTO_DATAGRAM_RAW, PORT, fromPort);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("sent connect reply with conn ID " + connID + " to " + from.toBase32());
|
||||
synchronized(_destCache) {
|
||||
_destCache.put(from.calculateHash(), from);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error sending connect reply", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from may be null
|
||||
*/
|
||||
private void handleAnnounce(I2PSession session, long connID, Hash fromHash, int fromPort, byte[] data) {
|
||||
int sz = data.length;
|
||||
if (sz < 96) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping short announce length " + sz);
|
||||
return;
|
||||
}
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
boolean ok = validateCID(fromHash, connID);
|
||||
if (!ok) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("conn ID invalid: " + connID);
|
||||
sendError(session, fromHash, fromPort, transID, INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
// parse packet
|
||||
byte[] bih = new byte[InfoHash.LENGTH];
|
||||
System.arraycopy(data, 16, bih, 0, InfoHash.LENGTH);
|
||||
InfoHash ih = new InfoHash(bih);
|
||||
byte[] bpid = new byte[PID.LENGTH];
|
||||
System.arraycopy(data, 36, bpid, 0, PID.LENGTH);
|
||||
PID pid = new PID(bpid);
|
||||
// ignored
|
||||
//long dl = DataHelper.fromLong8(data, 56);
|
||||
//long ul = DataHelper.fromLong8(data, 72);
|
||||
int event = (int) DataHelper.fromLong(data, 80, 4);
|
||||
long left = event == EVENT_COMPLETED ? 0 : DataHelper.fromLong8(data, 64);
|
||||
// ignored
|
||||
//long ip = DataHelper.fromLong(data, 84, 4);
|
||||
//long key = DataHelper.fromLong(data, 88, 4);
|
||||
long want = DataHelper.fromLong(data, 92, 4);
|
||||
if (want > MAX_RESPONSES)
|
||||
want = MAX_RESPONSES;
|
||||
// ignored
|
||||
//int port = (int) DataHelper.fromLong(data, 96, 2);
|
||||
|
||||
Torrents torrents = _zzzot.getTorrents();
|
||||
Peers peers = torrents.get(ih);
|
||||
if (peers == null && event != EVENT_STOPPED) {
|
||||
_announces.incrementAndGet();
|
||||
peers = new Peers();
|
||||
Peers p2 = torrents.putIfAbsent(ih, peers);
|
||||
if (p2 != null)
|
||||
peers = p2;
|
||||
}
|
||||
int size;
|
||||
int seeds;
|
||||
List<Peer> peerlist;
|
||||
if (event == EVENT_STOPPED) {
|
||||
if (peers != null)
|
||||
peers.remove(pid);
|
||||
peerlist = null;
|
||||
size = 0;
|
||||
seeds = 0;
|
||||
} else {
|
||||
Peer p = peers.get(pid);
|
||||
if (p == null) {
|
||||
p = new Peer(pid.getData(), fromHash);
|
||||
Peer p2 = peers.putIfAbsent(pid, p);
|
||||
if (p2 != null)
|
||||
p = p2;
|
||||
}
|
||||
p.setLeft(left);
|
||||
|
||||
size = peers.size();
|
||||
seeds = peers.countSeeds();
|
||||
if (want <= 0 || event == EVENT_STOPPED) {
|
||||
peerlist = null;
|
||||
} else {
|
||||
peerlist = new ArrayList<Peer>(peers.values());
|
||||
peerlist.remove(p); // them
|
||||
if (want < size - 1) {
|
||||
if (size > 150) {
|
||||
// If size is huge, use random iterator for efficiency
|
||||
List<Peer> rv = new ArrayList<Peer>(size);
|
||||
for (RandomIterator<Peer> iter = new RandomIterator<Peer>(peerlist); iter.hasNext(); ) {
|
||||
rv.add(iter.next());
|
||||
}
|
||||
peerlist = rv;
|
||||
} else {
|
||||
Collections.shuffle(peerlist, _context.random());
|
||||
peerlist = peerlist.subList(0, (int) want);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int count = peerlist != null ? peerlist.size() : 0;
|
||||
byte[] resp = new byte[22 + (32 * count)];
|
||||
resp[3] = (byte) ACTION_ANNOUNCE;
|
||||
DataHelper.toLong(resp, 4, 4, transID);
|
||||
DataHelper.toLong(resp, 8, 4, torrents.getInterval());
|
||||
DataHelper.toLong(resp, 12, 4, size - seeds);
|
||||
DataHelper.toLong(resp, 16, 4, seeds);
|
||||
DataHelper.toLong(resp, 20, 2, count);
|
||||
if (peerlist != null) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
System.arraycopy(peerlist.get(i).getHashBytes(), 0, resp, 22 + (i * 32), 32);
|
||||
}
|
||||
}
|
||||
|
||||
Destination from = lookupCache(fromHash);
|
||||
if (from == null) {
|
||||
try {
|
||||
_executor.execute(new Lookup(session, fromHash, fromPort, resp));
|
||||
} catch (RejectedExecutionException ree) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error sending announce reply - thread pool full");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
session.sendMessage(from, resp, I2PSession.PROTO_DATAGRAM_RAW, PORT, fromPort);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("sent announce reply to " + from);
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error sending announce reply", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from non-null
|
||||
* @param msg non-null
|
||||
*/
|
||||
private void sendError(I2PSession session, Hash toHash, int toPort, long transID, byte[] msg) {
|
||||
Destination to = lookupCache(toHash);
|
||||
if (to == null) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("don't have cached dest to send error to " + toHash.toBase32());
|
||||
return;
|
||||
}
|
||||
// don't bother looking up via I2CP
|
||||
sendError(session, to, toPort, transID, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from non-null
|
||||
* @param msg non-null
|
||||
*/
|
||||
private void sendError(I2PSession session, Destination to, int toPort, long transID, byte[] msg) {
|
||||
byte[] resp = new byte[8 + msg.length];
|
||||
DataHelper.toLong(resp, 0, 4, ACTION_ERROR);
|
||||
DataHelper.toLong(resp, 4, 4, transID);
|
||||
System.arraycopy(msg, 0, resp, 8, msg.length);
|
||||
try {
|
||||
session.sendMessage(to, resp, I2PSession.PROTO_DATAGRAM_RAW, PORT, toPort);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("sent error to " + to.toBase32());
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error sending connect reply", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking.
|
||||
* @return null on failure
|
||||
*/
|
||||
private Destination lookup(I2PSession session, Hash hash) {
|
||||
Destination rv = lookupCache(hash);
|
||||
if (rv != null)
|
||||
return rv;
|
||||
return lookupI2CP(session, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nonblocking.
|
||||
* @return null on failure
|
||||
*/
|
||||
private Destination lookupCache(Hash hash) {
|
||||
// Test deferred
|
||||
//if (true) return null;
|
||||
synchronized(_destCache) {
|
||||
return _destCache.get(hash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking.
|
||||
* @return null on failure
|
||||
*/
|
||||
private Destination lookupI2CP(I2PSession session, Hash hash) {
|
||||
Destination rv;
|
||||
try {
|
||||
rv = session.lookupDest(hash, LOOKUP_TIMEOUT);
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("lookup error", ise);
|
||||
return null;
|
||||
}
|
||||
if (rv == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("lookup failed for response to " + hash.toBase32());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private long generateCID(Hash hash) {
|
||||
byte[] buf = new byte[40];
|
||||
System.arraycopy(hash.getData(), 0, buf, 0, 32);
|
||||
long time = _context.clock().now() / CLEAN_TIME;
|
||||
DataHelper.toLong8(buf, 32, time);
|
||||
return SipHashInline.hash24(sipk0, sipk1, buf);
|
||||
}
|
||||
|
||||
private boolean validateCID(Hash hash, long cid) {
|
||||
byte[] buf = new byte[40];
|
||||
System.arraycopy(hash.getData(), 0, buf, 0, 32);
|
||||
// current epoch
|
||||
long time = _context.clock().now() / CLEAN_TIME;
|
||||
DataHelper.toLong8(buf, 32, time);
|
||||
long c = SipHashInline.hash24(sipk0, sipk1, buf);
|
||||
if (cid == c)
|
||||
return true;
|
||||
// previous epoch
|
||||
time--;
|
||||
DataHelper.toLong8(buf, 32, time);
|
||||
c = SipHashInline.hash24(sipk0, sipk1, buf);
|
||||
return cid == c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the announce stat and set the announce count to 0
|
||||
*/
|
||||
private class Cleaner extends SimpleTimer2.TimedEvent {
|
||||
public Cleaner() { super(_context.simpleTimer2()); }
|
||||
public void timeReached() {
|
||||
long count = _announces.getAndSet(0);
|
||||
_context.statManager().addRateData("plugin.zzzot.announces.udp", count / (STAT_TIME / (60*1000L)));
|
||||
schedule(STAT_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Until we have a nonblocking lookup API in I2CP
|
||||
*
|
||||
* @since 0.20.0
|
||||
*/
|
||||
private class Lookup implements Runnable {
|
||||
private final I2PSession _session;
|
||||
private final Hash _hash;
|
||||
private final int _port;
|
||||
private final byte[] _msg;
|
||||
|
||||
public Lookup(I2PSession sess, Hash h, int port, byte[] msg) {
|
||||
_session = sess;
|
||||
_hash = h;
|
||||
_port = port;
|
||||
_msg = msg;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// blocking
|
||||
Destination d = lookupI2CP(_session, _hash);
|
||||
if (d == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("deferred lookup failed for " + _hash.toBase32());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
_session.sendMessage(d, _msg, I2PSession.PROTO_DATAGRAM_RAW, PORT, _port);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("sent deferred reply to " + _hash.toBase32());
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error sending deferred reply", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Until we have a nonblocking lookup API in I2CP
|
||||
*
|
||||
* @since 0.20.0
|
||||
*/
|
||||
private static class CustomThreadPoolExecutor extends ThreadPoolExecutor {
|
||||
public CustomThreadPoolExecutor() {
|
||||
super(0, 25, HANDLER_KEEPALIVE_MS, TimeUnit.MILLISECONDS,
|
||||
new SynchronousQueue<Runnable>(), new CustomThreadFactory());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Just to set the name and set Daemon
|
||||
*
|
||||
* @since 0.20.0
|
||||
*/
|
||||
private static class CustomThreadFactory implements ThreadFactory {
|
||||
private final AtomicInteger _executorThreadCount = new AtomicInteger();
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread rv = Executors.defaultThreadFactory().newThread(r);
|
||||
rv.setName("ZzzOT lookup " + _executorThreadCount.incrementAndGet());
|
||||
rv.setDaemon(true);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,37 +17,94 @@ package net.i2p.zzzot;
|
||||
*/
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
* Instantiate this to fire it up
|
||||
*/
|
||||
class ZzzOT {
|
||||
|
||||
private Torrents _torrents;
|
||||
private static final long CLEAN_TIME = 4*60*1000;
|
||||
private static final long EXPIRE_TIME = 60*60*1000;
|
||||
private final I2PAppContext _context;
|
||||
private final Torrents _torrents;
|
||||
private final Cleaner _cleaner;
|
||||
private final long EXPIRE_TIME;
|
||||
|
||||
ZzzOT() {
|
||||
_torrents = new Torrents();
|
||||
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
|
||||
private static final String PROP_INTERVAL = "interval";
|
||||
private static final String PROP_UDP_LIFETIME = "lifetime";
|
||||
private static final long CLEAN_TIME = 2*60*1000;
|
||||
private static final int DEFAULT_INTERVAL = 27*60;
|
||||
private static final int DEFAULT_UDP_LIFETIME = 20*60;
|
||||
private static final int MIN_INTERVAL = 15*60;
|
||||
private static final int MAX_INTERVAL = 6*60*60;
|
||||
private static final int MIN_UDP_LIFETIME = 60;
|
||||
private static final int MAX_UDP_LIFETIME = 6*60*60;
|
||||
|
||||
ZzzOT(I2PAppContext ctx, Properties p) {
|
||||
String intv = p.getProperty(PROP_INTERVAL);
|
||||
int interval = DEFAULT_INTERVAL;
|
||||
if (intv != null) {
|
||||
try {
|
||||
interval = Integer.parseInt(intv);
|
||||
if (interval < MIN_INTERVAL)
|
||||
interval = MIN_INTERVAL;
|
||||
else if (interval > MAX_INTERVAL)
|
||||
interval = MAX_INTERVAL;
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
intv = p.getProperty(PROP_UDP_LIFETIME);
|
||||
int lifetime = DEFAULT_UDP_LIFETIME;
|
||||
if (intv != null) {
|
||||
try {
|
||||
lifetime = Integer.parseInt(intv);
|
||||
if (lifetime < MIN_UDP_LIFETIME)
|
||||
interval = MIN_UDP_LIFETIME;
|
||||
else if (interval > MAX_UDP_LIFETIME)
|
||||
interval = MAX_UDP_LIFETIME;
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
_torrents = new Torrents(interval, lifetime);
|
||||
EXPIRE_TIME = 1000 * (interval + interval / 2);
|
||||
_cleaner = new Cleaner(ctx);
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
Torrents getTorrents() {
|
||||
return _torrents;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
_torrents.clear();
|
||||
// no way to stop the cleaner
|
||||
void start() {
|
||||
_cleaner.forceReschedule(CLEAN_TIME);
|
||||
long[] r = new long[] { 5*60*1000 };
|
||||
_context.statManager().createRequiredRateStat("plugin.zzzot.announces", "Total announces per minute", "Plugins", r);
|
||||
_context.statManager().createRequiredRateStat("plugin.zzzot.peers", "Number of peers", "Plugins", r);
|
||||
_context.statManager().createRequiredRateStat("plugin.zzzot.torrents", "Number of torrents", "Plugins", r);
|
||||
}
|
||||
|
||||
private class Cleaner implements SimpleTimer.TimedEvent {
|
||||
void stop() {
|
||||
_cleaner.cancel();
|
||||
_torrents.clear();
|
||||
_context.statManager().removeRateStat("plugin.zzzot.announces");
|
||||
_context.statManager().removeRateStat("plugin.zzzot.peers");
|
||||
_context.statManager().removeRateStat("plugin.zzzot.torrents");
|
||||
}
|
||||
|
||||
private class Cleaner extends SimpleTimer2.TimedEvent {
|
||||
|
||||
private final AtomicInteger _runCount = new AtomicInteger();
|
||||
|
||||
/** must schedule later */
|
||||
public Cleaner(I2PAppContext ctx) {
|
||||
super(ctx.simpleTimer2());
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
long now = System.currentTimeMillis();
|
||||
int peers = 0;
|
||||
for (Iterator<Peers> iter = _torrents.values().iterator(); iter.hasNext(); ) {
|
||||
Peers p = iter.next();
|
||||
int recent = 0;
|
||||
@ -60,7 +117,13 @@ class ZzzOT {
|
||||
}
|
||||
if (recent <= 0)
|
||||
iter.remove();
|
||||
else
|
||||
peers += recent;
|
||||
}
|
||||
_context.statManager().addRateData("plugin.zzzot.announces", _torrents.getAnnounces() / (CLEAN_TIME / (60*1000L)));
|
||||
_context.statManager().addRateData("plugin.zzzot.peers", peers);
|
||||
_context.statManager().addRateData("plugin.zzzot.torrents", _torrents.size());
|
||||
schedule(CLEAN_TIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,20 +21,30 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientApp;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.ClientAppState;
|
||||
import static net.i2p.app.ClientAppState.*;
|
||||
import net.i2p.apps.systray.UrlLauncher;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.apps.systray.UrlLauncher;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
import org.mortbay.http.HttpContext;
|
||||
import org.mortbay.jetty.Server;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.xml.XmlConfiguration;
|
||||
|
||||
/**
|
||||
* This handles the starting and stopping of an eepsite tunnel and jetty
|
||||
@ -47,34 +57,203 @@ import org.mortbay.jetty.Server;
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class ZzzOTController {
|
||||
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ZzzOTController.class);
|
||||
private static Server _server;
|
||||
private static TunnelController _tunnel;
|
||||
private static ZzzOT _zzzot;
|
||||
private static Object _lock = new Object();
|
||||
public class ZzzOTController implements ClientApp {
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
private final String[] _args;
|
||||
private final ClientAppManager _mgr;
|
||||
private Server _server;
|
||||
private TunnelController _tunnel;
|
||||
private final ZzzOT _zzzot;
|
||||
/** only for main() */
|
||||
private static volatile ZzzOTController _controller;
|
||||
// you wouldn't run two instances in the same JVM, would you?
|
||||
private static String _sitename;
|
||||
private static boolean _showfooter;
|
||||
private static String _footertext;
|
||||
private static boolean _fullScrape;
|
||||
private final boolean _enableUDP;
|
||||
private final int _udpPort;
|
||||
private UDPHandler _udp;
|
||||
private String _b32;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 3 || (!"-d".equals(args[0])))
|
||||
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
|
||||
if ("start".equals(args[2]))
|
||||
start(args);
|
||||
else if ("stop".equals(args[2]))
|
||||
stop();
|
||||
else
|
||||
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
|
||||
}
|
||||
private ClientAppState _state = UNINITIALIZED;
|
||||
|
||||
public static Torrents getTorrents() {
|
||||
synchronized(_lock) {
|
||||
if (_zzzot == null)
|
||||
_zzzot = new ZzzOT();
|
||||
private static final String NAME = "ZzzOT";
|
||||
private static final String DEFAULT_SITENAME = "ZZZOT";
|
||||
private static final String PROP_SITENAME = "sitename";
|
||||
private static final String VERSION = "0.20.0-beta2";
|
||||
private static final String DEFAULT_SHOWFOOTER = "true";
|
||||
private static final String PROP_SHOWFOOTER = "showfooter";
|
||||
private static final String DEFAULT_FOOTERTEXT = "Running <a href=\"http://git.idk.i2p/i2p-hackers/i2p.plugins.zzzot\" target=\"_blank\">ZZZOT</a> " + VERSION;
|
||||
private static final String PROP_FOOTERTEXT = "footertext";
|
||||
private static final String PROP_FULLSCRAPE = "allowFullScrape";
|
||||
private static final String DEFAULT_FULLSCRAPE = "false";
|
||||
private static final String PROP_UDP = "udp";
|
||||
private static final String DEFAULT_UDP = "false";
|
||||
private static final String PROP_UDP_PORT = "udp";
|
||||
private static final int DEFAULT_UDP_PORT = 6969;
|
||||
private static final String CONFIG_FILE = "zzzot.config";
|
||||
private static final String BACKUP_SUFFIX = ".jetty8";
|
||||
private static final String[] xmlFiles = {
|
||||
"jetty.xml", "contexts/base-context.xml", "contexts/cgi-context.xml",
|
||||
"etc/realm.properties", "etc/webdefault.xml" };
|
||||
|
||||
/**
|
||||
* @since 0.12.0
|
||||
*/
|
||||
public ZzzOTController(I2PAppContext ctx, ClientAppManager mgr, String args[]) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(ZzzOTController.class);
|
||||
_mgr = mgr;
|
||||
_args = args;
|
||||
File cfile = new File(_context.getAppDir(), "plugins/zzzot/" + CONFIG_FILE);
|
||||
Properties props = new Properties();
|
||||
if (cfile.exists()) {
|
||||
try {
|
||||
DataHelper.loadProps(props, cfile);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Failed loading zzzot config from " + cfile, ioe);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No config file " + cfile);
|
||||
}
|
||||
return _zzzot.getTorrents();
|
||||
_zzzot = new ZzzOT(ctx, props);
|
||||
_sitename = props.getProperty(PROP_SITENAME, DEFAULT_SITENAME);
|
||||
_showfooter = Boolean.parseBoolean(props.getProperty(PROP_SHOWFOOTER, DEFAULT_SHOWFOOTER));
|
||||
_footertext = props.getProperty(PROP_FOOTERTEXT, DEFAULT_FOOTERTEXT);
|
||||
_fullScrape = Boolean.parseBoolean(props.getProperty(PROP_FULLSCRAPE, DEFAULT_FULLSCRAPE));
|
||||
_enableUDP = Boolean.parseBoolean(props.getProperty(PROP_UDP, DEFAULT_UDP));
|
||||
int p = DEFAULT_UDP_PORT;
|
||||
String port = props.getProperty(PROP_UDP_PORT);
|
||||
if (port != null) {
|
||||
try {
|
||||
p = Integer.parseInt(port);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
_udpPort = p;
|
||||
_state = INITIALIZED;
|
||||
}
|
||||
|
||||
private static void start(String args[]) {
|
||||
File pluginDir = new File(args[1]);
|
||||
/**
|
||||
* No longer supported, as we now need the ClientAppManager for the webapp to find us
|
||||
*/
|
||||
public synchronized static void main(String args[]) {
|
||||
throw new UnsupportedOperationException("Must use ClientApp interface");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if not running
|
||||
*/
|
||||
private static ZzzOTController getThis() {
|
||||
ClientAppManager mgr = I2PAppContext.getGlobalContext().clientAppManager();
|
||||
if (mgr == null)
|
||||
return null;
|
||||
ClientApp z = mgr.getRegisteredApp(NAME);
|
||||
if (z == null)
|
||||
return null;
|
||||
return (ZzzOTController) z;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if not running
|
||||
*/
|
||||
public static Torrents getTorrents() {
|
||||
ZzzOTController ctrlr = getThis();
|
||||
if (ctrlr == null)
|
||||
return null;
|
||||
return ctrlr._zzzot.getTorrents();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return announces per minute, 0 if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static double getAnnounceRate() {
|
||||
RateStat rs = I2PAppContext.getGlobalContext().statManager().getRate("plugin.zzzot.announces");
|
||||
if (rs == null)
|
||||
return 0;
|
||||
Rate r = rs.getRate(5*60*1000);
|
||||
if (r == null)
|
||||
return 0;
|
||||
return r.getAvgOrLifetimeAvg();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return announces per minute, 0 if not running or UDP not enabled
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static double getUDPAnnounceRate() {
|
||||
RateStat rs = I2PAppContext.getGlobalContext().statManager().getRate("plugin.zzzot.announces.udp");
|
||||
if (rs == null)
|
||||
return 0;
|
||||
Rate r = rs.getRate(5*60*1000);
|
||||
if (r == null)
|
||||
return 0;
|
||||
return r.getAvgOrLifetimeAvg();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static boolean isUDPEnabled() {
|
||||
ZzzOTController ctrlr = getThis();
|
||||
if (ctrlr == null)
|
||||
return false;
|
||||
return ctrlr.getUDPEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
private boolean getUDPEnabled() {
|
||||
return _enableUDP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static int udpPort() {
|
||||
ZzzOTController ctrlr = getThis();
|
||||
if (ctrlr == null)
|
||||
return 0;
|
||||
return ctrlr.getUDPPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public int getUDPPort() {
|
||||
return _udpPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static String b32() {
|
||||
ZzzOTController ctrlr = getThis();
|
||||
if (ctrlr == null)
|
||||
return null;
|
||||
return ctrlr.getB32();
|
||||
}
|
||||
/**
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public String getB32() {
|
||||
return _b32;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args ignored
|
||||
*/
|
||||
private void start(String args[]) {
|
||||
//File pluginDir = new File(args[1]);
|
||||
File pluginDir = new File(_context.getAppDir(), "plugins/zzzot");
|
||||
if (!pluginDir.exists())
|
||||
throw new IllegalArgumentException("Plugin directory " + pluginDir.getAbsolutePath() + " does not exist");
|
||||
|
||||
@ -91,19 +270,25 @@ public class ZzzOTController {
|
||||
_log.error("Unable to create " + key.getAbsolutePath() + ' ' + e);
|
||||
throw new IllegalArgumentException("Unable to create " + key.getAbsolutePath() + ' ' + e);
|
||||
}
|
||||
_log.error("NOTICE: ZzzOT: New eepsite keys created in " + key.getAbsolutePath());
|
||||
_log.error("NOTICE: ZzzOT: You should back up this file!");
|
||||
_log.logAlways(Log.INFO, "NOTICE: ZzzOT: New eepsite keys created in " + key.getAbsolutePath());
|
||||
_log.logAlways(Log.INFO, "NOTICE: ZzzOT: You should back up this file!");
|
||||
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
|
||||
String b64 = dest.toBase64();
|
||||
_log.error("NOTICE: ZzzOT: Your base 32 address is " + b32);
|
||||
_log.error("NOTICE: ZzzOT: Your base 64 address is " + b64);
|
||||
_log.logAlways(Log.INFO, "NOTICE: ZzzOT: Your base 32 address is " + b32);
|
||||
_log.logAlways(Log.INFO, "NOTICE: ZzzOT: Your base 64 address is " + b64);
|
||||
}
|
||||
startJetty(pluginDir, dest);
|
||||
startI2PTunnel(pluginDir, dest);
|
||||
_zzzot.start();
|
||||
// requires I2P 0.9.66 (2.9.0)
|
||||
if (_enableUDP) {
|
||||
_udp = new UDPHandler(_context, _tunnel.getTunnel(), _zzzot, _udpPort);
|
||||
_udp.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void startI2PTunnel(File pluginDir, Destination dest) {
|
||||
private void startI2PTunnel(File pluginDir, Destination dest) {
|
||||
File i2ptunnelConfig = new File(pluginDir, "i2ptunnel.config");
|
||||
Properties i2ptunnelProps = new Properties();
|
||||
try {
|
||||
@ -112,32 +297,39 @@ public class ZzzOTController {
|
||||
_log.error("Cannot open " + i2ptunnelConfig.getAbsolutePath() + ' ' + ioe);
|
||||
throw new IllegalArgumentException("Cannot open " + i2ptunnelConfig.getAbsolutePath() + ' ' + ioe);
|
||||
}
|
||||
String p = i2ptunnelProps.getProperty("tunnel.0.option.i2cp.leaseSetEncType");
|
||||
if (p == null || p.equals("4,0"))
|
||||
i2ptunnelProps.setProperty("tunnel.0.option.i2cp.leaseSetEncType", "4");
|
||||
TunnelController tun = new TunnelController(i2ptunnelProps, "tunnel.0.");
|
||||
// start in foreground so we can get the destination
|
||||
//tun.startTunnelBackground();
|
||||
tun.startTunnel();
|
||||
if (dest != null) {
|
||||
if (dest == null) {
|
||||
// start in foreground so we can get the destination
|
||||
tun.startTunnel();
|
||||
_b32 = tun.getMyDestHashBase32();
|
||||
List msgs = tun.clearMessages();
|
||||
for (Object s : msgs) {
|
||||
_log.error("NOTICE: ZzzOT Tunnel message: " + s);
|
||||
_log.logAlways(Log.INFO, "NOTICE: ZzzOT Tunnel message: " + s);
|
||||
}
|
||||
} else {
|
||||
_b32 = dest.calculateHash().toBase32();
|
||||
tun.startTunnelBackground();
|
||||
}
|
||||
_tunnel = tun;
|
||||
}
|
||||
|
||||
private static void startJetty(File pluginDir, Destination dest) {
|
||||
private void startJetty(File pluginDir, Destination dest) {
|
||||
if (_server != null)
|
||||
throw new IllegalArgumentException("Jetty already running!");
|
||||
migrateJettyXML(pluginDir);
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
File tmpdir = new File(context.getTempDir().getAbsolutePath(), "/zzzot-work");
|
||||
File tmpdir = new File(_context.getTempDir().getAbsolutePath(), "/zzzot-work");
|
||||
tmpdir.mkdir();
|
||||
File jettyXml = new File(pluginDir, "jetty.xml");
|
||||
try {
|
||||
Server serv = new Server(jettyXml.getAbsolutePath());
|
||||
HttpContext[] hcs = serv.getContexts();
|
||||
for (int i = 0; i < hcs.length; i++)
|
||||
hcs[i].setTempDirectory(tmpdir);
|
||||
Resource.setDefaultUseCaches(false);
|
||||
XmlConfiguration xmlc = new XmlConfiguration(jettyXml.toURI().toURL());
|
||||
Server serv = (Server) xmlc.configure();
|
||||
//HttpContext[] hcs = serv.getContexts();
|
||||
//for (int i = 0; i < hcs.length; i++)
|
||||
// hcs[i].setTempDirectory(tmpdir);
|
||||
serv.start();
|
||||
_server = serv;
|
||||
} catch (Throwable t) {
|
||||
@ -148,18 +340,28 @@ public class ZzzOTController {
|
||||
launchHelp(pluginDir, dest);
|
||||
}
|
||||
|
||||
private static void stop() {
|
||||
private void stop() {
|
||||
stopI2PTunnel();
|
||||
stopJetty();
|
||||
if (_zzzot != null)
|
||||
_zzzot.stop();
|
||||
if (_udp != null)
|
||||
_udp.stop();
|
||||
_zzzot.stop();
|
||||
}
|
||||
|
||||
private static void stopI2PTunnel() {
|
||||
private void stopI2PTunnel() {
|
||||
if (_tunnel == null)
|
||||
return;
|
||||
try {
|
||||
_tunnel.stopTunnel();
|
||||
// destroyTunnel() not available until 0.9.17
|
||||
if (VersionComparator.comp(CoreVersion.VERSION, "0.9.17") >= 0) {
|
||||
try {
|
||||
_tunnel.destroyTunnel();
|
||||
} catch (Throwable t) {
|
||||
_tunnel.stopTunnel();
|
||||
}
|
||||
} else {
|
||||
_tunnel.stopTunnel();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.error("ZzzOT tunnel stop failed", t);
|
||||
throw new IllegalArgumentException("Tunnel stop failed " + t);
|
||||
@ -167,7 +369,7 @@ public class ZzzOTController {
|
||||
_tunnel = null;
|
||||
}
|
||||
|
||||
private static void stopJetty() {
|
||||
private void stopJetty() {
|
||||
if (_server == null)
|
||||
return;
|
||||
try {
|
||||
@ -179,41 +381,132 @@ public class ZzzOTController {
|
||||
_server = null;
|
||||
}
|
||||
|
||||
/** put the directory in the jetty.xml file */
|
||||
private static void migrateJettyXML(File pluginDir) {
|
||||
File outFile = new File(pluginDir, "jetty.xml");
|
||||
if (outFile.exists())
|
||||
/**
|
||||
* Migate the jetty configuration files.
|
||||
* Save old jetty.xml if moving from jetty 5 to jetty 6
|
||||
*/
|
||||
private void migrateJettyXML(File pluginDir) {
|
||||
// contexts dir does not exist in Jetty 5
|
||||
File file = new File(pluginDir, "contexts");
|
||||
file.mkdir();
|
||||
file = new File(pluginDir, "etc");
|
||||
file.mkdir();
|
||||
file = new File(pluginDir, "jetty.xml");
|
||||
if (!shouldMigrate(file))
|
||||
return;
|
||||
File fileTmpl = new File(pluginDir, "templates/jetty.xml");
|
||||
for (int i = 0; i < xmlFiles.length; i++) {
|
||||
backupAndMigrateFile(pluginDir, xmlFiles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return should we copy over all the files, based on the contents of this one
|
||||
* @since 0.10 (Jetty 7)
|
||||
*/
|
||||
private static boolean shouldMigrate(File f) {
|
||||
String xml = FileUtil.readTextFile(f.getAbsolutePath(), 400, true);
|
||||
if (xml == null)
|
||||
return true;
|
||||
return xml.contains("class=\"org.eclipse.jetty.server.nio.SelectChannelConnector\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup a file and migrate new XML
|
||||
* @return success
|
||||
* @since Jetty 7
|
||||
*/
|
||||
private boolean backupAndMigrateFile(File toDir, String filename) {
|
||||
File to = new File(toDir, filename);
|
||||
boolean rv = backupFile(to);
|
||||
boolean rv2 = migrateJettyFile(toDir, filename);
|
||||
return rv && rv2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup a file
|
||||
* @return success
|
||||
* @since Jetty 7
|
||||
*/
|
||||
private static boolean backupFile(File from) {
|
||||
if (!from.exists())
|
||||
return true;
|
||||
File to = new File(from.getAbsolutePath() + BACKUP_SUFFIX);
|
||||
if (to.exists())
|
||||
to = new File(to.getAbsolutePath() + "." + System.currentTimeMillis());
|
||||
boolean rv = FileUtil.copy(from, to, false, true);
|
||||
if (rv)
|
||||
System.err.println("Backed up file " + from + " to " + to);
|
||||
else
|
||||
System.err.println("WARNING: Failed to back up file " + from + " to " + to);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate a single jetty config file, replacing $PLUGIN as we copy it.
|
||||
*/
|
||||
private boolean migrateJettyFile(File pluginDir, String name) {
|
||||
File templateDir = new File(pluginDir, "templates");
|
||||
File fileTmpl = new File(templateDir, name);
|
||||
File outFile = new File(pluginDir, name);
|
||||
FileOutputStream os = null;
|
||||
try {
|
||||
String props = FileUtil.readTextFile(fileTmpl.getAbsolutePath(), 250, true);
|
||||
String props = FileUtil.readTextFile(fileTmpl.getAbsolutePath(), 600, true);
|
||||
if (props == null)
|
||||
throw new IOException(fileTmpl.getAbsolutePath() + " open failed");
|
||||
props = props.replace("$PLUGIN", pluginDir.getAbsolutePath());
|
||||
FileOutputStream os = new FileOutputStream(outFile);
|
||||
os = new FileOutputStream(outFile);
|
||||
os.write(props.getBytes("UTF-8"));
|
||||
os.close();
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("jetty.xml migrate failed", ioe);
|
||||
_log.error(outFile + " migrate failed", ioe);
|
||||
return false;
|
||||
} finally {
|
||||
if (os != null) try { os.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/** put the directory, base32, and base64 info in the help.html file and launch a browser window to display it */
|
||||
private static void launchHelp(File pluginDir, Destination dest) {
|
||||
private void launchHelp(File pluginDir, Destination dest) {
|
||||
File fileTmpl = new File(pluginDir, "templates/help.html");
|
||||
File outFile = new File(pluginDir, "eepsite/docroot/help.html");
|
||||
File index_in = new File(pluginDir, "templates/index.html");
|
||||
File index_out = new File(pluginDir, "eepsite/docroot/index.html");
|
||||
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
|
||||
String b64 = dest.toBase64();
|
||||
try {
|
||||
String html = FileUtil.readTextFile(fileTmpl.getAbsolutePath(), 100, true);
|
||||
// help.html
|
||||
String html = FileUtil.readTextFile(fileTmpl.getAbsolutePath(), 500, true);
|
||||
if (html == null)
|
||||
throw new IOException(fileTmpl.getAbsolutePath() + " open failed");
|
||||
html = html.replace("$PLUGIN", pluginDir.getAbsolutePath());
|
||||
// replace $HOME in path
|
||||
String home = System.getProperty("user.home");
|
||||
String pdir = pluginDir.getAbsolutePath();
|
||||
if (pdir.startsWith(home)) {
|
||||
pdir = "$HOME" + pdir.substring(home.length());
|
||||
// only warn about username in help if we haven't replaced it with $HOME
|
||||
html = html.replace("<p class=\"warn\" id=\"docroot\">", "<p id=\"docroot\">");
|
||||
html = html.replace("<br><span class=\"emphasis\"><b>You should probably move it outside of the document root " +
|
||||
"before you announce your eepsite as it may contain your username.</b></span>", "");
|
||||
}
|
||||
html = html.replace("$PLUGIN", pdir);
|
||||
html = html.replace("$B32", b32);
|
||||
html = html.replace("$B64", b64);
|
||||
html = html.replace("$VERSION", VERSION);
|
||||
String bdir = _context.getBaseDir().getAbsolutePath();
|
||||
if (bdir.startsWith(home))
|
||||
bdir = "$HOME" + bdir.substring(home.length());
|
||||
html = html.replace("$I2P", bdir);
|
||||
FileOutputStream os = new FileOutputStream(outFile);
|
||||
os.write(html.getBytes("UTF-8"));
|
||||
os.close();
|
||||
// index.html
|
||||
String html2 = FileUtil.readTextFile(index_in.getAbsolutePath(), 50, true);
|
||||
if (html2 == null)
|
||||
throw new IOException(fileTmpl.getAbsolutePath() + " open failed");
|
||||
html2 = html2.replace("$B32", b32);
|
||||
FileOutputStream os2 = new FileOutputStream(index_out);
|
||||
os2.write(html2.getBytes("UTF-8"));
|
||||
os2.close();
|
||||
Thread t = new I2PAppThread(new Launcher(), "ZzzOTHelp", true);
|
||||
t.start();
|
||||
} catch (IOException ioe) {
|
||||
@ -226,4 +519,108 @@ public class ZzzOTController {
|
||||
UrlLauncher.main(new String[] { "http://127.0.0.1:7662/help.html" } );
|
||||
}
|
||||
}
|
||||
|
||||
/////// ClientApp methods
|
||||
|
||||
/** @since 0.12.0 */
|
||||
public synchronized void startup() {
|
||||
if (_mgr != null) {
|
||||
// this is really ugly, but thru 0.9.16,
|
||||
// stopping a ClientApp plugin with $PLUGIN in the args fails,
|
||||
// and it tries to start a second one instead.
|
||||
// Find the first one and stop it.
|
||||
ClientApp z = _mgr.getRegisteredApp(NAME);
|
||||
if (z != null) {
|
||||
if (VersionComparator.comp(CoreVersion.VERSION, "0.9.17") < 0) {
|
||||
ZzzOTController ctrlr = (ZzzOTController) z;
|
||||
_log.warn("Got start when another zzzot running, stopping him instead");
|
||||
ctrlr.shutdown(null);
|
||||
} else {
|
||||
_log.error("ZzzOT already running");
|
||||
}
|
||||
changeState(START_FAILED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_state != STOPPED && _state != INITIALIZED && _state != START_FAILED) {
|
||||
_log.error("Start while state = " + _state);
|
||||
return;
|
||||
}
|
||||
changeState(STARTING);
|
||||
try {
|
||||
start(_args);
|
||||
changeState(RUNNING);
|
||||
if (_mgr != null)
|
||||
_mgr.register(this);
|
||||
} catch (Exception e) {
|
||||
changeState(START_FAILED, "Start failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.12.0 */
|
||||
public synchronized void shutdown(String[] args) {
|
||||
if (_state == STOPPED)
|
||||
return;
|
||||
changeState(STOPPING);
|
||||
if (_mgr != null)
|
||||
_mgr.unregister(this);
|
||||
stop();
|
||||
changeState(STOPPED);
|
||||
}
|
||||
|
||||
/** @since 0.12.0 */
|
||||
public ClientAppState getState() {
|
||||
return _state;
|
||||
}
|
||||
|
||||
/** @since 0.12.0 */
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/** @since 0.12.0 */
|
||||
public String getDisplayName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/////// end ClientApp methods
|
||||
|
||||
/** @since 0.17.0 */
|
||||
public static String getSiteName() {
|
||||
return _sitename;
|
||||
}
|
||||
|
||||
/** @since 0.17.0 */
|
||||
public static String getVersion() {
|
||||
return VERSION;
|
||||
}
|
||||
|
||||
/** @since 0.17.0 */
|
||||
public static boolean shouldShowFooter() {
|
||||
return _showfooter;
|
||||
}
|
||||
|
||||
/** @since 0.17.0 */
|
||||
public static String footerText() {
|
||||
return _footertext;
|
||||
}
|
||||
|
||||
/** @since 0.19.0 */
|
||||
public static boolean allowFullScrape() {
|
||||
return _fullScrape;
|
||||
}
|
||||
|
||||
/** @since 0.12.0 */
|
||||
private synchronized void changeState(ClientAppState state) {
|
||||
_state = state;
|
||||
if (_mgr != null)
|
||||
_mgr.notify(this, state, null, null);
|
||||
}
|
||||
|
||||
/** @since 0.12.0 */
|
||||
private synchronized void changeState(ClientAppState state, String msg, Exception e) {
|
||||
_state = state;
|
||||
if (_mgr != null)
|
||||
_mgr.notify(this, state, msg, e);
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,24 @@
|
||||
<!-- precompiled servlets -->
|
||||
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.html</welcome-file>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.zzzot.index_jsp</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.zzzot.index_jsp</servlet-name>
|
||||
<url-pattern>/index</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.zzzot.index_jsp</servlet-name>
|
||||
<url-pattern>/index.html</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.zzzot.announce_jsp</servlet-name>
|
||||
<url-pattern>/announce.php</url-pattern>
|
||||
@ -33,4 +47,10 @@
|
||||
<url-pattern>/scrape.php</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- BiglyBT -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.zzzot.scrape_jsp</servlet-name>
|
||||
<url-pattern>/a/scrape</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%@page import="java.util.ArrayList" %><%@page import="java.util.Collections" %><%@page import="java.util.List" %><%@page import="java.util.Map" %><%@page import="java.util.HashMap" %><%@page import="net.i2p.data.Base64" %><%@page import="net.i2p.data.Destination" %><%@page import="net.i2p.zzzot.*" %><%@page import="org.klomp.snark.bencode.BEncoder" %><%
|
||||
<%@page import="java.io.ByteArrayInputStream,java.util.ArrayList,java.util.Collections,java.util.List,java.util.Map,java.util.HashMap,java.util.concurrent.ConcurrentMap,net.i2p.data.Base64,net.i2p.data.Destination,net.i2p.zzzot.*,org.klomp.snark.bencode.BEncoder" %><%
|
||||
|
||||
/*
|
||||
* Above one-liner is so there is no whitespace -> IllegalStateException
|
||||
@ -27,12 +27,16 @@
|
||||
*/
|
||||
// would be nice to make these configurable
|
||||
final int MAX_RESPONSES = 25;
|
||||
final int INTERVAL = 27*60;
|
||||
final boolean ALLOW_IP_MISMATCH = false;
|
||||
final boolean ALLOW_COMPACT_RESPONSE = true;
|
||||
final boolean ALLOW_NONCOMPACT_RESPONSE = false;
|
||||
|
||||
// so the chars will turn into bytes correctly
|
||||
request.setCharacterEncoding("ISO-8859-1");
|
||||
// above doesn't work for the query string
|
||||
// https://wiki.eclipse.org/Jetty/Howto/International_Characters
|
||||
// we could also do ((org.eclipse.jetty.server.Request) request).setQueryEncoding("ISO-8859-1")
|
||||
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
|
||||
java.io.OutputStream cout = response.getOutputStream();
|
||||
response.setCharacterEncoding("ISO-8859-1");
|
||||
response.setContentType("text/plain");
|
||||
@ -61,14 +65,25 @@
|
||||
if (xff != null || xfs != null) {
|
||||
fail = true;
|
||||
msg = "Non-I2P access denied";
|
||||
response.setStatus(403, msg);
|
||||
//response.setStatus(403, msg);
|
||||
response.setStatus(403);
|
||||
}
|
||||
|
||||
if (!compact && !ALLOW_NONCOMPACT_RESPONSE && !fail) {
|
||||
fail = true;
|
||||
msg = "non-compact responses unsupported";
|
||||
}
|
||||
|
||||
if (info_hash == null && !fail) {
|
||||
fail = true;
|
||||
msg = "no info hash";
|
||||
}
|
||||
|
||||
if (!fail && info_hash.length() != 20) {
|
||||
fail = true;
|
||||
msg = "bad info hash length " + info_hash.length();
|
||||
}
|
||||
|
||||
if (ip == null && !fail) {
|
||||
fail = true;
|
||||
msg = "no ip (dest)";
|
||||
@ -79,11 +94,22 @@
|
||||
msg = "no peer id";
|
||||
}
|
||||
|
||||
if (!fail && peer_id.length() != 20) {
|
||||
fail = true;
|
||||
msg = "bad peer id length " + peer_id.length();
|
||||
}
|
||||
|
||||
Torrents torrents = ZzzOTController.getTorrents();
|
||||
if (torrents == null && !fail) {
|
||||
fail = true;
|
||||
msg = "tracker is down";
|
||||
}
|
||||
|
||||
InfoHash ih = null;
|
||||
if (!fail) {
|
||||
try {
|
||||
ih = new InfoHash(info_hash);
|
||||
} catch (Exception e) {
|
||||
ih = torrents.createInfoHash(info_hash);
|
||||
} catch (IllegalArgumentException e) {
|
||||
fail = true;
|
||||
msg = "bad infohash " + e;
|
||||
}
|
||||
@ -94,7 +120,10 @@
|
||||
try {
|
||||
if (ip.endsWith(".i2p"))
|
||||
ip = ip.substring(0, ip.length() - 4);
|
||||
d = new Destination(ip); // from b64 string
|
||||
byte[] b = Base64.decode(ip);
|
||||
if (b == null)
|
||||
throw new Exception();
|
||||
d = Destination.create(new ByteArrayInputStream(b)); // cache
|
||||
} catch (Exception e) {
|
||||
fail = true;
|
||||
msg = "bad dest " + e;
|
||||
@ -104,8 +133,8 @@
|
||||
PID pid = null;
|
||||
if (!fail) {
|
||||
try {
|
||||
pid = new PID(peer_id);
|
||||
} catch (Exception e) {
|
||||
pid = torrents.createPID(peer_id);
|
||||
} catch (IllegalArgumentException e) {
|
||||
fail = true;
|
||||
msg = "bad peer id " + e;
|
||||
}
|
||||
@ -155,15 +184,14 @@
|
||||
} catch (NumberFormatException nfe) {};
|
||||
}
|
||||
|
||||
Torrents torrents = ZzzOTController.getTorrents();
|
||||
Map<String, Object> m = new HashMap();
|
||||
Map<String, Object> m = new HashMap<String, Object>(8);
|
||||
if (fail) {
|
||||
m.put("failure reason", msg);
|
||||
} else if ("stopped".equals(event)) {
|
||||
Peers peers = torrents.get(ih);
|
||||
if (matchIP && peers != null)
|
||||
peers.remove(pid);
|
||||
m.put("interval", Integer.valueOf(INTERVAL));
|
||||
m.put("interval", torrents.getInterval());
|
||||
} else {
|
||||
Peers peers = torrents.get(ih);
|
||||
if (peers == null) {
|
||||
@ -188,7 +216,7 @@
|
||||
if (matchIP)
|
||||
p.setLeft(left);
|
||||
|
||||
m.put("interval", Integer.valueOf(INTERVAL));
|
||||
m.put("interval", torrents.getInterval());
|
||||
int size = peers.size();
|
||||
int seeds = peers.countSeeds();
|
||||
m.put("complete", Integer.valueOf(seeds));
|
||||
@ -197,20 +225,39 @@
|
||||
// snark < 0.7.13 always wants a list
|
||||
m.put("peers", java.util.Collections.EMPTY_LIST);
|
||||
} else {
|
||||
List<Peer> peerlist = new ArrayList(peers.values());
|
||||
List<Peer> peerlist = new ArrayList<Peer>(peers.values());
|
||||
peerlist.remove(p); // them
|
||||
if (want < size - 1) {
|
||||
Collections.shuffle(peerlist);
|
||||
peerlist = peerlist.subList(0, want);
|
||||
if (size > 150) {
|
||||
// If size is huge, use random iterator for efficiency
|
||||
List<Peer> rv = new ArrayList<Peer>(size);
|
||||
for (RandomIterator<Peer> iter = new RandomIterator<Peer>(peerlist); iter.hasNext(); ) {
|
||||
rv.add(iter.next());
|
||||
}
|
||||
peerlist = rv;
|
||||
} else {
|
||||
Collections.shuffle(peerlist);
|
||||
peerlist = peerlist.subList(0, want);
|
||||
}
|
||||
}
|
||||
if (compact) {
|
||||
List<String> peerhashes = new ArrayList(peerlist.size());
|
||||
for (Peer pe : peerlist) {
|
||||
peerhashes.add(pe.getHash());
|
||||
}
|
||||
// one big string
|
||||
byte[] peerhashes = new byte[32 * peerlist.size()];
|
||||
for (int i = 0; i < peerlist.size(); i++)
|
||||
System.arraycopy(peerlist.get(i).getHashBytes(), 0, peerhashes, i * 32, 32);
|
||||
m.put("peers", peerhashes);
|
||||
} else if (ALLOW_NONCOMPACT_RESPONSE) {
|
||||
// This requires the Peer entries to be Maps
|
||||
// so they can be bencoded, but we don't save
|
||||
// the full Destination any more, and Peer does not.
|
||||
// extend HashMap, to greatly reduce memory usage.
|
||||
// We could create a Map here with the b32 as the IP,
|
||||
// but that's nonstandard. So if non-compact is enabled,
|
||||
// don't return any peers.
|
||||
//m.put("peers", peerlist);
|
||||
m.put("peers", java.util.Collections.EMPTY_LIST);
|
||||
} else {
|
||||
m.put("peers", peerlist);
|
||||
// won't get here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url=index.jsp" />
|
||||
<title>zzzot</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="index.jsp">Enter</a>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,14 +1,100 @@
|
||||
<%@page import="net.i2p.zzzot.ZzzOTController" %>
|
||||
<%@page import="net.i2p.zzzot.ZzzOTController,net.i2p.zzzot.Torrents" %>
|
||||
<%@page trimDirectiveWhitespaces="true"%>
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>ZzzOT</title>
|
||||
</head><body style="background-color: #000; color: #c30; font-size: 400%;">
|
||||
<p>
|
||||
zzzot
|
||||
<p>
|
||||
<table cellspacing="8">
|
||||
<tr><td>Torrents:<td align="right"><%=ZzzOTController.getTorrents().size()%>
|
||||
<tr><td>Peers:<td align="right"><%=ZzzOTController.getTorrents().countPeers()%>
|
||||
</table>
|
||||
<meta charset="UTF-8">
|
||||
<noscript><meta http-equiv="refresh" content="300;url=."></noscript>
|
||||
<title><%=ZzzOTController.getSiteName()%> OPENTRACKER | STATS</title>
|
||||
<link href="/tracker.css" rel="stylesheet" type="text/css">
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
</head>
|
||||
<body id="stats">
|
||||
<div id="container">
|
||||
<div id="panel">
|
||||
<a href="/" title="Return to home page" alt="Return to home page"><span id="sitename"><%=ZzzOTController.getSiteName()%></span></a><hr>
|
||||
<%
|
||||
Torrents torrents = ZzzOTController.getTorrents();
|
||||
if (torrents != null) {
|
||||
%>
|
||||
<p id="totals">
|
||||
<b>Torrents:</b> <%=torrents.size()%><br>
|
||||
<b>Peers:</b> <%=torrents.countPeers()%><br>
|
||||
<%
|
||||
boolean udp = ZzzOTController.isUDPEnabled();
|
||||
if (udp) {
|
||||
%>
|
||||
<b>Total Announce Rate:</b>
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
<b>Announce Rate:</b>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
<%=String.format(java.util.Locale.US, "%.1f", ZzzOTController.getAnnounceRate())%> / minute<br>
|
||||
<b>Announce Interval:</b> <%=torrents.getInterval() / 60%> minutes<br>
|
||||
<%
|
||||
String host = ZzzOTController.b32();
|
||||
if (host != null) {
|
||||
%><b>Announce URL:</b> <a href="http://<%=host%>/a">http://<%=host%>/a</a><br><%
|
||||
}
|
||||
%>
|
||||
<b>UDP Announce Support:</b> <%=udp ? "yes" : "no"%><br>
|
||||
<%
|
||||
if (udp) {
|
||||
%>
|
||||
<b>UDP Announce Rate:</b> <%=String.format(java.util.Locale.US, "%.1f", ZzzOTController.getUDPAnnounceRate())%> / minute<br>
|
||||
<b>UDP Connection Lifetime:</b> <%=torrents.getUDPLifetime() / 60%> minutes<br>
|
||||
<%
|
||||
if (host != null) {
|
||||
int port = ZzzOTController.udpPort();
|
||||
%>
|
||||
<b>UDP Announce URL:</b> <a href="udp://<%=host%>:<%=port%>/"</a>udp://<%=host%>:<%=port%>/</a><br>
|
||||
<%
|
||||
}
|
||||
}
|
||||
%>
|
||||
</p>
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
<p id="initializing"><b><i>Initializing OpenTracker…</i></b></p>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
<%
|
||||
boolean showfooter = ZzzOTController.shouldShowFooter();
|
||||
if (showfooter) {
|
||||
%>
|
||||
<span id="footer" class="version"><%=ZzzOTController.footerText()%></span>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
setInterval(function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/tracker/?' + new Date().getTime(), true);
|
||||
xhr.responseType = "text";
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState==4 && xhr.status==200) {
|
||||
document.getElementById("stats").innerHTML = xhr.responseText;
|
||||
}
|
||||
}
|
||||
xhr.send();
|
||||
<%
|
||||
if (torrents != null) {
|
||||
%>
|
||||
}, 60000);
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
}, 15000);
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%@page import="java.util.ArrayList" %><%@page import="java.util.List" %><%@page import="java.util.Map" %><%@page import="java.util.HashMap" %><%@page import="net.i2p.zzzot.*" %><%@page import="org.klomp.snark.bencode.BEncoder" %><%
|
||||
<%@page import="java.util.ArrayList,java.util.List,java.util.Map,java.util.HashMap,net.i2p.zzzot.*,org.klomp.snark.bencode.BEncoder" %><%
|
||||
|
||||
/*
|
||||
* Above one-liner is so there is no whitespace -> IllegalStateException
|
||||
@ -27,6 +27,10 @@
|
||||
*/
|
||||
// so the chars will turn into bytes correctly
|
||||
request.setCharacterEncoding("ISO-8859-1");
|
||||
// above doesn't work for the query string
|
||||
// https://wiki.eclipse.org/Jetty/Howto/International_Characters
|
||||
// we could also do ((org.eclipse.jetty.server.Request) request).setQueryEncoding("ISO-8859-1")
|
||||
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
|
||||
java.io.OutputStream cout = response.getOutputStream();
|
||||
response.setCharacterEncoding("ISO-8859-1");
|
||||
response.setContentType("text/plain");
|
||||
@ -41,49 +45,64 @@
|
||||
if (xff != null || xfs != null) {
|
||||
fail = true;
|
||||
msg = "Non-I2P access denied";
|
||||
response.setStatus(403, msg);
|
||||
//response.setStatus(403, msg);
|
||||
response.setStatus(403);
|
||||
}
|
||||
|
||||
boolean all = info_hash == null;
|
||||
if (all && !ZzzOTController.allowFullScrape()) {
|
||||
fail = true;
|
||||
msg = "unsupported";
|
||||
}
|
||||
|
||||
Torrents torrents = fail ? null : ZzzOTController.getTorrents();
|
||||
if (torrents == null && !fail) {
|
||||
fail = true;
|
||||
msg = "tracker is down";
|
||||
}
|
||||
|
||||
InfoHash ih = null;
|
||||
if ((!all) && !fail) {
|
||||
try {
|
||||
ih = new InfoHash(info_hash);
|
||||
ih = torrents.createInfoHash(info_hash);
|
||||
} catch (Exception e) {
|
||||
fail = true;
|
||||
msg = "bad infohash " + e;
|
||||
}
|
||||
}
|
||||
|
||||
Torrents torrents = ZzzOTController.getTorrents();
|
||||
|
||||
// build 3-level dictionary
|
||||
Map<String, Object> m = new HashMap();
|
||||
Map<String, Object> m = new HashMap<String, Object>(4);
|
||||
if (fail) {
|
||||
m.put("failure reason", msg);
|
||||
} else {
|
||||
List<InfoHash> ihList = new ArrayList();
|
||||
List<InfoHash> ihList = new ArrayList<InfoHash>();
|
||||
if (all)
|
||||
ihList.addAll(torrents.keySet());
|
||||
else
|
||||
ihList.add(ih);
|
||||
Map<String, Map> files = new HashMap();
|
||||
// requires I2P 0.9.30-8
|
||||
Map<byte[], Map> files = new HashMap<byte[], Map>();
|
||||
for (InfoHash ihash : ihList) {
|
||||
Peers peers = torrents.get(ihash);
|
||||
if (peers == null)
|
||||
continue;
|
||||
Map<String, Object> dict = new HashMap();
|
||||
Map<String, Object> dict = new HashMap<String, Object>();
|
||||
int size = peers.size();
|
||||
int seeds = peers.countSeeds();
|
||||
dict.put("complete", Integer.valueOf(seeds));
|
||||
dict.put("incomplete", Integer.valueOf(size - seeds));
|
||||
dict.put("downloaded", Integer.valueOf(0));
|
||||
files.put(new String(ihash.getData(), "ISO-8859-1"), dict);
|
||||
files.put(ihash.getData(), dict);
|
||||
}
|
||||
m.put("files", files);
|
||||
}
|
||||
BEncoder.bencode(m, cout);
|
||||
try {
|
||||
BEncoder.bencode(m, cout);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// before I2P 0.9.30-8
|
||||
// just let it truncate, wasn't valid before anyway
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the newline on the last line or
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%@page import="net.i2p.crypto.SHA256Generator" %><%@page import="net.i2p.data.Base32" %><%@page import="net.i2p.data.Base64" %><%@page import="net.i2p.data.DataHelper" %><%@page import="net.i2p.zzzot.*" %><%
|
||||
<%@page import="net.i2p.crypto.SHA256Generator,net.i2p.data.Base32,net.i2p.data.Base64,net.i2p.data.DataHelper,net.i2p.zzzot.*" %><%
|
||||
|
||||
/*
|
||||
* Copyright 2010 zzz (zzz@mail.i2p)
|
||||
@ -17,71 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
String req = request.getHeader("X-Seedless");
|
||||
// extension for ease of eepget and browser
|
||||
if (req == null)
|
||||
req = request.getParameter("X-Seedless");
|
||||
// we should really put in our own b32
|
||||
String me = request.getHeader("Host");
|
||||
if (me == null)
|
||||
me = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.b32.i2p";
|
||||
// unused, we don't accept announces
|
||||
String him = request.getHeader("X-I2P-DestB32");
|
||||
String xff = request.getHeader("X-Forwarded-For");
|
||||
String xfs = request.getHeader("X-Forwarded-Server");
|
||||
|
||||
response.setContentType("text/plain");
|
||||
response.setHeader("X-Seedless", me);
|
||||
|
||||
final int US_MINUTES = 360;
|
||||
final int PEER_MINUTES = 60;
|
||||
|
||||
if (xff != null || xfs != null) {
|
||||
String msg = "Non-I2P access denied";
|
||||
response.setStatus(403, msg);
|
||||
out.println(msg);
|
||||
} else if (req == null) {
|
||||
// probe
|
||||
out.println("tracker " + US_MINUTES);
|
||||
out.println("eepsite " + US_MINUTES);
|
||||
out.println("seedless " + US_MINUTES);
|
||||
} else if (req.startsWith("announce")) {
|
||||
out.println("thanks");
|
||||
} else if (req.startsWith("locate")) {
|
||||
// ignore the search string, if any, in the request
|
||||
// us
|
||||
out.println(Base64.encode(me + ' ' + US_MINUTES + " tracker"));
|
||||
out.println(Base64.encode(me + ' ' + US_MINUTES + " seedless"));
|
||||
out.println(Base64.encode(me + ' ' + US_MINUTES + " eepsite"));
|
||||
// all the peers
|
||||
Torrents torrents = ZzzOTController.getTorrents();
|
||||
for (InfoHash ihash : torrents.keySet()) {
|
||||
Peers peers = torrents.get(ihash);
|
||||
if (peers == null)
|
||||
continue;
|
||||
for (Peer p : peers.values()) {
|
||||
// dest to b32
|
||||
String ip = (String) p.get("ip");
|
||||
if (ip.endsWith(".i2p"))
|
||||
ip = ip.substring(0, ip.length() - 4);
|
||||
String b32 = Base32.encode(SHA256Generator.getInstance().calculateHash(Base64.decode(ip)).getData()) + ".b32.i2p ";
|
||||
// service type
|
||||
String role;
|
||||
if (p.isSeed())
|
||||
role = " bt-seed";
|
||||
else
|
||||
role = " bt-leech";
|
||||
// spg wants UTF-8 but all we have is binary data, so hex it
|
||||
String ihs = DataHelper.toHexString(ihash.getData());
|
||||
String ids = DataHelper.toHexString((byte[])p.get("peer id"));
|
||||
out.println(Base64.encode(b32 + PEER_MINUTES + role +
|
||||
" info_hash=" + ihs +
|
||||
";peer_id=" + ids));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// error code
|
||||
response.setStatus(406);
|
||||
out.println("SC_NOT_ACCEPTABLE");
|
||||
}
|
||||
|
||||
%>
|
Reference in New Issue
Block a user