Compare commits
146 Commits
zzzot-0.3
...
i2pcontrol
Author | SHA1 | Date | |
---|---|---|---|
2e34ffcedc | |||
cf0d59ab61 | |||
c5d0562493 | |||
0085d47d16 | |||
7ec4f0c52e | |||
e40bb3a2f4 | |||
cb535188b2 | |||
273f91b3d4 | |||
de334aa52e | |||
72e8463717 | |||
7dad6999a7 | |||
55199e666a | |||
d5c2f6b8e3 | |||
e58129e46a | |||
6aff0344a5 | |||
22aad9836f | |||
2c9042ef7d | |||
16a5e8b884 | |||
9edc0ae53e | |||
37251cbcb3 | |||
44263d9b26 | |||
04ac5b0bef | |||
af5d980154 | |||
442fb6aef4 | |||
be523f5e35 | |||
2048781b8f | |||
08b125f5e4 | |||
df5a9a60a5 | |||
03bf21ee52 | |||
82d294b3a7 | |||
26792beb5a | |||
869d302b28 | |||
f9b3dd8aa4 | |||
c93203b914 | |||
1810e89a45 | |||
45bc108e94 | |||
27fa1b31b0 | |||
b2f23cdaad | |||
44973255d8 | |||
18da573825 | |||
969f6328fb | |||
0b011eaa72 | |||
a2928361d9 | |||
fcd2a54754 | |||
8a39b639b9 | |||
88837ef572 | |||
b9f1e3f4df | |||
66d3ea28e0 | |||
eecdde0548 | |||
72aad1872e | |||
1a25505425 | |||
58f1b3cfa2 | |||
7936f753bc | |||
0cb0a307e6 | |||
b17dcc6198 | |||
9f2601d3ac | |||
81e99b319b | |||
4c99ab0402 | |||
bf822dad13 | |||
3d2c3aeb50 | |||
bd0b7ebbb2 | |||
83bfdf00a2 | |||
391c84cf76 | |||
b8512b66d9 | |||
90155bd60c | |||
c721c9ab48 | |||
8c2e870068 | |||
1eb1769ef4 | |||
9e2a8b0c1b | |||
64ba16d4f3 | |||
9673965345 | |||
541d9ad5da | |||
da4409c539 | |||
e6843e2781 | |||
b043b4b589 | |||
0ea28c8b5b | |||
434042a9d1 | |||
7468ac3d14 | |||
f57fa701d0 | |||
e1d292fff0 | |||
6bf929b62a | |||
7c90ced960 | |||
58a91be062 | |||
b897fc7e0f | |||
4a347ea086 | |||
2541c66292 | |||
d8e9639d0f | |||
32820a06ab | |||
1d263da0df | |||
d57af170b3 | |||
1a6c1f56e8 | |||
70afd93f2f | |||
563e85fe70 | |||
9e61a99c6d | |||
d54dce8c25 | |||
92739a725f | |||
fdfb187bde | |||
76fd7d3130 | |||
b072978cfb | |||
48617ebf19 | |||
8edfcc9c02 | |||
7a26124025 | |||
959fe71f32 | |||
a70177ec64 | |||
a6ae4c8405 | |||
fadfd0d0cf | |||
3e861bb749 | |||
8fa7c75e24 | |||
3fa499b198 | |||
48d1ff2915 | |||
50667e8196 | |||
7c0553c311 | |||
07f2db8d15 | |||
b4d71d1bc9 | |||
ffaabdd9af | |||
27d3df3403 | |||
65a8435141 | |||
acc97b01c9 | |||
ad4e96cf4c | |||
5a0b34889d | |||
be4eb2ed39 | |||
8f56d68bb7 | |||
38468b278e | |||
868f990f13 | |||
2ffc33eda0 | |||
435a667acb | |||
571240e349 | |||
4e05b4df06 | |||
68e42522f8 | |||
1139824349 | |||
87ee709eac | |||
7e3ca87c14 | |||
7d803faaf8 | |||
c6058b0ee9 | |||
3eec980855 | |||
275d3d707f | |||
dac6c2a26e | |||
1fcfb176ba | |||
7d36216ab0 | |||
153bb23f0c | |||
433f786a50 | |||
f354dd9c30 | |||
4f80d19e3a | |||
c636af7ead | |||
6e3b85ac97 | |||
48687daccc |
18
LICENSE-jBCrypt.txt
Normal file
18
LICENSE-jBCrypt.txt
Normal 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.
|
||||
*/
|
||||
|
205
LICENSE.txt
205
LICENSE.txt
@ -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.
|
||||
|
||||
|
52
README.txt
52
README.txt
@ -1,42 +1,18 @@
|
||||
This is a very simple in-memory open tracker, wrapped into an I2P plugin.
|
||||
I2PControl
|
||||
API for remote control of I2P via a JSON-RPC library.
|
||||
|
||||
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.
|
||||
|
||||
Valid announce URLs:
|
||||
/a
|
||||
/announce
|
||||
/announce.jsp
|
||||
/announce.php
|
||||
/tracker/a
|
||||
/tracker/announce
|
||||
/tracker/announce.jsp
|
||||
/tracker/announce.php
|
||||
|
||||
Valid scrape URLs:
|
||||
/scrape
|
||||
/scrape.jsp
|
||||
/scrape.php
|
||||
/tracker/scrape
|
||||
/tracker/scrape.jsp
|
||||
/tracker/scrape.php
|
||||
|
||||
The tracker also responds to seedless queries at
|
||||
/Seedless/index.jsp
|
||||
|
||||
You may use the rest of the eepsite for other purposes, for example you
|
||||
may place torrent files in eepsite/docroot/torrents.
|
||||
Version 2 API proposal:
|
||||
http://i2p-projekt.i2p/spec/proposals/118-i2pcontrol-api-2
|
||||
https://geti2p.net/spec/proposals/118-i2pcontrol-api-2
|
||||
|
23
TODO.txt
23
TODO.txt
@ -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
102
build.xml
@ -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>
|
||||
|
@ -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>
|
@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow:
|
@ -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: <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>
|
@ -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>
|
@ -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
18
scripts/format.sh
Executable 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
361
scripts/i2pcontrol.py
Executable 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()
|
@ -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
|
@ -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
|
||||
|
@ -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
15
src/.classpath
Normal 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>
|
17
src/.project
Normal file
17
src/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?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>
|
||||
</projectDescription>
|
@ -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="<!-- precompiled servlets -->" 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
10
src/java/.classpath
Normal 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>
|
17
src/java/.project
Normal file
17
src/java/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?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>
|
||||
</projectDescription>
|
274
src/java/com/thetransactioncompany/jsonrpc2/JSONRPC2Error.java
Normal file
274
src/java/com/thetransactioncompany/jsonrpc2/JSONRPC2Error.java
Normal 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();
|
||||
}
|
||||
}
|
251
src/java/com/thetransactioncompany/jsonrpc2/JSONRPC2Message.java
Normal file
251
src/java/com/thetransactioncompany/jsonrpc2/JSONRPC2Message.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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<Object> params = new Vector<Object>();
|
||||
* 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<Object>} for positional
|
||||
* (JSON array), {@code Map<String,Object>} 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<Object>}. For named (JSON object)
|
||||
* pass a {@code Map<String,Object>}. 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
654
src/java/com/thetransactioncompany/jsonrpc2/JSONRPC2Parser.java
Normal file
654
src/java/com/thetransactioncompany/jsonrpc2/JSONRPC2Parser.java
Normal 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;
|
||||
}
|
||||
}
|
507
src/java/com/thetransactioncompany/jsonrpc2/JSONRPC2Request.java
Normal file
507
src/java/com/thetransactioncompany/jsonrpc2/JSONRPC2Request.java
Normal 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<String,Object> params = new HashMap<String,Object>();
|
||||
* 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<Object>} for positional
|
||||
* (JSON array), {@code Map<String,Object>} 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<Object>}. For named (JSON object)
|
||||
* pass a {@code Map<String,Object>}. 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
@ -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
@ -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;
|
||||
|
||||
|
127
src/java/net/i2p/i2pcontrol/HostCheckHandler.java
Normal file
127
src/java/net/i2p/i2pcontrol/HostCheckHandler.java
Normal 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;
|
||||
}
|
||||
}
|
403
src/java/net/i2p/i2pcontrol/I2PControlController.java
Normal file
403
src/java/net/i2p/i2pcontrol/I2PControlController.java
Normal 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;
|
||||
}
|
||||
}
|
20
src/java/net/i2p/i2pcontrol/I2PControlVersion.java
Normal file
20
src/java/net/i2p/i2pcontrol/I2PControlVersion.java
Normal 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);
|
||||
}
|
||||
}
|
50
src/java/net/i2p/i2pcontrol/security/AuthToken.java
Normal file
50
src/java/net/i2p/i2pcontrol/security/AuthToken.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
218
src/java/net/i2p/i2pcontrol/security/KeyStoreProvider.java
Normal file
218
src/java/net/i2p/i2pcontrol/security/KeyStoreProvider.java
Normal 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();
|
||||
}
|
||||
}
|
252
src/java/net/i2p/i2pcontrol/security/SecurityManager.java
Normal file
252
src/java/net/i2p/i2pcontrol/security/SecurityManager.java
Normal 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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
245
src/java/net/i2p/i2pcontrol/servlets/JSONRPC2Servlet.java
Normal file
245
src/java/net/i2p/i2pcontrol/servlets/JSONRPC2Servlet.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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 string
|
||||
*/
|
||||
public synchronized void setConf(String settingName, String str) {
|
||||
stringConfigurations.put(settingName, str);
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific boolean setting
|
||||
* @param settingName
|
||||
* @param boolean
|
||||
*/
|
||||
public synchronized void setConf(String settingName, boolean bool) {
|
||||
booleanConfigurations.put(settingName, bool);
|
||||
_changed = true;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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" } );
|
||||
}
|
||||
}
|
||||
}
|
121
src/java/net/minidev/json/JSONArray.java
Normal file
121
src/java/net/minidev/json/JSONArray.java
Normal 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);
|
||||
}
|
||||
}
|
29
src/java/net/minidev/json/JSONAware.java
Normal file
29
src/java/net/minidev/json/JSONAware.java
Normal 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();
|
||||
}
|
32
src/java/net/minidev/json/JSONAwareEx.java
Normal file
32
src/java/net/minidev/json/JSONAwareEx.java
Normal 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);
|
||||
}
|
217
src/java/net/minidev/json/JSONObject.java
Normal file
217
src/java/net/minidev/json/JSONObject.java
Normal 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);
|
||||
}
|
||||
}
|
31
src/java/net/minidev/json/JSONStreamAware.java
Normal file
31
src/java/net/minidev/json/JSONStreamAware.java
Normal 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;
|
||||
}
|
31
src/java/net/minidev/json/JSONStreamAwareEx.java
Normal file
31
src/java/net/minidev/json/JSONStreamAwareEx.java
Normal 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;
|
||||
}
|
212
src/java/net/minidev/json/JSONStyle.java
Normal file
212
src/java/net/minidev/json/JSONStyle.java
Normal 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 {
|
||||
}
|
||||
|
||||
}
|
69
src/java/net/minidev/json/JSONUtil.java
Normal file
69
src/java/net/minidev/json/JSONUtil.java
Normal 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);
|
||||
}
|
||||
}
|
595
src/java/net/minidev/json/JSONValue.java
Normal file
595
src/java/net/minidev/json/JSONValue.java
Normal 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);
|
||||
}
|
||||
}
|
323
src/java/net/minidev/json/JStylerObj.java
Normal file
323
src/java/net/minidev/json/JStylerObj.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
70
src/java/net/minidev/json/parser/ContainerFactory.java
Normal file
70
src/java/net/minidev/json/parser/ContainerFactory.java
Normal 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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
123
src/java/net/minidev/json/parser/ContentHandler.java
Normal file
123
src/java/net/minidev/json/parser/ContentHandler.java
Normal 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;
|
||||
|
||||
}
|
131
src/java/net/minidev/json/parser/ContentHandlerCompressor.java
Normal file
131
src/java/net/minidev/json/parser/ContentHandlerCompressor.java
Normal 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;
|
||||
}
|
||||
}
|
65
src/java/net/minidev/json/parser/ContentHandlerDumy.java
Normal file
65
src/java/net/minidev/json/parser/ContentHandlerDumy.java
Normal 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;
|
||||
}
|
||||
}
|
81
src/java/net/minidev/json/parser/FakeContainerFactory.java
Normal file
81
src/java/net/minidev/json/parser/FakeContainerFactory.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
266
src/java/net/minidev/json/parser/JSONParser.java
Normal file
266
src/java/net/minidev/json/parser/JSONParser.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
666
src/java/net/minidev/json/parser/JSONParserBase.java
Normal file
666
src/java/net/minidev/json/parser/JSONParserBase.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
110
src/java/net/minidev/json/parser/JSONParserByteArray.java
Normal file
110
src/java/net/minidev/json/parser/JSONParserByteArray.java
Normal 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];
|
||||
}
|
||||
|
||||
}
|
61
src/java/net/minidev/json/parser/JSONParserInputStream.java
Normal file
61
src/java/net/minidev/json/parser/JSONParserInputStream.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
143
src/java/net/minidev/json/parser/JSONParserMemory.java
Normal file
143
src/java/net/minidev/json/parser/JSONParserMemory.java
Normal 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();
|
||||
}
|
||||
}
|
88
src/java/net/minidev/json/parser/JSONParserReader.java
Normal file
88
src/java/net/minidev/json/parser/JSONParserReader.java
Normal 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;
|
||||
//
|
||||
}
|
||||
}
|
142
src/java/net/minidev/json/parser/JSONParserStream.java
Normal file
142
src/java/net/minidev/json/parser/JSONParserStream.java
Normal 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();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
}
|
95
src/java/net/minidev/json/parser/JSONParserString.java
Normal file
95
src/java/net/minidev/json/parser/JSONParserString.java
Normal 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);
|
||||
}
|
||||
}
|
125
src/java/net/minidev/json/parser/ParseException.java
Normal file
125
src/java/net/minidev/json/parser/ParseException.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
21
src/java/net/minidev/json/reader/ArrayWriter.java
Normal file
21
src/java/net/minidev/json/reader/ArrayWriter.java
Normal 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);
|
||||
}
|
||||
}
|
63
src/java/net/minidev/json/reader/BeansWriter.java
Normal file
63
src/java/net/minidev/json/reader/BeansWriter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
395
src/java/net/minidev/json/reader/JsonWriter.java
Normal file
395
src/java/net/minidev/json/reader/JsonWriter.java
Normal 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);
|
||||
}
|
||||
}
|
9
src/java/net/minidev/json/reader/JsonWriterI.java
Normal file
9
src/java/net/minidev/json/reader/JsonWriterI.java
Normal 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;
|
||||
}
|
777
src/java/org/mindrot/jbcrypt/BCrypt.java
Normal file
777
src/java/org/mindrot/jbcrypt/BCrypt.java
Normal 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 />
|
||||
* System.out.println("It matches");<br />
|
||||
* else<br />
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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>
|
@ -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
|
||||
*
|
||||
*/
|
||||
%>
|
@ -1,10 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url=index.jsp" />
|
||||
<title>zzzot</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="index.jsp">Enter</a>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,14 +0,0 @@
|
||||
<%@page import="net.i2p.zzzot.ZzzOTController" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>ZzzOT</title>
|
||||
</head><body style="background-color: #000; color: #c30; font-size: 400%;">
|
||||
<p>
|
||||
zzzot
|
||||
<p>
|
||||
<table cellspacing="8">
|
||||
<tr><td>Torrents:<td align="right"><%=ZzzOTController.getTorrents().size()%>
|
||||
<tr><td>Peers:<td align="right"><%=ZzzOTController.getTorrents().countPeers()%>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -1,93 +0,0 @@
|
||||
<%@page import="java.util.ArrayList" %><%@page import="java.util.List" %><%@page import="java.util.Map" %><%@page import="java.util.HashMap" %><%@page import="net.i2p.zzzot.*" %><%@page import="org.klomp.snark.bencode.BEncoder" %><%
|
||||
|
||||
/*
|
||||
* 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 !!!
|
||||
*
|
||||
*/
|
||||
// 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 xff = request.getHeader("X-Forwarded-For");
|
||||
String xfs = request.getHeader("X-Forwarded-Server");
|
||||
|
||||
boolean fail = false;
|
||||
String msg = "bad";
|
||||
|
||||
if (xff != null || xfs != null) {
|
||||
fail = true;
|
||||
msg = "Non-I2P access denied";
|
||||
response.setStatus(403, msg);
|
||||
}
|
||||
|
||||
boolean all = info_hash == null;
|
||||
|
||||
InfoHash ih = null;
|
||||
if ((!all) && !fail) {
|
||||
try {
|
||||
ih = new InfoHash(info_hash);
|
||||
} catch (Exception e) {
|
||||
fail = true;
|
||||
msg = "bad infohash " + e;
|
||||
}
|
||||
}
|
||||
|
||||
Torrents torrents = ZzzOTController.getTorrents();
|
||||
|
||||
// build 3-level dictionary
|
||||
Map<String, Object> m = new HashMap();
|
||||
if (fail) {
|
||||
m.put("failure reason", msg);
|
||||
} else {
|
||||
List<InfoHash> ihList = new ArrayList();
|
||||
if (all)
|
||||
ihList.addAll(torrents.keySet());
|
||||
else
|
||||
ihList.add(ih);
|
||||
Map<String, Map> files = new HashMap();
|
||||
for (InfoHash ihash : ihList) {
|
||||
Peers peers = torrents.get(ihash);
|
||||
if (peers == null)
|
||||
continue;
|
||||
Map<String, Object> dict = new HashMap();
|
||||
int size = peers.size();
|
||||
int seeds = peers.countSeeds();
|
||||
dict.put("complete", Integer.valueOf(seeds));
|
||||
dict.put("incomplete", Integer.valueOf(size - seeds));
|
||||
dict.put("downloaded", Integer.valueOf(0));
|
||||
files.put(new String(ihash.getData(), "ISO-8859-1"), dict);
|
||||
}
|
||||
m.put("files", files);
|
||||
}
|
||||
BEncoder.bencode(m, cout);
|
||||
|
||||
/*
|
||||
* Remove the newline on the last line or
|
||||
* it will generate an IllegalStateException
|
||||
*
|
||||
*/
|
||||
%>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user