150 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
104 changed files with 15859 additions and 1618 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.3" />
<!-- 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="http://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,190 +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>
<Call name="addForward">
<Arg>/Seedless/seedless</Arg>
<Arg>/tracker/seedless.jsp</Arg>
</Call>
</New>
</Arg>
</Call>
</New>
</Arg>
</Call>
<Call name="addContext">
<Arg>/cgi-bin/*</Arg>
<Set name="ResourceBase">$PLUGIN/eepsite/cgi-bin</Set>
<Call name="addServlet">
<Arg>Common Gateway Interface</Arg>
<Arg>/</Arg>
<Arg>org.mortbay.servlet.CGI</Arg>
<Put name="Path">/usr/local/bin:/usr/ucb:/bin:/usr/bin</Put>
</Call>
</Call>
<!-- =============================================================== -->
<!-- Configure the Request Log -->
<!-- =============================================================== -->
<Set name="RequestLog">
<New class="org.mortbay.http.I2PRequestLog">
<Arg>$PLUGIN/eepsite/logs/yyyy_mm_dd.request.log</Arg>
<Set name="retainDays">30</Set>
<Set name="append">true</Set>
<Set name="extended">false</Set>
<Set name="buffered">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>
</Set>
<!-- =============================================================== -->
<!-- Configure the Other Server Options -->
<!-- =============================================================== -->
<Set name="requestsPerGC">2000</Set>
<Set name="statsOn">false</Set>
</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,92 +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="${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>
<property name="javac.compilerargs" value="" />
<target name="compile">
<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<Object, Object> entry : input.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
//Try parse as integer.
try {
int i = Integer.parseInt(value);
integerConfigurations.put(key, i);
continue;
} catch (NumberFormatException e) {}
//Check if value is a bool
if (value.toLowerCase().equals("true")) {
booleanConfigurations.put(key, Boolean.TRUE);
continue;
} else if (value.toLowerCase().equals("false")) {
booleanConfigurations.put(key, Boolean.FALSE);
continue;
}
stringConfigurations.put(key, value);
}
}
/**
* Check if a specific boolean configuration exists.
* @param settingName The key for the configuration.
* @param defaultValue If the configuration is not found, we use a default value.
* @return The value of a configuration: true if found, defaultValue if not found.
*/
public synchronized boolean getConf(String settingName, boolean defaultValue) {
Boolean value = booleanConfigurations.get(settingName);
if (value != null) {
return value;
} else {
booleanConfigurations.put(settingName, defaultValue);
_changed = true;
return defaultValue;
}
}
/**
* Check if a specific boolean configuration exists.
* @param settingName The key for the configuration.
* @param defaultValue If the configuration is not found, we use a default value.
* @return The value of a configuration: true if found, defaultValue if not found.
*/
public synchronized int getConf(String settingName, int defaultValue) {
Integer value = integerConfigurations.get(settingName);
if (value != null) {
return value;
} else {
integerConfigurations.put(settingName, defaultValue);
_changed = true;
return defaultValue;
}
}
/**
* Get a specific String configuration.
* @param settingName The key for the configuration.
* @param defaultValue If the configuration is not found, we use a default value.
* @return The value of the configuration, or the defaultValue.
*/
public synchronized String getConf(String settingName, String defaultValue) {
String value = stringConfigurations.get(settingName);
if (value != null) {
return value;
} else {
stringConfigurations.put(settingName, defaultValue);
_changed = true;
return defaultValue;
}
}
/**
* Set a specific int setting
* @param settingName
* @param nbr
*/
public synchronized void setConf(String settingName, int nbr) {
integerConfigurations.put(settingName, nbr);
_changed = true;
}
/**
* Set a specific string setting
* @param settingName
* @param str
*/
public synchronized void setConf(String settingName, String str) {
stringConfigurations.put(settingName, str);
_changed = true;
}
/**
* Set a specific boolean setting
* @param settingName
* @param bool
*/
public synchronized void setConf(String settingName, boolean bool) {
booleanConfigurations.put(settingName, bool);
_changed = true;
}
}

View File

@ -0,0 +1,200 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
import net.i2p.I2PAppContext;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class AdvancedSettingsHandler implements RequestHandler {
private final RouterContext _context;
private final Log _log;
private final JSONRPC2Helper _helper;
private static final String[] requiredArgs = {};
public AdvancedSettingsHandler(RouterContext ctx, JSONRPC2Helper helper) {
_helper = helper;
_context = ctx;
if (ctx != null)
_log = ctx.logManager().getLog(AdvancedSettingsHandler.class);
else
_log = I2PAppContext.getGlobalContext().logManager().getLog(AdvancedSettingsHandler.class);
}
// Reports the method names of the handled requests
public String[] handledRequests() {
return new String[] {"AdvancedSettings"};
}
// Processes the requests
@SuppressWarnings("unchecked")
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
if (req.getMethod().equals("AdvancedSettings")) {
JSONRPC2Error err = _helper.validateParams(requiredArgs, req);
if (err != null) {
return new JSONRPC2Response(err, req.getID());
}
if (_context == null) {
return new JSONRPC2Response(new JSONRPC2Error(
JSONRPC2Error.INTERNAL_ERROR.getCode(),
"RouterContext was not initialized. Query failed"),
req.getID());
}
@SuppressWarnings("rawtypes")
Map<String, Object> inParams = req.getNamedParams();
Map outParams = new HashMap();
if (inParams.containsKey("setAll")) {
Object obj = inParams.get("setAll");
if (!(obj instanceof Map)) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"Value of \"setAll\" is not a Map");
return new JSONRPC2Response(rpcErr, req.getID());
}
@SuppressWarnings("rawtypes")
Map objMap = (Map) inParams.get("setAll");
if (objMap.size() > 0)
{
if (!(objMap.keySet().toArray()[0] instanceof String) &&
!(objMap.values().toArray()[0] instanceof String)) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"Map of settings does not contain String keys and values");
return new JSONRPC2Response(rpcErr, req.getID());
}
if (!checkTypes(objMap)) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
"Some of the supplied values are not strings");
return new JSONRPC2Response(rpcErr, req.getID());
}
Map<String, String> allSettings = (Map<String, String>) objMap;
boolean success = setAdvancedSettings(allSettings, true);
if (!success) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
"Failed to save new config");
return new JSONRPC2Response(rpcErr, req.getID());
}
} else {
// Empty list of settings submitted
boolean success = setAdvancedSettings(null, true);
if (!success) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
"Failed to save new config");
return new JSONRPC2Response(rpcErr, req.getID());
}
}
}
if (inParams.containsKey("getAll")) {
outParams.put("getAll", getAdvancedSettings());
}
if (inParams.containsKey("set")) {
Object obj = inParams.get("set");
if (!(obj instanceof Map)) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"Value of \"set\" is not a Map");
return new JSONRPC2Response(rpcErr, req.getID());
}
Map objMap = (Map) inParams.get("set");
if (objMap.size() > 0)
{
if (!(objMap.keySet().toArray()[0] instanceof String) &&
!(objMap.values().toArray()[0] instanceof String)) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"Map of settings does not contain String keys and values");
return new JSONRPC2Response(rpcErr, req.getID());
}
if (!checkTypes(objMap)) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
"Some of the supplied values are not strings");
return new JSONRPC2Response(rpcErr, req.getID());
}
Map<String, String> allSettings = (Map<String, String>) objMap;
boolean success = setAdvancedSettings(allSettings, false);
if (!success) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
"Failed to save new config");
return new JSONRPC2Response(rpcErr, req.getID());
}
} else {
// Empty list of settings submitted
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"Map of settings does not contain any entries");
return new JSONRPC2Response(rpcErr, req.getID());
}
}
if (inParams.containsKey("get")) {
Object obj = inParams.get("get");
if (!(obj instanceof String)) {
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"Value of \"get\" is not a string");
return new JSONRPC2Response(rpcErr, req.getID());
}
String getStr = (String) obj;
String getVal = getAdvancedSetting(getStr);
Map<String, String> outMap = new HashMap<String, String>();
outMap.put(getStr, getVal);
outParams.put("get", outMap);
}
return new JSONRPC2Response(outParams, req.getID());
} else {
// Method name not supported
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
}
}
private String getAdvancedSetting(String key) {
return _context.router().getConfigSetting(key);
}
private Map<String, String> getAdvancedSettings() {
return _context.router().getConfigMap();
}
private boolean checkTypes(Map<String, Object> newSettings) {
for (String key : newSettings.keySet()) {
if (!(newSettings.get(key) instanceof String)) {
return false;
}
}
return true;
}
private boolean setAdvancedSettings(Map<String, String> newSettings, boolean clearConfig) {
Set<String> unsetKeys = null;
if (clearConfig) {
unsetKeys = new HashSet<String>(_context.router().getConfigSettings());
for (String key : newSettings.keySet()) {
unsetKeys.remove(key);
}
}
return _context.router().saveConfig(newSettings, unsetKeys);
}
}

View File

@ -0,0 +1,105 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
import net.i2p.i2pcontrol.I2PControlVersion;
import net.i2p.i2pcontrol.security.AuthToken;
import net.i2p.i2pcontrol.security.SecurityManager;
import java.util.HashMap;
import java.util.Map;
/*
* 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.
*
*/
public class AuthenticateHandler implements RequestHandler {
private static final String[] requiredArgs = {"Password", "API"};
private final JSONRPC2Helper _helper;
private final SecurityManager _secMan;
public AuthenticateHandler(JSONRPC2Helper helper, SecurityManager secMan) {
_helper = helper;
_secMan = secMan;
}
// Reports the method names of the handled requests
public String[] handledRequests() {
return new String[] {"Authenticate"};
}
// Processes the requests
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
if (req.getMethod().equals("Authenticate")) {
JSONRPC2Error err = _helper.validateParams(requiredArgs, req, JSONRPC2Helper.USE_NO_AUTH);
if (err != null)
return new JSONRPC2Response(err, req.getID());
Map<String, Object> inParams = req.getNamedParams();
String pwd = (String) inParams.get("Password");
// Try get an AuthToken
AuthToken token = _secMan.validatePasswd(pwd);
if (token == null) {
return new JSONRPC2Response(JSONRPC2ExtendedError.INVALID_PASSWORD, req.getID());
}
Object api = inParams.get("API");
err = validateAPIVersion(api);
if (err != null)
return new JSONRPC2Response(err, req.getID());
Map<String, Object> outParams = new HashMap<String, Object>(4);
outParams.put("Token", token.getId());
outParams.put("API", I2PControlVersion.API_VERSION);
return new JSONRPC2Response(outParams, req.getID());
} else {
// Method name not supported
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
}
}
/**
* Validate the provided I2PControl API version against the ones supported by I2PControl.
*/
private static JSONRPC2Error validateAPIVersion(Object api) {
Integer apiVersion;
try {
apiVersion = ((Long) api).intValue();
} catch (ClassCastException e) {
e.printStackTrace();
return JSONRPC2ExtendedError.UNSPECIFIED_API_VERSION;
}
if (!I2PControlVersion.SUPPORTED_API_VERSIONS.contains(apiVersion)) {
String supportedAPIVersions = "";
for (Integer i : I2PControlVersion.SUPPORTED_API_VERSIONS) {
supportedAPIVersions += ", " + i;
}
return new JSONRPC2Error(JSONRPC2ExtendedError.UNSUPPORTED_API_VERSION.getCode(),
"The provided API version \'" + apiVersion + "\' is not supported. The supported versions are" + supportedAPIVersions + ".");
}
return null;
}
}

View File

@ -0,0 +1,44 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
import java.util.HashMap;
import java.util.Map;
public class EchoHandler implements RequestHandler {
private static final String[] requiredArgs = {"Echo"};
private final JSONRPC2Helper _helper;
public EchoHandler(JSONRPC2Helper helper) {
_helper = helper;
}
// Reports the method names of the handled requests
public String[] handledRequests() {
return new String[] {"Echo"};
}
// Processes the requests
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
if (req.getMethod().equals("Echo")) {
JSONRPC2Error err = _helper.validateParams(requiredArgs, req);
if (err != null)
return new JSONRPC2Response(err, req.getID());
Map<String, Object> inParams = req.getNamedParams();
String echo = (String) inParams.get("Echo");
Map<String, Object> outParams = new HashMap<String, Object>(4);
outParams.put("Result", echo);
return new JSONRPC2Response(outParams, req.getID());
}
else {
// Method name not supported
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
}
}
}

View File

@ -0,0 +1,85 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
import net.i2p.I2PAppContext;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import java.util.HashMap;
import java.util.Map;
/*
* 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.
*
*/
public class GetRateHandler implements RequestHandler {
private static final String[] requiredArgs = {"Stat", "Period"};
private final JSONRPC2Helper _helper;
public GetRateHandler(JSONRPC2Helper helper) {
_helper = helper;
}
// Reports the method names of the handled requests
public String[] handledRequests() {
return new String[] {"GetRate"};
}
// Processes the requests
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
if (req.getMethod().equals("GetRate")) {
JSONRPC2Error err = _helper.validateParams(requiredArgs, req);
if (err != null)
return new JSONRPC2Response(err, req.getID());
Map<String, Object> inParams = req.getNamedParams();
String input = (String) inParams.get("Stat");
if (input == null) {
return new JSONRPC2Response(JSONRPC2Error.INVALID_PARAMS, req.getID());
}
long period;
try {
period = (Long) inParams.get("Period");
} catch (NumberFormatException e) {
return new JSONRPC2Response(JSONRPC2Error.INVALID_PARAMS, req.getID());
}
RateStat rateStat = I2PAppContext.getGlobalContext().statManager().getRate(input);
// If RateStat or the requested period doesn't already exist, create them.
if (rateStat == null || rateStat.getRate(period) == null) {
long[] tempArr = new long[1];
tempArr[0] = period;
I2PAppContext.getGlobalContext().statManager().createRequiredRateStat(input, "I2PControl", "I2PControl", tempArr);
rateStat = I2PAppContext.getGlobalContext().statManager().getRate(input);
}
if (rateStat.getRate(period) == null)
return new JSONRPC2Response(JSONRPC2Error.INTERNAL_ERROR, req.getID());
Map<String, Object> outParams = new HashMap<String, Object>(4);
Rate rate = rateStat.getRate(period);
rate.coalesce();
outParams.put("Result", rate.getAverageValue());
return new JSONRPC2Response(outParams, req.getID());
}
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
}
}

View File

@ -0,0 +1,205 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
import net.i2p.I2PAppContext;
import net.i2p.i2pcontrol.I2PControlController;
import net.i2p.i2pcontrol.security.SecurityManager;
import net.i2p.i2pcontrol.servlets.configuration.ConfigurationManager;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
/*
* 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.
*
*/
public class I2PControlHandler implements RequestHandler {
private static final int BW_BURST_PCT = 110;
private static final int BW_BURST_TIME = 20;
private final RouterContext _context;
private final Log _log;
//private final ConfigurationManager _conf;
private final SecurityManager _secMan;
private final JSONRPC2Helper _helper;
public I2PControlHandler(RouterContext ctx, JSONRPC2Helper helper, SecurityManager secMan) {
_helper = helper;
_secMan = secMan;
_context = ctx;
if (ctx != null)
_log = ctx.logManager().getLog(I2PControlHandler.class);
else
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PControlHandler.class);
}
// Reports the method names of the handled requests
public String[] handledRequests() {
return new String[] {"I2PControl"};
}
// Processes the requests
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
if (req.getMethod().equals("I2PControl")) {
return process(req);
} else {
// Method name not supported
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
}
}
private JSONRPC2Response process(JSONRPC2Request req) {
JSONRPC2Error err = _helper.validateParams(null, req);
if (err != null)
return new JSONRPC2Response(err, req.getID());
/**** only if we enable host/port changes
if (_context == null) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
"RouterContext was not initialized. Query failed"),
req.getID());
}
****/
Map<String, Object> inParams = req.getNamedParams();
Map<String, Object> outParams = new HashMap<String, Object>(4);
boolean restartNeeded = false;
boolean settingsSaved = false;
String inParam;
/****
if (inParams.containsKey("i2pcontrol.port")) {
Integer oldPort = _conf.getConf("i2pcontrol.listen.port", 7650);
if ((inParam = (String) inParams.get("i2pcontrol.port")) != null) {
if (oldPort == null || !inParam.equals(oldPort.toString())) {
Integer newPort;
try {
newPort = Integer.valueOf(inParam);
if (newPort < 1 || newPort > 65535) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2pcontrol.port\" must be a string representing a number in the range 1-65535. " + inParam + " isn't valid."),
req.getID());
}
try {
SslSocketConnector ssl = I2PControlController.buildSslListener(_conf.getConf("i2pcontrol.listen.address", "127.0.0.1"), newPort);
I2PControlController.clearListeners();
I2PControlController.replaceListener(ssl);
_conf.setConf("i2pcontrol.listen.port", newPort);
ConfigurationManager.writeConfFile();
outParams.put("i2pcontrol.port", null);
settingsSaved = true;
} catch (Exception e) {
try {
_conf.setConf("i2pcontrol.listen.port", oldPort);
SslSocketConnector ssl = I2PControlController.buildSslListener(_conf.getConf("i2pcontrol.listen.address", "127.0.0.1"), oldPort);
I2PControlController.clearListeners();
I2PControlController.replaceListener(ssl);
} catch (Exception e2) {
_log.log(Log.CRIT, "Unable to resume server on previous listening port.");
}
_log.error("Client tried to set listen port to, " + newPort + " which isn't valid.", e);
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2pcontrol.port\" has been set to a port that is already in use, reverting. " +
inParam + " is an already used port.\n"
+ "Exception: " + e.toString()),
req.getID());
}
}
}
outParams.put("RestartNeeded", restartNeeded);
}
****/
if (inParams.containsKey("i2pcontrol.password")) {
if ((inParam = (String) inParams.get("i2pcontrol.password")) != null) {
if (_secMan.setPasswd(inParam)) {
outParams.put("i2pcontrol.password", null);
settingsSaved = true;
}
}
}
/****
if (inParams.containsKey("i2pcontrol.address")) {
String oldAddress = _conf.getConf("i2pcontrol.listen.address", "127.0.0.1");
if ((inParam = (String) inParams.get("i2pcontrol.address")) != null) {
if ((oldAddress == null || !inParam.equals(oldAddress.toString()) &&
(inParam.equals("0.0.0.0") || inParam.equals("127.0.0.1")))) {
InetAddress[] newAddress;
try {
newAddress = InetAddress.getAllByName(inParam);
} catch (UnknownHostException e) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2pcontrol.address\" must be a string representing a hostname or ipaddress. " + inParam + " isn't valid."),
req.getID());
}
try {
SslSocketConnector ssl = I2PControlController.buildSslListener(inParam, _conf.getConf("i2pcontrol.listen.port", 7650));
I2PControlController.clearListeners();
I2PControlController.replaceListener(ssl);
_conf.setConf("i2pcontrol.listen.address", inParam);
ConfigurationManager.writeConfFile();
outParams.put("i2pcontrol.address", null);
settingsSaved = true;
} catch (Exception e) {
_conf.setConf("i2pcontrol.listen.address", oldAddress);
try {
SslSocketConnector ssl = I2PControlController.buildSslListener(inParam, _conf.getConf("i2pcontrol.listen.port", 7650));
I2PControlController.clearListeners();
I2PControlController.replaceListener(ssl);
} catch (Exception e2) {
_log.log(Log.CRIT, "Unable to resume server on previous listening ip.");
}
_log.error("Client tried to set listen address to, " + newAddress.toString() + " which isn't valid.", e);
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2pcontrol.address\" has been set to an invalid address, reverting. "), req.getID());
}
}
} else {
outParams.put("i2pcontrol.address", oldAddress);
}
outParams.put("RestartNeeded", restartNeeded);
}
****/
outParams.put("SettingsSaved", settingsSaved);
return new JSONRPC2Response(outParams, req.getID());
}
}

View File

@ -0,0 +1,124 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import net.minidev.json.JSONObject;
/*
* 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.
*
*/
/**
* Represents a JSON-RPC 2.0 error that occured during the processing of a
* request.
*
* <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.simple 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>
*
* <p>The JSON-RPC 2.0 specification and user group forum can be found
* <a href="http://groups.google.com/group/json-rpc">here</a>.
*
* @author <a href="http://dzhuvinov.com">Vladimir Dzhuvinov</a>
* @version 1.16 (2010-10-04)
*/
public class JSONRPC2ExtendedError extends JSONRPC2Error {
private static final long serialVersionUID = -6574632977222371077L;
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
public static final JSONRPC2Error INVALID_PASSWORD = new JSONRPC2ExtendedError(-32001, "Invalid password provided.");
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
public static final JSONRPC2Error NO_TOKEN = new JSONRPC2ExtendedError(-32002, "No authentication token presented.");
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
public static final JSONRPC2Error INVALID_TOKEN = new JSONRPC2ExtendedError(-32003, "Authentication token doesn't exist.");
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
public static final JSONRPC2Error TOKEN_EXPIRED = new JSONRPC2ExtendedError(-32004, "Provided authentication token was expired and will be removed.");
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
public static final JSONRPC2Error UNSPECIFIED_API_VERSION = new JSONRPC2ExtendedError(-32005, "The version of the I2PControl API wasn't specified, but is required to be specified.");
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
public static final JSONRPC2Error UNSUPPORTED_API_VERSION = new JSONRPC2ExtendedError(-32006, "The version of the I2PControl API specified is not supported by I2PControl.");
/**
* 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 JSONRPC2ExtendedError(int code, String message) {
super(code, message);
}
/**
* 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 JSONRPC2ExtendedError(int code, String message, Object data) {
super(code, message, data);
}
}

View File

@ -0,0 +1,111 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2ParamsType;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import net.i2p.i2pcontrol.security.ExpiredAuthTokenException;
import net.i2p.i2pcontrol.security.InvalidAuthTokenException;
import net.i2p.i2pcontrol.security.SecurityManager;
import java.util.Map;
/*
* 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.
*
*/
public class JSONRPC2Helper {
public final static Boolean USE_NO_AUTH = false;
public final static Boolean USE_AUTH = true;
private final SecurityManager _secMan;
public JSONRPC2Helper(SecurityManager secMan) {
_secMan = secMan;
}
/**
* Check incoming request for required arguments, to make sure they are valid.
* @param requiredArgs - Array of names of required arguments. If null don't check for any parameters.
* @param req - Incoming JSONRPC2 request
* @param useAuth - If true, will validate authentication token.
* @return - null if no errors were found. Corresponding JSONRPC2Error if error is found.
*/
public JSONRPC2Error validateParams(String[] requiredArgs, JSONRPC2Request req, Boolean useAuth) {
// Error on unnamed parameters
if (req.getParamsType() != JSONRPC2ParamsType.OBJECT) {
return JSONRPC2Error.INVALID_PARAMS;
}
Map<String, Object> params = req.getNamedParams();
// Validate authentication token.
if (useAuth) {
JSONRPC2Error err = validateToken(params);
if (err != null) {
return err;
}
}
// If there exist any required arguments.
if (requiredArgs != null && requiredArgs.length > 0) {
String missingArgs = "";
for (int i = 0; i < requiredArgs.length; i++) {
if (!params.containsKey(requiredArgs[i])) {
missingArgs = missingArgs.concat(requiredArgs[i] + ",");
}
}
if (missingArgs.length() > 0) {
missingArgs = missingArgs.substring(0, missingArgs.length() - 1);
return new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(), "Missing parameter(s): " + missingArgs);
}
}
return null;
}
/**
* Check incoming request for required arguments, to make sure they are valid. Will authenticate req.
* @param requiredArgs - Array of names of required arguments. If null don't check for any parameters.
* @param req - Incoming JSONRPC2 request
* @return - null if no errors were found. Corresponding JSONRPC2Error if error is found.
*/
public JSONRPC2Error validateParams(String[] requiredArgs, JSONRPC2Request req) {
return validateParams(requiredArgs, req, JSONRPC2Helper.USE_AUTH);
}
/**
* Will check incoming parameters to make sure they contain a valid token.
* @param req - Parameters of incoming request
* @return null if everything is fine, JSONRPC2Error for any corresponding error.
*/
private JSONRPC2Error validateToken(Map<String, Object> params) {
String tokenID = (String) params.get("Token");
if (tokenID == null) {
return JSONRPC2ExtendedError.NO_TOKEN;
}
try {
_secMan.verifyToken(tokenID);
} catch (InvalidAuthTokenException e) {
return JSONRPC2ExtendedError.INVALID_TOKEN;
} catch (ExpiredAuthTokenException e) {
JSONRPC2Error err = new JSONRPC2ExtendedError(JSONRPC2ExtendedError.TOKEN_EXPIRED.getCode(),
"Provided authentication token expired " + e.getExpirytime() + ", will be removed.");
return err;
}
return null;
}
}

View File

@ -0,0 +1,344 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
import net.i2p.I2PAppContext;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.FIFOBandwidthRefiller;
import net.i2p.router.transport.TransportManager;
import net.i2p.router.transport.ntcp.NTCPTransport;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.util.Log;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
/*
* 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.
*
*/
public class NetworkSettingHandler implements RequestHandler {
private static final int BW_BURST_PCT = 110;
private static final int BW_BURST_TIME = 20;
private final JSONRPC2Helper _helper;
private final RouterContext _context;
public NetworkSettingHandler(RouterContext ctx, JSONRPC2Helper helper) {
_helper = helper;
_context = ctx;
}
// Reports the method names of the handled requests
public String[] handledRequests() {
return new String[] {"NetworkSetting"};
}
// Processes the requests
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
if (req.getMethod().equals("NetworkSetting")) {
return process(req);
} else {
// Method name not supported
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
}
}
private JSONRPC2Response process(JSONRPC2Request req) {
JSONRPC2Error err = _helper.validateParams(null, req);
if (err != null)
return new JSONRPC2Response(err, req.getID());
if (_context == null) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
"RouterContext was not initialized. Query failed"),
req.getID());
}
Map<String, Object> inParams = req.getNamedParams();
Map<String, Object> outParams = new HashMap<String, Object>(4);
boolean restartNeeded = false;
boolean settingsSaved = false;
String inParam;
if (inParams.containsKey("i2p.router.net.ntcp.port")) {
String oldNTCPPort = _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_PORT);
if ((inParam = (String) inParams.get("i2p.router.net.ntcp.port")) != null) {
if (oldNTCPPort == null || !oldNTCPPort.equals(inParam.trim())) {
Integer newPort;
try {
newPort = Integer.valueOf(inParam);
if (newPort < 1 || newPort > 65535) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2p.router.net.ntcp.port\" must be a string representing a number in the range 1-65535. " + inParam + " isn't valid."),
req.getID());
}
Map<String, String> config = new HashMap<String, String>();
config.put(NTCPTransport.PROP_I2NP_NTCP_PORT, String.valueOf(newPort));
config.put(NTCPTransport.PROP_I2NP_NTCP_AUTO_PORT, "false");
_context.router().saveConfig(config, null);
restartNeeded = true;
}
settingsSaved = true;
} else {
String sAutoPort = _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_AUTO_PORT, "true");
boolean oldAutoPort = "true".equalsIgnoreCase(sAutoPort);
if (oldAutoPort) {
String oldSSUPort = "" + _context.getProperty(UDPTransport.PROP_INTERNAL_PORT, 8887);
outParams.put("i2p.router.net.ntcp.port", oldSSUPort);
} else {
outParams.put("i2p.router.net.ntcp.port", oldNTCPPort);
}
}
}
if (inParams.containsKey("i2p.router.net.ntcp.hostname")) {
String oldNTCPHostname = _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_HOSTNAME);
if ((inParam = (String) inParams.get("i2p.router.net.ntcp.hostname")) != null) {
if (oldNTCPHostname == null || !oldNTCPHostname.equals(inParam.trim())) {
_context.router().saveConfig(NTCPTransport.PROP_I2NP_NTCP_HOSTNAME, inParam);
restartNeeded = true;
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.ntcp.hostname", oldNTCPHostname);
}
}
if (inParams.containsKey("i2p.router.net.ntcp.autoip")) {
String oldNTCPAutoIP = _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_AUTO_IP);
if ((inParam = (String) inParams.get("i2p.router.net.ntcp.autoip")) != null) {
inParam = inParam.trim().toLowerCase();
if (oldNTCPAutoIP == null || !oldNTCPAutoIP.equals(inParam)) {
if ("always".equals(inParam) || "true".equals(inParam) || "false".equals(inParam)) {
_context.router().saveConfig(NTCPTransport.PROP_I2NP_NTCP_AUTO_IP, inParam);
restartNeeded = true;
} else {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2p.router.net.ntcp.autoip\" can only be always, true or false. " + inParam + " isn't valid."),
req.getID());
}
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.ntcp.autoip", oldNTCPAutoIP);
}
}
if (inParams.containsKey("i2p.router.net.ssu.port")) {
String oldSSUPort = "" + _context.getProperty(UDPTransport.PROP_INTERNAL_PORT, 8887);
if ((inParam = (String) inParams.get("i2p.router.net.ssu.port")) != null) {
if (oldSSUPort == null || !oldSSUPort.equals(inParam.trim())) {
Integer newPort;
try {
newPort = Integer.valueOf(inParam);
if (newPort < 1 || newPort > 65535) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2p.router.net.ssu.port\" must be a string representing a number in the range 1-65535. " + inParam + " isn't valid."),
req.getID());
}
Map<String, String> config = new HashMap<String, String>();
config.put(UDPTransport.PROP_EXTERNAL_PORT, String.valueOf(newPort));
config.put(UDPTransport.PROP_INTERNAL_PORT, String.valueOf(newPort));
_context.router().saveConfig(config, null);
restartNeeded = true;
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.ssu.port", oldSSUPort);
}
}
if (inParams.containsKey("i2p.router.net.ssu.hostname")) {
String oldSSUHostname = _context.getProperty(UDPTransport.PROP_EXTERNAL_HOST);
if ((inParam = (String) inParams.get("i2p.router.net.ssu.hostname")) != null) {
if (oldSSUHostname == null || !oldSSUHostname.equals(inParam.trim())) {
_context.router().saveConfig(UDPTransport.PROP_EXTERNAL_HOST, inParam);
restartNeeded = true;
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.ssu.hostname", oldSSUHostname);
}
}
if (inParams.containsKey("i2p.router.net.ssu.autoip")) {
String oldSSUAutoIP = _context.getProperty(UDPTransport.PROP_SOURCES);
if ((inParam = (String) inParams.get("i2p.router.net.ssu.autoip")) != null) {
inParam = inParam.trim().toLowerCase();
if (oldSSUAutoIP == null || !oldSSUAutoIP.equals(inParam)) {
if (inParam.equals("ssu") || inParam.equals("local,ssu") || inParam.equals("upnp,ssu") || inParam.equals("local,upnp,ssu")) {
_context.router().saveConfig(UDPTransport.PROP_SOURCES, inParam);
restartNeeded = true;
} else {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2p.router.net.ssu.autoip\" can only be ssu/local,upnp,ssu/local/ssu/upnp,ssu. " + inParam + " isn't valid."),
req.getID());
}
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.ssu.autoip", oldSSUAutoIP);
}
}
// Non-setable key.
if (inParams.containsKey("i2p.router.net.ssu.detectedip")) {
if ((inParam = (String) inParams.get("i2p.router.net.ssu.autoip")) == null) {
byte[] ipBytes = _context.router().getRouterInfo().getTargetAddress("SSU").getIP();
try {
InetAddress i = InetAddress.getByAddress(ipBytes);
outParams.put("i2p.router.net.ssu.detectedip", i.getHostAddress());
} catch (UnknownHostException e) {
outParams.put("i2p.router.net.ssu.detectedip", "Failed to parse ip address");
}
}
}
if (inParams.containsKey("i2p.router.net.upnp")) {
String oldUPNP = _context.getProperty(TransportManager.PROP_ENABLE_UPNP);
if ((inParam = (String) inParams.get("i2p.router.net.upnp")) != null) {
if (oldUPNP == null || !oldUPNP.equals(inParam.trim())) {
_context.router().saveConfig(TransportManager.PROP_ENABLE_UPNP, inParam);
restartNeeded = true;
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.upnp", oldUPNP);
}
}
if (inParams.containsKey("i2p.router.net.bw.share")) {
String oldShare = _context.router().getConfigSetting(Router.PROP_BANDWIDTH_SHARE_PERCENTAGE);
if ((inParam = (String) inParams.get("i2p.router.net.bw.share")) != null) {
if (oldShare == null || !oldShare.equals(inParam.trim())) {
Integer percent;
try {
percent = Integer.parseInt(inParam);
if (percent < 0 || percent > 100 || inParam.length() == 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2p.router.net.bw.share\" A positive integer must supplied, \"" + inParam + "\" isn't valid"),
req.getID());
}
_context.router().saveConfig(Router.PROP_BANDWIDTH_SHARE_PERCENTAGE, inParam);
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.bw.share", oldShare);
}
}
if (inParams.containsKey("i2p.router.net.bw.in")) {
String oldBWIn = _context.getProperty(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH);
if ((inParam = (String) inParams.get("i2p.router.net.bw.in")) != null) {
Integer rate;
try {
rate = Integer.parseInt(inParam);
if (rate < 0 || inParam.length() == 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2p.router.net.bw.in\" A positive integer must supplied, " + inParam + " isn't valid"),
req.getID());
}
Integer burstRate = (rate * BW_BURST_PCT) / 100;
Integer burstSize = (burstRate * BW_BURST_TIME);
if (oldBWIn == null || !oldBWIn.equals(rate.toString())) {
Map<String, String> config = new HashMap<String, String>();
config.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, rate.toString());
config.put(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, burstRate.toString());
config.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH_PEAK, burstSize.toString());
_context.router().saveConfig(config, null);
_context.bandwidthLimiter().reinitialize();
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.bw.in", oldBWIn);
}
}
if (inParams.containsKey("i2p.router.net.bw.out")) {
String oldBWOut = _context.getProperty(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH);
if ((inParam = (String) inParams.get("i2p.router.net.bw.out")) != null) {
Integer rate;
try {
rate = Integer.parseInt(inParam);
if (rate < 0 || inParam.length() == 0)
throw new NumberFormatException();
} catch (NumberFormatException e) {
return new JSONRPC2Response(
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
"\"i2p.router.net.bw.out\" A positive integer must supplied, " + inParam + " isn't valid"),
req.getID());
}
Integer burstRate = (rate * BW_BURST_PCT) / 100;
Integer burstSize = (burstRate * BW_BURST_TIME);
if (oldBWOut == null || !oldBWOut.equals(rate.toString())) {
Map<String, String> config = new HashMap<String, String>();
config.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, rate.toString());
config.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, burstRate.toString());
config.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH_PEAK, burstSize.toString());
_context.router().saveConfig(config, null);
_context.bandwidthLimiter().reinitialize();
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.bw.out", oldBWOut);
}
}
if (inParams.containsKey("i2p.router.net.laptopmode")) {
String oldLaptopMode = _context.getProperty(UDPTransport.PROP_LAPTOP_MODE);
if ((inParam = (String) inParams.get("i2p.router.net.laptopmode")) != null) {
if (oldLaptopMode == null || !oldLaptopMode.equals(inParam.trim())) {
_context.router().saveConfig(UDPTransport.PROP_LAPTOP_MODE, String.valueOf(inParam));
}
settingsSaved = true;
} else {
outParams.put("i2p.router.net.laptopmode", oldLaptopMode);
}
}
if (settingsSaved)
_context.router().saveConfig();
outParams.put("SettingsSaved", settingsSaved);
outParams.put("RestartNeeded", restartNeeded);
return new JSONRPC2Response(outParams, req.getID());
}
}

View File

@ -0,0 +1,213 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
import net.i2p.I2PAppContext;
import net.i2p.data.router.RouterAddress;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.TransportUtil;
import net.i2p.router.transport.ntcp.NTCPTransport;
import java.util.HashMap;
import java.util.Map;
/*
* 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.
*
*/
public class RouterInfoHandler implements RequestHandler {
private final JSONRPC2Helper _helper;
private final RouterContext _context;
public RouterInfoHandler(RouterContext ctx, JSONRPC2Helper helper) {
_helper = helper;
_context = ctx;
}
// Reports the method names of the handled requests
public String[] handledRequests() {
return new String[] { "RouterInfo" };
}
// Processes the requests
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
if (req.getMethod().equals("RouterInfo")) {
return process(req);
} else {
// Method name not supported
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND,
req.getID());
}
}
@SuppressWarnings("unchecked")
private JSONRPC2Response process(JSONRPC2Request req) {
JSONRPC2Error err = _helper.validateParams(null, req);
if (err != null)
return new JSONRPC2Response(err, req.getID());
if (_context == null) {
return new JSONRPC2Response(new JSONRPC2Error(
JSONRPC2Error.INTERNAL_ERROR.getCode(),
"RouterContext was not initialized. Query failed"),
req.getID());
}
Map<String, Object> inParams = req.getNamedParams();
Map outParams = new HashMap();
if (inParams.containsKey("i2p.router.version")) {
try {
Class rvClass = Class.forName("net.i2p.router.RouterVersion");
java.lang.reflect.Field field = rvClass.getDeclaredField("FULL_VERSION");
String fullVersion = (String) field.get(new RouterVersion());
outParams.put("i2p.router.version", fullVersion);
} catch (Exception e) {} // Ignore
}
if (inParams.containsKey("i2p.router.uptime")) {
Router router = _context.router();
if (router == null) {
outParams.put("i2p.router.uptime", 0);
} else {
outParams.put("i2p.router.uptime", router.getUptime());
}
}
if (inParams.containsKey("i2p.router.status")) {
outParams.put("i2p.router.status", _context.throttle().getTunnelStatus());
}
if (inParams.containsKey("i2p.router.net.status")) {
outParams.put("i2p.router.net.status", getNetworkStatus().ordinal());
}
if (inParams.containsKey("i2p.router.net.bw.inbound.1s")) {
outParams.put("i2p.router.net.bw.inbound.1s", _context.bandwidthLimiter().getReceiveBps());
}
if (inParams.containsKey("i2p.router.net.bw.outbound.1s")) {
outParams.put("i2p.router.net.bw.outbound.1s", _context.bandwidthLimiter().getSendBps());
}
if (inParams.containsKey("i2p.router.net.bw.inbound.15s")) {
outParams.put("i2p.router.net.bw.inbound.15s", _context.bandwidthLimiter().getReceiveBps15s());
}
if (inParams.containsKey("i2p.router.net.bw.outbound.15s")) {
outParams.put("i2p.router.net.bw.outbound.15s", _context.bandwidthLimiter().getSendBps15s());
}
if (inParams.containsKey("i2p.router.net.tunnels.participating")) {
outParams.put("i2p.router.net.tunnels.participating", _context.tunnelManager().getParticipatingCount());
}
if (inParams.containsKey("i2p.router.netdb.knownpeers")) {
// Why max(-1, 0) is used I don't know, it is the implementation used in the router console.
outParams.put("i2p.router.netdb.knownpeers", Math.max(_context.netDb().getKnownRouters() - 1, 0));
}
if (inParams.containsKey("i2p.router.netdb.activepeers")) {
outParams.put("i2p.router.netdb.activepeers", _context.commSystem().countActivePeers());
}
if (inParams.containsKey("i2p.router.netdb.fastpeers")) {
outParams.put("i2p.router.netdb.fastpeers", _context.profileOrganizer().countFastPeers());
}
if (inParams.containsKey("i2p.router.netdb.highcapacitypeers")) {
outParams.put("i2p.router.netdb.highcapacitypeers", _context.profileOrganizer().countHighCapacityPeers());
}
if (inParams.containsKey("i2p.router.netdb.isreseeding")) {
outParams.put("i2p.router.netdb.isreseeding", Boolean.valueOf(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress")).booleanValue());
}
return new JSONRPC2Response(outParams, req.getID());
}
private static enum NETWORK_STATUS {
OK,
TESTING,
FIREWALLED,
HIDDEN,
WARN_FIREWALLED_AND_FAST,
WARN_FIREWALLED_AND_FLOODFILL,
WARN_FIREWALLED_WITH_INBOUND_TCP,
WARN_FIREWALLED_WITH_UDP_DISABLED,
ERROR_I2CP,
ERROR_CLOCK_SKEW,
ERROR_PRIVATE_TCP_ADDRESS,
ERROR_SYMMETRIC_NAT,
ERROR_UDP_PORT_IN_USE,
ERROR_NO_ACTIVE_PEERS_CHECK_CONNECTION_AND_FIREWALL,
ERROR_UDP_DISABLED_AND_TCP_UNSET,
};
// Ripped out of SummaryHelper.java
private NETWORK_STATUS getNetworkStatus() {
if (_context.router().getUptime() > 60 * 1000
&& (!_context.router().gracefulShutdownInProgress())
&& !_context.clientManager().isAlive())
return (NETWORK_STATUS.ERROR_I2CP);
long skew = _context.commSystem().getFramedAveragePeerClockSkew(33);
// Display the actual skew, not the offset
if (Math.abs(skew) > 60 * 1000)
return NETWORK_STATUS.ERROR_CLOCK_SKEW;
if (_context.router().isHidden())
return (NETWORK_STATUS.HIDDEN);
int status = _context.commSystem().getStatus().getCode();
switch (status) {
case CommSystemFacade.STATUS_OK:
RouterAddress ra = _context.router().getRouterInfo().getTargetAddress("NTCP");
if (ra == null || TransportUtil.isPubliclyRoutable(ra.getIP(), true))
return NETWORK_STATUS.OK;
return NETWORK_STATUS.ERROR_PRIVATE_TCP_ADDRESS;
case CommSystemFacade.STATUS_DIFFERENT:
return NETWORK_STATUS.ERROR_SYMMETRIC_NAT;
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
if (_context.router().getRouterInfo().getTargetAddress("NTCP") != null)
return NETWORK_STATUS.WARN_FIREWALLED_WITH_INBOUND_TCP;
if (((FloodfillNetworkDatabaseFacade) _context.netDb()).floodfillEnabled())
return NETWORK_STATUS.WARN_FIREWALLED_AND_FLOODFILL;
if (_context.router().getRouterInfo().getCapabilities().indexOf('O') >= 0)
return NETWORK_STATUS.WARN_FIREWALLED_AND_FAST;
return NETWORK_STATUS.FIREWALLED;
case CommSystemFacade.STATUS_HOSED:
return NETWORK_STATUS.ERROR_UDP_PORT_IN_USE;
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
default:
ra = _context.router().getRouterInfo().getTargetAddress("SSU");
if (ra == null && _context.router().getUptime() > 5 * 60 * 1000) {
if (_context.commSystem().countActivePeers() <= 0)
return NETWORK_STATUS.ERROR_NO_ACTIVE_PEERS_CHECK_CONNECTION_AND_FIREWALL;
else if (_context.getProperty(NTCPTransport.PROP_I2NP_NTCP_HOSTNAME) == null || _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_PORT) == null)
return NETWORK_STATUS.ERROR_UDP_DISABLED_AND_TCP_UNSET;
else
return NETWORK_STATUS.WARN_FIREWALLED_WITH_UDP_DISABLED;
}
return NETWORK_STATUS.TESTING;
}
}
}

View File

@ -0,0 +1,231 @@
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientAppManager;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.networkdb.reseed.ReseedChecker;
import net.i2p.update.UpdateManager;
import net.i2p.update.UpdateType;
import net.i2p.util.Log;
import org.tanukisoftware.wrapper.WrapperManager;
import java.util.HashMap;
import java.util.Map;
/*
* 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.
*
*/
public class RouterManagerHandler implements RequestHandler {
private final JSONRPC2Helper _helper;
private final RouterContext _context;
private final static int SHUTDOWN_WAIT = 1500;
public RouterManagerHandler(RouterContext ctx, JSONRPC2Helper helper) {
_helper = helper;
_context = ctx;
}
// Reports the method names of the handled requests
public String[] handledRequests() {
return new String[] { "RouterManager" };
}
// Processes the requests
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
if (req.getMethod().equals("RouterManager")) {
return process(req);
} else {
// Method name not supported
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND,
req.getID());
}
}
private JSONRPC2Response process(JSONRPC2Request req) {
JSONRPC2Error err = _helper.validateParams(null, req);
if (err != null)
return new JSONRPC2Response(err, req.getID());
if (_context == null) {
return new JSONRPC2Response(new JSONRPC2Error(
JSONRPC2Error.INTERNAL_ERROR.getCode(),
"RouterContext was not initialized. Query failed"),
req.getID());
}
Map<String, Object> inParams = req.getNamedParams();
final Map<String, Object> outParams = new HashMap<String, Object>(4);
if (inParams.containsKey("Shutdown")) {
outParams.put("Shutdown", null);
(new Thread() {
@Override
public void run() {
try {
Thread.sleep(SHUTDOWN_WAIT);
} catch (InterruptedException e) {}
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD));
_context.router().shutdown(Router.EXIT_HARD);
}
}).start();
return new JSONRPC2Response(outParams, req.getID());
}
if (inParams.containsKey("Restart")) {
outParams.put("Restart", null);
(new Thread() {
@Override
public void run() {
try {
Thread.sleep(SHUTDOWN_WAIT);
} catch (InterruptedException e) {}
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
_context.router().shutdown(Router.EXIT_HARD_RESTART);
}
}).start();
return new JSONRPC2Response(outParams, req.getID());
}
if (inParams.containsKey("ShutdownGraceful")) {
outParams.put("ShutdownGraceful", null);
(new Thread() {
@Override
public void run() {
try {
Thread.sleep(SHUTDOWN_WAIT);
} catch (InterruptedException e) {}
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
_context.router().shutdownGracefully();
}
}).start();
return new JSONRPC2Response(outParams, req.getID());
}
if (inParams.containsKey("RestartGraceful")) {
outParams.put("RestartGraceful", null);
(new Thread() {
@Override
public void run() {
try {
Thread.sleep(SHUTDOWN_WAIT);
} catch (InterruptedException e) {}
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
}
}).start();
return new JSONRPC2Response(outParams, req.getID());
}
if (inParams.containsKey("Reseed")) {
outParams.put("Reseed", null);
(new Thread() {
@Override
public void run() {
ReseedChecker reseeder = new ReseedChecker(_context);
reseeder.requestReseed();
}
}).start();
return new JSONRPC2Response(outParams, req.getID());
}
if (inParams.containsKey("FindUpdates")) {
Thread t = new Thread() {
@Override
public void run() {
ClientAppManager clmgr = I2PAppContext.getCurrentContext().clientAppManager();
if (clmgr == null) {
outParams.put("FindUpdates", "ClientAppManager is null");
return;
}
UpdateManager upmgr = (UpdateManager) clmgr.getRegisteredApp(UpdateManager.APP_NAME);
if (upmgr == null) {
outParams.put("FindUpdates", "UpdateManager is null");
return;
}
boolean updateIsAvailable = upmgr.checkAvailable(UpdateType.ROUTER_SIGNED) != null;
outParams.put("FindUpdates", updateIsAvailable);
}
};
t.start();
try {
t.join();
} catch (InterruptedException e) {}
return new JSONRPC2Response(outParams, req.getID());
}
if (inParams.containsKey("Update")) {
Thread t = new Thread() {
@Override
public void run() {
ClientAppManager clmgr = I2PAppContext.getCurrentContext().clientAppManager();
if (clmgr == null) {
outParams.put("Update", "ClientAppManager is null");
return;
}
UpdateManager upmgr = (UpdateManager) clmgr.getRegisteredApp(UpdateManager.APP_NAME);
if (upmgr == null) {
outParams.put("Update", "UpdateManager is null");
return;
}
boolean updateStarted = upmgr.update(UpdateType.ROUTER_SIGNED);
if (!updateStarted) {
outParams.put("Update", "Update not started");
return;
}
boolean isUpdating = upmgr.isUpdateInProgress(UpdateType.ROUTER_SIGNED);
while (isUpdating) {
try {
Thread.sleep(100);
} catch (Exception e) {}
isUpdating = upmgr.isUpdateInProgress(UpdateType.ROUTER_SIGNED);
}
outParams.put("Update", upmgr.getStatus());
}
};
t.start();
try {
t.join();
} catch (InterruptedException e) {}
return new JSONRPC2Response(outParams, req.getID());
}
return new JSONRPC2Response(outParams, req.getID());
}
public static class UpdateWrapperManagerTask implements Runnable {
private int _exitCode;
public UpdateWrapperManagerTask(int exitCode) {
_exitCode = exitCode;
}
public void run() {
try {
WrapperManager.signalStopped(_exitCode);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}

View File

@ -1,37 +0,0 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.io.UnsupportedEncodingException;
import net.i2p.data.ByteArray;
/**
* A 20-byte SHA1 info hash
*/
public class InfoHash extends ByteArray {
public InfoHash(String data) throws UnsupportedEncodingException {
this(data.getBytes("ISO-8859-1"));
}
public InfoHash(byte[] data) {
super(data);
if (data.length != 20)
throw new IllegalArgumentException("Bad infohash length: " + data.length);
}
}

View File

@ -1,37 +0,0 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.io.UnsupportedEncodingException;
import net.i2p.data.ByteArray;
/**
* A 20-byte peer ID
*/
public class PID extends ByteArray {
public PID(String data) throws UnsupportedEncodingException {
this(data.getBytes("ISO-8859-1"));
}
public PID(byte[] data) {
super(data);
if (data.length != 20)
throw new IllegalArgumentException("Bad peer ID length: " + data.length);
}
}

View File

@ -1,78 +0,0 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.Destination;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/*
* A single peer for a single torrent.
* Save a couple stats, and implements
* a Map so we can BEncode it
* So it's like PeerID but in reverse - we make a Map from the
* data. PeerID makes the data from a Map.
*/
public class Peer extends HashMap<String, Object> {
private long lastSeen;
private long bytesLeft;
private static final ConcurrentHashMap<String, String> destCache = new ConcurrentHashMap();
private static final Integer PORT = Integer.valueOf(6881);
private static final long CLEAN_TIME = 3*60*60*1000;
static {
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
}
public Peer(byte[] id, Destination address) {
super(3);
if (id.length != 20)
throw new IllegalArgumentException("Bad peer ID length: " + id.length);
put("peer id", id);
put("port", PORT);
// cache the 520-byte address strings
String dest = address.toBase64() + ".i2p";
String oldDest = destCache.putIfAbsent(dest, dest);
if (oldDest != null)
dest = oldDest;
put("ip", dest);
}
public void setLeft(long l) {
bytesLeft = l;
lastSeen = System.currentTimeMillis();
}
public boolean isSeed() {
return bytesLeft <= 0;
}
public long lastSeen() {
return lastSeen;
}
private static class Cleaner implements SimpleTimer.TimedEvent {
public void timeReached() {
destCache.clear();
}
}
}

View File

@ -1,42 +0,0 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.util.concurrent.ConcurrentHashMap;
/**
* All the peers for a single torrent
*/
public class Peers extends ConcurrentHashMap<PID, Peer> {
public Peers() {
super();
}
public int countSeeds() {
int rv = 0;
for (Peer p : values()) {
if (p.isSeed())
rv++;
}
return rv;
}
public int countLeeches() {
return size() - countSeeds();
}
}

View File

@ -1,158 +0,0 @@
// ========================================================================
// $Id: ForwardHandler.java,v 1.16 2005/08/13 00:01:26 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package net.i2p.zzzot;
import java.io.IOException;
import java.util.Map;
import org.mortbay.http.HttpException;
import org.mortbay.http.HttpMessage;
import org.mortbay.http.HttpRequest;
import org.mortbay.http.HttpResponse;
import org.mortbay.http.PathMap;
import org.mortbay.http.handler.AbstractHttpHandler;
import org.mortbay.util.URI;
import org.mortbay.util.UrlEncoded;
/* ------------------------------------------------------------ */
/** Forward Request Handler.
* Forwards a request to a new URI. Experimental - use with caution.
* @version $Revision: 1.16 $
* @author Greg Wilkins (gregw)
*
* Just like ForwardHandler but forwards query parameters too
* And took out the dependency on apache logging
* @author zzz
*/
public class QForwardHandler extends AbstractHttpHandler
{
PathMap _forward = new PathMap();
String _root;
boolean _handleQueries = false;
/* ------------------------------------------------------------ */
/** Constructor.
*/
public QForwardHandler()
{}
/* ------------------------------------------------------------ */
/** Constructor.
* @param rootForward
*/
public QForwardHandler(String rootForward)
{
_root=rootForward;
}
/* ------------------------------------------------------------ */
/** Add a forward mapping.
* @param pathSpecInContext The path to forward from
* @param newPath The path to forward to.
*/
public void addForward(String pathSpecInContext,
String newPath)
{
_forward.put(pathSpecInContext,newPath);
}
/* ------------------------------------------------------------ */
/** Add a forward mapping for root path.
* This allows a forward for exactly / which is the default
* path in a pathSpec.
* @param newPath The path to forward to.
*/
public void setRootForward(String newPath)
{
_root=newPath;
}
/* ------------------------------------------------------------ */
/** Set the Handler up to cope with forwards to paths that contain query
* elements (e.g. "/blah"->"/foo?a=b").
* AND (I2P) pass params through (e.g. "/blah?c=d? -> "/foo?c=d? ).
* @param b
*/
public void setHandleQueries(boolean b)
{
_handleQueries = b;
}
/* ------------------------------------------------------------ */
public void handle(String pathInContext,
String pathParams,
HttpRequest request,
HttpResponse response)
throws HttpException, IOException
{
String newPath=null;
String query=null;
if (_root!=null && ("/".equals(pathInContext) || pathInContext.startsWith("/;")))
newPath=_root;
else
{
Map.Entry entry = _forward.getMatch(pathInContext);
if (entry!=null)
{
String match = (String)entry.getValue();
if (_handleQueries)
{
int hook = match.indexOf('?');
if (hook != -1){
query = match.substring(hook+1);
match = match.substring(0, hook);
}
}
String info=PathMap.pathInfo((String)entry.getKey(),pathInContext);
newPath=info==null?match:(URI.addPaths(match,info));
}
}
if (newPath!=null)
{
// this is the new part for i2p
// setPath() changes the request URI and loses the parameters
// so save them and add them back
Map saved = null;
if (_handleQueries){
request.setCharacterEncoding("ISO-8859-1", false);
saved = request.getParameters();
}
int last=request.setState(HttpMessage.__MSG_EDITABLE);
String context=getHttpContext().getContextPath();
if (context.length()==1)
request.setPath(newPath);
else
request.setPath(URI.addPaths(context,newPath));
if (_handleQueries && query != null){
// add forwarded to query string to parameters
UrlEncoded.decodeTo(query, request.getParameters());
}
// this is the new part for i2p
if (_handleQueries){
// add them back
request.getParameters().putAll(saved);
}
request.setState(last);
getHttpContext().getHttpServer().service(request,response);
return;
}
}
}

View File

@ -1,37 +0,0 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.util.concurrent.ConcurrentHashMap;
/**
* All the torrents
*/
public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
public Torrents() {
super();
}
public int countPeers() {
int rv = 0;
for (Peers p : values()) {
rv += p.size();
}
return rv;
}
}

View File

@ -1,66 +0,0 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.util.Iterator;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Instantiate this to fire it up
*/
class ZzzOT {
private Torrents _torrents;
private static final long CLEAN_TIME = 4*60*1000;
private static final long EXPIRE_TIME = 60*60*1000;
ZzzOT() {
_torrents = new Torrents();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
}
Torrents getTorrents() {
return _torrents;
}
void stop() {
_torrents.clear();
// no way to stop the cleaner
}
private class Cleaner implements SimpleTimer.TimedEvent {
public void timeReached() {
long now = System.currentTimeMillis();
for (Iterator<Peers> iter = _torrents.values().iterator(); iter.hasNext(); ) {
Peers p = iter.next();
int recent = 0;
for (Iterator<Peer> iterp = p.values().iterator(); iterp.hasNext(); ) {
Peer peer = iterp.next();
if (peer.lastSeen() < now - EXPIRE_TIME)
iterp.remove();
else
recent++;
}
if (recent <= 0)
iter.remove();
}
}
}
}

View File

@ -1,229 +0,0 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.apps.systray.UrlLauncher;
import org.mortbay.http.HttpContext;
import org.mortbay.jetty.Server;
/**
* This handles the starting and stopping of an eepsite tunnel and jetty
* from a single static class so it can be called via clients.config.
*
* This makes installation of a new eepsite a turnkey operation -
* the user is not required to configure a new tunnel in i2ptunnel manually.
*
* Usage: ZzzOTController -d $PLUGIN [start|stop]
*
* @author zzz
*/
public class ZzzOTController {
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ZzzOTController.class);
private static Server _server;
private static TunnelController _tunnel;
private static ZzzOT _zzzot;
private static Object _lock = new Object();
public static void main(String args[]) {
if (args.length != 3 || (!"-d".equals(args[0])))
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
if ("start".equals(args[2]))
start(args);
else if ("stop".equals(args[2]))
stop();
else
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
}
public static Torrents getTorrents() {
synchronized(_lock) {
if (_zzzot == null)
_zzzot = new ZzzOT();
}
return _zzzot.getTorrents();
}
private static void start(String args[]) {
File pluginDir = new File(args[1]);
if (!pluginDir.exists())
throw new IllegalArgumentException("Plugin directory " + pluginDir.getAbsolutePath() + " does not exist");
// We create the private key file in advance, so that we can
// create the help.html file from the templates
// without waiting for i2ptunnel to create it AND build the tunnels before returning.
Destination dest = null;
File key = new File(pluginDir, "eepPriv.dat");
if (!key.exists()) {
PrivateKeyFile pkf = new PrivateKeyFile(new File(pluginDir, "eepPriv.dat"));
try {
dest = pkf.createIfAbsent();
} catch (Exception e) {
_log.error("Unable to create " + key.getAbsolutePath() + ' ' + e);
throw new IllegalArgumentException("Unable to create " + key.getAbsolutePath() + ' ' + e);
}
_log.error("NOTICE: ZzzOT: New eepsite keys created in " + key.getAbsolutePath());
_log.error("NOTICE: ZzzOT: You should back up this file!");
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
String b64 = dest.toBase64();
_log.error("NOTICE: ZzzOT: Your base 32 address is " + b32);
_log.error("NOTICE: ZzzOT: Your base 64 address is " + b64);
}
startJetty(pluginDir, dest);
startI2PTunnel(pluginDir, dest);
}
private static void startI2PTunnel(File pluginDir, Destination dest) {
File i2ptunnelConfig = new File(pluginDir, "i2ptunnel.config");
Properties i2ptunnelProps = new Properties();
try {
DataHelper.loadProps(i2ptunnelProps, i2ptunnelConfig);
} catch (IOException ioe) {
_log.error("Cannot open " + i2ptunnelConfig.getAbsolutePath() + ' ' + ioe);
throw new IllegalArgumentException("Cannot open " + i2ptunnelConfig.getAbsolutePath() + ' ' + ioe);
}
TunnelController tun = new TunnelController(i2ptunnelProps, "tunnel.0.");
// start in foreground so we can get the destination
//tun.startTunnelBackground();
tun.startTunnel();
if (dest != null) {
List msgs = tun.clearMessages();
for (Object s : msgs) {
_log.error("NOTICE: ZzzOT Tunnel message: " + s);
}
}
_tunnel = tun;
}
private static void startJetty(File pluginDir, Destination dest) {
if (_server != null)
throw new IllegalArgumentException("Jetty already running!");
migrateJettyXML(pluginDir);
I2PAppContext context = I2PAppContext.getGlobalContext();
File tmpdir = new File(context.getTempDir().getAbsolutePath(), "/zzzot-work");
tmpdir.mkdir();
File jettyXml = new File(pluginDir, "jetty.xml");
try {
Server serv = new Server(jettyXml.getAbsolutePath());
HttpContext[] hcs = serv.getContexts();
for (int i = 0; i < hcs.length; i++)
hcs[i].setTempDirectory(tmpdir);
serv.start();
_server = serv;
} catch (Throwable t) {
_log.error("ZzzOT jetty start failed", t);
throw new IllegalArgumentException("Jetty start failed " + t);
}
if (dest != null)
launchHelp(pluginDir, dest);
}
private static void stop() {
stopI2PTunnel();
stopJetty();
if (_zzzot != null)
_zzzot.stop();
}
private static void stopI2PTunnel() {
if (_tunnel == null)
return;
try {
_tunnel.stopTunnel();
} catch (Throwable t) {
_log.error("ZzzOT tunnel stop failed", t);
throw new IllegalArgumentException("Tunnel stop failed " + t);
}
_tunnel = null;
}
private static void stopJetty() {
if (_server == null)
return;
try {
_server.stop();
} catch (Throwable t) {
_log.error("ZzzOT jetty stop failed", t);
throw new IllegalArgumentException("Jetty stop failed " + t);
}
_server = null;
}
/** put the directory in the jetty.xml file */
private static void migrateJettyXML(File pluginDir) {
File outFile = new File(pluginDir, "jetty.xml");
if (outFile.exists())
return;
File fileTmpl = new File(pluginDir, "templates/jetty.xml");
try {
String props = FileUtil.readTextFile(fileTmpl.getAbsolutePath(), 250, true);
if (props == null)
throw new IOException(fileTmpl.getAbsolutePath() + " open failed");
props = props.replace("$PLUGIN", pluginDir.getAbsolutePath());
FileOutputStream os = new FileOutputStream(outFile);
os.write(props.getBytes("UTF-8"));
os.close();
} catch (IOException ioe) {
_log.error("jetty.xml migrate failed", ioe);
}
}
/** put the directory, base32, and base64 info in the help.html file and launch a browser window to display it */
private static void launchHelp(File pluginDir, Destination dest) {
File fileTmpl = new File(pluginDir, "templates/help.html");
File outFile = new File(pluginDir, "eepsite/docroot/help.html");
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
String b64 = dest.toBase64();
try {
String html = FileUtil.readTextFile(fileTmpl.getAbsolutePath(), 100, true);
if (html == null)
throw new IOException(fileTmpl.getAbsolutePath() + " open failed");
html = html.replace("$PLUGIN", pluginDir.getAbsolutePath());
html = html.replace("$B32", b32);
html = html.replace("$B64", b64);
FileOutputStream os = new FileOutputStream(outFile);
os.write(html.getBytes("UTF-8"));
os.close();
Thread t = new I2PAppThread(new Launcher(), "ZzzOTHelp", true);
t.start();
} catch (IOException ioe) {
_log.error("ZzzOT help launch failed", ioe);
}
}
private static class Launcher implements Runnable {
public void run() {
UrlLauncher.main(new String[] { "http://127.0.0.1:7662/help.html" } );
}
}
}

View File

@ -0,0 +1,121 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.minidev.json.reader.JsonWriter;
/**
* A JSON array. JSONObject supports java.util.List interface.
*
* @author FangYidong<fangyidong@yahoo.com.cn>
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public class JSONArray extends ArrayList<Object> implements List<Object>, JSONAwareEx, JSONStreamAwareEx {
private static final long serialVersionUID = 9106884089231309568L;
public static String toJSONString(List<? extends Object> list) {
return toJSONString(list, JSONValue.COMPRESSION);
}
/**
* Convert a list to JSON text. The result is a JSON array. If this list is
* also a JSONAware, JSONAware specific behaviours will be omitted at this
* top level.
*
* @see net.minidev.json.JSONValue#toJSONString(Object)
*
* @param list
* @param compression
* Indicate compression level
* @return JSON text, or "null" if list is null.
*/
public static String toJSONString(List<? extends Object> list, JSONStyle compression) {
StringBuilder sb = new StringBuilder();
try {
writeJSONString(list, sb, compression);
} catch (IOException e) {
// Can not append on a string builder
}
return sb.toString();
}
/**
* Encode a list into JSON text and write it to out. If this list is also a
* JSONStreamAware or a JSONAware, JSONStreamAware and JSONAware specific
* behaviours will be ignored at this top level.
*
* @see JSONValue#writeJSONString(Object, Appendable)
*
* @param list
* @param out
*/
public static void writeJSONString(Iterable<? extends Object> list, Appendable out, JSONStyle compression)
throws IOException {
if (list == null) {
out.append("null");
return;
}
JsonWriter.JSONIterableWriter.writeJSONString(list, out, compression);
}
public static void writeJSONString(List<? extends Object> list, Appendable out) throws IOException {
writeJSONString(list, out, JSONValue.COMPRESSION);
}
public void merge(Object o2) {
JSONObject.merge(this, o2);
}
/**
* Explicitely Serialize Object as JSon String
*/
public String toJSONString() {
return toJSONString(this, JSONValue.COMPRESSION);
}
public String toJSONString(JSONStyle compression) {
return toJSONString(this, compression);
}
/**
* Override natif toStirng()
*/
public String toString() {
return toJSONString();
}
/**
* JSONAwareEx inferface
*
* @param compression
* compression param
*/
public String toString(JSONStyle compression) {
return toJSONString(compression);
}
public void writeJSONString(Appendable out) throws IOException {
writeJSONString(this, out, JSONValue.COMPRESSION);
}
public void writeJSONString(Appendable out, JSONStyle compression) throws IOException {
writeJSONString(this, out, compression);
}
}

View File

@ -0,0 +1,29 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* 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.
*/
/**
* Beans that support customized output of JSON text shall implement this
* interface.
*
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public interface JSONAware {
/**
* @return JSON text
*/
String toJSONString();
}

View File

@ -0,0 +1,32 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* 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.
*/
/**
* Beans that support advanced output of JSON text shall implement this
* interface.
*
* Adding compressions and formating features
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public interface JSONAwareEx extends JSONAware {
/**
* @return JSON text
*/
String toJSONString(JSONStyle compression);
}

View File

@ -0,0 +1,217 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import net.minidev.json.reader.JsonWriter;
/**
* A JSON object. Key value pairs are unordered. JSONObject supports
* java.util.Map interface.
*
* @author FangYidong<fangyidong@yahoo.com.cn>
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public class JSONObject extends HashMap<String, Object> implements JSONAware, JSONAwareEx, JSONStreamAwareEx {
private static final long serialVersionUID = -503443796854799292L;
public JSONObject() {
super();
}
// /**
// * Allow simply casting to Map<String, XXX>
// */
// @SuppressWarnings("unchecked")
// public <T> T cast() {
// return (T) this;
// }
/**
* Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters
* (U+0000 through U+001F). It's the same as JSONValue.escape() only for
* compatibility here.
*
* @see JSONValue#escape(String)
*/
public static String escape(String s) {
return JSONValue.escape(s);
}
public static String toJSONString(Map<String, ? extends Object> map) {
return toJSONString(map, JSONValue.COMPRESSION);
}
/**
* Convert a map to JSON text. The result is a JSON object. If this map is
* also a JSONAware, JSONAware specific behaviours will be omitted at this
* top level.
*
* @see net.minidev.json.JSONValue#toJSONString(Object)
*
* @param map
* @return JSON text, or "null" if map is null.
*/
public static String toJSONString(Map<String, ? extends Object> map, JSONStyle compression) {
StringBuilder sb = new StringBuilder();
try {
writeJSON(map, sb, compression);
} catch (IOException e) {
// can not append on a StringBuilder
}
return sb.toString();
}
// /**
// * return a Key:value entry as stream
// */
// public static String toString(String key, Object value) {
// return toString(key, value, JSONValue.COMPRESSION);
// }
// /**
// * return a Key:value entry as stream
// */
// public static String toString(String key, Object value, JSONStyle
// compression) {
// StringBuilder sb = new StringBuilder();
// try {
// writeJSONKV(key, value, sb, compression);
// } catch (IOException e) {
// // can not append on a StringBuilder
// }
// return sb.toString();
// }
/**
* Allows creation of a JSONObject from a Map. After that, both the
* generated JSONObject and the Map can be modified independently.
*/
public JSONObject(Map<String, ?> map) {
super(map);
}
public static void writeJSON(Map<String, Object> map, Appendable out) throws IOException {
writeJSON(map, out, JSONValue.COMPRESSION);
}
/**
* Encode a map into JSON text and write it to out. If this map is also a
* JSONAware or JSONStreamAware, JSONAware or JSONStreamAware specific
* behaviours will be ignored at this top level.
*
* @see JSONValue#writeJSONString(Object, Appendable)
*/
public static void writeJSON(Map<String, ? extends Object> map, Appendable out, JSONStyle compression)
throws IOException {
if (map == null) {
out.append("null");
return;
}
JsonWriter.JSONMapWriter.writeJSONString(map, out, compression);
}
/**
* serialize Object as json to an stream
*/
public void writeJSONString(Appendable out) throws IOException {
writeJSON(this, out, JSONValue.COMPRESSION);
}
/**
* serialize Object as json to an stream
*/
public void writeJSONString(Appendable out, JSONStyle compression) throws IOException {
writeJSON(this, out, compression);
}
public void merge(Object o2) {
merge(this, o2);
}
protected static JSONObject merge(JSONObject o1, Object o2) {
if (o2 == null)
return o1;
if (o2 instanceof JSONObject)
return merge(o1, (JSONObject) o2);
throw new RuntimeException("JSON megre can not merge JSONObject with " + o2.getClass());
}
private static JSONObject merge(JSONObject o1, JSONObject o2) {
if (o2 == null)
return o1;
for (String key : o1.keySet()) {
Object value1 = o1.get(key);
Object value2 = o2.get(key);
if (value2 == null)
continue;
if (value1 instanceof JSONArray) {
o1.put(key, merge((JSONArray) value1, value2));
continue;
}
if (value1 instanceof JSONObject) {
o1.put(key, merge((JSONObject) value1, value2));
continue;
}
if (value1.equals(value2))
continue;
if (value1.getClass().equals(value2.getClass()))
throw new RuntimeException("JSON merge can not merge two " + value1.getClass().getName()
+ " Object together");
throw new RuntimeException("JSON merge can not merge " + value1.getClass().getName() + " with "
+ value2.getClass().getName());
}
for (String key : o2.keySet()) {
if (o1.containsKey(key))
continue;
o1.put(key, o2.get(key));
}
return o1;
}
protected static JSONArray merge(JSONArray o1, Object o2) {
if (o2 == null)
return o1;
if (o1 instanceof JSONArray)
return merge(o1, (JSONArray) o2);
o1.add(o2);
return o1;
}
private static JSONArray merge(JSONArray o1, JSONArray o2) {
o1.addAll(o2);
return o1;
}
public String toJSONString() {
return toJSONString(this, JSONValue.COMPRESSION);
}
public String toJSONString(JSONStyle compression) {
return toJSONString(this, compression);
}
public String toString(JSONStyle compression) {
return toJSONString(this, compression);
}
public String toString() {
return toJSONString(this, JSONValue.COMPRESSION);
}
}

View File

@ -0,0 +1,31 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
/**
* Beans that support customized output of JSON text to a writer shall implement
* this interface.
*
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public interface JSONStreamAware {
/**
* write JSON string to out.
*/
void writeJSONString(Appendable out) throws IOException;
}

View File

@ -0,0 +1,31 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
/**
* Beans that support customized output of JSON text to a writer shall implement
* this interface.
*
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public interface JSONStreamAwareEx extends JSONStreamAware {
/**
* write JSON string to out.
*/
void writeJSONString(Appendable out, JSONStyle compression) throws IOException;
}

View File

@ -0,0 +1,212 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import net.minidev.json.JStylerObj.MustProtect;
import net.minidev.json.JStylerObj.StringProtector;
/**
* JSONStyle object configure JSonSerializer reducing output size
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public class JSONStyle {
/**
* for advanced usage sample see
*
* @see net.minidev.json.test.TestCompressorFlags
*/
public final static int FLAG_PROTECT_KEYS = 1;
public final static int FLAG_PROTECT_4WEB = 2;
public final static int FLAG_PROTECT_VALUES = 4;
/**
* AGRESSIVE have no effect without PROTECT_KEYS or PROTECT_VALUE
*
* AGRESSIVE mode allows Json-smart to not protect String containing special
* chars
*/
public final static int FLAG_AGRESSIVE = 8;
/**
* @since 1.3.1
*/
public final static int FLAG_IGNORE_NULL = 16;
public final static JSONStyle NO_COMPRESS = new JSONStyle();
public final static JSONStyle MAX_COMPRESS = new JSONStyle(-1);
/**
* @since 1.0.9.1
*/
public final static JSONStyle LT_COMPRESS = new JSONStyle(FLAG_PROTECT_4WEB);
private boolean _protectKeys;
private boolean _protect4Web;
private boolean _protectValues;
private boolean _ignore_null;
private MustProtect mpKey;
private MustProtect mpValue;
private StringProtector esc;
public JSONStyle(int FLAG) {
_protectKeys = (FLAG & FLAG_PROTECT_KEYS) == 0;
_protectValues = (FLAG & FLAG_PROTECT_VALUES) == 0;
_protect4Web = (FLAG & FLAG_PROTECT_4WEB) == 0;
_ignore_null = (FLAG & FLAG_IGNORE_NULL) > 0;
MustProtect mp;
if ((FLAG & FLAG_AGRESSIVE) > 0)
mp = JStylerObj.MP_AGGRESIVE;
else
mp = JStylerObj.MP_SIMPLE;
if (_protectValues)
mpValue = JStylerObj.MP_TRUE;
else
mpValue = mp;
if (_protectKeys)
mpKey = JStylerObj.MP_TRUE;
else
mpKey = mp;
if (_protect4Web)
esc = JStylerObj.ESCAPE4Web;
else
esc = JStylerObj.ESCAPE_LT;
}
public JSONStyle() {
this(0);
}
public boolean protectKeys() {
return _protectKeys;
}
public boolean protectValues() {
return _protectValues;
}
public boolean protect4Web() {
return _protect4Web;
}
public boolean ignoreNull() {
return _ignore_null;
}
public boolean indent() {
return false;
}
public boolean mustProtectKey(String s) {
return mpKey.mustBeProtect(s);
}
public boolean mustProtectValue(String s) {
return mpValue.mustBeProtect(s);
}
public void writeString(Appendable out, String value) throws IOException {
if (!this.mustProtectValue(value))
out.append(value);
else {
out.append('"');
JSONValue.escape(value, out, this);
out.append('"');
}
}
public void escape(String s, Appendable out) {
esc.escape(s, out);
}
/**
* begin Object
*/
public void objectStart(Appendable out) throws IOException {
out.append('{');
}
/**
* terminate Object
*/
public void objectStop(Appendable out) throws IOException {
out.append('}');
}
/**
* Start the first Obeject element
*/
public void objectFirstStart(Appendable out) throws IOException {
}
/**
* Start a new Object element
*/
public void objectNext(Appendable out) throws IOException {
out.append(',');
}
/**
* End Of Object element
*/
public void objectElmStop(Appendable out) throws IOException {
}
/**
* end of Key in json Object
*/
public void objectEndOfKey(Appendable out) throws IOException {
out.append(':');
}
/**
* Array start
*/
public void arrayStart(Appendable out) throws IOException {
out.append('[');
}
/**
* Array Done
*/
public void arrayStop(Appendable out) throws IOException {
out.append(']');
}
/**
* Start the first Array element
*/
public void arrayfirstObject(Appendable out) throws IOException {
}
/**
* Start a new Array element
*/
public void arrayNextElm(Appendable out) throws IOException {
out.append(',');
}
/**
* End of an Array element
*/
public void arrayObjectEnd(Appendable out) throws IOException {
}
}

View File

@ -0,0 +1,69 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
*
* 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.
*/
/**
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public class JSONUtil {
public static String getSetterName(String key) {
int len = key.length();
char[] b = new char[len + 3];
b[0] = 's';
b[1] = 'e';
b[2] = 't';
char c = key.charAt(0);
if (c >= 'a' && c <= 'z')
c += 'A' - 'a';
b[3] = c;
for (int i = 1; i < len; i++) {
b[i + 3] = key.charAt(i);
}
return new String(b);
}
public static String getGetterName(String key) {
int len = key.length();
char[] b = new char[len + 3];
b[0] = 'g';
b[1] = 'e';
b[2] = 't';
char c = key.charAt(0);
if (c >= 'a' && c <= 'z')
c += 'A' - 'a';
b[3] = c;
for (int i = 1; i < len; i++) {
b[i + 3] = key.charAt(i);
}
return new String(b);
}
public static String getIsName(String key) {
int len = key.length();
char[] b = new char[len + 2];
b[0] = 'i';
b[1] = 's';
char c = key.charAt(0);
if (c >= 'a' && c <= 'z')
c += 'A' - 'a';
b[2] = c;
for (int i = 1; i < len; i++) {
b[i + 2] = key.charAt(i);
}
return new String(b);
}
}

View File

@ -0,0 +1,595 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* 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 static net.minidev.json.parser.ContainerFactory.FACTORY_ORDERED;
import static net.minidev.json.parser.ContainerFactory.FACTORY_SIMPLE;
import static net.minidev.json.parser.JSONParser.DEFAULT_PERMISSIVE_MODE;
import static net.minidev.json.parser.JSONParser.MODE_RFC4627;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Map;
import net.minidev.json.parser.ContentHandler;
import net.minidev.json.parser.ContentHandlerCompressor;
import net.minidev.json.parser.FakeContainerFactory;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
import net.minidev.json.reader.JsonWriter;
import net.minidev.json.reader.JsonWriterI;
/**
* JSONValue is the helper class In most of case you should use those static
* methode to user JSON-smart
*
*
* The most commonly use methode are {@link #parse(String)}
* {@link #toJSONString(Object)}
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public class JSONValue {
/**
* Global default compression type
*/
public static JSONStyle COMPRESSION = JSONStyle.NO_COMPRESS;
/**
* Used for validating Json inputs
*/
private final static FakeContainerFactory FACTORY_FAKE_COINTAINER = new FakeContainerFactory();
/**
* Parse JSON text into java object from the input source. Please use
* parseWithException() if you don't want to ignore the exception. if you
* want strict input check use parseStrict()
*
* @see JSONParser#parse(Reader)
* @see #parseWithException(Reader)
*
* @since 1.0.9-2
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*
*/
public static Object parse(byte[] in) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in);
} catch (Exception e) {
return null;
}
}
/**
* Parse JSON text into java object from the input source. Please use
* parseWithException() if you don't want to ignore the exception. if you
* want strict input check use parseStrict()
*
* @see JSONParser#parse(Reader)
* @see #parseWithException(Reader)
*
* @since 1.1.2
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*
*/
public static Object parse(byte[] in, int offset, int length) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, offset, length);
} catch (Exception e) {
return null;
}
}
/**
* Parse JSON text into java object from the input source. Please use
* parseWithException() if you don't want to ignore the exception. if you
* want strict input check use parseStrict()
*
* @see JSONParser#parse(Reader)
* @see #parseWithException(Reader)
*
* @since 1.0.9-2
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*
*/
public static Object parse(InputStream in) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in);
} catch (Exception e) {
return null;
}
}
/**
* Parse JSON text into java object from the input source. Please use
* parseWithException() if you don't want to ignore the exception. if you
* want strict input check use parseStrict()
*
* @see JSONParser#parse(Reader)
* @see #parseWithException(Reader)
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*
*/
public static Object parse(Reader in) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in);
} catch (Exception e) {
return null;
}
}
/**
* Parse JSON text into java object from the input source. Please use
* parseWithException() if you don't want to ignore the exception. if you
* want strict input check use parseStrict()
*
* @see JSONParser#parse(String)
* @see #parseWithException(String)
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*
*/
public static Object parse(String s) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(s);
} catch (Exception e) {
return null;
}
}
/**
* Parse Json input to a java Object keeping element order
*
* @since 1.0.9-2
*/
public static Object parseKeepingOrder(byte[] in) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, FACTORY_ORDERED);
} catch (Exception e) {
return null;
}
}
/**
* Parse Json input to a java Object keeping element order
*
* @since 1.1.2
*/
public static Object parseKeepingOrder(byte[] in, int offset, int length) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, offset, length, FACTORY_ORDERED);
} catch (Exception e) {
return null;
}
}
/**
* Parse Json input to a java Object keeping element order
*
* @since 1.0.9-2
*/
public static Object parseKeepingOrder(InputStream in) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, FACTORY_ORDERED);
} catch (Exception e) {
return null;
}
}
/**
* Parse Json input to a java Object keeping element order
*
* @since 1.0.6.1
*/
public static Object parseKeepingOrder(Reader in) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, FACTORY_ORDERED);
} catch (Exception e) {
return null;
}
}
/**
* Parse Json input to a java Object keeping element order
*
* @since 1.0.6.1
*/
public static Object parseKeepingOrder(String in) {
try {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, FACTORY_ORDERED);
} catch (Exception e) {
return null;
}
}
/**
* Parse Json Using SAX event handler
*
* @since 1.0.9-2
*/
public static void SAXParse(InputStream input, ContentHandler handler) throws ParseException, IOException {
JSONParser p = new JSONParser(DEFAULT_PERMISSIVE_MODE);
p.parse(input, FACTORY_FAKE_COINTAINER, handler);
}
/**
* Parse Json Using SAX event handler
*
* @since 1.0.6.2
*/
public static void SAXParse(Reader input, ContentHandler handler) throws ParseException, IOException {
JSONParser p = new JSONParser(DEFAULT_PERMISSIVE_MODE);
p.parse(input, FACTORY_FAKE_COINTAINER, handler);
}
/**
* Parse Json Using SAX event handler
*
* @since 1.0.6.2
*/
public static void SAXParse(String input, ContentHandler handler) throws ParseException {
JSONParser p = new JSONParser(DEFAULT_PERMISSIVE_MODE);
p.parse(input, FACTORY_FAKE_COINTAINER, handler);
}
/**
* Reformat Json input keeping element order
*
* @since 1.0.6.2
*/
public static String compress(String input, JSONStyle style) {
try {
StringBuilder sb = new StringBuilder();
ContentHandlerCompressor comp = new ContentHandlerCompressor(sb, style);
JSONParser p = new JSONParser(DEFAULT_PERMISSIVE_MODE);
p.parse(input, FACTORY_FAKE_COINTAINER, comp);
return sb.toString();
} catch (Exception e) {
return input;
}
}
/**
* Compress Json input keeping element order
*
* @since 1.0.6.1
*/
public static String compress(String s) {
return compress(s, JSONStyle.MAX_COMPRESS);
}
/**
* Compress Json input keeping element order
*
* @since 1.0.6.1
*/
public static String uncompress(String s) {
return compress(s, JSONStyle.NO_COMPRESS);
}
/**
* Parse JSON text into java object from the input source.
*
* @see JSONParser
*
* @since 1.0.9-2
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseWithException(byte[] in) throws IOException, ParseException {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, FACTORY_SIMPLE);
}
/**
* Parse JSON text into java object from the input source.
*
* @see JSONParser
*
* @since 1.1.2
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseWithException(byte[] in, int offset, int length) throws IOException, ParseException {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, offset, length, FACTORY_SIMPLE);
}
/**
* Parse JSON text into java object from the input source.
*
* @see JSONParser
*
* @since 1.0.9-2
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseWithException(InputStream in) throws IOException, ParseException {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, FACTORY_SIMPLE);
}
/**
* Parse JSON text into java object from the input source.
*
* @see JSONParser
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseWithException(Reader in) throws IOException, ParseException {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, FACTORY_SIMPLE);
}
/**
* Parse JSON text into java object from the input source.
*
* @see JSONParser
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseWithException(String s) throws ParseException {
return new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(s, FACTORY_SIMPLE);
}
/**
* Parse valid RFC4627 JSON text into java object from the input source.
*
* @see JSONParser
*
* @since 1.0.9-2
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseStrict(InputStream in) throws IOException, ParseException {
return new JSONParser(MODE_RFC4627).parse(in, FACTORY_SIMPLE);
}
/**
* Parse valid RFC4627 JSON text into java object from the input source.
*
* @see JSONParser
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseStrict(Reader in) throws IOException, ParseException {
return new JSONParser(MODE_RFC4627).parse(in, FACTORY_SIMPLE);
}
/**
* Parse valid RFC4627 JSON text into java object from the input source.
*
* @see JSONParser
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseStrict(String s) throws ParseException {
return new JSONParser(MODE_RFC4627).parse(s, FACTORY_SIMPLE);
}
/**
* Parse valid RFC4627 JSON text into java object from the input source.
*
* @see JSONParser
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseStrict(byte[] s) throws ParseException {
return new JSONParser(MODE_RFC4627).parse(s, FACTORY_SIMPLE);
}
/**
* Parse valid RFC4627 JSON text into java object from the input source.
*
* @see JSONParser
*
* @since 1.1.2
*
* @return Instance of the following: JSONObject, JSONArray, String,
* java.lang.Number, java.lang.Boolean, null
*/
public static Object parseStrict(byte[] s, int offset, int length) throws ParseException {
return new JSONParser(MODE_RFC4627).parse(s, offset, length, FACTORY_SIMPLE);
}
/**
* Check RFC4627 Json Syntax from input Reader
*
* @return if the input is valid
*/
public static boolean isValidJsonStrict(Reader in) throws IOException {
try {
new JSONParser(MODE_RFC4627).parse(in, FACTORY_FAKE_COINTAINER);
return true;
} catch (ParseException e) {
return false;
}
}
/**
* check RFC4627 Json Syntax from input String
*
* @return if the input is valid
*/
public static boolean isValidJsonStrict(String s) {
try {
new JSONParser(MODE_RFC4627).parse(s, FACTORY_FAKE_COINTAINER);
return true;
} catch (ParseException e) {
return false;
}
}
/**
* Check Json Syntax from input Reader
*
* @return if the input is valid
*/
public static boolean isValidJson(Reader in) throws IOException {
try {
new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(in, FACTORY_FAKE_COINTAINER);
return true;
} catch (ParseException e) {
return false;
}
}
/**
* Check Json Syntax from input String
*
* @return if the input is valid
*/
public static boolean isValidJson(String s) {
try {
new JSONParser(DEFAULT_PERMISSIVE_MODE).parse(s, FACTORY_FAKE_COINTAINER);
return true;
} catch (ParseException e) {
return false;
}
}
/**
* Encode an object into JSON text and write it to out.
* <p>
* If this object is a Map or a List, and it's also a JSONStreamAware or a
* JSONAware, JSONStreamAware or JSONAware will be considered firstly.
* <p>
*
* @see JSONObject#writeJSON(Map, Appendable)
* @see JSONArray#writeJSONString(List, Appendable)
*/
public static void writeJSONString(Object value, Appendable out) throws IOException {
writeJSONString(value, out, COMPRESSION);
}
public static JsonWriter defaultWriter = new JsonWriter();
/**
* Encode an object into JSON text and write it to out.
* <p>
* If this object is a Map or a List, and it's also a JSONStreamAware or a
* JSONAware, JSONStreamAware or JSONAware will be considered firstly.
* <p>
*
* @see JSONObject#writeJSON(Map, Appendable)
* @see JSONArray#writeJSONString(List, Appendable)
*/
@SuppressWarnings("unchecked")
public static void writeJSONString(Object value, Appendable out, JSONStyle compression) throws IOException {
if (value == null) {
out.append("null");
return;
}
Class<?> clz = value.getClass();
@SuppressWarnings("rawtypes")
JsonWriterI w = defaultWriter.getWrite(clz);
if (w == null) {
if (clz.isArray())
w = JsonWriter.arrayWriter;
else {
w = defaultWriter.getWriterByInterface(value.getClass());
if (w == null)
w = JsonWriter.beansWriter;
// w = JsonWriter.beansWriterASM;
}
defaultWriter.registerWriter(w, clz);
}
w.writeJSONString(value, out, compression);
}
/**
* Encode an object into JSON text and write it to out.
* <p>
* If this object is a Map or a List, and it's also a JSONStreamAware or a
* JSONAware, JSONStreamAware or JSONAware will be considered firstly.
* <p>
*
* @see JSONObject#writeJSON(Map, Appendable)
* @see JSONArray#writeJSONString(List, Appendable)
*/
public static String toJSONString(Object value) {
return toJSONString(value, COMPRESSION);
}
/**
* Convert an object to JSON text.
* <p>
* If this object is a Map or a List, and it's also a JSONAware, JSONAware
* will be considered firstly.
* <p>
* DO NOT call this method from toJSONString() of a class that implements
* both JSONAware and Map or List with "this" as the parameter, use
* JSONObject.toJSONString(Map) or JSONArray.toJSONString(List) instead.
*
* @see JSONObject#toJSONString(Map)
* @see JSONArray#toJSONString(List)
*
* @return JSON text, or "null" if value is null or it's an NaN or an INF
* number.
*/
public static String toJSONString(Object value, JSONStyle compression) {
StringBuilder sb = new StringBuilder();
try {
writeJSONString(value, sb, compression);
} catch (IOException e) {
// can not append on a StringBuilder
}
return sb.toString();
}
public static String escape(String s) {
return escape(s, COMPRESSION);
}
/**
* Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters
* (U+0000 through U+001F).
*/
public static String escape(String s, JSONStyle compression) {
if (s == null)
return null;
StringBuilder sb = new StringBuilder();
compression.escape(s, sb);
return sb.toString();
}
public static void escape(String s, Appendable ap) {
escape(s, ap, COMPRESSION);
}
public static void escape(String s, Appendable ap, JSONStyle compression) {
if (s == null)
return;
compression.escape(s, ap);
}
}

View File

@ -0,0 +1,323 @@
package net.minidev.json;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
/**
* protected class used to stored Internal methods
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
class JStylerObj {
public final static MPSimple MP_SIMPLE = new MPSimple();
public final static MPTrue MP_TRUE = new MPTrue();
public final static MPAgressive MP_AGGRESIVE = new MPAgressive();
public final static EscapeLT ESCAPE_LT = new EscapeLT();
public final static Escape4Web ESCAPE4Web = new Escape4Web();
public static interface MustProtect {
public boolean mustBeProtect(String s);
}
private static class MPTrue implements MustProtect {
public boolean mustBeProtect(String s) {
return true;
}
}
private static class MPSimple implements MustProtect {
/**
* can a String can be store without enclosing quotes. ie: should not
* contain any special json char
*
* @param s
* @return
*/
public boolean mustBeProtect(String s) {
if (s == null)
return false;
int len = s.length();
if (len == 0)
return true;
if (s.trim() != s)
return true;
char ch = s.charAt(0);
if (ch >= '0' && ch <= '9' || ch == '-')
return true;
for (int i = 0; i < len; i++) {
ch = s.charAt(i);
if (isSpace(ch))
return true;
if (isSpecial(ch))
return true;
if (isSpecialChar(ch))
return true;
if (isUnicode(ch))
return true;
}
// keyword check
if (isKeyword(s))
return true;
return false;
}
}
private static class MPAgressive implements MustProtect {
public boolean mustBeProtect(final String s) {
if (s == null)
return false;
int len = s.length();
// protect empty String
if (len == 0)
return true;
// protect trimable String
if (s.trim() != s)
return true;
// json special char
char ch = s.charAt(0);
if (isSpecial(ch) || isUnicode(ch))
return true;
for (int i = 1; i < len; i++) {
ch = s.charAt(i);
if (isSpecialClose(ch) || isUnicode(ch))
return true;
}
// keyWord must be protect
if (isKeyword(s))
return true;
// Digit like text must be protect
ch = s.charAt(0);
// only test String if First Ch is a digit
if (ch >= '0' && ch <= '9' || ch == '-') {
int p = 1;
// skip first digits
for (; p < len; p++) {
ch = s.charAt(p);
if (ch < '0' || ch > '9')
break;
}
// int/long
if (p == len)
return true;
// Floating point
if (ch == '.') {
p++;
}
// Skip digits
for (; p < len; p++) {
ch = s.charAt(p);
if (ch < '0' || ch > '9')
break;
}
if (p == len)
return true; // can be read as an floating number
// Double
if (ch == 'E' || ch == 'e') {
p++;
if (p == len) // no power data not a digits
return false;
ch = s.charAt(p);
if (ch == '+' || ch == '-') {
p++;
ch = s.charAt(p);
}
}
if (p == len) // no power data => not a digit
return false;
for (; p < len; p++) {
ch = s.charAt(p);
if (ch < '0' || ch > '9')
break;
}
// floating point With power of data.
if (p == len)
return true;
return false;
}
return false;
}
}
public static boolean isSpace(char c) {
return (c == '\r' || c == '\n' || c == '\t' || c == ' ');
}
public static boolean isSpecialChar(char c) {
return (c == '\b' || c == '\f' || c == '\n');
}
public static boolean isSpecialOpen(char c) {
return (c == '{' || c == '[' || c == ',' || c == ':');
}
public static boolean isSpecialClose(char c) {
return (c == '}' || c == ']' || c == ',' || c == ':');
}
public static boolean isSpecial(char c) {
return (c == '{' || c == '[' || c == ',' || c == '}' || c == ']' || c == ':' || c == '\'' || c == '"');
}
public static boolean isUnicode(char c) {
return ((c >= '\u0000' && c <= '\u001F') || (c >= '\u007F' && c <= '\u009F') || (c >= '\u2000' && c <= '\u20FF'));
}
public static boolean isKeyword(String s) {
if (s.length() < 3)
return false;
char c = s.charAt(0);
if (c == 'n')
return s.equals("null");
if (c == 't')
return s.equals("true");
if (c == 'f')
return s.equals("false");
if (c == 'N')
return s.equals("NaN");
return false;
}
public static interface StringProtector {
public void escape(String s, Appendable out);
}
private static class EscapeLT implements StringProtector {
/**
* Escape special chars form String except /
*
* @param s
* - Must not be null.
* @param out
*/
public void escape(String s, Appendable out) {
try {
int len = s.length();
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
switch (ch) {
case '"':
out.append("\\\"");
break;
case '\\':
out.append("\\\\");
break;
case '\b':
out.append("\\b");
break;
case '\f':
out.append("\\f");
break;
case '\n':
out.append("\\n");
break;
case '\r':
out.append("\\r");
break;
case '\t':
out.append("\\t");
break;
default:
// Reference:
// http://www.unicode.org/versions/Unicode5.1.0/
if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F')
|| (ch >= '\u2000' && ch <= '\u20FF')) {
out.append("\\u");
String hex = "0123456789ABCDEF";
out.append(hex.charAt(ch >> 12 & 0x000F));
out.append(hex.charAt(ch >> 8 & 0x000F));
out.append(hex.charAt(ch >> 4 & 0x000F));
out.append(hex.charAt(ch >> 0 & 0x000F));
} else {
out.append(ch);
}
}
}
} catch (IOException e) {
throw new RuntimeException("Impossible Exeption");
}
}
}
private static class Escape4Web implements StringProtector {
/**
* Escape special chars form String including /
*
* @param s
* - Must not be null.
* @param sb
*/
public void escape(String s, Appendable sb) {
try {
int len = s.length();
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
switch (ch) {
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\b':
sb.append("\\b");
break;
case '\f':
sb.append("\\f");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
case '/':
sb.append("\\/");
break;
default:
// Reference:
// http://www.unicode.org/versions/Unicode5.1.0/
if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F')
|| (ch >= '\u2000' && ch <= '\u20FF')) {
sb.append("\\u");
String hex = "0123456789ABCDEF";
sb.append(hex.charAt(ch >> 12 & 0x0F));
sb.append(hex.charAt(ch >> 8 & 0x0F));
sb.append(hex.charAt(ch >> 4 & 0x0F));
sb.append(hex.charAt(ch >> 0 & 0x0F));
} else {
sb.append(ch);
}
}
}
} catch (IOException e) {
throw new RuntimeException("Impossible Error");
}
}
}
}

View File

@ -0,0 +1,70 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
/**
* Container factory for creating containers for JSON object and JSON array.
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public interface ContainerFactory {
/**
* @return A Map instance to build JSON object.
*/
public Map<String, Object> createObjectContainer();
/**
* @return A List instance to store JSON array.
*/
public List<Object> createArrayContainer();
/**
* Default factory
*/
public final static ContainerFactory FACTORY_SIMPLE = new ContainerFactory() {
// @Override JDK 1.5 compatibility change
public Map<String, Object> createObjectContainer() {
return new JSONObject();
}
// @Override JDK 1.5 compatibility change
public List<Object> createArrayContainer() {
return new JSONArray();
}
};
public final static ContainerFactory FACTORY_ORDERED = new ContainerFactory() {
// @Override JDK 1.5 compatibility change
public Map<String, Object> createObjectContainer() {
return new LinkedHashMap<String, Object>();
}
// @Override JDK 1.5 compatibility change
public List<Object> createArrayContainer() {
return new JSONArray();
}
};
}

View File

@ -0,0 +1,123 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
/**
* A simplified and stoppable SAX-like content handler for stream processing of
* JSON text.
*
* @see org.xml.sax.ContentHandler
*
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public interface ContentHandler {
/**
* Receive notification of the beginning of JSON processing. The parser will
* invoke this method only once.
*
* @throws ParseException
* - JSONParser will stop and throw the same exception to the
* caller when receiving this exception.
*/
void startJSON() throws ParseException, IOException;
/**
* Receive notification of the end of JSON processing.
*
* @throws ParseException
*/
void endJSON() throws ParseException, IOException;
/**
* Receive notification of the beginning of a JSON object.
*
* @return false if the handler wants to stop parsing after return.
* @throws ParseException
* - JSONParser will stop and throw the same exception to the
* caller when receiving this exception.
* @see #endJSON
*/
boolean startObject() throws ParseException, IOException;
/**
* Receive notification of the end of a JSON object.
*
* @return false if the handler wants to stop parsing after return.
* @throws ParseException
*
* @see #startObject
*/
boolean endObject() throws ParseException, IOException;
/**
* Receive notification of the beginning of a JSON object entry.
*
* @param key
* - Key of a JSON object entry.
*
* @return false if the handler wants to stop parsing after return.
* @throws ParseException
*
* @see #endObjectEntry
*/
boolean startObjectEntry(String key) throws ParseException, IOException;
/**
* Receive notification of the end of the value of previous object entry.
*
* @return false if the handler wants to stop parsing after return.
* @throws ParseException
*
* @see #startObjectEntry
*/
boolean endObjectEntry() throws ParseException, IOException;
/**
* Receive notification of the beginning of a JSON array.
*
* @return false if the handler wants to stop parsing after return.
* @throws ParseException
*
* @see #endArray
*/
boolean startArray() throws ParseException, IOException;
/**
* Receive notification of the end of a JSON array.
*
* @return false if the handler wants to stop parsing after return.
* @throws ParseException
*
* @see #startArray
*/
boolean endArray() throws ParseException, IOException;
/**
* Receive notification of the JSON primitive values: java.lang.String,
* java.lang.Number, java.lang.Boolean null
*
* @param value
* - Instance of the following: java.lang.String,
* java.lang.Number, java.lang.Boolean null
*
* @return false if the handler wants to stop parsing after return.
* @throws ParseException
*/
boolean primitive(Object value) throws ParseException, IOException;
}

View File

@ -0,0 +1,131 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import net.minidev.json.JSONStyle;
import net.minidev.json.JSONValue;
public class ContentHandlerCompressor implements ContentHandler {
Appendable out;
JSONStyle compression;
int[] stack = new int[10];
int pos;
// push 0 = < Object
// push 1 = < Array
private void push(int type) {
pos += 2;
if (pos >= stack.length) {
int[] tmp = new int[stack.length * 2];
System.arraycopy(stack, 0, tmp, 0, stack.length);
stack = tmp;
}
stack[pos] = type;
stack[pos + 1] = 0;
}
private boolean isInObject() {
return stack[pos] == 0;
}
private boolean isInArray() {
return stack[pos] == 1;
}
public ContentHandlerCompressor(Appendable out, JSONStyle compression) {
this.out = out;
this.compression = compression;
}
// @Override JDK 1.5 compatibility change
public void startJSON() throws ParseException, IOException {
}
// @Override JDK 1.5 compatibility change
public void endJSON() throws ParseException, IOException {
}
// @Override JDK 1.5 compatibility change
public boolean startObject() throws ParseException, IOException {
if (isInArray() && stack[pos + 1]++ > 0)
out.append(',');
out.append('{');
push(0);
// stack.add(JsonStructure.newObj());
return false;
}
// @Override JDK 1.5 compatibility change
public boolean endObject() throws ParseException, IOException {
out.append('}');
pos -= 2;
// stack.pop();
return false;
}
// @Override JDK 1.5 compatibility change
public boolean startObjectEntry(String key) throws ParseException, IOException {
if (stack[pos + 1]++ > 0)
out.append(',');
if (key == null)
out.append("null");
else if (!compression.mustProtectKey(key))
out.append(key);
else {
out.append('"');
JSONValue.escape(key, out, compression);
out.append('"');
}
out.append(':');
return false;
}
// @Override JDK 1.5 compatibility change
public boolean endObjectEntry() throws ParseException, IOException {
return false;
}
// @Override JDK 1.5 compatibility change
public boolean startArray() throws ParseException, IOException {
if (isInArray() && stack[pos + 1]++ > 0)
out.append(',');
out.append('[');
push(1);
return false;
}
// @Override JDK 1.5 compatibility change
public boolean endArray() throws ParseException, IOException {
out.append(']');
pos -= 2;
return false;
}
// @Override JDK 1.5 compatibility change
public boolean primitive(Object value) throws ParseException, IOException {
if (!isInObject() && stack[pos + 1]++ > 0)
out.append(',');
if (value instanceof String) {
compression.writeString(out, (String) value);
} else
JSONValue.writeJSONString(value, out, compression);
return false;
}
}

View File

@ -0,0 +1,65 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
public class ContentHandlerDumy implements ContentHandler {
public static ContentHandlerDumy HANDLER = new ContentHandlerDumy();
// @Override JDK 1.5 compatibility change
public void startJSON() throws ParseException {
}
// @Override JDK 1.5 compatibility change
public void endJSON() throws ParseException {
}
// @Override JDK 1.5 compatibility change
public boolean startObject() throws ParseException, IOException {
return false;
}
// @Override JDK 1.5 compatibility change
public boolean endObject() throws ParseException {
return false;
}
// @Override JDK 1.5 compatibility change
public boolean startObjectEntry(String key) throws ParseException {
return false;
}
// @Override JDK 1.5 compatibility change
public boolean endObjectEntry() throws ParseException {
return false;
}
// @Override JDK 1.5 compatibility change
public boolean startArray() throws ParseException {
return false;
}
// @Override JDK 1.5 compatibility change
public boolean endArray() throws ParseException {
return false;
}
// @Override JDK 1.5 compatibility change
public boolean primitive(Object value) throws ParseException {
return false;
}
}

View File

@ -0,0 +1,81 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Fake Container factory used for JSon check and SaX parsing
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public class FakeContainerFactory implements ContainerFactory {
public FackList list;
public FackMap map;
// @Override JDK 1.5 compatibility change
public List<Object> createArrayContainer() {
if (list == null)
list = new FackList();
return list;
}
// @Override JDK 1.5 compatibility change
public Map<String, Object> createObjectContainer() {
if (map == null)
map = new FackMap();
return map;
}
/**
* dummy AbstractMap
*/
static class FackMap extends AbstractMap<String, Object> {
public Object put(String key, Object value) {
return null;
}
@Override
public Set<java.util.Map.Entry<String, Object>> entrySet() {
return null;
}
}
/**
* dummy AbstractList
* replace AbsractList by list to make it compile on jdk 1.7
*/
@SuppressWarnings("serial")
static class FackList extends ArrayList<Object> {
public boolean add(Object e) {
return false;
}
@Override
public Object get(int index) {
return null;
}
@Override
public int size() {
return 0;
}
}
}

View File

@ -0,0 +1,266 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
public class JSONParser {
/**
* allow simple quote as String quoting char
*/
public final static int ACCEPT_SIMPLE_QUOTE = 1;
/**
* allow non quoted test
*/
public final static int ACCEPT_NON_QUOTE = 2;
/**
* Parse NaN as Float.NaN
*/
public final static int ACCEPT_NAN = 4;
/**
* Ignore control char in input text.
*/
public final static int IGNORE_CONTROL_CHAR = 8;
/**
* Use int datatype to store number when it's possible.
*
* @since 1.0.7
*/
public final static int USE_INTEGER_STORAGE = 16;
/**
* Throws exception on excessive 0 leading in digits
*
* @since 1.0.7
*/
public final static int ACCEPT_LEADING_ZERO = 32;
/**
* Throws exception on useless comma in object and array
*
* @since 1.0.8
*/
public final static int ACCEPT_USELESS_COMMA = 64;
/**
* Allow Json-smart to use Double or BigDecimal to store floating point
* value
*
* You may need to disable HI_PRECISION_FLOAT feature on 32bit to improve
* parsing performances.
*
* @since 1.0.9
*/
public final static int USE_HI_PRECISION_FLOAT = 128;
/**
* If enabled json-smart will throws exception if datas are present after
* the end of the Json data.
*
* @since 1.0.9-2
*/
public final static int ACCEPT_TAILLING_DATA = 256;
/**
* smart mode, fastest parsing mode. accept lots of non standard json syntax
*
* @since 1.3.1
*/
public final static int ACCEPT_TAILLING_SPACE = 512;
/**
* smart mode, fastest parsing mode. accept lots of non standard json syntax
*
* @since 1.0.6
*/
public final static int MODE_PERMISSIVE = -1;
/**
* strict RFC4627 mode.
*
* slower than PERMISIF MODE.
*
* @since 1.0.6
*/
public final static int MODE_RFC4627 = USE_INTEGER_STORAGE | USE_HI_PRECISION_FLOAT | ACCEPT_TAILLING_DATA;
/**
* Parse Object like json-simple
*
* Best for an iso-bug json-simple API port.
*
* @since 1.0.7
*/
public final static int MODE_JSON_SIMPLE = ACCEPT_USELESS_COMMA | USE_HI_PRECISION_FLOAT | ACCEPT_TAILLING_DATA | ACCEPT_TAILLING_SPACE;
/**
* Strictest parsing mode
*
* @since 1.0.9-2
*/
public final static int MODE_STRICTEST = USE_INTEGER_STORAGE | USE_HI_PRECISION_FLOAT;
/**
* Default json-smart processing mode
*/
public static int DEFAULT_PERMISSIVE_MODE = (System.getProperty("JSON_SMART_SIMPLE") != null) ? MODE_JSON_SIMPLE
: MODE_PERMISSIVE;
/*
* internal fields
*/
private int mode;
private JSONParserReader pStream;
private JSONParserInputStream pSBintream;
private JSONParserString pString;
private JSONParserByteArray pBytes;
/**
* @deprecated prefer usage of new JSONParser(JSONParser.MODE_*)
*/
public JSONParser() {
this.mode = DEFAULT_PERMISSIVE_MODE;
}
public JSONParser(int permissifMode) {
this.mode = permissifMode;
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(String in) throws ParseException {
if (pString == null)
pString = new JSONParserString(mode);
return pString.parse(in);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(String in, ContainerFactory containerFactory) throws ParseException {
if (pString == null)
pString = new JSONParserString(mode);
return pString.parse(in, containerFactory);
}
public Object parse(String in, ContainerFactory containerFactory, ContentHandler handler) throws ParseException {
if (pString == null)
pString = new JSONParserString(mode);
return pString.parse(in, containerFactory, handler);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(byte[] in) throws ParseException {
if (pBytes == null)
pBytes = new JSONParserByteArray(mode);
return pBytes.parse(in);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(byte[] in, ContainerFactory containerFactory) throws ParseException {
if (pBytes == null)
pBytes = new JSONParserByteArray(mode);
return pBytes.parse(in, containerFactory);
}
public Object parse(byte[] in, ContainerFactory containerFactory, ContentHandler handler) throws ParseException {
if (pBytes == null)
pBytes = new JSONParserByteArray(mode);
return pBytes.parse(in, containerFactory, handler);
}
public Object parse(byte[] in, int offset, int length) throws ParseException {
if (pBytes == null)
pBytes = new JSONParserByteArray(mode);
return pBytes.parse(in, offset, length, ContainerFactory.FACTORY_SIMPLE, ContentHandlerDumy.HANDLER);
}
public Object parse(byte[] in, int offset, int length, ContainerFactory containerFactory) throws ParseException {
if (pBytes == null)
pBytes = new JSONParserByteArray(mode);
return pBytes.parse(in, offset, length, containerFactory, ContentHandlerDumy.HANDLER);
}
public Object parse(byte[] in, int offset, int length, ContainerFactory containerFactory, ContentHandler handler)
throws ParseException {
if (pBytes == null)
pBytes = new JSONParserByteArray(mode);
return pBytes.parse(in, offset, length, containerFactory, handler);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(Reader in) throws ParseException {
if (pStream == null)
pStream = new JSONParserReader(mode);
return pStream.parse(in);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(Reader in, ContainerFactory containerFactory) throws ParseException {
if (pStream == null)
pStream = new JSONParserReader(mode);
return pStream.parse(in, containerFactory);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(Reader in, ContainerFactory containerFactory, ContentHandler handler) throws ParseException {
if (pStream == null)
pStream = new JSONParserReader(mode);
return pStream.parse(in, containerFactory, handler);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(InputStream in) throws ParseException, UnsupportedEncodingException {
if (pSBintream == null)
pSBintream = new JSONParserInputStream(mode);
return pSBintream.parse(in);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(InputStream in, ContainerFactory containerFactory) throws ParseException, UnsupportedEncodingException {
if (pSBintream == null)
pSBintream = new JSONParserInputStream(mode);
return pSBintream.parse(in, containerFactory);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(InputStream in, ContainerFactory containerFactory, ContentHandler handler)
throws ParseException, UnsupportedEncodingException {
if (pSBintream == null)
pSBintream = new JSONParserInputStream(mode);
return pSBintream.parse(in, containerFactory, handler);
}
}

View File

@ -0,0 +1,666 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* 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 static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_CHAR;
import static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_DUPLICATE_KEY;
import static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_EOF;
import static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_LEADING_0;
import static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_TOKEN;
import static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_UNICODE;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
/**
* JSONParserBase is the common code between {@link JSONParserString} and
* {@link JSONParserReader}
*
* @see JSONParserMemory
* @see JSONParserStream
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
abstract class JSONParserBase {
protected char c;
public final static byte EOI = 0x1A;
protected static final char MAX_STOP = 126; // '}' -> 125
//
protected static boolean[] stopAll = new boolean[MAX_STOP];
protected static boolean[] stopArray = new boolean[MAX_STOP];
protected static boolean[] stopKey = new boolean[MAX_STOP];
protected static boolean[] stopValue = new boolean[MAX_STOP];
protected static boolean[] stopX = new boolean[MAX_STOP];
static {
stopKey[':'] = stopKey[EOI] = true;
stopValue[','] = stopValue['}'] = stopValue[EOI] = true;
stopArray[','] = stopArray[']'] = stopArray[EOI] = true;
stopX[EOI] = true;
stopAll[','] = stopAll[':'] = true;
stopAll[']'] = stopAll['}'] = stopAll[EOI] = true;
}
/*
* End of static declaration
*/
protected ContainerFactory containerFactory;
protected ContentHandler handler;
protected final MSB sb = new MSB(15);
protected Object xo;
protected String xs;
protected int pos;
/*
* Parsing flags
*/
protected final boolean acceptLeadinZero;
protected final boolean acceptNaN;
protected final boolean acceptNonQuote;
protected final boolean acceptSimpleQuote;
protected final boolean acceptUselessComma;
protected final boolean checkTaillingData;
protected final boolean checkTaillingSpace;
protected final boolean ignoreControlChar;
protected final boolean useHiPrecisionFloat;
protected final boolean useIntegerStorage;
public JSONParserBase(int permissiveMode) {
this.acceptNaN = (permissiveMode & JSONParser.ACCEPT_NAN) > 0;
this.acceptNonQuote = (permissiveMode & JSONParser.ACCEPT_NON_QUOTE) > 0;
this.acceptSimpleQuote = (permissiveMode & JSONParser.ACCEPT_SIMPLE_QUOTE) > 0;
this.ignoreControlChar = (permissiveMode & JSONParser.IGNORE_CONTROL_CHAR) > 0;
this.useIntegerStorage = (permissiveMode & JSONParser.USE_INTEGER_STORAGE) > 0;
this.acceptLeadinZero = (permissiveMode & JSONParser.ACCEPT_LEADING_ZERO) > 0;
this.acceptUselessComma = (permissiveMode & JSONParser.ACCEPT_USELESS_COMMA) > 0;
this.useHiPrecisionFloat = (permissiveMode & JSONParser.USE_HI_PRECISION_FLOAT) > 0;
this.checkTaillingData = (permissiveMode & (JSONParser.ACCEPT_TAILLING_DATA | JSONParser.ACCEPT_TAILLING_SPACE)) != (JSONParser.ACCEPT_TAILLING_DATA | JSONParser.ACCEPT_TAILLING_SPACE);
this.checkTaillingSpace = (permissiveMode & JSONParser.ACCEPT_TAILLING_SPACE) == 0;
}
public void checkControleChar() throws ParseException {
if (ignoreControlChar)
return;
int l = xs.length();
for (int i = 0; i < l; i++) {
char c = xs.charAt(i);
if (c < 0)
continue;
if (c <= 31)
throw new ParseException(pos + i, ParseException.ERROR_UNEXPECTED_CHAR, c);
if (c == 127)
throw new ParseException(pos + i, ParseException.ERROR_UNEXPECTED_CHAR, c);
}
}
public void checkLeadinZero() throws ParseException {
int len = xs.length();
if (len == 1)
return;
if (len == 2) {
if (xs.equals("00"))
throw new ParseException(pos, ERROR_UNEXPECTED_LEADING_0, xs);
return;
}
char c1 = xs.charAt(0);
char c2 = xs.charAt(1);
if (c1 == '-') {
char c3 = xs.charAt(2);
if (c2 == '0' && c3 >= '0' && c3 <= '9')
throw new ParseException(pos, ERROR_UNEXPECTED_LEADING_0, xs);
return;
}
if (c1 == '0' && c2 >= '0' && c2 <= '9')
throw new ParseException(pos, ERROR_UNEXPECTED_LEADING_0, xs);
}
protected Number extractFloat() throws ParseException {
if (!acceptLeadinZero)
checkLeadinZero();
if (!useHiPrecisionFloat)
return Float.parseFloat(xs);
if (xs.length() > 18) // follow JSonIJ parsing method
return new BigDecimal(xs);
return Double.parseDouble(xs);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
protected Object parse(ContainerFactory containerFactory, ContentHandler handler) throws ParseException {
this.containerFactory = containerFactory;
this.handler = handler;
// this.pos = -1;
Object result;
try {
read();
handler.startJSON();
result = readMain(stopX);
handler.endJSON();
if (checkTaillingData) {
if (!checkTaillingSpace)
skipSpace();
if (c != EOI)
throw new ParseException(pos - 1, ERROR_UNEXPECTED_TOKEN, c);
}
} catch (IOException e) {
throw new ParseException(pos, e);
}
xs = null;
xo = null;
return result;
}
protected Number parseNumber(String s) throws ParseException {
// pos
int p = 0;
// len
int l = s.length();
// max pos long base 10 len
int max = 19;
boolean neg;
if (s.charAt(0) == '-') {
p++;
max++;
neg = true;
if (!acceptLeadinZero && l >= 3 && s.charAt(1) == '0')
throw new ParseException(pos, ERROR_UNEXPECTED_LEADING_0, s);
} else {
neg = false;
if (!acceptLeadinZero && l >= 2 && s.charAt(0) == '0')
throw new ParseException(pos, ERROR_UNEXPECTED_LEADING_0, s);
}
boolean mustCheck;
if (l < max) {
max = l;
mustCheck = false;
} else if (l > max) {
return new BigInteger(s, 10);
} else {
max = l - 1;
mustCheck = true;
}
long r = 0;
while (p < max) {
r = (r * 10L) + ('0' - s.charAt(p++));
}
if (mustCheck) {
boolean isBig;
if (r > -922337203685477580L) {
isBig = false;
} else if (r < -922337203685477580L) {
isBig = true;
} else {
if (neg)
isBig = (s.charAt(p) > '8');
else
isBig = (s.charAt(p) > '7');
}
if (isBig)
return new BigInteger(s, 10);
r = r * 10L + ('0' - s.charAt(p));
}
if (neg) {
if (this.useIntegerStorage && r >= Integer.MIN_VALUE)
return (int) r;
return r;
}
r = -r;
if (this.useIntegerStorage && r <= Integer.MAX_VALUE)
return (int) r;
return r;
}
/**
* Read one char in this.c
*
* @throws IOException
*/
abstract protected void read() throws IOException;
protected List<Object> readArray() throws ParseException, IOException {
List<Object> obj = containerFactory.createArrayContainer();
if (c != '[')
throw new RuntimeException("Internal Error");
read();
boolean needData = false;
handler.startArray();
for (;;) {
switch (c) {
case ' ':
case '\r':
case '\n':
case '\t':
read();
continue;
case ']':
if (needData && !acceptUselessComma)
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, (char) c);
read(); /* unstack */
handler.endArray();
return obj;
case ':':
case '}':
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, (char) c);
case ',':
if (needData && !acceptUselessComma)
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, (char) c);
read();
needData = true;
continue;
case EOI:
throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF");
default:
obj.add(readMain(stopArray));
needData = false;
continue;
}
}
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
// protected <T> T readFirst(AMapper<T> mapper) throws ParseException,
// IOException {
// }
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
protected Object readMain(boolean stop[]) throws ParseException, IOException {
for (;;) {
switch (c) {
// skip spaces
case ' ':
case '\r':
case '\n':
case '\t':
read();
continue;
// invalid stats
case ':':
case '}':
case ']':
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c);
// start object
case '{':
return readObject();
// start Array
case '[':
return readArray();
// start string
case '"':
case '\'':
readString();
handler.primitive(xs);
return xs;
// string or null
case 'n':
readNQString(stop);
if ("null".equals(xs)) {
handler.primitive(null);
return null;
}
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
handler.primitive(xs);
return xs;
// string or false
case 'f':
readNQString(stop);
if ("false".equals(xs)) {
handler.primitive(Boolean.FALSE);
return Boolean.FALSE;
}
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
handler.primitive(xs);
return xs;
// string or true
case 't':
readNQString(stop);
if ("true".equals(xs)) {
handler.primitive(Boolean.TRUE);
return Boolean.TRUE;
}
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
handler.primitive(xs);
return xs;
// string or NaN
case 'N':
readNQString(stop);
if (!acceptNaN)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
if ("NaN".equals(xs)) {
handler.primitive(Float.NaN);
return Float.valueOf(Float.NaN);
}
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
handler.primitive(xs);
return xs;
// digits
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
xo = readNumber(stop);
handler.primitive(xo);
return xo;
default:
readNQString(stop);
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
handler.primitive(xs);
return xs;
}
}
}
abstract protected void readNoEnd() throws ParseException, IOException;
abstract protected void readNQString(boolean[] stop) throws IOException;
abstract protected Object readNumber(boolean[] stop) throws ParseException, IOException;
protected Map<String, Object> readObject() throws ParseException, IOException {
Map<String, Object> obj = this.containerFactory.createObjectContainer();
if (c != '{')
throw new RuntimeException("Internal Error");
handler.startObject();
boolean needData = false;
boolean acceptData = true;
for (;;) {
read();
switch (c) {
case ' ':
case '\r':
case '\t':
case '\n':
continue;
case ':':
case ']':
case '[':
case '{':
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c);
case '}':
if (needData && !acceptUselessComma)
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, (char) c);
read(); /* unstack */
handler.endObject();
return obj;
case ',':
if (needData && !acceptUselessComma)
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, (char) c);
acceptData = needData = true;
continue;
case '"':
case '\'':
default:
int keyStart = pos;
if (c == '\"' || c == '\'') {
readString();
} else {
readNQString(stopKey);
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
}
String key = xs;
if (!acceptData)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, key);
handler.startObjectEntry(key);
//Skip spaces
skipSpace();
if (c != ':') {
if (c == EOI)
throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null);
throw new ParseException(pos - 1, ERROR_UNEXPECTED_CHAR, c);
}
readNoEnd(); /* skip : */
Object duplicate = obj.put(key, readMain(stopValue));
if (duplicate != null)
throw new ParseException(keyStart, ERROR_UNEXPECTED_DUPLICATE_KEY, key);
handler.endObjectEntry();
// should loop skipping read step
//
//
//
//
skipSpace();
if (c == '}') {
read(); /* unstack */
handler.endObject();
return obj;
}
if (c == EOI) // Fixed on 18/10/2011 reported by vladimir
throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null);
// if c==, continue
if (c == ',')
acceptData = needData = true;
else
throw new ParseException(pos - 1, ERROR_UNEXPECTED_TOKEN, c);
// acceptData = needData = false;
}
}
}
/**
* store and read
*/
abstract void readS() throws IOException;
abstract protected void readString() throws ParseException, IOException;
protected void readString2() throws ParseException, IOException {
/* assert (c == '\"' || c == '\'') */
char sep = c;
for (;;) {
read();
switch (c) {
case EOI:
throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null);
case '"':
case '\'':
if (sep == c) {
read();
xs = sb.toString();
return;
}
sb.append(c);
break;
case '\\':
read();
switch (c) {
case 't':
sb.append('\t');
break;
case 'n':
sb.append('\n');
break;
case 'r':
sb.append('\r');
break;
case 'f':
sb.append('\f');
break;
case 'b':
sb.append('\b');
break;
case '\\':
sb.append('\\');
break;
case '/':
sb.append('/');
break;
case '\'':
sb.append('\'');
break;
case '"':
sb.append('"');
break;
case 'u':
sb.append(readUnicode(4));
break;
case 'x': // issue 39
sb.append(readUnicode(2));
default:
break;
}
break;
case '\0': // end of string
case (char) 1: // Start of heading
case (char) 2: // Start of text
case (char) 3: // End of text
case (char) 4: // End of transmission
case (char) 5: // Enquiry
case (char) 6: // Acknowledge
case (char) 7: // Bell
case '\b': // 8: backSpase
case '\t': // 9: horizontal tab
case '\n': // 10: new line
case (char) 11: // Vertical tab
case '\f': // 12: form feed
case '\r': // 13: return carriage
case (char) 14: // Shift Out, alternate character set
case (char) 15: // Shift In, resume defaultn character set
case (char) 16: // Data link escape
case (char) 17: // XON, with XOFF to pause listings;
case (char) 18: // Device control 2, block-mode flow control
case (char) 19: // XOFF, with XON is TERM=18 flow control
case (char) 20: // Device control 4
case (char) 21: // Negative acknowledge
case (char) 22: // Synchronous idle
case (char) 23: // End transmission block, not the same as EOT
case (char) 24: // Cancel line, MPE echoes !!!
case (char) 25: // End of medium, Control-Y interrupt
// case (char) 26: // Substitute
case (char) 27: // escape
case (char) 28: // File Separator
case (char) 29: // Group Separator
case (char) 30: // Record Separator
case (char) 31: // Unit Separator
case (char) 127: // del
if (ignoreControlChar)
continue;
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c);
default:
sb.append(c);
}
}
}
protected char readUnicode(int totalChars) throws ParseException, IOException {
int value = 0;
for (int i = 0; i < totalChars; i++) {
value = value * 16;
read();
if (c <= '9' && c >= '0')
value += c - '0';
else if (c <= 'F' && c >= 'A')
value += (c - 'A') + 10;
else if (c >= 'a' && c <= 'f')
value += (c - 'a') + 10;
else if (c == EOI)
throw new ParseException(pos, ERROR_UNEXPECTED_EOF, "EOF");
else
throw new ParseException(pos, ERROR_UNEXPECTED_UNICODE, c);
}
return (char) value;
}
protected void skipDigits() throws IOException {
for (;;) {
if (c < '0' || c > '9')
return;
readS();
}
}
protected void skipNQString(boolean[] stop) throws IOException {
for (;;) {
if ((c == EOI) || (c >= 0 && c < MAX_STOP && stop[c]))
return;
readS();
}
}
protected void skipSpace() throws IOException {
for (;;) {
if (c > ' ' || c == EOI)
return;
readS();
}
}
public static class MSB {
char b[];
int p;
public MSB(int size) {
b = new char[size];
p = -1;
}
public void append(char c) {
p++;
if (b.length <= p) {
char[] t = new char[b.length * 2 + 1];
System.arraycopy(b, 0, t, 0, b.length);
b = t;
}
b[p] = c;
}
public void append(int c) {
p++;
if (b.length <= p) {
char[] t = new char[b.length * 2 + 1];
System.arraycopy(b, 0, t, 0, b.length);
b = t;
}
b[p] = (char) c;
}
public String toString() {
return new String(b, 0, p + 1);
}
public void clear() {
p = -1;
}
}
}

View File

@ -0,0 +1,110 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* 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 static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_EOF;
/**
* Parser for JSON text. Please note that JSONParser is NOT thread-safe.
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
class JSONParserByteArray extends JSONParserMemory {
private byte[] in;
public JSONParserByteArray(int permissiveMode) {
super(permissiveMode);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(byte[] in) throws ParseException {
return parse(in, ContainerFactory.FACTORY_SIMPLE, ContentHandlerDumy.HANDLER);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(byte[] in, ContainerFactory containerFactory) throws ParseException {
return parse(in, containerFactory, ContentHandlerDumy.HANDLER);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(byte[] in, ContainerFactory containerFactory, ContentHandler handler) throws ParseException {
this.in = in;
this.len = in.length;
this.pos = -1;
return parse(containerFactory, handler);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*
*
* Processing from offset position until length
*/
public Object parse(byte[] in, int offset, int length, ContainerFactory containerFactory, ContentHandler handler)
throws ParseException {
this.in = in;
this.len = length;
this.pos = offset - 1;
return parse(containerFactory, handler);
}
protected void extractString(int beginIndex, int endIndex) {
xs = new String(in, beginIndex, endIndex - beginIndex);
}
protected int indexOf(char c, int pos) {
for (int i = pos; pos < len; i++)
if (in[i] == (byte) c)
return i;
return -1;
}
protected void read() {
if (++pos >= len)
this.c = EOI;
else
this.c = (char) in[pos];
}
/**
* Same as read() in memory parsing
*/
protected void readS() {
if (++pos >= len)
this.c = EOI;
else
this.c = (char) in[pos];
}
protected void readNoEnd() throws ParseException {
if (++pos >= len) {
this.c = EOI;
throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF");
} else
this.c = (char) in[pos];
}
}

View File

@ -0,0 +1,61 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
/**
* Parser for JSON text. Please note that JSONParser is NOT thread-safe.
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
class JSONParserInputStream extends JSONParserReader {
// len
public JSONParserInputStream(int permissiveMode) {
super(permissiveMode);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(InputStream in) throws ParseException, UnsupportedEncodingException {
return parse(in, ContainerFactory.FACTORY_SIMPLE, ContentHandlerDumy.HANDLER);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(InputStream in, ContainerFactory containerFactory) throws ParseException, UnsupportedEncodingException {
return parse(in, containerFactory, ContentHandlerDumy.HANDLER);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
* @throws UnsupportedEncodingException
*/
public Object parse(InputStream in, ContainerFactory containerFactory, ContentHandler handler)
throws ParseException, UnsupportedEncodingException {
InputStreamReader i2 = new InputStreamReader(in, "utf8");
this.pos = -1;
return super.parse(i2, containerFactory, handler);
}
}

View File

@ -0,0 +1,143 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* 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 static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_CHAR;
import static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_EOF;
import static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_TOKEN;
import java.io.IOException;
/**
* Parser for JSON text. Please note that JSONParser is NOT thread-safe.
*
* @author Uriel Chemouni <uchemouni@gmail.com>
* @see JSONParserString
* @see JSONParserByteArray
*/
abstract class JSONParserMemory extends JSONParserBase {
protected int len;
public JSONParserMemory(int permissiveMode) {
super(permissiveMode);
}
protected void readNQString(boolean[] stop) throws IOException {
int start = pos;
skipNQString(stop);
extractStringTrim(start, pos);
}
protected Object readNumber(boolean[] stop) throws ParseException, IOException {
int start = pos;
// accept first char digit or -
read();
skipDigits();
// Integer digit
if (c != '.' && c != 'E' && c != 'e') {
skipSpace();
if (c >= 0 && c < MAX_STOP && !stop[c] && c != EOI) {
// convert string
skipNQString(stop);
extractStringTrim(start, pos);
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
return xs;
}
extractStringTrim(start, pos);
return parseNumber(xs);
}
// floating point
if (c == '.') {
//
read();
skipDigits();
}
if (c != 'E' && c != 'e') {
skipSpace();
if (c >= 0 && c < MAX_STOP && !stop[c] && c != EOI) {
// convert string
skipNQString(stop);
extractStringTrim(start, pos);
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
return xs;
}
extractStringTrim(start, pos);
return extractFloat();
}
sb.append('E');
read();
if (c == '+' || c == '-' || c >= '0' && c <= '9') {
sb.append(c);
read(); // skip first char
skipDigits();
skipSpace();
if (c >= 0 && c < MAX_STOP && !stop[c] && c != EOI) {
// convert string
skipNQString(stop);
extractStringTrim(start, pos);
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
return xs;
}
extractStringTrim(start, pos);
return extractFloat();
} else {
skipNQString(stop);
extractStringTrim(start, pos);
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
if (!acceptLeadinZero)
checkLeadinZero();
return xs;
}
// throw new ParseException(pos - 1, ERROR_UNEXPECTED_CHAR, null);
}
protected void readString() throws ParseException, IOException {
if (!acceptSimpleQuote && c == '\'') {
if (acceptNonQuote) {
readNQString(stopAll);
return;
}
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c);
}
int tmpP = indexOf(c, pos + 1);
if (tmpP == -1)
throw new ParseException(len, ERROR_UNEXPECTED_EOF, null);
extractString(pos + 1, tmpP);
if (xs.indexOf('\\') == -1) {
checkControleChar();
pos = tmpP;
read();
// handler.primitive(tmp);
return;
}
sb.clear();
readString2();
}
abstract protected void extractString(int start, int stop);
abstract protected int indexOf(char c, int pos);
protected void extractStringTrim(int start, int stop) {
extractString(start, stop);
xs = xs.trim();
}
}

View File

@ -0,0 +1,88 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* 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 static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_EOF;
import java.io.IOException;
import java.io.Reader;
/**
* Parser for JSON text. Please note that JSONParser is NOT thread-safe.
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
class JSONParserReader extends JSONParserStream {
private Reader in;
// len
public JSONParserReader(int permissiveMode) {
super(permissiveMode);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(Reader in) throws ParseException {
return parse(in, ContainerFactory.FACTORY_SIMPLE, ContentHandlerDumy.HANDLER);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(Reader in, ContainerFactory containerFactory) throws ParseException {
return parse(in, containerFactory, ContentHandlerDumy.HANDLER);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(Reader in, ContainerFactory containerFactory, ContentHandler handler) throws ParseException {
//
this.in = in;
this.pos = -1;
return super.parse(containerFactory, handler);
}
protected void read() throws IOException {
int i = in.read();
c = (i == -1) ? (char) EOI : (char) i;
pos++;
//
}
protected void readS() throws IOException {
sb.append(c);
int i = in.read();
if (i == -1) {
c = EOI;
} else {
c = (char) i;
pos++;
}
}
protected void readNoEnd() throws ParseException, IOException {
int i = in.read();
if (i == -1)
throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF");
c = (char) i;
//
}
}

View File

@ -0,0 +1,142 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* 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 static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_CHAR;
import static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_TOKEN;
import java.io.IOException;
/**
* Parser for JSON text. Please note that JSONParser is NOT thread-safe.
*
* @author Uriel Chemouni <uchemouni@gmail.com>
* @see JSONParserInputStream
* @see JSONParserReader
*/
abstract class JSONParserStream extends JSONParserBase {
// len
//
public JSONParserStream(int permissiveMode) {
super(permissiveMode);
}
protected void readNQString(boolean[] stop) throws IOException {
sb.clear();
skipNQString(stop);
xs = sb.toString().trim();
}
protected Object readNumber(boolean[] stop) throws ParseException, IOException {
sb.clear();
sb.append(c);// accept first char digit or -
read();
skipDigits();
// Integer digit
if (c != '.' && c != 'E' && c != 'e') {
skipSpace();
if (c >= 0 && c < MAX_STOP && !stop[c] && c != EOI) {
// convert string
skipNQString(stop);
xs = sb.toString().trim();
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
return xs;
}
xs = sb.toString().trim();
return parseNumber(xs);
}
// floating point
if (c == '.') {
sb.append(c);
read();
skipDigits();
}
if (c != 'E' && c != 'e') {
skipSpace();
if (c >= 0 && c < MAX_STOP && !stop[c] && c != EOI) {
// convert string
skipNQString(stop);
xs = sb.toString().trim();
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
return xs;
}
xs = sb.toString().trim();
return extractFloat();
}
sb.append('E');
read();
if (c == '+' || c == '-' || c >= '0' && c <= '9') {
sb.append(c);
read(); // skip first char
skipDigits();
skipSpace();
if (c >= 0 && c < MAX_STOP && !stop[c] && c != EOI) {
// convert string
skipNQString(stop);
xs = sb.toString().trim();
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
return xs;
}
xs = sb.toString().trim();
return extractFloat();
} else {
skipNQString(stop);
xs = sb.toString().trim();
if (!acceptNonQuote)
throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs);
if (!acceptLeadinZero)
checkLeadinZero();
return xs;
}
// throw new ParseException(pos - 1, ERROR_UNEXPECTED_CHAR, null);
}
protected void readString() throws ParseException, IOException {
if (!acceptSimpleQuote && c == '\'') {
if (acceptNonQuote) {
readNQString(stopAll);
return;
}
throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c);
}
sb.clear();
//
//
//
//
//
//
//
//
//
//
/* assert (c == '\"' || c == '\'') */
readString2();
}
//
//
//
//
//
//
//
//
}

View File

@ -0,0 +1,95 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* 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 static net.minidev.json.parser.ParseException.ERROR_UNEXPECTED_EOF;
/**
* Parser for JSON text. Please note that JSONParser is NOT thread-safe.
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
class JSONParserString extends JSONParserMemory {
private String in;
public JSONParserString(int permissiveMode) {
super(permissiveMode);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(String in) throws ParseException {
return parse(in, ContainerFactory.FACTORY_SIMPLE, ContentHandlerDumy.HANDLER);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(String in, ContainerFactory containerFactory) throws ParseException {
return parse(in, containerFactory, ContentHandlerDumy.HANDLER);
}
/**
* use to return Primitive Type, or String, Or JsonObject or JsonArray
* generated by a ContainerFactory
*/
public Object parse(String in, ContainerFactory containerFactory, ContentHandler handler) throws ParseException {
this.in = in;
this.len = in.length();
this.pos = -1;
return parse(containerFactory, handler);
}
protected void extractString(int beginIndex, int endIndex) {
xs = in.substring(beginIndex, endIndex);
}
protected int indexOf(char c, int pos) {
return in.indexOf(c, pos);
}
/**
* Read next char or END OF INPUT
*/
protected void read() {
if (++pos >= len)
this.c = EOI;
else
this.c = in.charAt(pos);
}
/**
* Same as read() in memory parsing
*/
protected void readS() {
if (++pos >= len)
this.c = EOI;
else
this.c = in.charAt(pos);
}
/**
* read data can not be EOI
*/
protected void readNoEnd() throws ParseException {
if (++pos >= len) {
this.c = EOI;
throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF");
} else
this.c = in.charAt(pos);
}
}

View File

@ -0,0 +1,125 @@
package net.minidev.json.parser;
/*
* Copyright 2011 JSON-SMART authors
*
* 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.
*/
/**
* ParseException explains why and where the error occurs in source JSON text.
*
* @author Uriel Chemouni <uchemouni@gmail.com>
*/
public class ParseException extends Exception {
private static final long serialVersionUID = 8879024178584091857L;
public static final int ERROR_UNEXPECTED_CHAR = 0;
public static final int ERROR_UNEXPECTED_TOKEN = 1;
public static final int ERROR_UNEXPECTED_EXCEPTION = 2;
public static final int ERROR_UNEXPECTED_EOF = 3;
public static final int ERROR_UNEXPECTED_UNICODE = 4;
public static final int ERROR_UNEXPECTED_DUPLICATE_KEY = 5;
public static final int ERROR_UNEXPECTED_LEADING_0 = 6;
private int errorType;
private Object unexpectedObject;
private int position;
public ParseException(int position, int errorType, Object unexpectedObject) {
super(toMessage(position, errorType, unexpectedObject));
this.position = position;
this.errorType = errorType;
this.unexpectedObject = unexpectedObject;
}
public ParseException(int position, Throwable cause) {
super(toMessage(position, ERROR_UNEXPECTED_EXCEPTION, cause), cause);
this.position = position;
this.errorType = ERROR_UNEXPECTED_EXCEPTION;
this.unexpectedObject = cause;
}
public int getErrorType() {
return errorType;
}
/**
* @return The character position (starting with 0) of the input where the
* error occurs.
*/
public int getPosition() {
return position;
}
/**
* @return One of the following base on the value of errorType:
* ERROR_UNEXPECTED_CHAR java.lang.Character ERROR_UNEXPECTED_TOKEN
* ERROR_UNEXPECTED_EXCEPTION java.lang.Exception
*/
public Object getUnexpectedObject() {
return unexpectedObject;
}
private static String toMessage(int position, int errorType, Object unexpectedObject) {
StringBuilder sb = new StringBuilder();
if (errorType == ERROR_UNEXPECTED_CHAR) {
sb.append("Unexpected character (");
sb.append(unexpectedObject);
sb.append(") at position ");
sb.append(position);
sb.append(".");
} else if (errorType == ERROR_UNEXPECTED_TOKEN) {
sb.append("Unexpected token ");
sb.append(unexpectedObject);
sb.append(" at position ");
sb.append(position);
sb.append(".");
} else if (errorType == ERROR_UNEXPECTED_EXCEPTION) {
sb.append("Unexpected exception ");
sb.append(unexpectedObject);
sb.append(" occur at position ");
sb.append(position);
sb.append(".");
} else if (errorType == ERROR_UNEXPECTED_EOF) {
sb.append("Unexpected End Of File position ");
sb.append(position);
sb.append(": ");
sb.append(unexpectedObject);
} else if (errorType == ERROR_UNEXPECTED_UNICODE) {
sb.append("Unexpected unicode escape sequence ");
sb.append(unexpectedObject);
sb.append(" at position ");
sb.append(position);
sb.append(".");
} else if (errorType == ERROR_UNEXPECTED_DUPLICATE_KEY) {
sb.append("Unexpected duplicate key:");
sb.append(unexpectedObject);
sb.append(" at position ");
sb.append(position);
sb.append(".");
} else if (errorType == ERROR_UNEXPECTED_LEADING_0) {
sb.append("Unexpected leading 0 in digit for token:");
sb.append(unexpectedObject);
sb.append(" at position ");
sb.append(position);
sb.append(".");
} else {
sb.append("Unkown error at position ");
sb.append(position);
sb.append(".");
}
return sb.toString();
}
}

View File

@ -0,0 +1,21 @@
package net.minidev.json.reader;
import java.io.IOException;
import net.minidev.json.JSONStyle;
import net.minidev.json.JSONValue;
public class ArrayWriter implements JsonWriterI<Object> {
public <E> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException {
compression.arrayStart(out);
boolean needSep = false;
for (Object o : ((Object[]) value)) {
if (needSep)
compression.objectNext(out);
else
needSep = true;
JSONValue.writeJSONString(o, out, compression);
}
compression.arrayStop(out);
}
}

View File

@ -0,0 +1,63 @@
package net.minidev.json.reader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import net.minidev.json.JSONStyle;
import net.minidev.json.JSONUtil;
public class BeansWriter implements JsonWriterI<Object> {
public <E> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException {
try {
Class<?> nextClass = value.getClass();
boolean needSep = false;
compression.objectStart(out);
while (nextClass != Object.class) {
Field[] fields = nextClass.getDeclaredFields();
for (Field field : fields) {
int m = field.getModifiers();
if ((m & (Modifier.STATIC | Modifier.TRANSIENT | Modifier.FINAL)) > 0)
continue;
Object v = null;
if ((m & Modifier.PUBLIC) > 0) {
v = field.get(value);
} else {
String g = JSONUtil.getGetterName(field.getName());
Method mtd = null;
try {
mtd = nextClass.getDeclaredMethod(g);
} catch (Exception e) {
}
if (mtd == null) {
Class<?> c2 = field.getType();
if (c2 == Boolean.TYPE || c2 == Boolean.class) {
g = JSONUtil.getIsName(field.getName());
mtd = nextClass.getDeclaredMethod(g);
}
}
if (mtd == null)
continue;
v = mtd.invoke(value);
}
if (v == null && compression.ignoreNull())
continue;
if (needSep)
compression.objectNext(out);
else
needSep = true;
String key = field.getName();
JsonWriter.writeJSONKV(key, v, out, compression);
// compression.objectElmStop(out);
}
nextClass = nextClass.getSuperclass();
}
compression.objectStop(out);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,395 @@
package net.minidev.json.reader;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.minidev.json.JSONAware;
import net.minidev.json.JSONAwareEx;
import net.minidev.json.JSONStreamAware;
import net.minidev.json.JSONStreamAwareEx;
import net.minidev.json.JSONStyle;
import net.minidev.json.JSONValue;
public class JsonWriter {
private ConcurrentHashMap<Class<?>, JsonWriterI<?>> data;
private LinkedList<WriterByInterface> writerInterfaces;
public JsonWriter() {
data = new ConcurrentHashMap<Class<?>, JsonWriterI<?>>();
writerInterfaces = new LinkedList<WriterByInterface>();
init();
}
static class WriterByInterface {
public Class<?> _interface;
public JsonWriterI<?> _writer;
public WriterByInterface(Class<?> _interface, JsonWriterI<?> _writer) {
this._interface = _interface;
this._writer = _writer;
}
}
/**
* try to find a Writer by Cheking implemented interface
* @param clazz class to serialize
* @return a Writer or null
*/
@SuppressWarnings("rawtypes")
public JsonWriterI getWriterByInterface(Class<?> clazz) {
for (WriterByInterface w : writerInterfaces) {
if (w._interface.isAssignableFrom(clazz))
return w._writer;
}
return null;
}
@SuppressWarnings("rawtypes")
public JsonWriterI getWrite(Class cls) {
return data.get(cls);
}
final static public JsonWriterI<JSONStreamAwareEx> JSONStreamAwareWriter = new JsonWriterI<JSONStreamAwareEx>() {
public <E extends JSONStreamAwareEx> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException {
value.writeJSONString(out);
}
};
final static public JsonWriterI<JSONStreamAwareEx> JSONStreamAwareExWriter = new JsonWriterI<JSONStreamAwareEx>() {
public <E extends JSONStreamAwareEx> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException {
value.writeJSONString(out, compression);
}
};
final static public JsonWriterI<JSONAwareEx> JSONJSONAwareExWriter = new JsonWriterI<JSONAwareEx>() {
public <E extends JSONAwareEx> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException {
out.append(value.toJSONString(compression));
}
};
final static public JsonWriterI<JSONAware> JSONJSONAwareWriter = new JsonWriterI<JSONAware>() {
public <E extends JSONAware> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException {
out.append(value.toJSONString());
}
};
final static public JsonWriterI<Iterable<? extends Object>> JSONIterableWriter = new JsonWriterI<Iterable<? extends Object>>() {
public <E extends Iterable<? extends Object>> void writeJSONString(E list, Appendable out, JSONStyle compression) throws IOException {
boolean first = true;
compression.arrayStart(out);
for (Object value : list) {
if (first) {
first = false;
compression.arrayfirstObject(out);
} else {
compression.arrayNextElm(out);
}
if (value == null)
out.append("null");
else
JSONValue.writeJSONString(value, out, compression);
compression.arrayObjectEnd(out);
}
compression.arrayStop(out);
}
};
final static public JsonWriterI<Enum<?>> EnumWriter = new JsonWriterI<Enum<?>>() {
public <E extends Enum<?>> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException {
@SuppressWarnings("rawtypes")
String s = ((Enum) value).name();
compression.writeString(out, s);
}
};
final static public JsonWriterI<Map<String, ? extends Object>> JSONMapWriter = new JsonWriterI<Map<String, ? extends Object>>() {
public <E extends Map<String, ? extends Object>> void writeJSONString(E map, Appendable out, JSONStyle compression) throws IOException {
boolean first = true;
compression.objectStart(out);
/**
* do not use <String, Object> to handle non String key maps
*/
for (Map.Entry<?, ?> entry : map.entrySet()) {
Object v = entry.getValue();
if (v == null && compression.ignoreNull())
continue;
if (first) {
compression.objectFirstStart(out);
first = false;
} else {
compression.objectNext(out);
}
JsonWriter.writeJSONKV(entry.getKey().toString(), v, out, compression);
// compression.objectElmStop(out);
}
compression.objectStop(out);
}
};
/**
* Json-Smart V2 Beans serialiser
*
* Based on ASM
*/
// final static public JsonWriterI<Object> beansWriterASM = new BeansWriterASM();
/**
* Json-Smart V1 Beans serialiser
*/
final static public JsonWriterI<Object> beansWriter = new BeansWriter();
/**
* Json-Smart ArrayWriterClass
*/
final static public JsonWriterI<Object> arrayWriter = new ArrayWriter();
/**
* ToString Writer
*/
final static public JsonWriterI<Object> toStringWriter = new JsonWriterI<Object>() {
public void writeJSONString(Object value, Appendable out, JSONStyle compression) throws IOException {
out.append(value.toString());
}
};
public void init() {
registerWriter(new JsonWriterI<String>() {
public void writeJSONString(String value, Appendable out, JSONStyle compression) throws IOException {
compression.writeString(out, (String) value);
}
}, String.class);
registerWriter(new JsonWriterI<Double>() {
public void writeJSONString(Double value, Appendable out, JSONStyle compression) throws IOException {
if (value.isInfinite())
out.append("null");
else
out.append(value.toString());
}
}, Double.class);
registerWriter(new JsonWriterI<Date>() {
public void writeJSONString(Date value, Appendable out, JSONStyle compression) throws IOException {
out.append('"');
JSONValue.escape(value.toString(), out, compression);
out.append('"');
}
}, Date.class);
registerWriter(new JsonWriterI<Float>() {
public void writeJSONString(Float value, Appendable out, JSONStyle compression) throws IOException {
if (value.isInfinite())
out.append("null");
else
out.append(value.toString());
}
}, Float.class);
registerWriter(toStringWriter, Integer.class, Long.class, Byte.class, Short.class, BigInteger.class, BigDecimal.class);
registerWriter(toStringWriter, Boolean.class);
/**
* Array
*/
registerWriter(new JsonWriterI<int[]>() {
public void writeJSONString(int[] value, Appendable out, JSONStyle compression) throws IOException {
boolean needSep = false;
compression.arrayStart(out);
for (int b : value) {
if (needSep)
compression.objectNext(out);
else
needSep = true;
out.append(Integer.toString(b));
}
compression.arrayStop(out);
}
}, int[].class);
registerWriter(new JsonWriterI<short[]>() {
public void writeJSONString(short[] value, Appendable out, JSONStyle compression) throws IOException {
boolean needSep = false;
compression.arrayStart(out);
for (short b : value) {
if (needSep)
compression.objectNext(out);
else
needSep = true;
out.append(Short.toString(b));
}
compression.arrayStop(out);
}
}, short[].class);
registerWriter(new JsonWriterI<long[]>() {
public void writeJSONString(long[] value, Appendable out, JSONStyle compression) throws IOException {
boolean needSep = false;
compression.arrayStart(out);
for (long b : value) {
if (needSep)
compression.objectNext(out);
else
needSep = true;
out.append(Long.toString(b));
}
compression.arrayStop(out);
}
}, long[].class);
registerWriter(new JsonWriterI<float[]>() {
public void writeJSONString(float[] value, Appendable out, JSONStyle compression) throws IOException {
boolean needSep = false;
compression.arrayStart(out);
for (float b : value) {
if (needSep)
compression.objectNext(out);
else
needSep = true;
out.append(Float.toString(b));
}
compression.arrayStop(out);
}
}, float[].class);
registerWriter(new JsonWriterI<double[]>() {
public void writeJSONString(double[] value, Appendable out, JSONStyle compression) throws IOException {
boolean needSep = false;
compression.arrayStart(out);
for (double b : value) {
if (needSep)
compression.objectNext(out);
else
needSep = true;
out.append(Double.toString(b));
}
compression.arrayStop(out);
}
}, double[].class);
registerWriter(new JsonWriterI<boolean[]>() {
public void writeJSONString(boolean[] value, Appendable out, JSONStyle compression) throws IOException {
boolean needSep = false;
compression.arrayStart(out);
for (boolean b : value) {
if (needSep)
compression.objectNext(out);
else
needSep = true;
out.append(Boolean.toString(b));
}
compression.arrayStop(out);
}
}, boolean[].class);
registerWriterInterface(JSONStreamAwareEx.class, JsonWriter.JSONStreamAwareExWriter);
registerWriterInterface(JSONStreamAware.class, JsonWriter.JSONStreamAwareWriter);
registerWriterInterface(JSONAwareEx.class, JsonWriter.JSONJSONAwareExWriter);
registerWriterInterface(JSONAware.class, JsonWriter.JSONJSONAwareWriter);
registerWriterInterface(Map.class, JsonWriter.JSONMapWriter);
registerWriterInterface(Iterable.class, JsonWriter.JSONIterableWriter);
registerWriterInterface(Enum.class, JsonWriter.EnumWriter);
registerWriterInterface(Number.class, JsonWriter.toStringWriter);
}
/**
* associate an Writer to a interface With Hi priority
* @param interFace interface to map
* @param writer writer Object
* @deprecated use registerWriterInterfaceFirst
*/
public void addInterfaceWriterFirst(Class<?> interFace, JsonWriterI<?> writer) {
registerWriterInterfaceFirst(interFace, writer);
}
/**
* associate an Writer to a interface With Low priority
* @param interFace interface to map
* @param writer writer Object
* @deprecated use registerWriterInterfaceLast
*/
public void addInterfaceWriterLast(Class<?> interFace, JsonWriterI<?> writer) {
registerWriterInterfaceLast(interFace, writer);
}
/**
* associate an Writer to a interface With Low priority
* @param interFace interface to map
* @param writer writer Object
*/
public void registerWriterInterfaceLast(Class<?> interFace, JsonWriterI<?> writer) {
writerInterfaces.addLast(new WriterByInterface(interFace, writer));
}
/**
* associate an Writer to a interface With Hi priority
* @param interFace interface to map
* @param writer writer Object
*/
public void registerWriterInterfaceFirst(Class<?> interFace, JsonWriterI<?> writer) {
writerInterfaces.addFirst(new WriterByInterface(interFace, writer));
}
/**
* an alias for registerWriterInterfaceLast
* @param interFace interface to map
* @param writer writer Object
*/
public void registerWriterInterface(Class<?> interFace, JsonWriterI<?> writer) {
registerWriterInterfaceLast(interFace, writer);
}
/**
* associate an Writer to a Class
* @param writer
* @param cls
*/
public <T> void registerWriter(JsonWriterI<T> writer, Class<?>... cls) {
for (Class<?> c : cls)
data.put(c, writer);
}
/**
* Write a Key : value entry to a stream
*/
public static void writeJSONKV(String key, Object value, Appendable out, JSONStyle compression) throws IOException {
if (key == null)
out.append("null");
else if (!compression.mustProtectKey(key))
out.append(key);
else {
out.append('"');
JSONValue.escape(key, out, compression);
out.append('"');
}
compression.objectEndOfKey(out);
if (value instanceof String) {
compression.writeString(out, (String) value);
} else
JSONValue.writeJSONString(value, out, compression);
compression.objectElmStop(out);
}
}

View File

@ -0,0 +1,9 @@
package net.minidev.json.reader;
import java.io.IOException;
import net.minidev.json.JSONStyle;
public interface JsonWriterI<T> {
public <E extends T> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException;
}

View File

@ -0,0 +1,777 @@
// 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.
package org.mindrot.jbcrypt;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
/**
* BCrypt implements OpenBSD-style Blowfish password hashing using
* the scheme described in "A Future-Adaptable Password Scheme" by
* Niels Provos and David Mazieres.
* <p>
* This password hashing system tries to thwart off-line password
* cracking using a computationally-intensive hashing algorithm,
* based on Bruce Schneier's Blowfish cipher. The work factor of
* the algorithm is parameterised, so it can be increased as
* computers get faster.
* <p>
* Usage is really simple. To hash a password for the first time,
* call the hashpw method with a random salt, like this:
* <p>
* <code>
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br />
* </code>
* <p>
* To check whether a plaintext password matches one that has been
* hashed previously, use the checkpw method:
* <p>
* <code>
* if (BCrypt.checkpw(candidate_password, stored_hash))<br />
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It matches");<br />
* else<br />
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It does not match");<br />
* </code>
* <p>
* The gensalt() method takes an optional parameter (log_rounds)
* that determines the computational complexity of the hashing:
* <p>
* <code>
* String strong_salt = BCrypt.gensalt(10)<br />
* String stronger_salt = BCrypt.gensalt(12)<br />
* </code>
* <p>
* The amount of work increases exponentially (2**log_rounds), so
* each increment is twice as much work. The default log_rounds is
* 10, and the valid range is 4 to 30.
*
* @author Damien Miller
* @version 0.2
*/
public class BCrypt {
// BCrypt parameters
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
private static final int BCRYPT_SALT_LEN = 16;
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
private static final int P_orig[] = {
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
};
private static final int S_orig[] = {
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
};
// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
// this "ciphertext", but it is really plaintext or an IV. We keep
// the name to make code comparison easier.
static private final int bf_crypt_ciphertext[] = {
0x4f727068, 0x65616e42, 0x65686f6c,
0x64657253, 0x63727944, 0x6f756274
};
// Table for Base64 encoding
static private final char base64_code[] = {
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
};
// Table for Base64 decoding
static private final byte index_64[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, -1, -1, -1, -1, -1
};
// Expanded Blowfish key
private int P[];
private int S[];
/**
* Encode a byte array using bcrypt's slightly-modified base64
* encoding scheme. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
*
* @param d the byte array to encode
* @param len the number of bytes to encode
* @return base64-encoded string
* @exception IllegalArgumentException if the length is invalid
*/
private static String encode_base64(byte d[], int len)
throws IllegalArgumentException {
int off = 0;
StringBuffer rs = new StringBuffer();
int c1, c2;
if (len <= 0 || len > d.length)
throw new IllegalArgumentException ("Invalid len");
while (off < len) {
c1 = d[off++] & 0xff;
rs.append(base64_code[(c1 >> 2) & 0x3f]);
c1 = (c1 & 0x03) << 4;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 4) & 0x0f;
rs.append(base64_code[c1 & 0x3f]);
c1 = (c2 & 0x0f) << 2;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 6) & 0x03;
rs.append(base64_code[c1 & 0x3f]);
rs.append(base64_code[c2 & 0x3f]);
}
return rs.toString();
}
/**
* Look up the 3 bits base64-encoded by the specified character,
* range-checking againt conversion table
* @param x the base64-encoded value
* @return the decoded value of x
*/
private static byte char64(char x) {
if ((int)x < 0 || (int)x > index_64.length)
return -1;
return index_64[(int)x];
}
/**
* Decode a string encoded using bcrypt's base64 scheme to a
* byte array. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
* @param s the string to decode
* @param maxolen the maximum number of bytes to decode
* @return an array containing the decoded bytes
* @throws IllegalArgumentException if maxolen is invalid
*/
private static byte[] decode_base64(String s, int maxolen)
throws IllegalArgumentException {
StringBuffer rs = new StringBuffer();
int off = 0, slen = s.length(), olen = 0;
byte ret[];
byte c1, c2, c3, c4, o;
if (maxolen <= 0)
throw new IllegalArgumentException ("Invalid maxolen");
while (off < slen - 1 && olen < maxolen) {
c1 = char64(s.charAt(off++));
c2 = char64(s.charAt(off++));
if (c1 == -1 || c2 == -1)
break;
o = (byte)(c1 << 2);
o |= (c2 & 0x30) >> 4;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c3 = char64(s.charAt(off++));
if (c3 == -1)
break;
o = (byte)((c2 & 0x0f) << 4);
o |= (c3 & 0x3c) >> 2;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c4 = char64(s.charAt(off++));
o = (byte)((c3 & 0x03) << 6);
o |= c4;
rs.append((char)o);
++olen;
}
ret = new byte[olen];
for (off = 0; off < olen; off++)
ret[off] = (byte)rs.charAt(off);
return ret;
}
/**
* Blowfish encipher a single 64-bit block encoded as
* two 32-bit halves
* @param lr an array containing the two 32-bit half blocks
* @param off the position in the array of the blocks
*/
private final void encipher(int lr[], int off) {
int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0];
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
// Feistel substitution on left word
n = S[(l >> 24) & 0xff];
n += S[0x100 | ((l >> 16) & 0xff)];
n ^= S[0x200 | ((l >> 8) & 0xff)];
n += S[0x300 | (l & 0xff)];
r ^= n ^ P[++i];
// Feistel substitution on right word
n = S[(r >> 24) & 0xff];
n += S[0x100 | ((r >> 16) & 0xff)];
n ^= S[0x200 | ((r >> 8) & 0xff)];
n += S[0x300 | (r & 0xff)];
l ^= n ^ P[++i];
}
lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
lr[off + 1] = l;
}
/**
* Cycically extract a word of key material
* @param data the string to extract the data from
* @param offp a "pointer" (as a one-entry array) to the
* current offset into data
* @return the next word of material from data
*/
private static int streamtoword(byte data[], int offp[]) {
int i;
int word = 0;
int off = offp[0];
for (i = 0; i < 4; i++) {
word = (word << 8) | (data[off] & 0xff);
off = (off + 1) % data.length;
}
offp[0] = off;
return word;
}
/**
* Initialise the Blowfish key schedule
*/
private void init_key() {
P = (int[])P_orig.clone();
S = (int[])S_orig.clone();
}
/**
* Key the Blowfish cipher
* @param key an array containing the key
*/
private void key(byte key[]) {
int i;
int koffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamtoword(key, koffp);
for (i = 0; i < plen; i += 2) {
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the "enhanced key schedule" step described by
* Provos and Mazieres in "A Future-Adaptable Password Scheme"
* http://www.openbsd.org/papers/bcrypt-paper.ps
* @param data salt information
* @param key password information
*/
private void ekskey(byte data[], byte key[]) {
int i;
int koffp[] = { 0 }, doffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamtoword(key, koffp);
for (i = 0; i < plen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the central password hashing step in the
* bcrypt scheme
* @param password the password to hash
* @param salt the binary salt to hash with the password
* @param log_rounds the binary logarithm of the number
* of rounds of hashing to apply
* @param cdata the plaintext to encrypt
* @return an array containing the binary hashed password
*/
public byte[] crypt_raw(byte password[], byte salt[], int log_rounds,
int cdata[]) {
int rounds, i, j;
int clen = cdata.length;
byte ret[];
if (log_rounds < 4 || log_rounds > 30)
throw new IllegalArgumentException ("Bad number of rounds");
rounds = 1 << log_rounds;
if (salt.length != BCRYPT_SALT_LEN)
throw new IllegalArgumentException ("Bad salt length");
init_key();
ekskey(salt, password);
for (i = 0; i != rounds; i++) {
key(password);
key(salt);
}
for (i = 0; i < 64; i++) {
for (j = 0; j < (clen >> 1); j++)
encipher(cdata, j << 1);
}
ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++) {
ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
ret[j++] = (byte)(cdata[i] & 0xff);
}
return ret;
}
/**
* Hash a password using the OpenBSD bcrypt scheme
* @param password the password to hash
* @param salt the salt to hash with (perhaps generated
* using BCrypt.gensalt)
* @return the hashed password
*/
public static String hashpw(String password, String salt) {
BCrypt B;
String real_salt;
byte passwordb[], saltb[], hashed[];
char minor = (char)0;
int rounds, off = 0;
StringBuffer rs = new StringBuffer();
if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
throw new IllegalArgumentException ("Invalid salt version");
if (salt.charAt(2) == '$')
off = 3;
else {
minor = salt.charAt(2);
if (minor != 'a' || salt.charAt(3) != '$')
throw new IllegalArgumentException ("Invalid salt revision");
off = 4;
}
// Extract number of rounds
if (salt.charAt(off + 2) > '$')
throw new IllegalArgumentException ("Missing salt rounds");
rounds = Integer.parseInt(salt.substring(off, off + 2));
real_salt = salt.substring(off + 3, off + 25);
try {
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
throw new AssertionError("UTF-8 is not supported");
}
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds,
(int[])bf_crypt_ciphertext.clone());
rs.append("$2");
if (minor >= 'a')
rs.append(minor);
rs.append("$");
if (rounds < 10)
rs.append("0");
if (rounds > 30) {
throw new IllegalArgumentException(
"rounds exceeds maximum (30)");
}
rs.append(Integer.toString(rounds));
rs.append("$");
rs.append(encode_base64(saltb, saltb.length));
rs.append(encode_base64(hashed,
bf_crypt_ciphertext.length * 4 - 1));
return rs.toString();
}
/**
* Generate a salt for use with the BCrypt.hashpw() method
* @param log_rounds the log2 of the number of rounds of
* hashing to apply - the work factor therefore increases as
* 2**log_rounds.
* @param random an instance of SecureRandom to use
* @return an encoded salt value
*/
public static String gensalt(int log_rounds, SecureRandom random) {
StringBuffer rs = new StringBuffer();
byte rnd[] = new byte[BCRYPT_SALT_LEN];
random.nextBytes(rnd);
rs.append("$2a$");
if (log_rounds < 10)
rs.append("0");
if (log_rounds > 30) {
throw new IllegalArgumentException(
"log_rounds exceeds maximum (30)");
}
rs.append(Integer.toString(log_rounds));
rs.append("$");
rs.append(encode_base64(rnd, rnd.length));
return rs.toString();
}
/**
* Generate a salt for use with the BCrypt.hashpw() method
* @param log_rounds the log2 of the number of rounds of
* hashing to apply - the work factor therefore increases as
* 2**log_rounds.
* @return an encoded salt value
*/
public static String gensalt(int log_rounds) {
return gensalt(log_rounds, new SecureRandom());
}
/**
* Generate a salt for use with the BCrypt.hashpw() method,
* selecting a reasonable default for the number of hashing
* rounds to apply
* @return an encoded salt value
*/
public static String gensalt() {
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
}
/**
* Check that a plaintext password matches a previously hashed
* one
* @param plaintext the plaintext password to verify
* @param hashed the previously-hashed password
* @return true if the passwords match, false otherwise
*/
public static boolean checkpw(String plaintext, String hashed) {
byte hashed_bytes[];
byte try_bytes[];
try {
String try_pw = hashpw(plaintext, hashed);
hashed_bytes = hashed.getBytes("UTF-8");
try_bytes = try_pw.getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
return false;
}
if (hashed_bytes.length != try_bytes.length)
return false;
byte ret = 0;
for (int i = 0; i < try_bytes.length; i++)
ret |= hashed_bytes[i] ^ try_bytes[i];
return ret == 0;
}
}

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- precompiled servlets -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.announce_jsp</servlet-name>
<url-pattern>/announce.php</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.announce_jsp</servlet-name>
<url-pattern>/announce</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.announce_jsp</servlet-name>
<url-pattern>/a</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.scrape_jsp</servlet-name>
<url-pattern>/scrape</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.scrape_jsp</servlet-name>
<url-pattern>/scrape.php</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -1,215 +0,0 @@
<%@page import="java.util.ArrayList" %><%@page import="java.util.Collections" %><%@page import="java.util.List" %><%@page import="java.util.Map" %><%@page import="java.util.HashMap" %><%@page import="net.i2p.data.Base64" %><%@page import="net.i2p.data.Destination" %><%@page import="net.i2p.zzzot.*" %><%@page import="org.klomp.snark.bencode.BEncoder" %><%
/*
* Above one-liner is so there is no whitespace -> IllegalStateException
*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
* USE CAUTION WHEN EDITING
* Trailing whitespace OR NEWLINE on the last line will cause
* IllegalStateExceptions !!!
*
*/
// would be nice to make these configurable
final int MAX_RESPONSES = 25;
final int INTERVAL = 27*60;
final boolean ALLOW_IP_MISMATCH = false;
// so the chars will turn into bytes correctly
request.setCharacterEncoding("ISO-8859-1");
java.io.OutputStream cout = response.getOutputStream();
response.setCharacterEncoding("ISO-8859-1");
response.setContentType("text/plain");
response.setHeader("Pragma", "no-cache");
String info_hash = request.getParameter("info_hash");
String peer_id = request.getParameter("peer_id");
// ignored
String port = request.getParameter("port");
// ignored
String uploaded = request.getParameter("uploaded");
// ignored
String downloaded = request.getParameter("downloaded");
String sleft = request.getParameter("left");
String event = request.getParameter("event");
String ip = request.getParameter("ip");
String numwant = request.getParameter("numwant");
// use to enforce destination
String him = request.getHeader("X-I2P-DestB64");
String xff = request.getHeader("X-Forwarded-For");
String xfs = request.getHeader("X-Forwarded-Server");
boolean fail = false;
String msg = "bad announce";
if (xff != null || xfs != null) {
fail = true;
msg = "Non-I2P access denied";
response.setStatus(403, msg);
}
if (info_hash == null && !fail) {
fail = true;
msg = "no info hash";
}
if (ip == null && !fail) {
fail = true;
msg = "no ip (dest)";
}
if (peer_id == null && !fail) {
fail = true;
msg = "no peer id";
}
InfoHash ih = null;
if (!fail) {
try {
ih = new InfoHash(info_hash);
} catch (Exception e) {
fail = true;
msg = "bad infohash " + e;
}
}
Destination d = null;
if (!fail) {
try {
if (ip.endsWith(".i2p"))
ip = ip.substring(0, ip.length() - 4);
d = new Destination(ip); // from b64 string
} catch (Exception e) {
fail = true;
msg = "bad dest " + e;
}
}
PID pid = null;
if (!fail) {
try {
pid = new PID(peer_id);
} catch (Exception e) {
fail = true;
msg = "bad peer id " + e;
}
}
// int params
// ignored
long up = 0;
try {
up = Long.parseLong(uploaded);
if (up < 0)
up = 0;
} catch (NumberFormatException nfe) {};
// ignored
long down = 0;
try {
down = Long.parseLong(downloaded);
if (down < 0)
down = 0;
} catch (NumberFormatException nfe) {};
int want = MAX_RESPONSES;
try {
want = Integer.parseInt(numwant);
if (want > MAX_RESPONSES)
want = MAX_RESPONSES;
else if (want < 0)
want = 0;
} catch (NumberFormatException nfe) {};
// spoof check
// if him == null, we are not using the I2P HTTP server tunnel, or something is wrong
boolean matchIP = ALLOW_IP_MISMATCH || him == null || ip.equals(him);
if (want <= 0 && (!matchIP) && !fail) {
fail = true;
msg = "ip mismatch";
}
long left = 0;
if (!"completed".equals(event)) {
try {
left = Long.parseLong(sleft);
if (left < 0)
left = 0;
} catch (NumberFormatException nfe) {};
}
Torrents torrents = ZzzOTController.getTorrents();
Map<String, Object> m = new HashMap();
if (fail) {
m.put("failure reason", msg);
} else if ("stopped".equals(event)) {
Peers peers = torrents.get(ih);
if (matchIP && peers != null)
peers.remove(pid);
m.put("interval", Integer.valueOf(INTERVAL));
} else {
Peers peers = torrents.get(ih);
if (peers == null) {
peers = new Peers();
Peers p2 = torrents.putIfAbsent(ih, peers);
if (p2 != null)
peers = p2;
}
// fixme same peer id, different dest
Peer p = peers.get(pid);
if (p == null) {
p = new Peer(pid.getData(), d);
// don't add if spoofed
if (matchIP) {
Peer p2 = peers.putIfAbsent(pid, p);
if (p2 != null)
p = p2;
}
}
// don't update if spoofed
if (matchIP)
p.setLeft(left);
m.put("interval", Integer.valueOf(INTERVAL));
int size = peers.size();
int seeds = peers.countSeeds();
m.put("complete", Integer.valueOf(seeds));
m.put("incomplete", Integer.valueOf(size - seeds));
if (want <= 0) {
// snark < 0.7.13 always wants a list
m.put("peers", java.util.Collections.EMPTY_LIST);
} else {
List<Peer> peerlist = new ArrayList(peers.values());
peerlist.remove(p); // them
if (want < size - 1) {
Collections.shuffle(peerlist);
m.put("peers", peerlist.subList(0, want));
} else {
m.put("peers", peerlist);
}
}
}
BEncoder.bencode(m, cout);
/*
* Remove the newline on the last line or
* it will generate an IllegalStateException
*
*/
%>

View File

@ -1,10 +0,0 @@
<html>
<head>
<meta http-equiv="refresh" content="0;url=index.jsp" />
<title>zzzot</title>
</head>
<body>
<a href="index.jsp">Enter</a>
</body>
</html>

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