153 Commits

Author SHA1 Message Date
e1adb78f7d Add github sync for i2p.plugins.i2pcontrol
Some checks failed
Sync Primary Repository to GitHub Mirror / sync (push) Has been cancelled
2025-05-10 18:59:07 -04:00
zzz
1f717fa987 javadoc fixes 2018-04-15 13:21:16 +00:00
zzz
50c50ef9c7 update README 2018-02-07 16:16:42 +00:00
zzz
a18646f3a9 add change log 2018-02-07 16:00:38 +00:00
zzz
2e34ffcedc Update SSL cert params
Version 0.12.0
2018-02-07 13:47:01 +00:00
zzz
cf0d59ab61 Password change form handling 2018-02-06 14:36:31 +00:00
zzz
c5d0562493 Start of a password change form 2018-02-06 14:01:45 +00:00
zzz
0085d47d16 Command line settings for host/port/http 2018-02-03 18:03:40 +00:00
zzz
7ec4f0c52e Fixup raw type warnings 2018-02-03 14:21:45 +00:00
zzz
e40bb3a2f4 Register with PortMapper 2018-02-03 14:12:19 +00:00
zzz
cb535188b2 Don't create keystore if webapp 2018-02-03 13:16:56 +00:00
zzz
273f91b3d4 Change config file name if webapp 2018-02-03 13:05:02 +00:00
zzz
de334aa52e i2pcontrol.py enhancements 2018-02-02 17:33:11 +00:00
zzz
72e8463717 Update jBCrypt to version 0.4 2015-01-30
From: http://www.mindrot.org/projects/jBCrypt/
Previous version not indicated in checkin comments,
but was probably 0.3 2010-02-01
Changes not clear because previous version was reformatted.
Moved back to original package.
Moved license file to top level
Deleted commented-out test class.
Bundle jBCrypt license in plugin.
Update README
2018-02-02 14:48:14 +00:00
zzz
7dad6999a7 Build: Fix update/install plugins to be actually different 2018-02-02 14:00:31 +00:00
zzz
55199e666a SecurityManager:
Convert Timer to SimpleTimer2
Synch Sweeper cleanup, make more efficient
Delay first Sweeper run until min expiration
Re-enable changing password
Synch fixes
Clear tokens on shutdown
2018-02-02 13:50:14 +00:00
zzz
d5c2f6b8e3 SecurityManager:
Remove hardcoded salt; generate if necessary
Constant-time password hash comparison
Comment out unused certificate methods
AuthToken: finals
2018-02-02 13:09:47 +00:00
zzz
e58129e46a ConfigurationManager:
Use DataHelper for load/store
Synch everything
Only save if something changed
Comment out unused methods

Implement destroy() for servlet
Fix allowedhosts config
2018-02-02 11:56:30 +00:00
zzz
6aff0344a5 Update jsonrpc2 libs.
All are licensed Apache 2.0.
Overview:
http://software.dzhuvinov.com/json-rpc-2.0-server.html
Previous versions unknown, no version in checkin comments,
but were checked in 2011-06-13.
Not clear how much actually changed because
the sources here were reformatted on 2016-01-20.
This reverts a previous change to JSONRPC2Error.java that made two fields protected.

Jars from maven central:
jsonrpc2-base-1.38.1-sources.jar  22-Oct-2017
jsonrpc2-server-1.11-sources.jar  16-Mar-2015

Smart Mini files (net.minidev.json) from:
https://github.com/netplex/json-smart-v1
commit 51e1641 on Oct 23, 2015
Maven jar is 1.0.8 but we need at least 1.0.9
Based on the @since lines, this is at least version 1.3.1
Removed as unneeded: JSONNavi.java, JSONStyleIdent.java
Previous version was org.json.simple

Fix up use of deprecated methods in our code
Remove duplicate method overrides in our JSONRPC2ExtendedError.java
2018-02-01 22:53:25 +00:00
zzz
22aad9836f Add rebinding protection 2018-02-01 18:15:17 +00:00
zzz
2c9042ef7d Add version to GET info 2018-02-01 17:38:22 +00:00
zzz
16a5e8b884 Pass ctx to SecurityManager
Use ctx sha256
Add / to request path so it will work with the war
2018-02-01 17:34:08 +00:00
zzz
9edc0ae53e - Build fix
- Add war target
- Add override.properties for compiler args
- Change GET message and mime type
2018-02-01 16:36:49 +00:00
zzz
37251cbcb3 - Fixes for Jetty 9
- Convert I2PControlController to RouterApp
- Remove all static references
- Don't stop all running threads on shutdown
- Set restrictive permissions on configuration file
- Disable changing host/port/password via RPC
- Use I2P lib for setting supported ciphers
- Update makeplugin.sh
- Change signer and update URLs in plugin.config
- Don't include license, readme, and clients.config in the update
- Bundle i2pcontrol.py test script
- Make constants static and final
- Comment out unused methods
- Remove tabs in build.xml
- Remove unused dependencies in build.xml
- Clean up imports
2018-02-01 15:14:03 +00:00
zzz
44263d9b26 TODO update 2018-01-22 17:25:48 +00:00
zzz
04ac5b0bef update javac arguments 2017-03-04 15:40:23 +00:00
dev
af5d980154 Formatted format.sh 2016-01-20 18:07:31 +00:00
dev
442fb6aef4 Formatted .java files. 2016-01-20 18:04:46 +00:00
dev
be523f5e35 Added formatting support ot ant script. 2016-01-20 18:04:18 +00:00
dev
2048781b8f Moved actions around between targets. 2016-01-20 17:26:56 +00:00
dev
08b125f5e4 Fixed #1524, check type of input parameter. 2016-01-18 21:10:26 +00:00
dev
df5a9a60a5 Added support for adding and deleting Advanced Config Settings. 2016-01-18 20:44:17 +00:00
dev
03bf21ee52 Fixed #1607, where i2pcontrol is unable to switch to a new port. 2016-01-18 17:28:55 +00:00
dev
82d294b3a7 Added 'local' target to build script for faster plugin development. 2016-01-18 17:12:15 +00:00
dev
26792beb5a Switched to using the KeyStoreUtil implementation of SSL certificate generation. 2015-09-22 01:29:50 +00:00
869d302b28 Upgraded to support su3 from scripts in i2p.scripts. 2015-07-31 00:41:02 +00:00
dev
f9b3dd8aa4 Fixed i2p router version dependency 2015-07-30 18:13:46 +00:00
dev
c93203b914 Prevent NPE while shutting down after failing to start. 2015-07-30 16:49:46 +00:00
dev
1810e89a45 Fixed Java >=1.8 compatability on Raspberry Pi JREs 2015-06-28 18:29:55 +00:00
dev
45bc108e94 Fix i2pcontrol.listen.port to send exception string on failure. 2015-06-23 03:31:10 +00:00
dev
27fa1b31b0 Fix i2pcontrol.listen.port to reset to failing new ports to the old port. 2015-06-23 03:24:48 +00:00
dev
b2f23cdaad Return IP-address, not byte[]. 2015-06-09 18:43:09 +00:00
dev
44973255d8 Fixed JSON-RPC return values. 2015-06-09 18:41:40 +00:00
dev
18da573825 Bumped version to 0.1.0. 2015-06-09 17:32:18 +00:00
dev
969f6328fb Use saveConfig() in an atomic manner. 2015-06-09 15:05:14 +00:00
dev
0b011eaa72 Fail gracefully if ClientAppManager or UpdateManager is missing. 2015-06-09 14:26:10 +00:00
dev
a2928361d9 Changed name of RouterManager.CheckUpdates to RouterManager.FindUpdates. 2015-06-09 04:25:20 +00:00
dev
fcd2a54754 Add support for RouterManager.CheckUpdates and RouterManager.Update. 2015-06-09 04:06:00 +00:00
dev
8a39b639b9 Moved away from deprecated functions. 2015-06-09 03:18:18 +00:00
dev
88837ef572 UDPTransport.DEFAULT_INTERNAL_PORT deprecated in I2P. 2015-06-09 03:15:13 +00:00
dev
b9f1e3f4df Moved away from deprecated functions. 2015-06-09 03:11:26 +00:00
dev
66d3ea28e0 lazygravy: Make i2p.router.net.ssu.detectedip return actual ip 2015-04-14 03:06:41 +00:00
dev
eecdde0548 lazygravy: Verify that input is correctly formatted 2015-04-14 02:57:42 +00:00
dev
72aad1872e merge of '07e6170989c18bbe37502d87da185526159caad4'
and '788244417dd79a3834b0e08941161ea6cb2af0b8'
2015-04-14 02:44:39 +00:00
dev
1a25505425 Fix trac #606 2015-04-02 16:42:00 +00:00
zzz
58f1b3cfa2 add max-jetty-version 2014-10-24 14:46:56 +00:00
dev
7936f753bc Removed legacy code. Incremented version number. 2014-09-26 19:15:48 +00:00
zzz
0cb0a307e6 Fixes for data structures moving in the router.
Set min-i2p-version=0.9.16
2014-09-23 15:25:13 +00:00
dev
b17dcc6198 Added support for the crypto API of Java 8 2014-06-15 12:43:44 +00:00
dev
9f2601d3ac Changed version number to 0.0.7
Changed version to 0.0.7
2013-11-13 02:19:43 +00:00
zzz
81e99b319b Set min-i2p-version=0.9.8 as we are using fields and methods
only present as of that version.
2013-11-08 16:46:18 +00:00
4c99ab0402 Remove Jetty version from library paths
To build, i2p.i2p must be built first.
2013-11-05 23:15:34 +00:00
bf822dad13 Updated for changes in i2p.i2p 2013-11-05 02:51:04 +00:00
3d2c3aeb50 Updated paths to Jetty libs 2013-11-05 02:18:04 +00:00
dev
bd0b7ebbb2 Changed a few build paths to match that of i2p 0.9.6. 2013-05-28 09:27:25 +00:00
dev
83bfdf00a2 Updated locations for jetty libs in i2p.i2p. 2013-05-28 08:44:10 +00:00
dev
391c84cf76 Reversed the previous commit. 2013-02-06 17:09:19 +00:00
dev
b8512b66d9 Added max jetty-version 6.9999. 2013-02-06 17:01:07 +00:00
dev
90155bd60c Migrated to jetty7. Reflected changes in the I2P API. 2013-01-13 13:25:01 +00:00
c721c9ab48 Finished fixing inconsistent whitespace 2012-10-16 04:14:12 +00:00
8c2e870068 Fixed inconsistent whitespace 2012-10-16 03:52:09 +00:00
dev
1eb1769ef4 Added compability with 0.9.1 builds of I2P. 2012-08-28 18:59:58 +00:00
dev
9e2a8b0c1b Fixed typo. 2012-03-06 20:36:06 +00:00
dev
64ba16d4f3 Changed jetty support. 2012-03-02 20:22:00 +00:00
dev
9673965345 Ported from Jetty5->6. Bumped version numbers. 2012-02-29 21:03:23 +00:00
zzz
541d9ad5da Use ${ant.home}/lib/ant.jar instead of pulling ant.jar from Jetty 2012-01-23 16:53:43 +00:00
dev
da4409c539 New version nbr. 2012-01-15 22:55:38 +00:00
dev
e6843e2781 Added max jetty version flag. 2012-01-15 22:53:41 +00:00
dev
b043b4b589 Make the plugin shut down properly.
Moved SecurityManager from a static to a Singleton design.
2011-08-03 11:44:56 +00:00
dev
0ea28c8b5b Improved listen ip/port selection implemented. 2011-08-02 11:58:34 +00:00
dev
434042a9d1 Implemented safegaurd against bad listen addresses. 2011-08-01 12:05:57 +00:00
dev
7468ac3d14 Finalised listening address changes. 2011-08-01 12:00:31 +00:00
dev
f57fa701d0 Added support for changing listen address. 2011-08-01 10:02:03 +00:00
dev
e1d292fff0 Changed keys for some RouterInfo features. 2011-07-31 08:38:37 +00:00
dev
6bf929b62a Changed how the TCP port is represented. If using 'set to whatever UDP port is set to', present the TCP port as thesame number as the UDP port. 2011-07-27 10:26:37 +00:00
dev
7c90ced960 Fixed forcing final static FULL_VERSION string to be read via java reflections as to prevent it from being inlined in the bytecode. 2011-07-26 17:42:15 +00:00
dev
58a91be062 Renamed handlers to be more desciptive.
Send RouterInfo netstatus via enumerators rather than status strings.
2011-07-26 14:33:04 +00:00
dev
b897fc7e0f Load keystore from correct location as defined by KeyStoreFactory.
Removed duplicate&faulty keystore location.
2011-07-26 11:10:54 +00:00
dev
4a347ea086 Added router count features to RouterInfo method.
Disabled debug logging in ConfigurationManager.
2011-07-26 09:58:19 +00:00
dev
2541c66292 merge of '80815bd255483fdefdf99987d5dfe7220a948a15'
and '923f6825f7dd01c478010cc3e023f2b7bd503f8b'
2011-07-25 13:34:07 +00:00
dev
d8e9639d0f Maybe really fixed the permission issue this time. 2011-07-25 10:25:01 +00:00
dev
32820a06ab Fixed saving of settings to actually be run. 2011-07-25 07:38:06 +00:00
dev
1d263da0df Read/Write keystore to plugin dir instead of wherever I2PControl is launched from.
Bumped version.
2011-07-25 06:58:51 +00:00
dev
d57af170b3 Added missing class. 2011-07-22 14:06:15 +00:00
dev
1a6c1f56e8 Moved API argument to only exist in the Authenticate message. 2011-07-22 13:36:18 +00:00
dev
70afd93f2f Cleaned up build scripts.
Removed cruddy files from repo.
Added support for API versioning. Version 1 is the current one.
2011-07-22 12:19:16 +00:00
dev
563e85fe70 Removed unused parameters.
Fixed non-static usage of static function.
2011-07-22 09:06:58 +00:00
dev
9e61a99c6d Removed legacy logging&settings servlets. 2011-07-22 09:01:45 +00:00
dev
d54dce8c25 Me make english less baddy. 2011-07-21 14:08:44 +00:00
dev
92739a725f Changed update url. 2011-07-21 12:47:29 +00:00
dev
fdfb187bde merge of '46a47126c045aac7cc5c9781bb34549f52245931'
and 'ea8d90a320b7b00a6afaba1425c09a5503137f8b'
2011-07-21 12:44:54 +00:00
dev
76fd7d3130 Changed plugin website. 2011-07-21 12:44:51 +00:00
dev
b072978cfb Fixed proper license. 2011-07-20 14:24:58 +00:00
dev
48617ebf19 merge of '29012733dc3447224cbf8b0d62310bfa8effe860'
and 'd04d83184f51ae2af172f7274784eecc0db00dc8'
2011-07-20 14:15:24 +00:00
dev
8edfcc9c02 Removed legacy files. 2011-07-20 14:15:20 +00:00
dev
7a26124025 Added JSONRPC2 handler for I2PControl method. 2011-07-20 14:14:16 +00:00
dev
959fe71f32 Fixed Rates to manually coalesce to make sure that Rates with short periods work as intended. 2011-07-20 13:36:34 +00:00
dev
a70177ec64 Added support for port/password changing of I2PControl in accordance with API document. 2011-07-19 14:10:06 +00:00
dev
a6ae4c8405 Switched to I2Ps implementation of Base64.
Added support for the RouterInfo API call.
Added support for the RouterRunner API call.
2011-07-15 12:46:08 +00:00
dev
fadfd0d0cf Removed old ConfigurationManager mockup.
Add methods to set values of settings in ConfigurationManager.
2011-07-14 06:08:42 +00:00
dev
3e861bb749 Removed dependency on bouncycastle. Now uses a undocumented method JDK method for signing certificates however. The same method is as used by keytool. 2011-07-08 11:22:22 +00:00
dev
8fa7c75e24 Removed code for discarded API features. 2011-07-08 10:07:45 +00:00
dev
3fa499b198 Added support for starting a dummy router.
Added support for detecting whether I2PControl is run from a .jar (via RouterConsole).
Revised build.xml to include new libraries and remove old libraries.
Refactored NetworkInfo and changed name inte NetworkSetting.
2011-07-08 09:48:37 +00:00
dev
48d1ff2915 Enabled saving of config file. 2011-07-05 14:09:50 +00:00
dev
50667e8196 Fixed bad cast in StatHandler. 2011-07-05 12:30:50 +00:00
dev
7c0553c311 Fetched error code from appropriate place. 2011-07-05 11:29:08 +00:00
dev
07f2db8d15 Removed redunant declaration of error codes. 2011-07-05 11:27:33 +00:00
dev
b4d71d1bc9 Removed JSONRPC2 error type/code. 2011-07-05 11:25:05 +00:00
dev
ffaabdd9af Clarified error. 2011-07-05 11:19:18 +00:00
dev
27d3df3403 Added support for passing authentication tokens over ssl.
JSONRPC2 request (other than the authentication message) now require a valid token to be provided.
2011-07-05 10:48:48 +00:00
dev
65a8435141 Removed debug.
Changed getHash to return the Base64 of the SHA-256 of the input.
2011-07-01 14:13:52 +00:00
dev
acc97b01c9 Renamed JSONRPCServlet -> JSONRPC2Servlet.
Added package for JSONRPC2 handlers.
Added handler for providing authentication tokens, tokens will be needed later on to interface with the JSONRPC2Servlet.
2011-07-01 13:22:47 +00:00
dev
ad4e96cf4c Added read/write config file support.
Configuration request with no value will save the defaultValue provided.
2011-07-01 08:16:26 +00:00
dev
5a0b34889d Added jBcrypt for secure hashing of paswords. 2011-07-01 07:14:38 +00:00
dev
be4eb2ed39 Changed getDefaultKeyStore method to update static keystroe variable. 2011-06-30 07:38:01 +00:00
dev
8f56d68bb7 Added support for ssl in the server.
Added support for key/cert generation.
Added keystore support.
2011-06-30 07:04:45 +00:00
dev
38468b278e Removed debugging from output stream. 2011-06-27 11:39:24 +00:00
dev
868f990f13 Added clients.conf 2011-06-20 06:52:40 +00:00
dev
2ffc33eda0 Disabled console webapp. 2011-06-17 11:48:46 +00:00
dev
435a667acb Added Absolute singleton to work around multiple classloader symptoms. 2011-06-16 08:59:41 +00:00
dev
571240e349 Cleanup and webapp testing. 2011-06-13 14:36:09 +00:00
dev
4e05b4df06 Imported Apache 2 licensed json-rpc 2.0 libs from http://software.dzhuvinov.com 2011-06-13 13:38:37 +00:00
dev
68e42522f8 Cleaning.. 2011-06-13 13:23:13 +00:00
dev
1139824349 Removed unecessary config. 2011-06-13 13:18:19 +00:00
dev
87ee709eac Apparently I need config files.. 2011-06-13 12:50:39 +00:00
dev
7e3ca87c14 Added index.jsp for testing and dev purposes. 2011-06-13 12:47:22 +00:00
dev
7d803faaf8 Removed crud. 2011-06-13 12:45:11 +00:00
dev
c6058b0ee9 Cleanup. 2011-06-13 12:42:05 +00:00
dev
3eec980855 Finally builds a loading plugin. 2011-06-13 12:37:56 +00:00
dev
275d3d707f Removed old zzzot jsp. Patched build.xml with correct name. 2011-06-13 11:08:48 +00:00
dev
dac6c2a26e Fixed bad package name. 2011-06-13 11:04:48 +00:00
dev
1fcfb176ba Cleanup of plugins/eepsite 2011-06-13 10:04:24 +00:00
dev
7d36216ab0 Managed to get plugin installing correctly. 2011-06-10 11:38:58 +00:00
dev
153bb23f0c Changed package names and corresponding imports. 2011-06-10 08:33:43 +00:00
dev
433f786a50 Various string changes. The code heist is only beginning.. 2011-06-10 08:05:16 +00:00
dev
f354dd9c30 Builds, unfortunately into zzzot. 2011-06-10 06:45:25 +00:00
dev
4f80d19e3a Migration to I2PControl 2011-06-10 06:42:24 +00:00
dev
c636af7ead Steal zzz's zzzot tracker. Start migration to I2PControl 2011-06-10 06:40:36 +00:00
zzz
6e3b85ac97 0.5:
Final compact response format
2010-07-11 14:42:42 +00:00
zzz
48687daccc 0.4:
compact request/response support - may not be final format
Fix NPE if no ip parameter
2010-07-09 16:31:24 +00:00
zzz
66667de240 0.3: verify dest 2010-04-13 17:41:24 +00:00
zzz
eda5699f38 add xfs check 2010-04-12 22:01:50 +00:00
zzz
902377b92a 0.2:
cache b64 dest strings
help typo fix (thx duck)
lots of seedless fixes
build cleanups
2010-03-24 03:01:38 +00:00
104 changed files with 15859 additions and 1583 deletions

66
.github/workflows/sync.yaml vendored Normal file
View 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.i2pcontrol.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: {}

62
CHANGES.txt Normal file
View File

@ -0,0 +1,62 @@
Version 0.12.0 2018-02-07 zzz
* Fixes for Jetty 9 / I2P 0.9.30
* Convert to RouterApp interface
* Register with PortMapper
* HTML password change form
* Update SSL cert parameters
* Convert Timer to SimpleTimer2
* Remove hardcoded salt; generate if necessary
* Constant-time password hash comparison
* Update jBCrypt to version 0.4 2015-01-30
* Update jsonrpc2 libs: Base 1.38.1; Server 1.11; Mini 2015-10-23
* Update makeplugin.sh
* Add DNS rebinding protection
* Implement destroy() for servlet
* Don't stop all running threads on shutdown
* Set restrictive permissions on configuration file
* Disable changing host/port via RPC
* Use I2P libs for setting supported ciphers and for configuration file
* Change maintainer, signer, and update URLs
* Smaller update file
* i2pcontrol.py enhancements and options
* Bundle i2pcontrol.py test script
* Add support for building and running as a console webapp
* Remove all static references
* Various code cleanups
Version 0.11 2016-01-18 hottuna
* Implemented AdvancedSettings RPC method.
* Fix 2 bugs.
Version 0.1.0
* Fix 2 bugs.
Version 0.0.9
* Add support for I2P 0.9.16.
Version 0.0.8
* Add support for Java 8.
Version 0.0.7
* Add support for I2P v0.9.8 and greater.
Version 0.0.6
* Migrated to jetty7. Reflected changes in the API.
Version 0.0.5
* I2PControl has been updated to reflect changes in the reseed API of I2P.
Version 0.0.4
* I2PControl has been ported to Jetty6 as I2P is no longer including Jetty5. Shouldn't affect end users.
Version 0.0.3
* Switched signature file of I2PControl. Meaning that updates have to be made from scratch.
Version 0.0.2
* Support for monitoring netdb status and initiating a reseed if needed.</tt><br>
* Added support for changing which IP addresses I2PControl accepts. 127.0.0.1 is default, 0.0.0.0 is an option.</tt><br>
* Improved looks by realigning components and adding gradients to panels.
Version 0.0.1
* Added graphs.
* Added support for changing the port of I2PControl.

18
LICENSE-jBCrypt.txt Normal file
View File

@ -0,0 +1,18 @@
jBCrypt is subject to the following license:
/*
* Copyright (c) 2006 Damien Miller <djm@mindrot.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

View File

@ -1,4 +1,192 @@
Copyright 2010 zzz (zzz@mail.i2p)
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2011 uRobert Foss / hottuna
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -12,18 +200,3 @@
See the License for the specific language governing permissions and
limitations under the License.
========================================================================
Includes code from Jetty 5.1.15:
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.

View File

@ -1,42 +1,40 @@
This is a very simple in-memory open tracker, wrapped into an I2P plugin.
I2PControl
Server implementing JSON-RPC2 API for remote control of I2P.
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 .
See i2pcontrol.py for a test client.
Default host is 127.0.0.1.
Default port is 7650.
Default password is "itoopie".
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.
You may change the API password via the API,
or via a browser at https://127.0.0.1:7650/
I2P source must be installed and built in ../i2p.i2p to compile this package.
Version 1 API specification:
http://i2p-projekt.i2p/en/docs/api/i2pcontrol
https://geti2p.net/en/docs/api/i2pcontrol
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.
Version 2 API proposal:
http://i2p-projekt.i2p/spec/proposals/118-i2pcontrol-api-2
https://geti2p.net/spec/proposals/118-i2pcontrol-api-2
Valid announce URLs:
/a
/announce
/announce.jsp
/announce.php
/tracker/a
/tracker/announce
/tracker/announce.jsp
/tracker/announce.php
To build as a router console plugin: ant
To build as a router console webapp: ant war
Valid scrape URLs:
/scrape
/scrape.jsp
/scrape.php
/tracker/scrape
/tracker/scrape.jsp
/tracker/scrape.php
Command line test client:
scripts/i2pcontrol.py in this package
The tracker also responds to seedless queries at
/Seedless/index.jsp
GUI client itoopie version 0.3 (2015-03-02):
Clearnet installer: https://github.com/robertfoss/itoopie.net/raw/master/files/itoopie-install.exe
Clearnet SHA512: https://raw.githubusercontent.com/robertfoss/itoopie.net/master/files/itoopie-install.exe.sha512
I2P installer: http://stats.i2p/i2p/plugins/others/itoopie-install.exe
I2P SHA512: http://stats.i2p/i2p/plugins/others/itoopie-install.exe.sha512
Source: i2p.itoopie branch in monotone, or https://github.com/i2p/i2p.itoopie
java -jar itoopie-install.exe to install on non-Windows.
You may use the rest of the eepsite for other purposes, for example you
may place torrent files in eepsite/docroot/torrents.
Discussion forum:
http://zzz.i2p/forums/16
Bugs:
Report on above forum, or http://trac.i2p2.i2p/ or https://trac.i2p2.de/
License: Apache 2

View File

@ -1,19 +1,8 @@
Configuration file:
- interval
- clean time
- max peers in response
- disable full scrapes
- disable all scrapes
- disable seedless
http://zzz.i2p/topics/888
Stop the cleaner
https://geti2p.net/spec/proposals/118-i2pcontrol-api-2
Throttles:
- full scrapes
- per-requestor
Bans:
- refuse non-GETs
Verifier:
- Check dest vs. b32 in header
http://zzz.i2p/topics/2030
Prep for bundling into router package
Review auth requirements and implementation
bcrypt merge or move to PasswordManager

102
build.xml
View File

@ -1,67 +1,111 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<project basedir="." default="all" name="zzzot">
<target name="all" depends="clean,plugin" />
<project basedir="." default="all" name="I2PControl">
<target name="war" >
<!-- Include property files so that values can be easily overridden.
Users should create an override.properties file to make changes.
-->
<property file="override.properties"/>
<target name="all" depends="clean,plugin,release" />
<target name="local" depends="clean,plugin">
<property name="i2p.plugindir" value="${user.home}/.i2p/plugins/I2PControl" />
<delete dir="${i2p.plugindir}"/>
<mkdir dir="${i2p.plugindir}"/>
<mkdir dir="${i2p.plugindir}/lib"/>
<copy file="src/build/I2PControl.jar" todir="${i2p.plugindir}/lib" overwrite="true" />
<copy todir="${i2p.plugindir}" >
<fileset dir="plugin" includes="**"/>
</copy>
</target>
<target name="jar">
<ant dir="src" target="build" />
</target>
<target name="plugin" depends="war">
<delete file="plugin/i2ptunnel.config" />
<target name="war" depends="clean" >
<ant dir="src" target="war" />
<copy file="src/build/jsonrpc.war" todir="." />
</target>
<target name="plugin" depends="jar">
<!-- get version number -->
<buildnumber file="scripts/build.number" />
<property name="release.number" value="0.1" />
<!-- change in I2PControlVersion.java also! -->
<property name="release.number" value="0.12.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" />
<mkdir dir="plugin/lib"/>
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
<exec executable="pack200" failonerror="true">
<arg value="--no-gzip"/>
<arg value="--effort=9"/>
<arg value="plugin/lib/I2PControl.jar.pack" />
<arg value="src/build/I2PControl.jar" />
</exec>
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="update-only=true" />
</exec>
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="version=${release.number}-b${build.number}" />
</exec>
<exec executable="pack200" failonerror="true">
<arg value="-g" />
<arg value="plugin/lib/zzzot.jar.pack" />
<arg value="src/build/zzzot.jar" />
</exec>
<exec executable="pack200" failonerror="true">
<arg value="-g" />
<arg value="plugin/eepsite/webapps/tracker.war.pack" />
<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="I2PControl.xpi2p" tofile="I2PControl-update.xpi2p" overwrite="true" />
<move file="I2PControl.su3" tofile="I2PControl-update.su3" overwrite="true" />
<!-- make the install xpi2p -->
<copy file="scripts/i2ptunnel.config" todir="plugin/" overwrite="true" />
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
<copy file="LICENSE-jBCrypt.txt" todir="plugin/" overwrite="true" />
<copy file="README.txt" todir="plugin/" overwrite="true" />
<copy file="scripts/clients.config" todir="plugin/" overwrite="true" />
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
<copy file="scripts/i2pcontrol.py" todir="plugin/" overwrite="true" />
<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>
<target name="release" depends="plugin" />
<target name="distclean" depends="clean" />
<target name="format">
<exec executable="scripts/format.sh" failonerror="true" />
</target>
<target name="clean" >
<ant dir="src" target="clean" />
<delete file="plugin/i2ptunnel.config" />
<delete file="plugin/clients.config" />
<delete file="plugin/plugin.config" />
<delete file="plugin/lib/zzzot.jar.pack" />
<delete file="plugin/eepsite/webapps/tracker.war.pack" />
<delete file="plugin/console/webapp.config" />
<delete file="plugin/lib/I2PControl.jar.pack" />
<delete file="plugin/console/webapps/I2PControl.war.pack" />
<delete file="plugin/LICENSE.txt" />
<delete file="plugin/LICENSE-jBCrypt.txt" />
<delete file="plugin/README.txt" />
<delete file="zzzot.xpi2p" />
<delete file="zzzot-update.xpi2p" />
<delete file="plugin/i2pcontrol.py" />
<delete file="I2PControl.xpi2p" />
<delete file="I2PControl-update.xpi2p" />
<delete file="I2PControl.su3" />
<delete file="I2PControl-update.su3" />
<delete file="jsonrpc.war" />
</target>
</project>

View File

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

View File

@ -1,2 +0,0 @@
User-agent: *
Disallow:

View File

@ -1,56 +0,0 @@
<html><head><title>ZzzOT Plugin Help</title></head>
<body style="background-color: #ddd; color: #a30;">
<h2>Welcome to the ZzzOT I2P Plugin!</h2>
A new eepsite tunnel and Jetty server have been started for your open tracker.
<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.
<p>Report bugs or add comments on
<a href="htp://zzz.i2p//forums/16">the plugin forum on zzz.i2p</a>.
<h3>Eepsite Key and Helpful Hints for I2P</h3>
<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: &nbsp;&nbsp;&nbsp;<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>.
</body></html>

View File

@ -1,186 +0,0 @@
<?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">
<!-- ========================================================================= -->
<!-- This file configures the Jetty server. -->
<!-- All changes require a restart of I2P. -->
<!-- -->
<!-- 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 -->
<!-- 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 . -->
<!-- -->
<!-- Jetty errors and warnings will appear in wrapper.log, check there -->
<!-- to diagnose problems. -->
<!-- ========================================================================= -->
<!-- =============================================================== -->
<!-- Configure the Jetty Server -->
<!-- =============================================================== -->
<Configure class="org.mortbay.jetty.Server">
<!-- =============================================================== -->
<!-- Configure the Request Listeners -->
<!-- =============================================================== -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add and configure a HTTP listener to port 8080 -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Call name="addListener">
<Arg>
<New class="org.mortbay.http.SocketListener">
<Arg>
<New class="org.mortbay.util.InetAddrPort">
<Set name="host">127.0.0.1</Set>
<Set name="port">7662</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>
<!-- =============================================================== -->
<!-- Configure the Contexts -->
<!-- =============================================================== -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- 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>
</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>
</Configure>

View File

@ -1,8 +1,8 @@
clientApp.0.main=net.i2p.zzzot.ZzzOTController
clientApp.0.name=ZzzOT
clientApp.0.main=net.i2p.i2pcontrol.I2PControlController
clientApp.0.name=I2PControl
clientApp.0.args=-d $PLUGIN start
clientApp.0.stopargs=-d $PLUGIN stop
clientApp.0.delay=15
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
clientApp.0.classpath=$PLUGIN/lib/I2PControl.jar

18
scripts/format.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh
if ! command -v astyle >/dev/null 2>&1; then
echo "astyle required, but couldn't be found."
exit 1
fi
echo "Formating source files..."
# Modified kdelibs coding style as defined in
# http://techbase.kde.org/Policies/Kdelibs_Coding_Style
find -regex ".*\.\(java\)" -exec \
astyle --mode=java --indent=spaces=4 \
--indent-labels --pad-oper --unpad-paren --pad-header \
--keep-one-line-statements --convert-tabs \
--indent-preprocessor "{}" \;
echo "Done!"

361
scripts/i2pcontrol.py Executable file
View File

@ -0,0 +1,361 @@
#!/usr/bin/env python
#
# If it fails "No module named yaml"
# then sudo apt install python-yaml
#
import argparse
import json
import urllib2
import httplib
import socket
import ssl
import sys
import yaml
from urllib2 import HTTPError, URLError
from string import whitespace
# Info about requestable data can be found at https://geti2p.net/i2pcontrol.html & https://geti2p.net/ratestats.html
address = "127.0.0.1" # Default I2PControl Address
port = 7650 # Default I2PControl Port
usessl = 1 # Change to 0 for HTTP
apiPassword = "itoopie" # Default I2PControl password
## Do not edit below
apiVersion = 1 # Default API Version
msgId = 1
token = None
def checkToken():
global token
if (token == None):
token = getToken()
if (token == None):
print("Unable to login. Quitting..")
sys.exit()
def getToken():
loginStr = "{\"id\":" + str(msgId) + ", \"method\":\"Authenticate\",\"params\":{\"API\":" + str(apiVersion) + ", \"Password\":\"" + apiPassword + "\"}, \"jsonrpc\":\"2.0\"}"
try:
jsonResp = sendMsg(loginStr)
return jsonResp.get("result").get("Token")
except HTTPError, e:
print("HTTPError: %s" % e.reason)
except URLError, e:
print("URLError: %s" % e.reason)
def getRate(rateName, ratePeriod):
checkToken()
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"GetRate\",\"params\":{\"Stat\":\"" + rateName + "\", \"Period\":" + str(ratePeriod) + ", \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
jsonResp = sendMsg(msgStr)
return jsonResp.get("result").get("Result")
def getRouterInfo(infoName):
checkToken()
## The parameter names in 'params' defines which answers are requested
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"RouterInfo\",\"params\":{\""+infoName+"\":\"\", \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
jsonResp = sendMsg(msgStr)
return jsonResp.get("result").get(infoName)
def getControlInfo(infoName):
checkToken()
## The parameter names in 'params' defines which answers are requested
if ("=" in infoName):
toks = infoName.split("=", 2);
infoName = toks[0];
infoValue = toks[1];
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"I2PControl\",\"params\":{\""+infoName+"\":\""+infoValue+"\", \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
else:
return "Parameter value required for " + infoName
jsonResp = sendMsg(msgStr)
return "I2PControl setting " + infoName + " set to " + infoValue
def getRouterManagerInfo(infoName):
checkToken()
## The parameter names in 'params' defines which answers are requested
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"RouterManager\",\"params\":{\""+infoName+"\":\"\", \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
jsonResp = sendMsg(msgStr)
if (infoName == "FindUpdates" or infoName == "Update"):
return jsonResp.get("result").get(infoName)
else:
return "Sent Router Manager command: " + infoName
def getNetworkInfo(infoName):
checkToken()
isset = 0;
## The parameter names in 'params' defines which answers are requested
if ("=" in infoName):
toks = infoName.split("=", 2);
isset = 1;
infoName = toks[0];
infoValue = toks[1];
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"NetworkSetting\",\"params\":{\""+infoName+"\":\""+infoValue+"\", \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
else:
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"NetworkSetting\",\"params\":{\""+infoName+"\":null, \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
jsonResp = sendMsg(msgStr)
if (isset == 1):
return "Network setting " + infoName + " set to " + infoValue
else:
return jsonResp.get("result").get(infoName)
def getAdvancedInfo(infoName):
checkToken()
isset = 0;
## The parameter names in 'params' defines which answers are requested
if (infoName == "GetAll"):
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"AdvancedSettings\",\"params\":{\""+infoName+"\":\"\", \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
elif (infoName == "SetAll"):
return "SetAll unsupported"
elif ("=" in infoName):
toks = infoName.split("=", 2);
isset = 1;
infoName = toks[0];
infoValue = toks[1];
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"AdvancedSettings\",\"params\":{\"set\":{\""+infoName+"\":\""+infoValue+"\"}, \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
else:
msgStr = "{\"id\":" + str(msgId) + ", \"method\":\"AdvancedSettings\",\"params\":{\"get\":\""+infoName+"\", \"Token\":\"" + token +"\" }, \"jsonrpc\":\"2.0\"}"
jsonResp = sendMsg(msgStr)
if (infoName == "GetAll"):
return jsonResp.get("result").get(infoName)
elif (isset == 1):
return "Advanced configuration " + infoName + " set to " + infoValue
else:
return jsonResp.get("result").get("get").get(infoName)
def sendMsg(jsonStr):
global msgId
https_handler = UnauthenticatedHTTPSHandler()
url_opener = urllib2.build_opener(https_handler)
if (usessl != 0):
handle = url_opener.open("https://"+address+":"+ str(port) + "/jsonrpc/", jsonStr)
else:
handle = url_opener.open("http://"+address+":"+ str(port) + "/jsonrpc/", jsonStr)
response = handle.read()
handle.close()
msgId = msgId + 1;
jsonResp = json.loads(response)
if (jsonResp.has_key("error")):
print ("Remote server: I2PControl Error: " + str(jsonResp.get("error").get("code")) + ", " + jsonResp.get("error").get("message"))
sys.exit()
return jsonResp
###
# Overrides the version in httplib so that we can ignore server certificate authenticity
###
class UnauthenticatedHTTPSConnection(httplib.HTTPSConnection):
def connect(self):
#
sock = socket.create_connection((self.host, self.port), self.timeout)
if self._tunnel_host:
self.sock = sock
self._tunnel()
self.sock = ssl.wrap_socket(sock,
cert_reqs=ssl.CERT_NONE)
###
# HTTPS handler which uses SSLv3 and ignores server cert authenticity
###
class UnauthenticatedHTTPSHandler(urllib2.HTTPSHandler):
def __init__(self, connection_class = UnauthenticatedHTTPSConnection):
self.specialized_conn_class = connection_class
urllib2.HTTPSHandler.__init__(self)
def https_open(self, req):
return self.do_open(self.specialized_conn_class, req)
def zabbix_config(fileName, outfile):
yamlDict = dict()
for line in open(fileName):
li=line.strip()
if li.startswith("UserParameter"):
i2pCtrlOpt = li.strip("UserParameter=").split(",")
i2pCtrlOpt[1] = i2pCtrlOpt[1].split()
i2pCtrlOpt[1].pop(0) # Remove path of this script (i2pcontrol)
i2pCtrlParams = i2pCtrlOpt[1]
#print i2pCtrlOpt #Delete me!
result = ""
if (i2pCtrlParams[0] == "-i" or i2pCtrlParams[0] == "--router-info"):
result = getRouterInfo(i2pCtrlParams[1])
elif (i2pCtrlParams[0] == "-s" or i2pCtrlParams[0] == "--rate-stat"):
result = getRate(i2pCtrlParams[1], i2pCtrlParams[2])
else:
result = "Bad query syntax."
yamlDict[i2pCtrlParams[1]] = result
#print yaml.dump(yamlDict)
yaml.dump(yamlDict, open(outfile,'w'))
def from_file(infile, parameter):
try:
yamlDict = yaml.load(open(infile,'r'))
print yamlDict[parameter]
except IOError, e:
print "File \""+ infile +"\" couldn't be read."
def main():
global address
global port
global usessl
parser = argparse.ArgumentParser(description='Fetch I2P info via the I2PControl API.')
parser.add_argument("-l",
"--host",
nargs=1,
metavar="host",
dest="address",
action="store",
help="Listen host address of the i2pcontrol server")
parser.add_argument("-p",
"--port",
nargs=1,
metavar="port",
dest="port",
action="store",
help="Port of the i2pcontrol server")
parser.add_argument("-x",
"--no-ssl",
dest="http",
action="store_true",
help="Use HTTP instead of HTTPS")
parser.add_argument("-i",
"--router-info",
nargs=1,
metavar="info",
dest="router_info",
action="store",
help="Request info such as I2P version and uptime. Returned info can be of any type. Full list of options at https://geti2p.net/i2pcontrol.html. Usage: \"-i i2p.router.version\"")
parser.add_argument("-c",
"--i2pcontrol-info",
nargs=1,
metavar="key[=value]",
dest="i2pcontrol_info",
action="store",
help="Change settings such as password. Usage: \"-c i2pcontrol.password=foo\"")
parser.add_argument("-r",
"--routermanager-info",
nargs=1,
metavar="command",
dest="routermanager_info",
action="store",
help="Send a command to the router. Usage: \"-r FindUpdates|Reseed|Restart|RestartGraceful|Shutdown|ShutdownGraceful|Update\"")
parser.add_argument("-n",
"--network-info",
nargs=1,
metavar="key[=value]",
dest="network_info",
action="store",
help="Request info such as bandwidth. Usage: \"-n i2p.router.net.bw.in[=xxx]\"")
parser.add_argument("-a",
"--advanced-info",
nargs=1,
metavar="key[=value]",
dest="advanced_info",
action="store",
help="Request configuration info. Usage: \"-a GetAll|foo[=bar]\"")
parser.add_argument("-s",
"--rate-stat",
nargs=2,
metavar=("rateStatName", "period"),
dest="rate_stat",
action="store",
help="Request info such as bandwidth, number active peers, clock skew, etc.. The period is measured in ms and must be longer than 60s. Full list at https://geti2p.net/ratestats.html. Usage: \"-s bw.receiveBps 3600000\"")
parser.add_argument("-z",
"--zabbix",
nargs=2,
metavar=("\"path to zabbix_agent.conf\"", "\"path to output file\""),
dest="zabbix",
action="store",
help="Parse options to request, by reading a zabbix config file for \"UserParameter\"s relating to I2P. Usage: \"-z /etc/zabbix/zabbix_agent.conf\"")
parser.add_argument("-f",
"--from-file",
nargs=1,
metavar=("\"path to input file\""),
dest="from_file",
action="store",
help="Parse options to request, by reading a zabbix config file for \"UserParameter\"s relating to I2P. Usage: \"-z /etc/zabbix/zabbix_agent.conf\"")
if (len(sys.argv) == 1):
parser.parse_args(["-h"])
options = parser.parse_args()
# todo we don't check all the options
if ((options.rate_stat != None) and (options.router_info != None)):
print("Error: Choose _one_ option. \n\n")
parser.parse_args(["-h"])
# todo we don't check all the options
if ((options.zabbix != None) and ((options.rate_stat != None) or (options.router_info != None) or (options.from_file != None))):
print("Error: Don't combine option --zabbix with other options.\n")
parser.parse_args(["-h"])
# From-file can only be used when either router-info or rate-stat is enabled.
# todo we don't check all the options
if ((options.from_file != None) and (options.rate_stat == None) and (options.router_info == None)):
print("Error: --from-file must be used with either --router-info or --rate-stat.\n")
parser.parse_args(["-h"])
if (options.port != None):
port = int(options.port[0]);
if (options.address != None):
address = options.address[0];
if (options.http):
usessl = 0;
if (options.from_file != None):
if (options.router_info != None):
from_file(options.from_file[0], options.router_info[0])
if (options.rate_stat != None):
from_file(options.from_file[0], options.rate_stat[0])
sys.exit()
if (options.rate_stat != None):
try:
period = int(options.rate_stat[1])
if (period < 60000):
raise ValueError
print getRate(options.rate_stat[0], period)
except ValueError, e:
print("Error: \""+options.rate_stat[1]+"\" is not an integer > 60000 \n\n")
parser.parse_args(["-h"])
sys.exit()
if (options.router_info != None):
print getRouterInfo(options.router_info[0])
sys.exit()
if (options.i2pcontrol_info != None):
print getControlInfo(options.i2pcontrol_info[0])
sys.exit()
if (options.routermanager_info != None):
print getRouterManagerInfo(options.routermanager_info[0])
sys.exit()
if (options.network_info != None):
print getNetworkInfo(options.network_info[0])
sys.exit()
if (options.advanced_info != None):
print getAdvancedInfo(options.advanced_info[0])
sys.exit()
if (options.zabbix != None):
zabbix_config(options.zabbix[0], options.zabbix[1])
sys.exit()
if __name__ == "__main__":
main()

View File

@ -1,25 +0,0 @@
tunnel.0.description=ZzzOT
tunnel.0.i2cpHost=127.0.0.1
tunnel.0.i2cpPort=7654
tunnel.0.name=zzzot
tunnel.0.option.i2cp.enableAccessList=false
tunnel.0.option.i2cp.encryptLeaseSet=false
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.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.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.privKeyFile=plugins/zzzot/eepPriv.dat
tunnel.0.startOnLoad=true
tunnel.0.targetHost=127.0.0.1
tunnel.0.targetPort=7662
tunnel.0.type=httpserver

View File

@ -5,58 +5,95 @@
# usage: makeplugin.sh plugindir
#
# zzz 2010-02
# zzz 2014-08 added support for su3 files
#
if [ -z "$I2P" -a -d "$PWD/../i2p/pkg-temp" ]; then
export I2P=$PWD/../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
CPATH=$I2P/lib/i2p.jar:/usr/share/java/gnu-getopt.jar
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
# put your files in here
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 ]
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 signer 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 "$CPATH" net.i2p.crypto.TrustedUpdate keygen "$PUBKEYFILE" "$PRIVKEYFILE" || exit 1
java -cp "$CPATH" 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 "$CPATH" 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,41 +101,49 @@ 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
# update the date
grep -v '^date=' $PC > $PCT
DATE=`date '+%s000'`
echo "date=$DATE" >> $PCT
mv $PCT $PC
echo "date=$DATE" >> $PCT || exit 1
mv $PCT $PC || exit 1
# add our Base64 key
grep -v '^key=' $PC > $PCT
B64KEY=`cat $B64KEYFILE`
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
cd $OPWD
SU3=${NAME}.su3
cd "$OPWD"
# sign it
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign plugin.zip $XPI2P $PRIVKEYFILE $VERSION || exit 1
echo 'Signing. ...'
java -cp "$CPATH" net.i2p.crypto.TrustedUpdate sign plugin.zip "$XPI2P" "$PRIVKEYFILE" "$VERSION" || exit 1
java -cp "$CPATH" 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 "$CPATH" net.i2p.crypto.TrustedUpdate showversion "$XPI2P" || exit 1
java -cp "$CPATH" -Drouter.trustedUpdateKeys=$B64KEY net.i2p.crypto.TrustedUpdate verifysig "$XPI2P" || exit 1
java -cp "$CPATH" net.i2p.crypto.SU3File showversion "$SU3" || exit 1
java -cp "$CPATH" net.i2p.crypto.SU3File verifysig -k "$PUBKEYSTORE" "$SU3" || exit 1
rm -rf logs/
echo -n 'Plugin created: '
wc -c $XPI2P
echo 'Plugin files created: '
wc -c "$XPI2P"
wc -c "$SU3"
exit 0

View File

@ -1,8 +1,11 @@
name=zzzot
name=I2PControl
signer=zzz-plugin@mail.i2p
consoleLinkName=ZzzOT
consoleLinkURL=http://127.0.0.1:7662/tracker/index.jsp
description=Open tracker
author=zzz
updateURL=http://stats.i2p/i2p/plugins/zzzot-update.xpi2p
consoleLinkName=I2PControl
description=Remote Control Service
author=hottuna
websiteURL=http://zzz.i2p/forums/16
updateURL=http://stats.i2p/i2p/plugins/I2PControl-update.xpi2p
updateURL.su3=http://stats.i2p/i2p/plugins/I2PControl-update.su3
license=Apache 2.0
min-jetty-version=9
min-i2p-version=0.9.30

15
src/.classpath Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/i2p.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/javax.servlet.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/commons-logging.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/router.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/wrapper.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/jetty7/jetty-server-7.6.8.v20121106.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/jetty7/jetty-util-7.6.8.v20121106.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/jetty7/jetty-servlet-7.6.8.v20121106.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/jetty7/jetty-security-7.6.8.v20121106.jar"/>
<classpathentry kind="output" path="build/obj"/>
</classpath>

28
src/.project Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>i2pcontrol</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1746916390735</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View File

@ -1,110 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="i2psnark">
<project basedir="." default="all" name="source">
<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"/>
<property name="wrapperlib" value="${i2pbase}/installer/lib/wrapper/all"/>
<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="${i2plib}/routerconsole.jar" />
<pathelement location="${jettylib}/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}/commons-logging.jar" />
<pathelement location="${jettylib}/commons-el.jar" />
<pathelement location="${i2plib}/router.jar" />
<pathelement location="${i2plib}/org.mortbay.jetty.jar" />
<pathelement location="${i2plib}/javax.servlet.jar" />
<pathelement location="${jettylib}/jetty-servlet.jar" />
<pathelement location="${wrapperlib}/wrapper.jar" />
</path>
<target name="all" depends="clean, build" />
<target name="build" depends="jar, war" />
<target name="build" depends="jar" />
<target name="builddep">
</target>
<condition property="depend.available">
<typefound name="depend" />
</condition>
<target name="depend" if="depend.available">
<depend
cache="build"
srcdir="./src"
destdir="./build/obj" >
<!-- Depend on classes instead of jars where available -->
<classpath>
<pathelement location="../../../core/java/build/obj" />
<pathelement location="../../ministreaming/java/build/obj" />
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
</classpath>
</depend>
</target>
<property name="javac.compilerargs" value="" />
<target name="compile" depends="depend">
<target name="compile" depends="builddep" >
<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="1.7" target="1.7"
includeAntRuntime="false"
destdir="./build/obj"
classpath="${i2plib}/i2p.jar:${i2plib}/i2ptunnel.jar:${i2plib}/i2psnark.jar:${i2plib}/systray.jar:${i2plib}/org.mortbay.jetty.jar" >
classpath="${cp}">
<compilerarg line="${javac.compilerargs}" />
<classpath refid="cp"/>
</javac>
</target>
<target name="jar" depends="builddep, compile">
<jar destfile="build/zzzot.jar" basedir="./build/obj" includes="**/*.class" >
<target name="jar" depends="compile">
<jar destfile="build/I2PControl.jar" basedir="./build/obj" includes="**/*.class" >
</jar>
</target>
<target name="precompilejsp" depends="compile" >
<mkdir dir="build" />
<mkdir dir="build/war/WEB-INF/classes" />
<path id="jspcp">
<path refid="cp" />
<pathelement location="build/obj" />
</path>
<java classname="org.apache.jasper.JspC" fork="true" classpathref="jspcp" failonerror="true">
<arg value="-d" />
<arg value="build/jspjava" />
<arg value="-v" />
<arg value="-p" />
<arg value="net.i2p.zzzot" />
<arg value="-webinc" />
<arg value="build/web-fragment.xml" />
<arg value="-webapp" />
<arg value="jsp/" />
</java>
<javac
debug="true"
deprecation="on"
source="1.5" target="1.5"
destdir="build/war/WEB-INF/classes"
srcdir="./build/jspjava"
includes="**/*.java"
classpathref="jspcp"
failonerror="true" />
<copy file="jsp/WEB-INF/web.xml" tofile="build/web.xml" />
<loadfile property="jspc.web.fragment" srcfile="build/web-fragment.xml" />
<replace file="build/web.xml">
<replacefilter token="&lt;!-- precompiled servlets --&gt;" value="${jspc.web.fragment}" />
</replace>
</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" />
<target name="war" depends="compile" >
<war destfile="build/jsonrpc.war" webxml="web.xml" >
<classes dir="./build/obj" excludes="net/i2p/i2pcontrol/I2PControlController.class net/i2p/i2pcontrol/HostCheckHandler.class" />
</war>
</target>
<target name="clean">
<delete dir="./build" />
</target>

10
src/java/.classpath Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/commons-logging.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/i2p.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/javax.servlet.jar"/>
<classpathentry kind="lib" path="/home/hottuna/Apps/i2p/lib/org.mortbay.jetty.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

28
src/java/.project Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>i2pcontrol</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1746916390737</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View File

@ -0,0 +1,274 @@
package com.thetransactioncompany.jsonrpc2;
import net.minidev.json.JSONObject;
/**
* Represents a JSON-RPC 2.0 error that occurred during the processing of a
* request. This class is immutable.
*
* <p>The protocol expects error objects to be structured like this:
*
* <ul>
* <li>{@code code} An integer that indicates the error type.
* <li>{@code message} A string providing a short description of the
* error. The message should be limited to a concise single sentence.
* <li>{@code data} Additional information, which may be omitted. Its
* contents is entirely defined by the application.
* </ul>
*
* <p>Note that the "Error" word in the class name was put there solely to
* comply with the parlance of the JSON-RPC spec. This class doesn't inherit
* from {@code java.lang.Error}. It's a regular subclass of
* {@code java.lang.Exception} and, if thrown, it's to indicate a condition
* that a reasonable application might want to catch.
*
* <p>This class also includes convenient final static instances for all
* standard JSON-RPC 2.0 errors:
*
* <ul>
* <li>{@link #PARSE_ERROR} JSON parse error (-32700)
* <li>{@link #INVALID_REQUEST} Invalid JSON-RPC 2.0 Request (-32600)
* <li>{@link #METHOD_NOT_FOUND} Method not found (-32601)
* <li>{@link #INVALID_PARAMS} Invalid parameters (-32602)
* <li>{@link #INTERNAL_ERROR} Internal error (-32603)
* </ul>
*
* <p>Note that the range -32099..-32000 is reserved for additional server
* errors.
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Error extends Exception {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 4682571044532698806L;
/**
* JSON parse error (-32700).
*/
public static final JSONRPC2Error PARSE_ERROR = new JSONRPC2Error(-32700, "JSON parse error");
/**
* Invalid JSON-RPC 2.0 request error (-32600).
*/
public static final JSONRPC2Error INVALID_REQUEST = new JSONRPC2Error(-32600, "Invalid request");
/**
* Method not found error (-32601).
*/
public static final JSONRPC2Error METHOD_NOT_FOUND = new JSONRPC2Error(-32601, "Method not found");
/**
* Invalid parameters error (-32602).
*/
public static final JSONRPC2Error INVALID_PARAMS = new JSONRPC2Error(-32602, "Invalid parameters");
/**
* Internal JSON-RPC 2.0 error (-32603).
*/
public static final JSONRPC2Error INTERNAL_ERROR = new JSONRPC2Error(-32603, "Internal error");
/**
* The error code.
*/
private final int code;
/**
* The optional error data.
*/
private final Object data;
/**
* Appends the specified string to the message of a JSON-RPC 2.0 error.
*
* @param err The JSON-RPC 2.0 error. Must not be {@code null}.
* @param apx The string to append to the original error message.
*
* @return A new JSON-RPC 2.0 error with the appended message.
*/
@Deprecated
public static JSONRPC2Error appendMessage(final JSONRPC2Error err, final String apx) {
return new JSONRPC2Error(err.getCode(), err.getMessage() + apx, err.getData());
}
/**
* Sets the specified data to a JSON-RPC 2.0 error.
*
* @param err The JSON-RPC 2.0 error to have its data field set. Must
* not be {@code null}.
* @param data Optional error data, must <a href="#map">map</a> to a
* valid JSON type.
*
* @return A new JSON-RPC 2.0 error with the set data.
*/
@Deprecated
public static JSONRPC2Error setData(final JSONRPC2Error err, final Object data) {
return new JSONRPC2Error(err.getCode(), err.getMessage(), data);
}
/**
* Creates a new JSON-RPC 2.0 error with the specified code and
* message. The optional data is omitted.
*
* @param code The error code (standard pre-defined or
* application-specific).
* @param message The error message.
*/
public JSONRPC2Error(int code, String message) {
this(code, message, null);
}
/**
* Creates a new JSON-RPC 2.0 error with the specified code,
* message and data.
*
* @param code The error code (standard pre-defined or
* application-specific).
* @param message The error message.
* @param data Optional error data, must <a href="#map">map</a>
* to a valid JSON type.
*/
public JSONRPC2Error(int code, String message, Object data) {
super(message);
this.code = code;
this.data = data;
}
/**
* Gets the JSON-RPC 2.0 error code.
*
* @return The error code.
*/
public int getCode() {
return code;
}
/**
* Gets the JSON-RPC 2.0 error data.
*
* @return The error data, {@code null} if none was specified.
*/
public Object getData() {
return data;
}
/**
* Sets the specified data to a JSON-RPC 2.0 error.
*
* @param data Optional error data, must <a href="#map">map</a> to a
* valid JSON type.
*
* @return A new JSON-RPC 2.0 error with the set data.
*/
public JSONRPC2Error setData(final Object data) {
return new JSONRPC2Error(code, getMessage(), data);
}
/**
* Appends the specified string to the message of this JSON-RPC 2.0
* error.
*
* @param apx The string to append to the original error message.
*
* @return A new JSON-RPC 2.0 error with the appended message.
*/
public JSONRPC2Error appendMessage(final String apx) {
return new JSONRPC2Error(code, getMessage() + apx, data);
}
/**
* @see #toJSONObject
*/
@Deprecated
public JSONObject toJSON() {
return toJSONObject();
}
/**
* Returns a JSON object representation of this JSON-RPC 2.0 error.
*
* @return A JSON object representing this error object.
*/
public JSONObject toJSONObject() {
JSONObject out = new JSONObject();
out.put("code", code);
out.put("message", super.getMessage());
if (data != null)
out.put("data", data);
return out;
}
/**
* Serialises the error object to a JSON string.
*
* @return A JSON-encoded string representing this error object.
*/
@Override
public String toString() {
return toJSON().toString();
}
/**
* Overrides {@code Object.equals()}.
*
* @param object The object to compare to.
*
* @return {@code true} if both objects are instances if this class and
* their error codes are identical, {@code false} if not.
*/
@Override
public boolean equals(Object object) {
return object != null &&
object instanceof JSONRPC2Error &&
code == ((JSONRPC2Error)object).getCode();
}
}

View File

@ -0,0 +1,251 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minidev.json.JSONAware;
import net.minidev.json.JSONObject;
/**
* The base abstract class for JSON-RPC 2.0 requests, notifications and
* responses. Provides common methods for parsing (from JSON string) and
* serialisation (to JSON string) of these three message types.
*
* <p>Example parsing and serialisation back to JSON:
*
* <pre>
* String jsonString = "{\"method\":\"progressNotify\",\"params\":[\"75%\"],\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Message message = null;
*
* // parse
* try {
* message = JSONRPC2Message.parse(jsonString);
* } catch (JSONRPC2ParseException e) {
* // handle parse exception
* }
*
* if (message instanceof JSONRPC2Request)
* System.out.println("The message is a request");
* else if (message instanceof JSONRPC2Notification)
* System.out.println("The message is a notification");
* else if (message instanceof JSONRPC2Response)
* System.out.println("The message is a response");
*
* // serialise back to JSON string
* System.out.println(message);
*
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public abstract class JSONRPC2Message implements JSONAware {
/**
* Map of non-standard JSON-RPC 2.0 message attributes, {@code null} if
* none.
*/
private Map <String,Object> nonStdAttributes = null;
/**
* Provides common parsing of JSON-RPC 2.0 requests, notifications
* and responses. Use this method if you don't know which type of
* JSON-RPC message the input JSON string represents.
*
* <p>Batched requests / notifications are not supported.
*
* <p>This method is thread-safe.
*
* <p>If you are certain about the message type use the dedicated
* {@link JSONRPC2Request#parse}, {@link JSONRPC2Notification#parse}
* or {@link JSONRPC2Response#parse} methods. They are more efficient
* and provide a more detailed parse error reporting.
*
* <p>The member order of parsed JSON objects will not be preserved
* (for efficiency reasons) and the JSON-RPC 2.0 version field must be
* set to "2.0". To change this behaviour check the optional {@link
* #parse(String,boolean,boolean)} method.
*
* @param jsonString A JSON string representing a JSON-RPC 2.0 request,
* notification or response, UTF-8 encoded. Must not
* be {@code null}.
*
* @return An instance of {@link JSONRPC2Request},
* {@link JSONRPC2Notification} or {@link JSONRPC2Response}.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Message parse(final String jsonString)
throws JSONRPC2ParseException {
return parse(jsonString, false, false);
}
/**
* Provides common parsing of JSON-RPC 2.0 requests, notifications
* and responses. Use this method if you don't know which type of
* JSON-RPC message the input string represents.
*
* <p>Batched requests / notifications are not supported.
*
* <p>This method is thread-safe.
*
* <p>If you are certain about the message type use the dedicated
* {@link JSONRPC2Request#parse}, {@link JSONRPC2Notification#parse}
* or {@link JSONRPC2Response#parse} methods. They are more efficient
* and provide a more detailed parse error reporting.
*
* @param jsonString A JSON string representing a JSON-RPC 2.0
* request, notification or response, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder If {@code true} the member order of JSON objects
* in parameters and results must be preserved.
* @param ignoreVersion If {@code true} the {@code "jsonrpc":"2.0"}
* version field in the JSON-RPC 2.0 message will
* not be checked.
*
* @return An instance of {@link JSONRPC2Request},
* {@link JSONRPC2Notification} or {@link JSONRPC2Response}.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Message parse(final String jsonString, final boolean preserveOrder, final boolean ignoreVersion)
throws JSONRPC2ParseException {
JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, ignoreVersion);
return parser.parseJSONRPC2Message(jsonString);
}
/**
* Appends a non-standard attribute to this JSON-RPC 2.0 message. This is
* done by adding a new member (key / value pair) to the top level JSON
* object representing the message.
*
* <p>You may use this method to add meta and debugging attributes,
* such as the request processing time, to a JSON-RPC 2.0 message.
*
* @param name The attribute name. Must not conflict with the existing
* "method", "id", "params", "result", "error" and "jsonrpc"
* attributes reserved by the JSON-RPC 2.0 protocol, else
* an {@code IllegalArgumentException} will be thrown. Must
* not be {@code null} either.
* @param value The attribute value. Must be of type String, boolean,
* number, List, Map or null, else an
* {@code IllegalArgumentException} will be thrown.
*/
public void appendNonStdAttribute(final String name, final Object value) {
// Name check
if (name == null ||
name.equals("method") ||
name.equals("id") ||
name.equals("params") ||
name.equals("result") ||
name.equals("error") ||
name.equals("jsonrpc") )
throw new IllegalArgumentException("Non-standard attribute name violation");
// Value check
if ( value != null &&
! (value instanceof Boolean) &&
! (value instanceof Number) &&
! (value instanceof String) &&
! (value instanceof List) &&
! (value instanceof Map) )
throw new IllegalArgumentException("Illegal non-standard attribute value, must map to a valid JSON type");
if (nonStdAttributes == null)
nonStdAttributes = new HashMap<String,Object>();
nonStdAttributes.put(name, value);
}
/**
* Retrieves a non-standard JSON-RPC 2.0 message attribute.
*
* @param name The name of the non-standard attribute to retrieve. Must
* not be {@code null}.
*
* @return The value of the non-standard attribute (may also be
* {@code null}, {@code null} if not found.
*/
public Object getNonStdAttribute(final String name) {
if (nonStdAttributes == null)
return null;
return nonStdAttributes.get(name);
}
/**
* Retrieves the non-standard JSON-RPC 2.0 message attributes.
*
* @return The non-standard attributes as a map, {@code null} if none.
*/
public Map<String,Object> getNonStdAttributes() {
return nonStdAttributes;
}
/**
* Returns a JSON object representing this JSON-RPC 2.0 message.
*
* @return The JSON object.
*/
public abstract JSONObject toJSONObject();
/**
* Returns a JSON string representation of this JSON-RPC 2.0 message.
*
* @see #toString
*
* @return The JSON object string representing this JSON-RPC 2.0
* message.
*/
public String toJSONString() {
return toString();
}
/**
* Serialises this JSON-RPC 2.0 message to a JSON object string.
*
* @return The JSON object string representing this JSON-RPC 2.0
* message.
*/
@Override
public String toString() {
return toJSONObject().toString();
}
}

View File

@ -0,0 +1,448 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.List;
import java.util.Map;
import net.minidev.json.JSONObject;
/**
* Represents a JSON-RPC 2.0 notification.
*
* <p>Notifications provide a mean for calling a remote procedure without
* generating a response. Note that notifications are inherently unreliable
* as no confirmation is sent back to the caller.
*
* <p>Notifications have the same JSON structure as requests, except that they
* lack an identifier:
* <ul>
* <li>{@code method} The name of the remote method to call.
* <li>{@code params} The required method parameters (if any), which can
* be packed into a JSON array or object.
* <li>{@code jsonrpc} A string indicating the JSON-RPC protocol version
* set to "2.0".
* </ul>
*
* <p>Here is a sample JSON-RPC 2.0 notification string:
*
* <pre>
* {
* "method" : "progressNotify",
* "params" : ["75%"],
* "jsonrpc" : "2.0"
* }
* </pre>
*
* <p>This class provides two methods to obtain a request object:
* <ul>
* <li>Pass a JSON-RPC 2.0 notification string to the static
* {@link #parse} method, or
* <li>Invoke one of the constructors with the appropriate arguments.
* </ul>
*
* <p>Example 1: Parsing a notification string:
*
* <pre>
* String jsonString = "{\"method\":\"progressNotify\",\"params\":[\"75%\"],\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Notification notification = null;
*
* try {
* notification = JSONRPC2Notification.parse(jsonString);
*
* } catch (JSONRPC2ParseException e) {
* // handle exception
* }
* </pre>
*
* <p>Example 2: Recreating the above request:
*
* <pre>
* String method = "progressNotify";
* List&lt;Object&gt; params = new Vector&lt;Object&gt;();
* params.add("75%");
*
* JSONRPC2Notification notification = new JSONRPC2Notification(method, params);
*
* System.out.println(notification);
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Notification extends JSONRPC2Message {
/**
* The requested method name.
*/
private String method;
/**
* The positional parameters, {@code null} if none.
*/
private List<Object> positionalParams;
/**
* The named parameters, {@code null} if none.
*/
private Map<String,Object> namedParams;
/**
* Parses a JSON-RPC 2.0 notification string. This method is
* thread-safe.
*
* @param jsonString The JSON-RPC 2.0 notification string, UTF-8
* encoded. Must not be {@code null}.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Notification parse(final String jsonString)
throws JSONRPC2ParseException {
return parse(jsonString, false, false, false);
}
/**
* Parses a JSON-RPC 2.0 notification string. This method is
* thread-safe.
*
* @param jsonString The JSON-RPC 2.0 notification string, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in parameters.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Notification parse(final String jsonString,
final boolean preserveOrder)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, false, false);
}
/**
* Parses a JSON-RPC 2.0 notification string. This method is
* thread-safe.
*
* @param jsonString The JSON-RPC 2.0 notification string, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in parameters.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version attribute in the
* JSON-RPC 2.0 message.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Notification parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, ignoreVersion, false);
}
/**
* Parses a JSON-RPC 2.0 notification string. This method is
* thread-safe.
*
* @param jsonString The JSON-RPC 2.0 notification string,
* UTF-8 encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of
* JSON object members in parameters.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version
* attribute in the JSON-RPC 2.0 message.
* @param parseNonStdAttributes {@code true} to parse non-standard
* attributes found in the JSON-RPC 2.0
* message.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Notification parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdAttributes)
throws JSONRPC2ParseException {
JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, ignoreVersion, parseNonStdAttributes);
return parser.parseJSONRPC2Notification(jsonString);
}
/**
* Constructs a new JSON-RPC 2.0 notification with no parameters.
*
* @param method The name of the requested method. Must not be
* {@code null}.
*/
public JSONRPC2Notification(final String method) {
setMethod(method);
setParams(null);
}
/**
* Constructs a new JSON-RPC 2.0 notification with positional (JSON
* array) parameters.
*
* @param method The name of the requested method. Must not
* be {@code null}.
* @param positionalParams The positional (JSON array) parameters,
* {@code null} if none.
*/
public JSONRPC2Notification(final String method,
final List<Object> positionalParams) {
setMethod(method);
setPositionalParams(positionalParams);
}
/**
* Constructs a new JSON-RPC 2.0 notification with named (JSON object)
* parameters.
*
* @param method The name of the requested method.
* @param namedParams The named (JSON object) parameters, {@code null}
* if none.
*/
public JSONRPC2Notification(final String method,
final Map <String,Object> namedParams) {
setMethod(method);
setNamedParams(namedParams);
}
/**
* Gets the name of the requested method.
*
* @return The method name.
*/
public String getMethod() {
return method;
}
/**
* Sets the name of the requested method.
*
* @param method The method name. Must not be {@code null}.
*/
public void setMethod(final String method) {
// The method name is mandatory
if (method == null)
throw new IllegalArgumentException("The method name must not be null");
this.method = method;
}
/**
* Gets the parameters type ({@link JSONRPC2ParamsType#ARRAY positional},
* {@link JSONRPC2ParamsType#OBJECT named} or
* {@link JSONRPC2ParamsType#NO_PARAMS none}).
*
* @return The parameters type.
*/
public JSONRPC2ParamsType getParamsType() {
if (positionalParams == null && namedParams == null)
return JSONRPC2ParamsType.NO_PARAMS;
if (positionalParams != null)
return JSONRPC2ParamsType.ARRAY;
if (namedParams != null)
return JSONRPC2ParamsType.OBJECT;
else
return JSONRPC2ParamsType.NO_PARAMS;
}
/**
* Gets the notification parameters.
*
* <p>This method was deprecated in version 1.30. Use
* {@link #getPositionalParams} or {@link #getNamedParams} instead.
*
* @return The parameters as {@code List&lt;Object&gt;} for positional
* (JSON array), {@code Map&lt;String,Object&gt;} for named
* (JSON object), or {@code null} if none.
*/
@Deprecated
public Object getParams() {
switch (getParamsType()) {
case ARRAY:
return positionalParams;
case OBJECT:
return namedParams;
default:
return null;
}
}
/**
* Gets the positional (JSON array) parameters.
*
* @since 1.30
*
* @return The positional (JSON array) parameters, {@code null} if none
* or named.
*/
public List<Object> getPositionalParams() {
return positionalParams;
}
/**
* Gets the named parameters.
*
* @since 1.30
*
* @return The named (JSON object) parameters, {@code null} if none or
* positional.
*/
public Map<String,Object> getNamedParams() {
return namedParams;
}
/**
* Sets the notification parameters.
*
* <p>This method was deprecated in version 1.30. Use
* {@link #setPositionalParams} or {@link #setNamedParams} instead.
*
* @param params The parameters. For positional (JSON array) pass a
* {@code List&lt;Object&gt;}. For named (JSON object)
* pass a {@code Map&lt;String,Object&gt;}. If there are
* no parameters pass {@code null}.
*/
@Deprecated
@SuppressWarnings("unchecked")
public void setParams(final Object params) {
if (params == null) {
positionalParams = null;
namedParams = null;
} else if (params instanceof List) {
positionalParams = (List<Object>) params;
} else if (params instanceof Map) {
namedParams = (Map<String, Object>) params;
} else {
throw new IllegalArgumentException("The notification parameters must be of type List, Map or null");
}
}
/**
* Sets the positional (JSON array) request parameters.
*
* @since 1.30
*
* @param positionalParams The positional (JSON array) request
* parameters, {@code null} if none.
*/
public void setPositionalParams(final List<Object> positionalParams) {
if (positionalParams == null)
return;
this.positionalParams = positionalParams;
}
/**
* Sets the named (JSON object) request parameters.
*
* @since 1.30
*
* @param namedParams The named (JSON object) request parameters,
* {@code null} if none.
*/
public void setNamedParams(final Map<String,Object> namedParams) {
if (namedParams == null)
return;
this.namedParams = namedParams;
}
@Override
public JSONObject toJSONObject() {
JSONObject notf = new JSONObject();
notf.put("method", method);
// The params can be omitted if none
switch (getParamsType()) {
case ARRAY:
notf.put("params", positionalParams);
break;
case OBJECT:
notf.put("params", namedParams);
break;
}
notf.put("jsonrpc", "2.0");
Map <String,Object> nonStdAttributes = getNonStdAttributes();
if (nonStdAttributes != null) {
for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
notf.put(attr.getKey(), attr.getValue());
}
return notf;
}
}

View File

@ -0,0 +1,37 @@
package com.thetransactioncompany.jsonrpc2;
/**
* Enumeration of the three parameter types in JSON-RPC 2.0 requests and
* notifications.
*
* <ul>
* <li>{@link #NO_PARAMS} The method takes no parameters.
* <li>{@link #ARRAY} The method takes positional parameters, packed as a
* JSON array, e.g. {@code ["val1", "val2", ...]}.
* <li>{@link #OBJECT} The method takes named parameters, packed as a JSON
* object, e.g. {@code {"param1":"val1", "param2":"val2", ...}}.
* </ul>
*
* @author Vladimir Dzhuvinov
*/
public enum JSONRPC2ParamsType {
/**
* No parameters.
*/
NO_PARAMS,
/**
* Positional parameters, packed as a JSON array.
*/
ARRAY,
/**
* Named parameters, packed as a JSON object.
*/
OBJECT
}

View File

@ -0,0 +1,113 @@
package com.thetransactioncompany.jsonrpc2;
/**
* Thrown to indicate an exception during the parsing of a JSON-RPC 2.0
* message string.
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2ParseException extends Exception {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 3376608778436136410L;
/**
* Indicates a parse exception caused by a JSON message not conforming
* to the JSON-RPC 2.0 protocol.
*/
public static final int PROTOCOL = 0;
/**
* Indicates a parse exception caused by invalid JSON.
*/
public static final int JSON = 1;
/**
* The parse exception cause type. Default is {@link #PROTOCOL}.
*/
private int causeType = PROTOCOL;
/**
* The string that could't be parsed.
*/
private String unparsableString = null;
/**
* Creates a new parse exception with the specified message. The cause
* type is set to {@link #PROTOCOL}.
*
* @param message The exception message.
*/
public JSONRPC2ParseException(final String message) {
super(message);
}
/**
* Creates a new parse exception with the specified message and the
* original string that didn't parse. The cause type is set to
* {@link #PROTOCOL}.
*
* @param message The exception message.
* @param unparsableString The unparsable string.
*/
public JSONRPC2ParseException(final String message, final String unparsableString) {
super(message);
this.unparsableString = unparsableString;
}
/**
* Creates a new parse exception with the specified message, cause type
* and the original string that didn't parse.
*
* @param message The exception message.
* @param causeType The exception cause type, either
* {@link #PROTOCOL} or {@link #JSON}.
* @param unparsableString The unparsable string.
*/
public JSONRPC2ParseException(final String message, final int causeType, final String unparsableString) {
super(message);
if (causeType != PROTOCOL && causeType != JSON)
throw new IllegalArgumentException("Cause type must be either PROTOCOL or JSON");
this.causeType = causeType;
this.unparsableString = unparsableString;
}
/**
* Gets the parse exception cause type.
*
* @return The cause type, either {@link #PROTOCOL} or {@link #JSON}.
*/
public int getCauseType() {
return causeType;
}
/**
* Gets original string that caused the parse exception (if specified).
*
* @return The string that didn't parse, {@code null} if none.
*/
public String getUnparsableString() {
return unparsableString;
}
}

View File

@ -0,0 +1,654 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.List;
import java.util.Map;
import net.minidev.json.parser.ContainerFactory;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
/**
* Parses JSON-RPC 2.0 request, notification and response messages.
*
* <p>Parsing of batched requests / notifications is not supported.
*
* <p>This class is not thread-safe. A parser instance should not be used by
* more than one thread unless properly synchronised. Alternatively, you may
* use the thread-safe {@link JSONRPC2Message#parse} and its sister methods.
*
* <p>Example:
*
* <pre>
* String jsonString = "{\"method\":\"makePayment\"," +
* "\"params\":{\"recipient\":\"Penny Adams\",\"amount\":175.05}," +
* "\"id\":\"0001\","+
* "\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Request req = null;
*
* JSONRPC2Parser parser = new JSONRPC2Parser();
*
* try {
* req = parser.parseJSONRPC2Request(jsonString);
*
* } catch (JSONRPC2ParseException e) {
* // handle exception
* }
*
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Parser {
/**
* Reusable JSON parser. Not thread-safe!
*/
private final JSONParser parser;
/**
* If {@code true} the order of the parsed JSON object members must be
* preserved.
*/
private boolean preserveOrder;
/**
* If {@code true} the {@code "jsonrpc":"2.0"} version attribute in the
* JSON-RPC 2.0 message must be ignored during parsing.
*/
private boolean ignoreVersion;
/**
* If {@code true} non-standard JSON-RPC 2.0 message attributes must be
* parsed too.
*/
private boolean parseNonStdAttributes;
/**
* Creates a new JSON-RPC 2.0 message parser.
*
* <p>The member order of parsed JSON objects in parameters and results
* will not be preserved; strict checking of the 2.0 JSON-RPC version
* attribute will be enforced; non-standard message attributes will be
* ignored. Check the other constructors if you want to specify
* different behaviour.
*/
public JSONRPC2Parser() {
this(false, false, false);
}
/**
* Creates a new JSON-RPC 2.0 message parser.
*
* <p>Strict checking of the 2.0 JSON-RPC version attribute will be
* enforced; non-standard message attributes will be ignored. Check the
* other constructors if you want to specify different behaviour.
*
* @param preserveOrder If {@code true} the member order of JSON objects
* in parameters and results will be preserved.
*/
public JSONRPC2Parser(final boolean preserveOrder) {
this(preserveOrder, false, false);
}
/**
* Creates a new JSON-RPC 2.0 message parser.
*
* <p>Non-standard message attributes will be ignored. Check the other
* constructors if you want to specify different behaviour.
*
* @param preserveOrder If {@code true} the member order of JSON objects
* in parameters and results will be preserved.
* @param ignoreVersion If {@code true} the {@code "jsonrpc":"2.0"}
* version attribute in the JSON-RPC 2.0 message
* will not be checked.
*/
public JSONRPC2Parser(final boolean preserveOrder,
final boolean ignoreVersion) {
this(preserveOrder, ignoreVersion, false);
}
/**
* Creates a new JSON-RPC 2.0 message parser.
*
* <p>This constructor allows full specification of the available
* JSON-RPC message parsing properties.
*
* @param preserveOrder If {@code true} the member order of JSON
* objects in parameters and results will
* be preserved.
* @param ignoreVersion If {@code true} the
* {@code "jsonrpc":"2.0"} version
* attribute in the JSON-RPC 2.0 message
* will not be checked.
* @param parseNonStdAttributes If {@code true} non-standard attributes
* found in the JSON-RPC 2.0 messages will
* be parsed too.
*/
public JSONRPC2Parser(final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdAttributes) {
// Numbers parsed as long/double, requires JSON Smart 1.0.9+
parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
this.preserveOrder = preserveOrder;
this.ignoreVersion = ignoreVersion;
this.parseNonStdAttributes = parseNonStdAttributes;
}
/**
* Parses a JSON object string. Provides the initial parsing of
* JSON-RPC 2.0 messages. The member order of JSON objects will be
* preserved if {@link #preserveOrder} is set to {@code true}.
*
* @param jsonString The JSON string to parse. Must not be
* {@code null}.
*
* @return The parsed JSON object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
@SuppressWarnings("unchecked")
private Map<String,Object> parseJSONObject(final String jsonString)
throws JSONRPC2ParseException {
if (jsonString.trim().length()==0)
throw new JSONRPC2ParseException("Invalid JSON: Empty string",
JSONRPC2ParseException.JSON,
jsonString);
Object json;
// Parse the JSON string
try {
if (preserveOrder)
json = parser.parse(jsonString, ContainerFactory.FACTORY_ORDERED);
else
json = parser.parse(jsonString);
} catch (ParseException e) {
// Terse message, do not include full parse exception message
throw new JSONRPC2ParseException("Invalid JSON",
JSONRPC2ParseException.JSON,
jsonString);
}
if (json instanceof List)
throw new JSONRPC2ParseException("JSON-RPC 2.0 batch requests/notifications not supported", jsonString);
if (! (json instanceof Map))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 message: Message must be a JSON object", jsonString);
return (Map<String,Object>)json;
}
/**
* Ensures the specified parameter is a {@code String} object set to
* "2.0". This method is intended to check the "jsonrpc" attribute
* during parsing of JSON-RPC messages.
*
* @param version The version parameter. Must not be {@code null}.
* @param jsonString The original JSON string.
*
* @throws JSONRPC2ParseException If the parameter is not a string that
* equals "2.0".
*/
private static void ensureVersion2(final Object version, final String jsonString)
throws JSONRPC2ParseException {
if (version == null)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0: Version string missing", jsonString);
else if (! (version instanceof String))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0: Version not a JSON string", jsonString);
else if (! version.equals("2.0"))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0: Version must be \"2.0\"", jsonString);
}
/**
* Provides common parsing of JSON-RPC 2.0 requests, notifications
* and responses. Use this method if you don't know which type of
* JSON-RPC message the input string represents.
*
* <p>If a particular message type is expected use the dedicated
* {@link #parseJSONRPC2Request}, {@link #parseJSONRPC2Notification}
* and {@link #parseJSONRPC2Response} methods. They are more efficient
* and would provide you with more detailed parse error reporting.
*
* @param jsonString A JSON string representing a JSON-RPC 2.0 request,
* notification or response, UTF-8 encoded. Must not
* be {@code null}.
*
* @return An instance of {@link JSONRPC2Request},
* {@link JSONRPC2Notification} or {@link JSONRPC2Response}.
*
* @throws JSONRPC2ParseException With detailed message if the parsing
* failed.
*/
public JSONRPC2Message parseJSONRPC2Message(final String jsonString)
throws JSONRPC2ParseException {
// Try each of the parsers until one succeeds (or all fail)
try {
return parseJSONRPC2Request(jsonString);
} catch (JSONRPC2ParseException e) {
// throw on JSON error, ignore on protocol error
if (e.getCauseType() == JSONRPC2ParseException.JSON)
throw e;
}
try {
return parseJSONRPC2Notification(jsonString);
} catch (JSONRPC2ParseException e) {
// throw on JSON error, ignore on protocol error
if (e.getCauseType() == JSONRPC2ParseException.JSON)
throw e;
}
try {
return parseJSONRPC2Response(jsonString);
} catch (JSONRPC2ParseException e) {
// throw on JSON error, ignore on protocol error
if (e.getCauseType() == JSONRPC2ParseException.JSON)
throw e;
}
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 message",
JSONRPC2ParseException.PROTOCOL,
jsonString);
}
/**
* Parses a JSON-RPC 2.0 request string.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded.
* Must not be {@code null}.
*
* @return The corresponding JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
@SuppressWarnings("unchecked")
public JSONRPC2Request parseJSONRPC2Request(final String jsonString)
throws JSONRPC2ParseException {
// Initial JSON object parsing
Map<String,Object> jsonObject = parseJSONObject(jsonString);
// Check for JSON-RPC version "2.0"
Object version = jsonObject.remove("jsonrpc");
if (! ignoreVersion)
ensureVersion2(version, jsonString);
// Extract method name
Object method = jsonObject.remove("method");
if (method == null)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method name missing", jsonString);
else if (! (method instanceof String))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method name not a JSON string", jsonString);
else if (((String)method).length() == 0)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method name is an empty string", jsonString);
// Extract ID
if (! jsonObject.containsKey("id"))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Missing identifier", jsonString);
Object id = jsonObject.remove("id");
if ( id != null &&
!(id instanceof Number ) &&
!(id instanceof Boolean) &&
!(id instanceof String ) )
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Identifier not a JSON scalar", jsonString);
// Extract params
Object params = jsonObject.remove("params");
JSONRPC2Request request;
if (params == null)
request = new JSONRPC2Request((String)method, id);
else if (params instanceof List)
request = new JSONRPC2Request((String)method, (List<Object>)params, id);
else if (params instanceof Map)
request = new JSONRPC2Request((String)method, (Map<String,Object>)params, id);
else
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 request: Method parameters have unexpected JSON type", jsonString);
// Extract remaining non-std params?
if (parseNonStdAttributes) {
for (Map.Entry<String,Object> entry: jsonObject.entrySet()) {
request.appendNonStdAttribute(entry.getKey(), entry.getValue());
}
}
return request;
}
/**
* Parses a JSON-RPC 2.0 notification string.
*
* @param jsonString The JSON-RPC 2.0 notification string, UTF-8
* encoded. Must not be {@code null}.
*
* @return The corresponding JSON-RPC 2.0 notification object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
@SuppressWarnings("unchecked")
public JSONRPC2Notification parseJSONRPC2Notification(final String jsonString)
throws JSONRPC2ParseException {
// Initial JSON object parsing
Map<String,Object> jsonObject = parseJSONObject(jsonString);
// Check for JSON-RPC version "2.0"
Object version = jsonObject.remove("jsonrpc");
if (! ignoreVersion)
ensureVersion2(version, jsonString);
// Extract method name
Object method = jsonObject.remove("method");
if (method == null)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method name missing", jsonString);
else if (! (method instanceof String))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method name not a JSON string", jsonString);
else if (((String)method).length() == 0)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method name is an empty string", jsonString);
// Extract params
Object params = jsonObject.get("params");
JSONRPC2Notification notification;
if (params == null)
notification = new JSONRPC2Notification((String)method);
else if (params instanceof List)
notification = new JSONRPC2Notification((String)method, (List<Object>)params);
else if (params instanceof Map)
notification = new JSONRPC2Notification((String)method, (Map<String,Object>)params);
else
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 notification: Method parameters have unexpected JSON type", jsonString);
// Extract remaining non-std params?
if (parseNonStdAttributes) {
for (Map.Entry<String,Object> entry: jsonObject.entrySet()) {
notification.appendNonStdAttribute(entry.getKey(), entry.getValue());
}
}
return notification;
}
/**
* Parses a JSON-RPC 2.0 response string.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
* Must not be {@code null}.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
@SuppressWarnings("unchecked")
public JSONRPC2Response parseJSONRPC2Response(final String jsonString)
throws JSONRPC2ParseException {
// Initial JSON object parsing
Map<String,Object> jsonObject = parseJSONObject(jsonString);
// Check for JSON-RPC version "2.0"
Object version = jsonObject.remove("jsonrpc");
if (! ignoreVersion)
ensureVersion2(version, jsonString);
// Extract request ID
Object id = jsonObject.remove("id");
if ( id != null &&
! (id instanceof Boolean) &&
! (id instanceof Number ) &&
! (id instanceof String ) )
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Identifier not a JSON scalar", jsonString);
// Extract result/error and create response object
// Note: result and error are mutually exclusive
JSONRPC2Response response;
if (jsonObject.containsKey("result") && ! jsonObject.containsKey("error")) {
// Success
Object res = jsonObject.remove("result");
response = new JSONRPC2Response(res, id);
}
else if (! jsonObject.containsKey("result") && jsonObject.containsKey("error")) {
// Error JSON object
Object errorJSON = jsonObject.remove("error");
if (errorJSON == null)
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Missing error object", jsonString);
if (! (errorJSON instanceof Map))
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Error object not a JSON object");
Map<String,Object> error = (Map<String,Object>)errorJSON;
int errorCode;
try {
errorCode = ((Number)error.get("code")).intValue();
} catch (Exception e) {
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Error code missing or not an integer", jsonString);
}
String errorMessage;
try {
errorMessage = (String)error.get("message");
} catch (Exception e) {
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Error message missing or not a string", jsonString);
}
Object errorData = error.get("data");
response = new JSONRPC2Response(new JSONRPC2Error(errorCode, errorMessage, errorData), id);
}
else if (jsonObject.containsKey("result") && jsonObject.containsKey("error")) {
// Invalid response
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: You cannot have result and error at the same time", jsonString);
}
else if (! jsonObject.containsKey("result") && ! jsonObject.containsKey("error")){
// Invalid response
throw new JSONRPC2ParseException("Invalid JSON-RPC 2.0 response: Neither result nor error specified", jsonString);
}
else {
throw new AssertionError();
}
// Extract remaining non-std params?
if (parseNonStdAttributes) {
for (Map.Entry<String,Object> entry: jsonObject.entrySet()) {
response.appendNonStdAttribute(entry.getKey(), entry.getValue());
}
}
return response;
}
/**
* Controls the preservation of JSON object member order in parsed
* JSON-RPC 2.0 messages.
*
* @param preserveOrder {@code true} to preserve the order of JSON
* object members, else {@code false}.
*/
public void preserveOrder(final boolean preserveOrder) {
this.preserveOrder = preserveOrder;
}
/**
* Returns {@code true} if the order of JSON object members in parsed
* JSON-RPC 2.0 messages is preserved, else {@code false}.
*
* @return {@code true} if order is preserved, else {@code false}.
*/
public boolean preservesOrder() {
return preserveOrder;
}
/**
* Specifies whether to ignore the {@code "jsonrpc":"2.0"} version
* attribute during parsing of JSON-RPC 2.0 messages.
*
* <p>You may with to disable strict 2.0 version checking if the parsed
* JSON-RPC 2.0 messages don't include a version attribute or if you
* wish to achieve limited compatibility with older JSON-RPC protocol
* versions.
*
* @param ignore {@code true} to skip checks of the
* {@code "jsonrpc":"2.0"} version attribute in parsed
* JSON-RPC 2.0 messages, else {@code false}.
*/
public void ignoreVersion(final boolean ignore) {
ignoreVersion = ignore;
}
/**
* Returns {@code true} if the {@code "jsonrpc":"2.0"} version
* attribute in parsed JSON-RPC 2.0 messages is ignored, else
* {@code false}.
*
* @return {@code true} if the {@code "jsonrpc":"2.0"} version
* attribute in parsed JSON-RPC 2.0 messages is ignored, else
* {@code false}.
*/
public boolean ignoresVersion() {
return ignoreVersion;
}
/**
* Specifies whether to parse non-standard attributes found in JSON-RPC
* 2.0 messages.
*
* @param enable {@code true} to parse non-standard attributes, else
* {@code false}.
*/
public void parseNonStdAttributes(final boolean enable) {
parseNonStdAttributes = enable;
}
/**
* Returns {@code true} if non-standard attributes in JSON-RPC 2.0
* messages are parsed.
*
* @return {@code true} if non-standard attributes are parsed, else
* {@code false}.
*/
public boolean parsesNonStdAttributes() {
return parseNonStdAttributes;
}
}

View File

@ -0,0 +1,507 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.List;
import java.util.Map;
import net.minidev.json.JSONObject;
/**
* Represents a JSON-RPC 2.0 request.
*
* <p>A request carries four pieces of data:
* <ul>
* <li>{@code method} The name of the remote method to call.
* <li>{@code params} The required method parameters (if any), which can
* be packed into a JSON array or object.
* <li>{@code id} An identifier which is echoed back to the client with
* the response.
* <li>{@code jsonrpc} A string indicating the JSON-RPC protocol version
* set to "2.0".
* </ul>
*
* <p>Here is a sample JSON-RPC 2.0 request string:
*
* <pre>
* {
* "method" : "makePayment",
* "params" : { "recipient" : "Penny Adams", "amount":175.05 },
* "id" : "0001",
* "jsonrpc" : "2.0"
* }
* </pre>
*
* <p>This class provides two methods to obtain a request object:
* <ul>
* <li>Pass a JSON-RPC 2.0 request string to the static
* {@link #parse} method, or
* <li>Invoke one of the constructors with the appropriate arguments.
* </ul>
*
* <p>Example 1: Parsing a request string:
*
* <pre>
* String jsonString = "{\"method\":\"makePayment\"," +
* "\"params\":{\"recipient\":\"Penny Adams\",\"amount\":175.05}," +
* "\"id\":\"0001\","+
* "\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Request req = null;
*
* try {
* req = JSONRPC2Request.parse(jsonString);
*
* } catch (JSONRPC2ParseException e) {
* // handle exception
* }
* </pre>
*
* <p>Example 2: Recreating the above request:
*
* <pre>
* String method = "makePayment";
* Map&lt;String,Object&gt; params = new HashMap&lt;String,Object&gt;();
* params.put("recipient", "Penny Adams");
* params.put("amount", 175.05);
* String id = "0001";
*
* JSONRPC2Request req = new JSONRPC2Request(method, params, id);
*
* System.out.println(req);
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Request extends JSONRPC2Message {
/**
* The method name.
*/
private String method;
/**
* The positional parameters, {@code null} if none.
*/
private List<Object> positionalParams;
/**
* The named parameters, {@code null} if none.
*/
private Map<String,Object> namedParams;
/**
* The request identifier.
*/
private Object id;
/**
* Parses a JSON-RPC 2.0 request string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded.
* Must not be {@code null}.
*
* @return The corresponding JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Request parse(final String jsonString)
throws JSONRPC2ParseException {
return parse(jsonString, false, false, false);
}
/**
* Parses a JSON-RPC 2.0 request string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded.
* Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in parameters.
*
* @return The corresponding JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Request parse(final String jsonString,
final boolean preserveOrder)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, false, false);
}
/**
* Parses a JSON-RPC 2.0 request string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded.
* Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in parameters.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version attribute in the
* JSON-RPC 2.0 message.
*
* @return The corresponding JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Request parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, ignoreVersion, false);
}
/**
* Parses a JSON-RPC 2.0 request string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 request string, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of
* JSON object members in parameters.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version
* attribute in the JSON-RPC 2.0 message.
* @param parseNonStdAttributes {@code true} to parse non-standard
* attributes found in the JSON-RPC 2.0
* message.
*
* @return The corresponding JSON-RPC 2.0 request object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Request parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdAttributes)
throws JSONRPC2ParseException {
JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder,
ignoreVersion,
parseNonStdAttributes);
return parser.parseJSONRPC2Request(jsonString);
}
/**
* Constructs a new JSON-RPC 2.0 request with no parameters.
*
* @param method The name of the requested method. Must not be
* {@code null}.
* @param id The request identifier echoed back to the caller.
* The value must <a href="#map">map</a> to a JSON
* scalar ({@code null} and fractions, however, should
* be avoided).
*/
public JSONRPC2Request(final String method, final Object id) {
setMethod(method);
setID(id);
}
/**
* Constructs a new JSON-RPC 2.0 request with positional (JSON array)
* parameters.
*
* @param method The name of the requested method. Must not
* be {@code null}.
* @param positionalParams The positional (JSON array) parameters,
* {@code null} if none.
* @param id The request identifier echoed back to the
* caller. The value must <a href="#map">map</a>
* to a JSON scalar ({@code null} and
* fractions, however, should be avoided).
*/
public JSONRPC2Request(final String method,
final List<Object> positionalParams,
final Object id) {
setMethod(method);
setPositionalParams(positionalParams);
setID(id);
}
/**
* Constructs a new JSON-RPC 2.0 request with named (JSON object)
* parameters.
*
* @param method The name of the requested method.
* @param namedParams The named (JSON object) parameters, {@code null}
* if none.
* @param id The request identifier echoed back to the caller.
* The value must <a href="#map">map</a> to a JSON
* scalar ({@code null} and fractions, however,
* should be avoided).
*/
public JSONRPC2Request(final String method,
final Map <String,Object> namedParams,
final Object id) {
setMethod(method);
setNamedParams(namedParams);
setID(id);
}
/**
* Gets the name of the requested method.
*
* @return The method name.
*/
public String getMethod() {
return method;
}
/**
* Sets the name of the requested method.
*
* @param method The method name. Must not be {@code null}.
*/
public void setMethod(final String method) {
// The method name is mandatory
if (method == null)
throw new IllegalArgumentException("The method name must not be null");
this.method = method;
}
/**
* Gets the parameters type ({@link JSONRPC2ParamsType#ARRAY positional},
* {@link JSONRPC2ParamsType#OBJECT named} or
* {@link JSONRPC2ParamsType#NO_PARAMS none}).
*
* @return The parameters type.
*/
public JSONRPC2ParamsType getParamsType() {
if (positionalParams == null && namedParams == null)
return JSONRPC2ParamsType.NO_PARAMS;
if (positionalParams != null)
return JSONRPC2ParamsType.ARRAY;
if (namedParams != null)
return JSONRPC2ParamsType.OBJECT;
else
return JSONRPC2ParamsType.NO_PARAMS;
}
/**
* Gets the request parameters.
*
* <p>This method was deprecated in version 1.30. Use
* {@link #getPositionalParams} or {@link #getNamedParams} instead.
*
* @return The parameters as {@code List&lt;Object&gt;} for positional
* (JSON array), {@code Map&lt;String,Object&gt;} for named
* (JSON object), or {@code null} if none.
*/
@Deprecated
public Object getParams() {
switch (getParamsType()) {
case ARRAY:
return positionalParams;
case OBJECT:
return namedParams;
default:
return null;
}
}
/**
* Gets the positional (JSON array) parameters.
*
* @since 1.30
*
* @return The positional (JSON array) parameters, {@code null} if none
* or named.
*/
public List<Object> getPositionalParams() {
return positionalParams;
}
/**
* Gets the named parameters.
*
* @since 1.30
*
* @return The named (JSON object) parameters, {@code null} if none or
* positional.
*/
public Map<String,Object> getNamedParams() {
return namedParams;
}
/**
* Sets the request parameters.
*
* <p>This method was deprecated in version 1.30. Use
* {@link #setPositionalParams} or {@link #setNamedParams} instead.
*
* @param params The parameters. For positional (JSON array) pass a
* {@code List&lt;Object&gt;}. For named (JSON object)
* pass a {@code Map&lt;String,Object&gt;}. If there are
* no parameters pass {@code null}.
*/
@Deprecated
@SuppressWarnings("unchecked")
public void setParams(final Object params) {
if (params == null) {
positionalParams = null;
namedParams = null;
} else if (params instanceof List) {
positionalParams = (List<Object>) params;
} else if (params instanceof Map) {
namedParams = (Map<String, Object>) params;
} else {
throw new IllegalArgumentException("The request parameters must be of type List, Map or null");
}
}
/**
* Sets the positional (JSON array) request parameters.
*
* @since 1.30
*
* @param positionalParams The positional (JSON array) request
* parameters, {@code null} if none.
*/
public void setPositionalParams(final List<Object> positionalParams) {
if (positionalParams == null)
return;
this.positionalParams = positionalParams;
}
/**
* Sets the named (JSON object) request parameters.
*
* @since 1.30
*
* @param namedParams The named (JSON object) request parameters,
* {@code null} if none.
*/
public void setNamedParams(final Map<String,Object> namedParams) {
if (namedParams == null)
return;
this.namedParams = namedParams;
}
/**
* Gets the request identifier.
*
* @return The request identifier ({@code Number}, {@code Boolean},
* {@code String}) or {@code null}.
*/
public Object getID() {
return id;
}
/**
* Sets the request identifier (ID).
*
* @param id The request identifier echoed back to the caller.
* The value must <a href="#map">map</a> to a JSON
* scalar ({@code null} and fractions, however, should
* be avoided).
*/
public void setID(final Object id) {
if (id == null ||
id instanceof Boolean ||
id instanceof Number ||
id instanceof String
) {
this.id = id;
} else {
this.id = id.toString();
}
}
@Override
public JSONObject toJSONObject() {
JSONObject req = new JSONObject();
req.put("method", method);
// The params can be omitted if none
switch (getParamsType()) {
case ARRAY:
req.put("params", positionalParams);
break;
case OBJECT:
req.put("params", namedParams);
break;
}
req.put("id", id);
req.put("jsonrpc", "2.0");
Map <String,Object> nonStdAttributes = getNonStdAttributes();
if (nonStdAttributes != null) {
for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
req.put(attr.getKey(), attr.getValue());
}
return req;
}
}

View File

@ -0,0 +1,414 @@
package com.thetransactioncompany.jsonrpc2;
import java.util.Map;
import net.minidev.json.JSONObject;
/**
* Represents a JSON-RPC 2.0 response.
*
* <p>A response is returned to the caller after a JSON-RPC 2.0 request has
* been processed (notifications, however, don't produce a response). The
* response can take two different forms depending on the outcome:
*
* <ul>
* <li>The request was successful. The corresponding response returns
* a JSON object with the following information:
* <ul>
* <li>{@code result} The result, which can be of any JSON type
* - a number, a boolean value, a string, an array, an object
* or null.
* <li>{@code id} The request identifier which is echoed back back
* to the caller.
* <li>{@code jsonrpc} A string indicating the JSON-RPC protocol
* version set to "2.0".
* </ul>
* <li>The request failed. The returned JSON object contains:
* <ul>
* <li>{@code error} An object with:
* <ul>
* <li>{@code code} An integer indicating the error type.
* <li>{@code message} A brief error messsage.
* <li>{@code data} Optional error data.
* </ul>
* <li>{@code id} The request identifier. If it couldn't be
* determined, e.g. due to a request parse error, the ID is
* set to {@code null}.
* <li>{@code jsonrpc} A string indicating the JSON-RPC protocol
* version set to "2.0".
* </ul>
* </ul>
*
* <p>Here is an example JSON-RPC 2.0 response string where the request
* has succeeded:
*
* <pre>
* {
* "result" : true,
* "id" : "req-002",
* "jsonrpc" : "2.0"
* }
* </pre>
*
*
* <p>And here is an example JSON-RPC 2.0 response string indicating a failure:
*
* <pre>
* {
* "error" : { "code" : -32601, "message" : "Method not found" },
* "id" : "req-003",
* "jsonrpc" : "2.0"
* }
* </pre>
*
* <p>A response object is obtained either by passing a valid JSON-RPC 2.0
* response string to the static {@link #parse} method or by invoking the
* appropriate constructor.
*
* <p>Here is how parsing is done:
*
* <pre>
* String jsonString = "{\"result\":true,\"id\":\"req-002\",\"jsonrpc\":\"2.0\"}";
*
* JSONRPC2Response response = null;
*
* try {
* response = JSONRPC2Response.parse(jsonString);
*
* } catch (JSONRPC2Exception e) {
* // handle exception
* }
* </pre>
*
* <p>And here is how you can replicate the above example response strings:
*
* <pre>
* // success example
* JSONRPC2Response resp = new JSONRPC2Response(true, "req-002");
* System.out.println(resp);
*
* // failure example
* JSONRPC2Error err = new JSONRPC2Error(-32601, "Method not found");
* resp = new JSONRPC2Response(err, "req-003");
* System.out.println(resp);
*
* </pre>
*
* <p id="map">The mapping between JSON and Java entities (as defined by the
* underlying JSON Smart library):
*
* <pre>
* true|false <---> java.lang.Boolean
* number <---> java.lang.Number
* string <---> java.lang.String
* array <---> java.util.List
* object <---> java.util.Map
* null <---> null
* </pre>
*
* @author Vladimir Dzhuvinov
*/
public class JSONRPC2Response extends JSONRPC2Message {
/**
* The result.
*/
private Object result = null;
/**
* The error object.
*/
private JSONRPC2Error error = null;
/**
* The echoed request identifier.
*/
private Object id = null;
/**
* Parses a JSON-RPC 2.0 response string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
* Must not be {@code null}.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Response parse(final String jsonString)
throws JSONRPC2ParseException {
return parse(jsonString, false, false, false);
}
/**
* Parses a JSON-RPC 2.0 response string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
* Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in results.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if parsing
* failed.
*/
public static JSONRPC2Response parse(final String jsonString,
final boolean preserveOrder)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, false, false);
}
/**
* Parses a JSON-RPC 2.0 response string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8 encoded.
* Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of JSON
* object members in results.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version attribute in the
* JSON-RPC 2.0 message.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if the parsing
* failed.
*/
public static JSONRPC2Response parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion)
throws JSONRPC2ParseException {
return parse(jsonString, preserveOrder, ignoreVersion, false);
}
/**
* Parses a JSON-RPC 2.0 response string. This method is thread-safe.
*
* @param jsonString The JSON-RPC 2.0 response string, UTF-8
* encoded. Must not be {@code null}.
* @param preserveOrder {@code true} to preserve the order of
* JSON object members in results.
* @param ignoreVersion {@code true} to skip a check of the
* {@code "jsonrpc":"2.0"} version
* attribute in the JSON-RPC 2.0 message.
* @param parseNonStdAttributes {@code true} to parse non-standard
* attributes found in the JSON-RPC 2.0
* message.
*
* @return The corresponding JSON-RPC 2.0 response object.
*
* @throws JSONRPC2ParseException With detailed message if the parsing
* failed.
*/
public static JSONRPC2Response parse(final String jsonString,
final boolean preserveOrder,
final boolean ignoreVersion,
final boolean parseNonStdAttributes)
throws JSONRPC2ParseException {
JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, ignoreVersion, parseNonStdAttributes);
return parser.parseJSONRPC2Response(jsonString);
}
/**
* Creates a new JSON-RPC 2.0 response to a successful request.
*
* @param result The result. The value can <a href="#map">map</a>
* to any JSON type. May be {@code null}.
* @param id The request identifier echoed back to the caller. May
* be {@code null} though not recommended.
*/
public JSONRPC2Response(final Object result, final Object id) {
setResult(result);
setID(id);
}
/**
* Creates a new JSON-RPC 2.0 response to a successful request which
* result is {@code null}.
*
* @param id The request identifier echoed back to the caller. May be
* {@code null} though not recommended.
*/
public JSONRPC2Response(final Object id) {
setResult(null);
setID(id);
}
/**
* Creates a new JSON-RPC 2.0 response to a failed request.
*
* @param error A JSON-RPC 2.0 error instance indicating the
* cause of the failure. Must not be {@code null}.
* @param id The request identifier echoed back to the caller.
* Pass a {@code null} if the request identifier couldn't
* be determined (e.g. due to a parse error).
*/
public JSONRPC2Response(final JSONRPC2Error error, final Object id) {
setError(error);
setID(id);
}
/**
* Indicates a successful JSON-RPC 2.0 request and sets the result.
* Note that if the response was previously indicating failure this
* will turn it into a response indicating success. Any previously set
* error data will be invalidated.
*
* @param result The result. The value can <a href="#map">map</a> to
* any JSON type. May be {@code null}.
*/
public void setResult(final Object result) {
// result and error are mutually exclusive
this.result = result;
this.error = null;
}
/**
* Gets the result of the request. The returned value has meaning
* only if the request was successful. Use the
* {@link #getError getError} method to check this.
*
* @return The result.
*/
public Object getResult() {
return result;
}
/**
* Indicates a failed JSON-RPC 2.0 request and sets the error details.
* Note that if the response was previously indicating success this
* will turn it into a response indicating failure. Any previously set
* result data will be invalidated.
*
* @param error A JSON-RPC 2.0 error instance indicating the cause of
* the failure. Must not be {@code null}.
*/
public void setError(final JSONRPC2Error error) {
if (error == null)
throw new IllegalArgumentException("The error object cannot be null");
// result and error are mutually exclusive
this.error = error;
this.result = null;
}
/**
* Gets the error object indicating the cause of the request failure.
* If a {@code null} is returned, the request succeeded and there was
* no error.
*
* @return A JSON-RPC 2.0 error object, {@code null} if the response
* indicates success.
*/
public JSONRPC2Error getError() {
return error;
}
/**
* A convinience method to check if the response indicates success or
* failure of the request. Alternatively, you can use the
* {@code #getError} method for this purpose.
*
* @return {@code true} if the request succeeded, {@code false} if
* there was an error.
*/
public boolean indicatesSuccess() {
return error == null;
}
/**
* Sets the request identifier echoed back to the caller.
*
* @param id The value must <a href="#map">map</a> to a JSON scalar.
* Pass a {@code null} if the request identifier couldn't
* be determined (e.g. due to a parse error).
*/
public void setID(final Object id) {
if (id == null ||
id instanceof Boolean ||
id instanceof Number ||
id instanceof String
) {
this.id = id;
} else {
this.id = id.toString();
}
}
/**
* Gets the request identifier that is echoed back to the caller.
*
* @return The request identifier. If there was an error during the
* the request retrieval (e.g. parse error) and the identifier
* couldn't be determined, the value will be {@code null}.
*/
public Object getID() {
return id;
}
@Override
public JSONObject toJSONObject() {
JSONObject out = new JSONObject();
// Result and error are mutually exclusive
if (error != null) {
out.put("error", error.toJSONObject());
}
else {
out.put("result", result);
}
out.put("id", id);
out.put("jsonrpc", "2.0");
Map <String,Object> nonStdAttributes = getNonStdAttributes();
if (nonStdAttributes != null) {
for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
out.put(attr.getKey(), attr.getValue());
}
return out;
}
}

View File

@ -0,0 +1,32 @@
/**
* Classes to represent, parse and serialise JSON-RPC 2.0 requests,
* notifications and responses.
*
* <p>JSON-RPC is a protocol for
* <a href="http://en.wikipedia.org/wiki/Remote_procedure_call">remote
* procedure calls</a> (RPC) using <a href="http://www.json.org" >JSON</a>
* - encoded requests and responses. It can be easily relayed over HTTP
* and is of JavaScript origin, making it ideal for use in dynamic web
* applications in the spirit of Ajax and Web 2.0.
*
* <p>This package implements <b>version 2.0</b> of the protocol, with the
* exception of <i>batching / multicall</i>. This feature is deliberately left
* out as it tends to confuse users (judging by posts in the JSON-RPC forum).
*
* <p>See the <a href="http://www.jsonrpc.org/specification"></a>JSON-RPC 2.0
* specification</a> for more information or write to the
* <a href="https://groups.google.com/forum/#!forum/json-rpc">user group</a> if
* you have questions.
*
* <p><b>Package dependencies:</b> The classes in this package rely on the
* {@code net.minidev.json} and {@code net.minidev.json.parser} packages
* (version 1.1.1 and compabile) for JSON encoding and decoding. You can obtain
* them from the <a href="http://code.google.com/p/json-smart/">JSON-Smart</a>
* website.
*
* @author Vladimir Dzhuvinov
*/
package com.thetransactioncompany.jsonrpc2;

View File

@ -0,0 +1,263 @@
package com.thetransactioncompany.jsonrpc2.server;
import java.util.Hashtable;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Notification;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
/**
* Dispatcher for JSON-RPC 2.0 requests and notifications. This class is
* tread-safe.
*
* <p>Use the {@code register()} methods to add a request or notification
* handler for an RPC method.
*
* <p>Use the {@code process()} methods to have an incoming request or
* notification processed by the matching handler.
*
* <p>The {@code reportProcTime()} method enables reporting of request
* processing time (in microseconds) by appending a non-standard "xProcTime"
* attribute to the resulting JSON-RPC 2.0 response message.
*
* <p>Example:
*
* <pre>
* {
* "result" : "xyz",
* "id" : 1,
* "jsonrpc" : "2.0",
* "xProcTime" : "189 us"
* }
* </pre>
*
* <p>Note: The dispatch(...) methods were deprecated in version 1.7. Use
* process(...) instead.
*
* @author Vladimir Dzhuvinov
*/
public class Dispatcher implements RequestHandler, NotificationHandler {
/**
* Hashtable of request name / handler pairs.
*/
private final Hashtable<String,RequestHandler> requestHandlers;
/**
* Hashtable of notification name / handler pairs.
*/
private final Hashtable<String,NotificationHandler> notificationHandlers;
/**
* Controls reporting of request processing time by appending a
* non-standard "xProcTime" attribute to the JSON-RPC 2.0 response.
*/
private boolean reportProcTime = false;
/**
* Creates a new dispatcher with no registered handlers.
*/
public Dispatcher() {
requestHandlers = new Hashtable<String,RequestHandler>();
notificationHandlers = new Hashtable<String,NotificationHandler>();
}
/**
* Registers a new JSON-RPC 2.0 request handler.
*
* @param handler The request handler to register. Must not be
* {@code null}.
*
* @throws IllegalArgumentException On attempting to register a handler
* that duplicates an existing request
* name.
*/
public void register(final RequestHandler handler) {
for (String name: handler.handledRequests()) {
if (requestHandlers.containsKey(name))
throw new IllegalArgumentException("Cannot register a duplicate JSON-RPC 2.0 handler for request " + name);
requestHandlers.put(name, handler);
}
}
/**
* Registers a new JSON-RPC 2.0 notification handler.
*
* @param handler The notification handler to register. Must not be
* {@code null}.
*
* @throws IllegalArgumentException On attempting to register a handler
* that duplicates an existing
* notification name.
*/
public void register(final NotificationHandler handler) {
for (String name: handler.handledNotifications()) {
if (notificationHandlers.containsKey(name))
throw new IllegalArgumentException("Cannot register a duplicate JSON-RPC 2.0 handler for notification " + name);
notificationHandlers.put(name, handler);
}
}
@Override
public String[] handledRequests() {
java.util.Set<String> var = requestHandlers.keySet();
return var.toArray(new String[var.size()]);
}
@Override
public String[] handledNotifications() {
java.util.Set<String> var = notificationHandlers.keySet();
return var.toArray(new String[var.size()]);
}
/**
* Gets the handler for the specified JSON-RPC 2.0 request name.
*
* @param requestName The request name to lookup.
*
* @return The corresponding request handler or {@code null} if none
* was found.
*/
public RequestHandler getRequestHandler(final String requestName) {
return requestHandlers.get(requestName);
}
/**
* Gets the handler for the specified JSON-RPC 2.0 notification name.
*
* @param notificationName The notification name to lookup.
*
* @return The corresponding notification handler or {@code null} if
* none was found.
*/
public NotificationHandler getNotificationHandler(final String notificationName) {
return notificationHandlers.get(notificationName);
}
/**
* @deprecated
*/
public JSONRPC2Response dispatch(final JSONRPC2Request request, final MessageContext requestCtx) {
return process(request, requestCtx);
}
@Override
public JSONRPC2Response process(final JSONRPC2Request request, final MessageContext requestCtx) {
long startNanosec = 0;
// Measure request processing time?
if (reportProcTime)
startNanosec = System.nanoTime();
final String method = request.getMethod();
RequestHandler handler = getRequestHandler(method);
if (handler == null) {
// We didn't find a handler for the requested RPC
Object id = request.getID();
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, id);
}
// Process the request
JSONRPC2Response response = handler.process(request, requestCtx);
if (reportProcTime) {
final long procTimeNanosec = System.nanoTime() - startNanosec;
response.appendNonStdAttribute("xProcTime", procTimeNanosec / 1000 + " us");
}
return response;
}
/**
* @deprecated
*/
public void dispatch(final JSONRPC2Notification notification, final MessageContext notificationCtx) {
process(notification, notificationCtx);
}
@Override
public void process(final JSONRPC2Notification notification, final MessageContext notificationCtx) {
final String method = notification.getMethod();
NotificationHandler handler = getNotificationHandler(method);
if (handler == null) {
// We didn't find a handler for the requested RPC
return;
}
// Process the notification
handler.process(notification, notificationCtx);
}
/**
* Controls reporting of request processing time by appending a
* non-standard "xProcTime" attribute to the JSON-RPC 2.0 response.
* Reporting is disabled by default.
*
* @param enable {@code true} to enable proccessing time reporting,
* {@code false} to disable it.
*/
public void reportProcTime(final boolean enable) {
reportProcTime = enable;
}
/**
* Returns {@code true} if reporting of request processing time is
* enabled. See the {@link #reportProcTime} description for more
* information.
*
* @return {@code true} if reporting of request processing time is
* enabled, else {@code false}.
*/
public boolean reportsProcTime() {
return reportProcTime;
}
}

View File

@ -0,0 +1,428 @@
package com.thetransactioncompany.jsonrpc2.server;
import java.net.InetAddress;
import java.net.URLConnection;
import java.security.Principal;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
/**
* Context information about JSON-RPC 2.0 request and notification messages.
* This class is immutable.
*
* <ul>
* <li>The client's host name.
* <li>The client's IP address.
* <li>Whether the request / notification was transmitted securely (e.g.
* via HTTPS).
* <li>The client principal(s) (user), if authenticated.
* </ul>
*
* @author Vladimir Dzhuvinov
*/
public class MessageContext {
/**
* The client hostname, {@code null} if none was specified.
*/
private String clientHostName = null;
/**
* The client IP address, {@code null} if none was specified.
*/
private String clientInetAddress = null;
/**
* Indicates whether the request was received over a secure channel
* (typically HTTPS).
*/
private boolean secure = false;
/**
* The authenticated client principals, {@code null} if none were
* specified.
*/
private Principal[] principals = null;
/**
* Minimal implementation of the {@link java.security.Principal}
* interface.
*/
public class BasicPrincipal implements Principal {
/**
* The principal name.
*/
private String name;
/**
* Creates a new principal.
*
* @param name The principal name, must not be {@code null} or
* empty string.
*
* @throws IllegalArgumentException On a {@code null} or empty
* principal name.
*/
public BasicPrincipal(final String name) {
if (name == null || name.trim().isEmpty())
throw new IllegalArgumentException("The principal name must be defined");
this.name = name;
}
/**
* Checks for equality.
*
* @param another The object to compare to.
*/
public boolean equals(final Object another) {
return another != null &&
another instanceof Principal &&
((Principal)another).getName().equals(this.getName());
}
/**
* Returns a hash code for this principal.
*
* @return The hash code.
*/
public int hashCode() {
return getName().hashCode();
}
/**
* Returns the principal name.
*
* @return The principal name.
*/
public String getName() {
return name;
}
}
/**
* Creates a new JSON-RPC 2.0 request / notification context.
*
* @param clientHostName The client host name, {@code null} if
* unknown.
* @param clientInetAddress The client IP address, {@code null} if
* unknown.
* @param secure Specifies a request received over HTTPS.
* @param principalName Specifies the authenticated client principle
* name, {@code null} if unknown. The name must
* not be an empty or blank string.
*/
public MessageContext(final String clientHostName,
final String clientInetAddress,
final boolean secure,
final String principalName) {
this.clientHostName = clientHostName;
this.clientInetAddress = clientInetAddress;
this.secure = secure;
if (principalName != null) {
principals = new Principal[1];
principals[0] = new BasicPrincipal(principalName);
}
}
/**
* Creates a new JSON-RPC 2.0 request / notification context.
*
* @param clientHostName The client host name, {@code null} if
* unknown.
* @param clientInetAddress The client IP address, {@code null} if
* unknown.
* @param secure Specifies a request received over HTTPS.
* @param principalNames Specifies the authenticated client principle
* names, {@code null} if unknown. The names
* must not be an empty or blank string.
*/
public MessageContext(final String clientHostName,
final String clientInetAddress,
final boolean secure,
final String[] principalNames) {
this.clientHostName = clientHostName;
this.clientInetAddress = clientInetAddress;
this.secure = secure;
if (principalNames != null) {
principals = new Principal[principalNames.length];
for (int i=0; i < principals.length; i++)
principals[0] = new BasicPrincipal(principalNames[i]);
}
}
/**
* Creates a new JSON-RPC 2.0 request / notification context. No
* authenticated client principal is specified.
*
* @param clientHostName The client host name, {@code null} if
* unknown.
* @param clientInetAddress The client IP address, {@code null} if
* unknown.
* @param secure Specifies a request received over HTTPS.
*/
public MessageContext(final String clientHostName,
final String clientInetAddress,
final boolean secure) {
this.clientHostName = clientHostName;
this.clientInetAddress = clientInetAddress;
this.secure = secure;
}
/**
* Creates a new JSON-RPC 2.0 request / notification context. Indicates
* an insecure transport (plain HTTP) and no authenticated client
* principal.
*
* @param clientHostName The client host name, {@code null} if
* unknown.
* @param clientInetAddress The client IP address, {@code null} if
* unknown.
*/
public MessageContext(final String clientHostName,
final String clientInetAddress) {
this.clientHostName = clientHostName;
this.clientInetAddress = clientInetAddress;
this.secure = false;
}
/**
* Creates a new JSON-RPC 2.0 request / notification context. Indicates
* an insecure transport (plain HTTP) and no authenticated client
* principal. Not client host name / IP is specified.
*/
public MessageContext() {
this.secure = false;
}
/**
* Creates a new JSON-RPC 2.0 request / notification context from the
* specified HTTP request.
*
* @param httpRequest The HTTP request.
*/
public MessageContext(final HttpServletRequest httpRequest) {
clientInetAddress = httpRequest.getRemoteAddr();
clientHostName = httpRequest.getRemoteHost();
if (clientHostName != null && clientHostName.equals(clientInetAddress))
clientHostName = null; // not resolved actually
secure = httpRequest.isSecure();
X509Certificate[] certs = (X509Certificate[])httpRequest.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null && certs.length > 0) {
principals = new Principal[certs.length];
for (int i=0; i < principals.length; i++)
principals[i] = certs[i].getSubjectX500Principal();
}
}
/**
* Creates a new JSON-RPC 2.0 request / notification context from the
* specified URL connection. Use this constructor in cases when the
* HTTP server is the origin of the JSON-RPC 2.0 requests /
* notifications. If the IP address of the HTTP server cannot be
* resolved {@link #getClientInetAddress} will return {@code null}.
*
* @param connection The URL connection, must be established and not
* {@code null}.
*/
public MessageContext(final URLConnection connection) {
clientHostName = connection.getURL().getHost();
InetAddress ip = null;
if (clientHostName != null) {
try {
ip = InetAddress.getByName(clientHostName);
} catch (Exception e) {
// UnknownHostException, SecurityException
// ignore
}
}
if (ip != null)
clientInetAddress = ip.getHostAddress();
if (connection instanceof HttpsURLConnection) {
secure = true;
HttpsURLConnection httpsConnection = (HttpsURLConnection)connection;
Principal prn = null;
try {
prn = httpsConnection.getPeerPrincipal();
} catch (Exception e) {
// SSLPeerUnverifiedException, IllegalStateException
// ignore
}
if (prn != null) {
principals = new Principal[1];
principals[0] = prn;
}
}
}
/**
* Gets the host name of the client that sent the request /
* notification.
*
* @return The client host name, {@code null} if unknown.
*/
public String getClientHostName() {
return clientHostName;
}
/**
* Gets the IP address of the client that sent the request /
* notification.
*
* @return The client IP address, {@code null} if unknown.
*/
public String getClientInetAddress() {
return clientInetAddress;
}
/**
* Indicates whether the request / notification was received over a
* secure HTTPS connection.
*
* @return {@code true} If the request was received over HTTPS,
* {@code false} if it was received over plain HTTP.
*/
public boolean isSecure() {
return secure;
}
/**
* Returns the first authenticated client principal, {@code null} if
* none.
*
* @return The first client principal, {@code null} if none.
*/
public Principal getPrincipal() {
if (principals != null)
return principals[0];
else
return null;
}
/**
* Returns the authenticated client principals, {@code null} if
* none.
*
* @return The client principals, {@code null} if none.
*/
public Principal[] getPrincipals() {
return principals;
}
/**
* Returns the first authenticated client principal name, {@code null}
* if none.
*
* @return The first client principal name, {@code null} if none.
*/
public String getPrincipalName() {
if (principals != null)
return principals[0].getName();
else
return null;
}
/**
* Returns the authenticated client principal names, {@code null}
* if none.
*
* @return The client principal names, {@code null} if none.
*/
public String[] getPrincipalNames() {
String[] names = new String[principals.length];
for (int i=0; i < names.length; i++)
names[i] = principals[i].getName();
return names;
}
@Override
public String toString() {
String s = "[host=" + clientHostName + " hostIP=" + clientInetAddress + " secure=" + secure;
if (principals != null) {
int i = 0;
for (Principal p: principals)
s += " principal[" + (i++) + "]=" + p;
}
return s + "]";
}
}

View File

@ -0,0 +1,35 @@
package com.thetransactioncompany.jsonrpc2.server;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Notification;
/**
* Interface for handling JSON-RPC 2.0 notifications.
*
* @author Vladimir Dzhuvinov
*/
public interface NotificationHandler {
/**
* Gets the names of the handled JSON-RPC 2.0 notification methods.
*
* @return The names of the handled JSON-RPC 2.0 notification methods.
*/
public String[] handledNotifications();
/**
* Processes a JSON-RPC 2.0 notification.
*
* <p>Note that JSON-RPC 2.0 notifications don't produce a response!
*
* @param notification A valid JSON-RPC 2.0 notification instance.
* Must not be {@code null}.
* @param notificationCtx Context information about the notification
* message, may be {@code null} if undefined.
*/
public void process(final JSONRPC2Notification notification, final MessageContext notificationCtx);
}

View File

@ -0,0 +1,36 @@
package com.thetransactioncompany.jsonrpc2.server;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
/**
* Interface for handling JSON-RPC 2.0 requests.
*
* @author Vladimir Dzhuvinov
*/
public interface RequestHandler {
/**
* Gets the names of the handled JSON-RPC 2.0 request methods.
*
* @return The names of the handled JSON-RPC 2.0 request methods.
*/
public String[] handledRequests();
/**
* Processes a JSON-RPC 2.0 request.
*
* @param request A valid JSON-RPC 2.0 request instance. Must not be
* {@code null}.
* @param requestCtx Context information about the request message, may
* be {@code null} if undefined.
*
* @return The resulting JSON-RPC 2.0 response. It indicates success
* or an error, such as METHOD_NOT_FOUND.
*/
public JSONRPC2Response process(final JSONRPC2Request request, final MessageContext requestCtx);
}

View File

@ -0,0 +1,34 @@
/**
* Simple server framework for processing JSON-RPC 2.0 requests and
* notifications.
*
* <p>Usage:
*
* <ol>
* <li>Implement {@link com.thetransactioncompany.jsonrpc2.server.RequestHandler request}
* and / or {@link com.thetransactioncompany.jsonrpc2.server.NotificationHandler notification}
* handlers for the various expected JSON-RPC 2.0 messages. A handler
* may process one or more request/notification methods (identified by
* method name).
* <li>Create a new {@link com.thetransactioncompany.jsonrpc2.server.Dispatcher}
* and register the handlers with it.
* <li>Pass the received JSON-RPC 2.0 requests and notifications to the
* appropriate {@code Dispatcher.dispatch(...)} method, then, if the
* message is a request, pass the resulting JSON-RPC 2.0 response back
* to the client.
* </ol>
*
* <p>Direct package dependencies:
*
* <ul>
* <li><b><a href="http://software.dzhuvinov.com/json-rpc-2.0-base.html">JSON-RPC 2.0 Base</a></b>
* [<i>com.thetransactioncompany.jsonrpc2</i>] to construct and represent
* JSON-RPC 2.0 messages.
* <li><b>Java Servlet API</b> [<i>javax.servlet.http</i>] for constructing
* {@link com.thetransactioncompany.jsonrpc2.server.MessageContext}
* objects from HTTP servlet requests.
* </ul>
*
* @author Vladimir Dzhuvinov
*/
package com.thetransactioncompany.jsonrpc2.server;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
package com.thetransactioncompany.jsonrpc2.util;
/**
* The base abstract class for the JSON-RPC 2.0 parameter retrievers.
*
* @author Vladimir Dzhuvinov
*/
public abstract class ParamsRetriever {
/**
* Returns the parameter count.
*
* @return The parameters count.
*/
public abstract int size();
/**
* Matches a string against an array of acceptable values.
*
* @param input The string to match.
* @param enumStrings The acceptable string values. Must not be
* {@code null}.
* @param ignoreCase {@code true} for a case insensitive match.
*
* @return The matching string value, {@code null} if no match was
* found.
*/
protected static String getEnumStringMatch(final String input,
final String[] enumStrings,
final boolean ignoreCase) {
for (final String en: enumStrings) {
if (ignoreCase) {
if (en.equalsIgnoreCase(input))
return en;
}
else {
if (en.equals(input))
return en;
}
}
return null;
}
/**
* Matches a string against an enumeration of acceptable values.
*
* @param input The string to match.
* @param enumClass The enumeration class specifying the acceptable
* string values. Must not be {@code null}.
* @param ignoreCase {@code true} for a case insensitive match.
*
* @return The matching enumeration constant, {@code null} if no match
* was found.
*/
protected static <T extends Enum<T>> T getEnumStringMatch(final String input,
final Class<T> enumClass,
final boolean ignoreCase) {
for (T en: enumClass.getEnumConstants()) {
if (ignoreCase) {
if (en.toString().equalsIgnoreCase(input))
return en;
}
else {
if (en.toString().equals(input))
return en;
}
}
return null;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
/**
* Utility classes for typed retrieval of JSON-RPC 2.0 request parameters on the
* server side.
*
* <p>The following parameter type conversion choices are available:
*
* <ul>
* <li>JSON true/false to Java {@code boolean}
* <li>JSON number to Java {@code int}, {@code long}, {@code float} or
* {@code double}
* <li>JSON string to {@code java.lang.String}
* <li>Predefined (enumerated) JSON string to a Java {@code enum} constant
* or {@code java.lang.String}
* <li>JSON array to Java {@code boolean[]}, {@code int[]}, {@code long[]},
* {@code float[]}, {@code double[]} or {@code string[]} array, or
* to mixed type {@code java.util.List}
* <li>JSON object to {@code java.util.Map}
* </ul>
*
* <p>If a parameter cannot be retrieved, either because it's missing or
* is of the wrong type, a standard
* {@link com.thetransactioncompany.jsonrpc2.JSONRPC2Error#INVALID_PARAMS}
* exception is thrown.
*
* <p>There are two concrete classes:
*
* <ul>
* <li>The {@link com.thetransactioncompany.jsonrpc2.util.PositionalParamsRetriever}
* class is for extracting <em>positional parameters</em> (packed in a
* JSON array).
* <li>The {@link com.thetransactioncompany.jsonrpc2.util.NamedParamsRetriever}
* class is for extracting <em>named parameters</em> (packed in a JSON
* object).
* </ul>
*
*
* <p><b>Package dependencies:</b> The classes in this package depend on the
* sister {@link com.thetransactioncompany.jsonrpc2} package.
*
* @author Vladimir Dzhuvinov
*/
package com.thetransactioncompany.jsonrpc2.util;

View File

@ -0,0 +1,127 @@
package net.i2p.i2pcontrol;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import org.apache.http.conn.util.InetAddressUtils;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
/**
* Block certain Host headers to prevent DNS rebinding attacks.
*
* This Handler wraps the ContextHandlerCollection, which handles
* all the webapps (not just routerconsole).
* Therefore, this protects all the webapps.
*
* @since 0.12 copied from routerconsole
*/
public class HostCheckHandler extends HandlerWrapper
{
private final I2PAppContext _context;
private final Set<String> _listenHosts;
/**
* MUST call setListenHosts() afterwards.
*/
public HostCheckHandler(I2PAppContext ctx) {
super();
_context = ctx;
_listenHosts = new HashSet<String>(8);
}
/**
* Set the legal hosts.
* Not synched. Call this BEFORE starting.
* If empty, all are allowed.
*
* @param hosts contains hostnames or IPs. But we allow all IPs anyway.
*/
public void setListenHosts(Set<String> hosts) {
_listenHosts.clear();
_listenHosts.addAll(hosts);
}
/**
* Block by Host header, pass everything else to the delegate.
*/
public void handle(String pathInContext,
Request baseRequest,
HttpServletRequest httpRequest,
HttpServletResponse httpResponse)
throws IOException, ServletException
{
String host = httpRequest.getHeader("Host");
if (!allowHost(host)) {
Log log = _context.logManager().getLog(HostCheckHandler.class);
host = DataHelper.stripHTML(getHost(host));
String s = "Console request denied.\n" +
" To allow access using the hostname \"" + host + "\", add the line \"" +
I2PControlController.PROP_ALLOWED_HOSTS + '=' + host +
"\" to I2PControl.conf and restart.";
log.logAlways(Log.WARN, s);
httpResponse.sendError(403, s);
return;
}
super.handle(pathInContext, baseRequest, httpRequest, httpResponse);
}
/**
* Should we allow a request with this Host header?
*
* ref: https://en.wikipedia.org/wiki/DNS_rebinding
*
* @param host the HTTP Host header, null ok
* @return true if OK
*/
private boolean allowHost(String host) {
if (host == null)
return true;
// common cases
if (host.equals("127.0.0.1:7650") ||
host.equals("localhost:7650"))
return true;
// all allowed?
if (_listenHosts.isEmpty())
return true;
host = getHost(host);
if (_listenHosts.contains(host))
return true;
// allow all IP addresses
if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host))
return true;
//System.out.println(host + " not found in " + s);
return false;
}
/**
* Strip [] and port from a host header
*
* @param host the HTTP Host header non-null
*/
private static String getHost(String host) {
if (host.startsWith("[")) {
host = host.substring(1);
int brack = host.indexOf(']');
if (brack >= 0)
host = host.substring(0, brack);
} else {
int colon = host.indexOf(':');
if (colon >= 0)
host = host.substring(0, colon);
}
return host;
}
}

View File

@ -0,0 +1,403 @@
package net.i2p.i2pcontrol;
/*
* Copyright 2010 hottuna (dev@robertfoss.se)
*
* 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 net.i2p.I2PAppContext;
import net.i2p.app.ClientAppManager;
import net.i2p.app.ClientAppState;
import static net.i2p.app.ClientAppState.*;
import net.i2p.router.RouterContext;
import net.i2p.router.app.RouterApp;
import net.i2p.util.I2PSSLSocketFactory;
import net.i2p.util.Log;
import net.i2p.util.PortMapper;
import net.i2p.i2pcontrol.security.KeyStoreProvider;
import net.i2p.i2pcontrol.security.SecurityManager;
import net.i2p.i2pcontrol.servlets.JSONRPC2Servlet;
import net.i2p.i2pcontrol.servlets.configuration.ConfigurationManager;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
/**
* This handles the starting and stopping of Jetty
* from a single static class so it can be called via clients.config.
*
* This makes installation of a new eepsite a turnkey operation.
*
* Usage: I2PControlController -d $PLUGIN [start|stop]
*
* @author hottuna
*/
public class I2PControlController implements RouterApp {
// non-null
private final I2PAppContext _appContext;
// warning, null in app context
private final RouterContext _context;
private final ClientAppManager _mgr;
private final Log _log;
private final String _pluginDir;
private final ConfigurationManager _conf;
private final KeyStoreProvider _ksp;
private final SecurityManager _secMan;
private final Server _server;
private ClientAppState _state = UNINITIALIZED;
// only for main()
private static I2PControlController _instance;
static final String PROP_ALLOWED_HOSTS = "i2pcontrol.allowedhosts";
private static final String SVC_HTTPS_I2PCONTROL = "https_i2pcontrol";
/**
* RouterApp (new way)
*/
public I2PControlController(RouterContext ctx, ClientAppManager mgr, String args[]) {
_appContext = _context = ctx;
_mgr = mgr;
_log = _appContext.logManager().getLog(I2PControlController.class);
File pluginDir = new File(_context.getAppDir(), "plugins/I2PControl");
_pluginDir = pluginDir.getAbsolutePath();
_conf = new ConfigurationManager(_appContext, pluginDir, true);
_ksp = new KeyStoreProvider(_pluginDir);
_secMan = new SecurityManager(_appContext, _ksp, _conf);
_server = buildServer();
_state = INITIALIZED;
}
/**
* From main() (old way)
*/
public I2PControlController(File pluginDir) {
_appContext = I2PAppContext.getGlobalContext();
if (_appContext instanceof RouterContext)
_context = (RouterContext) _appContext;
else
_context = null;
_mgr = null;
_log = _appContext.logManager().getLog(I2PControlController.class);
_pluginDir = pluginDir.getAbsolutePath();
_conf = new ConfigurationManager(_appContext, pluginDir, true);
_ksp = new KeyStoreProvider(_pluginDir);
_secMan = new SecurityManager(_appContext, _ksp, _conf);
_server = buildServer();
_state = INITIALIZED;
}
/////// ClientApp methods
public synchronized void startup() {
changeState(STARTING);
try {
start(null);
changeState(RUNNING);
} catch (Exception e) {
changeState(START_FAILED, "Failed to start", e);
_log.error("Unable to start jetty server", e);
stop();
}
}
public synchronized void shutdown(String[] args) {
if (_state == STOPPED)
return;
changeState(STOPPING);
stop();
changeState(STOPPED);
}
public synchronized ClientAppState getState() {
return _state;
}
public String getName() {
return "I2PControl";
}
public String getDisplayName() {
return "I2PControl";
}
/////// end ClientApp methods
private void changeState(ClientAppState state) {
changeState(state, null, null);
}
private synchronized void changeState(ClientAppState state, String msg, Exception e) {
_state = state;
if (_mgr != null)
_mgr.notify(this, state, msg, e);
if (_context == null) {
if (msg != null)
System.out.println(state + ": " + msg);
if (e != null)
e.printStackTrace();
}
}
/**
* Deprecated, use constructor
*/
public static void main(String args[]) {
if (args.length != 3 || (!"-d".equals(args[0])))
throw new IllegalArgumentException("Usage: PluginController -d $PLUGINDIR [start|stop]");
if ("start".equals(args[2])) {
File pluginDir = new File(args[1]);
if (!pluginDir.exists())
throw new IllegalArgumentException("Plugin directory " + pluginDir.getAbsolutePath() + " does not exist");
synchronized(I2PControlController.class) {
if (_instance != null)
throw new IllegalStateException();
I2PControlController i2pcc = new I2PControlController(pluginDir);
try {
i2pcc.startup();
_instance = i2pcc;
} catch (Exception e) {
e.printStackTrace();
}
}
} else if ("stop".equals(args[2])) {
synchronized(I2PControlController.class) {
if (_instance != null) {
_instance.shutdown(null);
_instance = null;
}
}
} else {
throw new IllegalArgumentException("Usage: PluginController -d $PLUGINDIR [start|stop]");
}
}
private synchronized void start(String args[]) throws Exception {
_appContext.logManager().getLog(JSONRPC2Servlet.class).setMinimumPriority(Log.DEBUG);
_server.start();
_context.portMapper().register(SVC_HTTPS_I2PCONTROL,
_conf.getConf("i2pcontrol.listen.address", "127.0.0.1"),
_conf.getConf("i2pcontrol.listen.port", 7650));
}
/**
* Builds a new server. Used for changing ports during operation and such.
* @return Server - A new server built from current configuration.
*/
private Connector buildDefaultListener(Server server) {
Connector ssl = buildSslListener(server, _conf.getConf("i2pcontrol.listen.address", "127.0.0.1"),
_conf.getConf("i2pcontrol.listen.port", 7650));
return ssl;
}
/**
* Builds a new server. Used for changing ports during operation and such.
*
* Does NOT start the server. Must call start() on the returned server.
*
* @return Server - A new server built from current configuration.
*/
public Server buildServer() {
Server server = new Server();
Connector ssl = buildDefaultListener(server);
server.addConnector(ssl);
ServletHandler sh = new ServletHandler();
sh.addServletWithMapping(new ServletHolder(new JSONRPC2Servlet(_context, _secMan)), "/");
HostCheckHandler hch = new HostCheckHandler(_appContext);
Set<String> listenHosts = new HashSet<String>(8);
// fix up the allowed hosts set (see HostCheckHandler)
// empty set says all are valid
String address = _conf.getConf("i2pcontrol.listen.address", "127.0.0.1");
if (!(address.equals("0.0.0.0") ||
address.equals("::") ||
address.equals("0:0:0:0:0:0:0:0"))) {
listenHosts.add("localhost");
listenHosts.add("127.0.0.1");
listenHosts.add("::1");
listenHosts.add("0:0:0:0:0:0:0:1");
String allowed = _conf.getConf(PROP_ALLOWED_HOSTS, "");
if (!allowed.equals("")) {
StringTokenizer tok = new StringTokenizer(allowed, " ,");
while (tok.hasMoreTokens()) {
listenHosts.add(tok.nextToken());
}
}
}
hch.setListenHosts(listenHosts);
hch.setHandler(sh);
server.getServer().setHandler(hch);
_conf.writeConfFile();
return server;
}
/**
* Creates a SSLListener with all the default options. The listener will use all the default options.
* @param address - The address the listener will listen to.
* @param port - The port the listener will listen to.
* @return - Newly created listener
*/
private Connector buildSslListener(Server server, String address, int port) {
int listeners = 0;
if (server != null) {
listeners = server.getConnectors().length;
}
// the keystore path and password
SslContextFactory sslFactory = new SslContextFactory(_ksp.getKeyStoreLocation());
sslFactory.setKeyStorePassword(KeyStoreProvider.DEFAULT_KEYSTORE_PASSWORD);
// the X.509 cert password (if not present, verifyKeyStore() returned false)
sslFactory.setKeyManagerPassword(KeyStoreProvider.DEFAULT_CERTIFICATE_PASSWORD);
sslFactory.addExcludeProtocols(I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.toArray(
new String[I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.size()]));
sslFactory.addExcludeCipherSuites(I2PSSLSocketFactory.EXCLUDE_CIPHERS.toArray(
new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()]));
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
httpConfig.setSecurePort(port);
httpConfig.addCustomizer(new SecureRequestCustomizer());
// number of acceptors, (default) number of selectors
ServerConnector ssl = new ServerConnector(server, 1, 0,
new SslConnectionFactory(sslFactory, "http/1.1"),
new HttpConnectionFactory(httpConfig));
ssl.setHost(address);
ssl.setPort(port);
ssl.setIdleTimeout(90*1000); // default 10 sec
// all with same name will use the same thread pool
ssl.setName("I2PControl");
ssl.setName("SSL Listener-" + ++listeners);
return ssl;
}
/**
* Add a listener to the server
* If a listener listening to the same port as the provided listener
* uses already exists within the server, replace the one already used by
* the server with the provided listener.
* @param listener
* @throws Exception
*/
/****
public synchronized void replaceListener(Connector listener) throws Exception {
if (_server != null) {
stopServer();
}
_server = buildServer(listener);
}
****/
/**
* Get all listeners of the server.
* @return
*/
/****
public synchronized Connector[] getListeners() {
if (_server != null) {
return _server.getConnectors();
}
return new Connector[0];
}
****/
/**
* Removes all listeners
*/
/****
public synchronized void clearListeners() {
if (_server != null) {
for (Connector listen : getListeners()) {
_server.removeConnector(listen);
}
}
}
****/
/**
* Stop it
*/
private synchronized void stopServer()
{
try {
if (_server != null) {
_appContext.portMapper().unregister(SVC_HTTPS_I2PCONTROL);
_server.stop();
for (Connector listener : _server.getConnectors()) {
listener.stop();
}
_server.destroy();
}
} catch (Exception e) {
_log.error("Stopping server", e);
}
}
private synchronized void stop() {
_conf.writeConfFile();
_secMan.stopTimedEvents();
stopServer();
/****
// Get and stop all running threads
ThreadGroup threadgroup = Thread.currentThread().getThreadGroup();
Thread[] threads = new Thread[threadgroup.activeCount() + 3];
threadgroup.enumerate(threads, true);
for (Thread thread : threads) {
if (thread != null) {//&& thread.isAlive()){
thread.interrupt();
}
}
for (Thread thread : threads) {
if (thread != null) {
System.out.println("Active thread: " + thread.getName());
}
}
threadgroup.interrupt();
//Thread.currentThread().getThreadGroup().destroy();
****/
}
public String getPluginDir() {
return _pluginDir;
}
}

View File

@ -0,0 +1,20 @@
package net.i2p.i2pcontrol;
import java.util.HashSet;
import java.util.Set;
public class I2PControlVersion {
/** The current version of I2PControl */
public final static String VERSION = "0.12.0";
/** The current version of the I2PControl API being primarily being implemented */
public final static int API_VERSION = 1;
/** The supported versions of the I2PControl API */
public final static Set<Integer> SUPPORTED_API_VERSIONS;
static {
SUPPORTED_API_VERSIONS = new HashSet<Integer>();
SUPPORTED_API_VERSIONS.add(1);
}
}

View File

@ -0,0 +1,50 @@
package net.i2p.i2pcontrol.security;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class AuthToken {
static final int VALIDITY_TIME = 1; // Measured in days
private final SecurityManager _secMan;
private final String id;
private final Date expiry;
public AuthToken(SecurityManager secMan, String password) {
_secMan = secMan;
String hash = _secMan.getPasswdHash(password);
this.id = _secMan.getHash(hash + Calendar.getInstance().getTimeInMillis());
Calendar expiry = Calendar.getInstance();
expiry.add(Calendar.DAY_OF_YEAR, VALIDITY_TIME);
this.expiry = expiry.getTime();
}
public String getId() {
return id;
}
/**
* Checks whether the AuthToken has expired.
* @return True if AuthToken hasn't expired. False in any other case.
*/
public boolean isValid() {
return Calendar.getInstance().getTime().before(expiry);
}
public String getExpiryTime() {
SimpleDateFormat sdf = new SimpleDateFormat();
sdf.applyPattern("yyyy-MM-dd HH:mm:ss");
return sdf.format(expiry);
}
@Override
public String toString() {
return id;
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View File

@ -0,0 +1,16 @@
package net.i2p.i2pcontrol.security;
public class ExpiredAuthTokenException extends Exception {
private static final long serialVersionUID = 2279019346592900289L;
private String expiryTime;
public ExpiredAuthTokenException(String str, String expiryTime) {
super(str);
this.expiryTime = expiryTime;
}
public String getExpirytime() {
return expiryTime;
}
}

View File

@ -0,0 +1,9 @@
package net.i2p.i2pcontrol.security;
public class InvalidAuthTokenException extends Exception {
private static final long serialVersionUID = 7605321329341235577L;
public InvalidAuthTokenException(String str) {
super(str);
}
}

View File

@ -0,0 +1,218 @@
package net.i2p.i2pcontrol.security;
import net.i2p.crypto.KeyStoreUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class KeyStoreProvider {
public static final String DEFAULT_CERTIFICATE_ALGORITHM_STRING = "RSA";
public static final int DEFAULT_CERTIFICATE_KEY_LENGTH = 4096;
public static final int DEFAULT_CERTIFICATE_VALIDITY = 365 * 10;
public final static String DEFAULT_CERTIFICATE_DOMAIN = "localhost";
public final static String DEFAULT_CERTIFICATE_ALIAS = "I2PControl CA";
public static final String DEFAULT_KEYSTORE_NAME = "i2pcontrol.ks";
public static final String DEFAULT_KEYSTORE_PASSWORD = KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD;
public static final String DEFAULT_CERTIFICATE_PASSWORD = "nut'nfancy";
private final String _pluginDir;
private KeyStore _keystore;
public KeyStoreProvider(String pluginDir) {
_pluginDir = pluginDir;
}
public void initialize() {
KeyStoreUtil.createKeys(new File(getKeyStoreLocation()),
DEFAULT_KEYSTORE_PASSWORD,
DEFAULT_CERTIFICATE_ALIAS,
DEFAULT_CERTIFICATE_DOMAIN,
"i2pcontrol",
DEFAULT_CERTIFICATE_VALIDITY,
DEFAULT_CERTIFICATE_ALGORITHM_STRING,
DEFAULT_CERTIFICATE_KEY_LENGTH,
DEFAULT_CERTIFICATE_PASSWORD);
}
/**
* @param password unused
* @return null on failure
*/
public static X509Certificate readCert(KeyStore ks, String certAlias, String password) {
try {
X509Certificate cert = (X509Certificate) ks.getCertificate(certAlias);
if (cert == null) {
throw new RuntimeException("Got null cert from keystore!");
}
try {
cert.verify(cert.getPublicKey());
return cert;
} catch (Exception e) {
System.err.println("Failed to verify caCert certificate against caCert");
e.printStackTrace();
}
} catch (KeyStoreException e) {
e.printStackTrace();
}
return null;
}
/**
* @param password for the keystore
* @return null on failure
*/
/****
public static X509Certificate readCert(File keyStoreFile, String certAlias, String password) {
try {
KeyStore ks = getDefaultKeyStore();
ks.load(new FileInputStream(keyStoreFile), password.toCharArray());
X509Certificate cert = (X509Certificate) ks.getCertificate(certAlias);
if (cert == null) {
throw new RuntimeException("Got null cert from keystore!");
}
try {
cert.verify(cert.getPublicKey());
return cert;
} catch (Exception e) {
System.err.println("Failed to verify caCert certificate against caCert");
e.printStackTrace();
}
} catch (IOException e) {
System.err.println("Couldn't read keystore from: " + keyStoreFile.toString());
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
System.err.println("No certificate with alias: " + certAlias + " found.");
e.printStackTrace();
}
return null;
}
****/
/**
* @param password for the key
* @return null on failure, or throws RuntimeException...
*/
/****
public static PrivateKey readPrivateKey(KeyStore ks, String alias, String password) {
try {
// load the key entry from the keystore
Key key = ks.getKey(alias, password.toCharArray());
if (key == null) {
throw new RuntimeException("Got null key from keystore!");
}
PrivateKey privKey = (PrivateKey) key;
return privKey;
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return null;
}
****/
/**
* @return null on failure
*/
/****
public static PrivateKey readPrivateKey(String alias, File keyStoreFile, String keyStorePassword, String keyPassword) {
try {
KeyStore ks = getDefaultKeyStore();
ks.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray());
return readPrivateKey(ks, alias, keyStorePassword);
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
System.err.println("Couldn't read keystore from: " + keyStoreFile.toString());
e.printStackTrace();
}
return null;
}
****/
/**
* @return null on failure
*/
/****
public static KeyStore writeCACertToKeyStore(KeyStore keyStore, String keyPassword, String alias, PrivateKey caPrivKey, X509Certificate caCert) {
try {
X509Certificate[] chain = new X509Certificate[1];
chain[0] = caCert;
keyStore.setKeyEntry(alias, caPrivKey, keyPassword.toCharArray(), chain);
File keyStoreFile = new File(I2PControlController.getPluginDir() + File.separator + DEFAULT_KEYSTORE_NAME);
keyStore.store(new FileOutputStream(keyStoreFile), DEFAULT_KEYSTORE_PASSWORD.toCharArray());
return keyStore;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
****/
/**
* @return null on failure
*/
public synchronized KeyStore getDefaultKeyStore() {
if (_keystore == null) {
File keyStoreFile = new File(getKeyStoreLocation());
try {
_keystore = KeyStore.getInstance(KeyStore.getDefaultType());
if (keyStoreFile.exists()) {
InputStream is = new FileInputStream(keyStoreFile);
_keystore.load(is, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
return _keystore;
}
initialize();
if (keyStoreFile.exists()) {
InputStream is = new FileInputStream(keyStoreFile);
_keystore.load(is, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
return _keystore;
} else {
throw new IOException("KeyStore file " + keyStoreFile.getAbsolutePath() + " wasn't readable");
}
} catch (Exception e) {
// Ignore. Not an issue. Let's just create a new keystore instead.
}
return null;
} else {
return _keystore;
}
}
public String getKeyStoreLocation() {
File keyStoreFile = new File(_pluginDir, DEFAULT_KEYSTORE_NAME);
return keyStoreFile.getAbsolutePath();
}
}

View File

@ -0,0 +1,252 @@
package net.i2p.i2pcontrol.security;
/*
* Copyright 2011 hottuna (dev@robertfoss.se)
*
* 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 net.i2p.I2PAppContext;
import net.i2p.crypto.SHA256Generator;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
import org.mindrot.jbcrypt.BCrypt;
import net.i2p.i2pcontrol.servlets.configuration.ConfigurationManager;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.security.KeyStore;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
/**
* Manage the password storing for I2PControl.
*/
public class SecurityManager {
public final static String DEFAULT_AUTH_PASSWORD = "itoopie";
private final HashMap<String, AuthToken> authTokens;
private final SimpleTimer2.TimedEvent timer;
private final KeyStore _ks;
private final Log _log;
private final ConfigurationManager _conf;
private final I2PAppContext _context;
/**
* @param ksp may be null (if webapp)
*/
public SecurityManager(I2PAppContext ctx, KeyStoreProvider ksp, ConfigurationManager conf) {
_context = ctx;
_conf = conf;
_log = ctx.logManager().getLog(SecurityManager.class);
authTokens = new HashMap<String, AuthToken>();
timer = new Sweeper();
_ks = ksp != null ? ksp.getDefaultKeyStore() : null;
}
public void stopTimedEvents() {
timer.cancel();
synchronized (authTokens) {
authTokens.clear();
}
}
/**
* Return the X509Certificate of the server as a Base64 encoded string.
* @return base64 encode of X509Certificate
*/
/**** unused and incorrectly uses I2P Base64. Switch to CertUtil.exportCert() if needed.
public String getBase64Cert() {
X509Certificate caCert = KeyStoreProvider.readCert(_ks,
KeyStoreProvider.DEFAULT_CERTIFICATE_ALIAS,
KeyStoreProvider.DEFAULT_KEYSTORE_PASSWORD);
return getBase64FromCert(caCert);
}
****/
/**
* Return the X509Certificate as a base64 encoded string.
* @param cert
* @return base64 encode of X509Certificate
*/
/**** unused and incorrectly uses I2P Base64. Switch to CertUtil.exportCert() if needed.
private static String getBase64FromCert(X509Certificate cert) {
try {
return Base64.encode(cert.getEncoded());
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
return null;
}
****/
/**
* Hash pwd with using BCrypt with the default salt.
* @param pwd
* @return BCrypt hash of salt and input string
*/
public String getPasswdHash(String pwd) {
String salt;
synchronized(_conf) {
salt = _conf.getConf("auth.salt", "");
if (salt.equals("")) {
salt = BCrypt.gensalt(10, _context.random());
_conf.setConf("auth.salt", salt);
_conf.writeConfFile();
}
}
return BCrypt.hashpw(pwd, salt);
}
/**
* Get saved password hash. Stores if not previously set.
* @return BCrypt hash of salt and password
* @since 0.12
*/
private String getSavedPasswdHash() {
String pw;
synchronized(_conf) {
pw = _conf.getConf("auth.password", "");
if (pw.equals("")) {
pw = getPasswdHash(DEFAULT_AUTH_PASSWORD);
_conf.setConf("auth.password", pw);
_conf.writeConfFile();
}
}
return pw;
}
/**
* Hash input one time with SHA-256, return Base64 encdoded string.
* @param string
* @return Base64 encoded string
*/
public String getHash(String string) {
SHA256Generator hashGen = _context.sha();
byte[] bytes = string.getBytes();
bytes = hashGen.calculateHash(bytes).toByteArray();
return Base64.encode(bytes);
}
/**
* Is this password correct?
* @return true if password is valid.
* @since 0.12
*/
public boolean isValid(String pwd) {
String storedPass = getSavedPasswdHash();
byte[] p1 = DataHelper.getASCII(getPasswdHash(pwd));
byte[] p2 = DataHelper.getASCII(storedPass);
return p1.length == p2.length && DataHelper.eqCT(p1, 0, p2, 0, p1.length);
}
/**
* Is this password correct?
* @return true if password is valid.
* @since 0.12
*/
public boolean isDefaultPasswordValid() {
return isValid(DEFAULT_AUTH_PASSWORD);
}
/**
* Add a Authentication Token if the provided password is valid.
* The token will be valid for one day.
* @return AuthToken if password is valid. If password is invalid null will be returned.
*/
public AuthToken validatePasswd(String pwd) {
if (isValid(pwd)) {
AuthToken token = new AuthToken(this, pwd);
synchronized (authTokens) {
authTokens.put(token.getId(), token);
}
return token;
} else {
return null;
}
}
/**
* Set new password. Old tokens will NOT remain valid, to encourage the new password being tested.
* @param newPasswd
* @return Returns true if a new password was set.
*/
public boolean setPasswd(String newPasswd) {
String newHash = getPasswdHash(newPasswd);
String oldHash = getSavedPasswdHash();
if (!newHash.equals(oldHash)) {
_conf.setConf("auth.password", newHash);
_conf.writeConfFile();
synchronized (authTokens) {
authTokens.clear();
}
return true;
}
return false;
}
/**
* Checks whether the AuthToken with the given ID exists and if it does whether is has expired.
* @param tokenID - The token to validate
* @throws InvalidAuthTokenException
* @throws ExpiredAuthTokenException
*/
public void verifyToken(String tokenID) throws InvalidAuthTokenException, ExpiredAuthTokenException {
synchronized (authTokens) {
AuthToken token = authTokens.get(tokenID);
if (token == null)
throw new InvalidAuthTokenException("AuthToken with ID: " + tokenID + " couldn't be found.");
if (!token.isValid()) {
authTokens.remove(tokenID);
throw new ExpiredAuthTokenException("AuthToken with ID: " + tokenID + " expired " + token.getExpiryTime(), token.getExpiryTime());
}
}
// Everything is fine. :)
}
/**
* Clean up old authorization tokens to keep the token store slim and fit.
* @author hottuna
*
*/
private class Sweeper extends SimpleTimer2.TimedEvent {
// Start running periodic task after 1 day, run periodically every 30 minutes.
public Sweeper() {
super(_context.simpleTimer2(), AuthToken.VALIDITY_TIME * 24*60*60*1000L);
}
public void timeReached() {
_log.debug("Starting cleanup job..");
synchronized (authTokens) {
for (Iterator<AuthToken> iter = authTokens.values().iterator(); iter.hasNext(); ) {
AuthToken token = iter.next();
if (!token.isValid())
iter.remove();
}
}
_log.debug("Cleanup job done.");
schedule(30*60*1000L);
}
}
}

View File

@ -0,0 +1,245 @@
package net.i2p.i2pcontrol.servlets;
/*
* Copyright 2011 hottuna (dev@robertfoss.se)
*
* 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 com.thetransactioncompany.jsonrpc2.*;
import com.thetransactioncompany.jsonrpc2.server.Dispatcher;
import net.i2p.I2PAppContext;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import net.i2p.util.PortMapper;
import net.i2p.i2pcontrol.I2PControlVersion;
import net.i2p.i2pcontrol.security.KeyStoreProvider;
import net.i2p.i2pcontrol.security.SecurityManager;
import net.i2p.i2pcontrol.servlets.jsonrpc2handlers.*;
import net.i2p.i2pcontrol.servlets.configuration.ConfigurationManager;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
/**
* Provide an JSON-RPC 2.0 API for remote controlling of I2P
*/
public class JSONRPC2Servlet extends HttpServlet {
private static final long serialVersionUID = -45075606818515212L;
private static final int BUFFER_LENGTH = 2048;
private static final String SVC_HTTP_I2PCONTROL = "http_i2pcontrol";
private static final String SVC_HTTPS_I2PCONTROL = "https_i2pcontrol";
private Dispatcher disp;
private Log _log;
private final SecurityManager _secMan;
private final ConfigurationManager _conf;
private final JSONRPC2Helper _helper;
private final RouterContext _context;
private final boolean _isWebapp;
private boolean _isHTTP, _isHTTPS;
/**
* Webapp
*/
public JSONRPC2Servlet() {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
if (!ctx.isRouterContext())
throw new IllegalStateException();
_context = (RouterContext) ctx;
File appDir = ctx.getAppDir();
_conf = new ConfigurationManager(ctx, appDir, false);
// we don't really need a keystore
//File ksDir = new File(ctx.getConfigDir(), "keystore");
//ksDir.mkDir();
//KeyStoreProvider ksp = new KeyStoreProvider(ksDir.getAbsolutePath());
//_secMan = new SecurityManager(ctx, ksp, _conf);
_secMan = new SecurityManager(ctx, null, _conf);
_helper = new JSONRPC2Helper(_secMan);
_log = ctx.logManager().getLog(JSONRPC2Servlet.class);
_conf.writeConfFile();
_isWebapp = true;
}
/**
* Plugin
*/
public JSONRPC2Servlet(RouterContext ctx, SecurityManager secMan) {
_context = ctx;
_secMan = secMan;
_helper = new JSONRPC2Helper(_secMan);
if (ctx != null)
_log = ctx.logManager().getLog(JSONRPC2Servlet.class);
else
_log = I2PAppContext.getGlobalContext().logManager().getLog(JSONRPC2Servlet.class);
_conf = null;
_isWebapp = false;
}
@Override
public void init() throws ServletException {
super.init();
disp = new Dispatcher();
disp.register(new EchoHandler(_helper));
disp.register(new GetRateHandler(_helper));
disp.register(new AuthenticateHandler(_helper, _secMan));
disp.register(new NetworkSettingHandler(_context, _helper));
disp.register(new RouterInfoHandler(_context, _helper));
disp.register(new RouterManagerHandler(_context, _helper));
disp.register(new I2PControlHandler(_context, _helper, _secMan));
disp.register(new AdvancedSettingsHandler(_context, _helper));
if (_isWebapp) {
PortMapper pm = _context.portMapper();
int port = pm.getPort(PortMapper.SVC_CONSOLE);
if (port > 0) {
String host = pm.getHost(PortMapper.SVC_CONSOLE, "127.0.0.1");
pm.register(SVC_HTTP_I2PCONTROL, host, port);
_isHTTP = true;
}
port = pm.getPort(PortMapper.SVC_HTTPS_CONSOLE);
if (port > 0) {
String host = pm.getHost(PortMapper.SVC_HTTPS_CONSOLE, "127.0.0.1");
pm.register(SVC_HTTPS_I2PCONTROL, host, port);
_isHTTPS = true;
}
}
}
@Override
public void destroy() {
if (_isWebapp) {
PortMapper pm = _context.portMapper();
if (_isHTTP)
pm.unregister(SVC_HTTP_I2PCONTROL);
if (_isHTTPS)
pm.unregister(SVC_HTTPS_I2PCONTROL);
_secMan.stopTimedEvents();
_conf.writeConfFile();
}
super.destroy();
}
@Override
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
httpServletResponse.setContentType("text/html");
PrintWriter out = httpServletResponse.getWriter();
out.println("<p>I2PControl RPC Service version " + I2PControlVersion.VERSION + " : Running");
if ("/password".equals(httpServletRequest.getServletPath())) {
out.println("<form method=\"POST\" action=\"password\">");
if (_secMan.isDefaultPasswordValid()) {
out.println("<p>The current API password is the default, \"" + _secMan.DEFAULT_AUTH_PASSWORD + "\". You should change it.");
} else {
out.println("<p>Current API password:<input name=\"password\" type=\"password\">");
}
out.println("<p>New API password (twice):<input name=\"password2\" type=\"password\">" +
"<input name=\"password3\" type=\"password\">" +
"<input name=\"save\" type=\"submit\" value=\"Change API Password\">" +
"<p>If you forget the API password, stop i2pcontrol, delete the file <tt>" + _conf.getConfFile() +
"</tt>, and restart i2pcontrol.");
} else {
out.println("<p><a href=\"password\">Change API Password</a>");
}
out.close();
}
/** @since 0.12 */
private void doPasswordChange(HttpServletRequest req, HttpServletResponse httpServletResponse) throws ServletException, IOException {
httpServletResponse.setContentType("text/html");
PrintWriter out = httpServletResponse.getWriter();
String pw = req.getParameter("password");
if (pw == null)
pw = _secMan.DEFAULT_AUTH_PASSWORD;
else
pw = pw.trim();
String pw2 = req.getParameter("password2");
String pw3 = req.getParameter("password3");
if (pw2 == null || pw3 == null) {
out.println("<p>Enter new password twice!");
} else {
pw2 = pw2.trim();
pw3 = pw3.trim();
if (!pw2.equals(pw3)) {
out.println("<p>New passwords don't match!");
} else if (pw2.length() <= 0) {
out.println("<p>Enter new password twice!");
} else if (_secMan.isValid(pw)) {
_secMan.setPasswd(pw2);
out.println("<p>API Password changed");
} else {
out.println("<p>Incorrect old password, not changed");
}
}
out.println("<p><a href=\"password\">Change API Password</a>");
}
@Override
protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
if ("/password".equals(httpServletRequest.getServletPath())) {
doPasswordChange(httpServletRequest, httpServletResponse);
return;
}
String req = getRequest(httpServletRequest.getInputStream());
httpServletResponse.setContentType("application/json");
PrintWriter out = httpServletResponse.getWriter();
JSONRPC2Message msg = null;
JSONRPC2Response jsonResp = null;
try {
msg = JSONRPC2Message.parse(req);
if (msg instanceof JSONRPC2Request) {
jsonResp = disp.process((JSONRPC2Request)msg, null);
jsonResp.toJSONObject().put("API", I2PControlVersion.API_VERSION);
if (_log.shouldDebug()) {
_log.debug("Request: " + msg);
_log.debug("Response: " + jsonResp);
}
}
else if (msg instanceof JSONRPC2Notification) {
disp.process((JSONRPC2Notification)msg, null);
if (_log.shouldDebug())
_log.debug("Notification: " + msg);
}
out.println(jsonResp);
out.close();
} catch (JSONRPC2ParseException e) {
_log.error("Unable to parse JSONRPC2Message: " + e.getMessage());
}
}
private String getRequest(ServletInputStream sis) throws IOException {
Writer writer = new StringWriter();
BufferedReader reader = new BufferedReader(new InputStreamReader(sis, "UTF-8"));
char[] readBuffer = new char[BUFFER_LENGTH];
int n;
while ((n = reader.read(readBuffer)) != -1) {
writer.write(readBuffer, 0, n);
}
return writer.toString();
}
}

View File

@ -0,0 +1,219 @@
package net.i2p.i2pcontrol.servlets.configuration;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
/**
* Manage the configuration of I2PControl.
* @author mathias
* modified: hottuna
*
*/
public class ConfigurationManager {
private final String CONFIG_FILE = "I2PControl.conf";
private final String WEBAPP_CONFIG_FILE = "i2pcontrol.config";
private final File configLocation;
private final Log _log;
private boolean _changed;
//Configurations with a String as value
private final Map<String, String> stringConfigurations = new HashMap<String, String>();
//Configurations with a Boolean as value
private final Map<String, Boolean> booleanConfigurations = new HashMap<String, Boolean>();
//Configurations with an Integer as value
private final Map<String, Integer> integerConfigurations = new HashMap<String, Integer>();
public ConfigurationManager(I2PAppContext ctx, File dir, boolean isPlugin) {
_log = ctx.logManager().getLog(ConfigurationManager.class);
if (isPlugin) {
configLocation = new File(dir, CONFIG_FILE);
} else {
configLocation = new File(dir, WEBAPP_CONFIG_FILE);
}
readConfFile();
}
/** @since 0.12 */
public File getConfFile() {
return configLocation;
}
/**
* Collects arguments of the form --word, --word=otherword and -blah
* to determine user parameters.
* @param settingNames Command line arguments to the application
*/
/****
public void loadArguments(String[] settingNames) {
for (int i = 0; i < settingNames.length; i++) {
String settingName = settingNames[i];
if (settingName.startsWith("--")) {
parseConfigStr(settingName.substring(2));
}
}
}
****/
/**
* Reads configuration from file, every line is parsed as key=value.
*/
public synchronized void readConfFile() {
try {
Properties input = new Properties();
// true: map to lower case
DataHelper.loadProps(input, configLocation, true);
parseConfigStr(input);
_changed = false;
} catch (FileNotFoundException e) {
if (_log.shouldInfo())
_log.info("Unable to find config file, " + configLocation);
} catch (IOException e) {
_log.error("Unable to read from config file, " + configLocation, e);
}
}
/**
* Write configuration into default config file.
* As of 0.12, doesn't actually write unless something changed.
*/
public synchronized void writeConfFile() {
if (!_changed)
return;
Properties tree = new OrderedProperties();
tree.putAll(stringConfigurations);
for (Entry<String, Integer> e : integerConfigurations.entrySet()) {
tree.put(e.getKey(), e.getValue().toString());
}
for (Entry<String, Boolean> e : booleanConfigurations.entrySet()) {
tree.put(e.getKey(), e.getValue().toString());
}
try {
DataHelper.storeProps(tree, configLocation);
_changed = false;
} catch (IOException e1) {
_log.error("Couldn't open file, " + configLocation + " for writing config.");
}
}
/**
* Try to parse the input as 'key=value',
* where value will (in order) be parsed as integer/boolean/string.
* @param str
*/
private void parseConfigStr(Properties input) {
for (Entry<