This commit is contained in:
2026-07-22 17:42:32 -03:00
commit 3d36f2421e
645 changed files with 163624 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
.gradle/
.idea/
**/build/
**/.cxx/
local.properties
*.iml
captures/
.externalNativeBuild/
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+107
View File
@@ -0,0 +1,107 @@
# DragonSSH XHTTP Public Example
A deliberately small Android reference client for the DragonSSH XHTTP-SSH transport.
It contains one home screen, the XHTTP split-stream transport, SSH password authentication,
an Android `VpnService`, a local SOCKS relay, and BadVPN tun2socks with UDPGW.
## Public scope
Included:
- XHTTP over TLS/HTTP/2
- streamed downlink: `GET {path}/{sessionId}`
- ordered packet uplink: `POST {path}/{sessionId}/{sequence}`
- simple Server, Port, SNI, XHTTP Host, and XHTTP Path configuration
- SSH username/password authentication
- silent SSH/XHTTP transport reconnect while the Android VPN remains established
- configurable VPN DNS resolvers
- UDPGW forwarding through BadVPN tun2socks
- separate Home, Settings, VPN/UDPGW, and Logs screens
Not included:
- paid application screens, branding, panel URLs, certificates, signing fingerprints, or secrets
- remote/online configuration
- config import/export or config protection
- Xray, DNSTT/SlowDNS, SSL payload modes, ads, analytics, accounts, profiles, or reseller features
- production server addresses or credentials
The large `com.trilead`, `com.jcraft`, `org.spongycastle`, and `badvpn` trees are vendored
transport dependencies. The native build compiles only `libancillary`, the socket-protection
bridge, and the UDPGW-enabled `tun2socks` executable.
## Dependency note
The ConnectBot bcrypt fork is resolved from Maven Central as `org.connectbot:jbcrypt:1.0.2`. The former relocated coordinate `org.connectbot.jbcrypt:jbcrypt:1.0.0` is intentionally not used.
## Build fix in 1.0.7
- Corrected the password storage notice so it compiles with Android AAPT2.
- Updated the password storage notice and its resource reference.
- No tunnel behavior or saved-password behavior changed.
## Build
1. Open the root folder in Android Studio.
2. Install Android SDK 36 and an Android NDK compatible with Gradle's `ndkBuild` integration.
3. Let Android Studio create `local.properties` with your SDK path.
4. Build and install the `app` module.
The minimum Android version is API 26. Four ABIs are enabled: ARMv7, ARM64, x86, and x86_64.
## System-bar behavior
The sample handles Android edge-to-edge window insets explicitly. The configuration screen adds
safe padding for the status bar, display cutouts, gesture navigation, three-button navigation,
and the on-screen keyboard, so controls remain visible on Android 15/16 and earlier versions.
## Configuration mapping
The app uses the same names as SocksRevive VOID:
- **Server**: the XHTTP proxy IP address or hostname.
- **Port**: the XHTTP listener port, normally `443`.
- **User name / Password**: SSH credentials carried inside the XHTTP stream.
- **SNI**: the hostname sent during the TLS handshake.
- **XHTTP Host**: the CDN or reverse-proxy routing hostname.
- **XHTTP Path**: the base path, such as `/ssh`.
- **XHTTP TLS**: enables TLS and HTTP/2 for the XHTTP connection.
There is no separate SSH destination. **Server is the XHTTP proxy**, and the XHTTP session exposes the SSH byte stream used for authentication.
## TLS certificate behavior
To match the original SocksRevive VOID implementation, XHTTP TLS intentionally accepts every server certificate and skips hostname verification. Self-signed, expired, mismatched, and otherwise untrusted certificates are accepted. SNI is still sent for CDN/fronting routing, but it is not used to validate the certificate.
This is intentionally insecure against man-in-the-middle attacks and is included only because it is required by this transport example. There is no certificate-validation toggle in the sample.
## Log viewer
The sample log is structured rather than rendered as one large text block:
- timestamp per entry
- INFO, WARNING, ERROR, DEBUG, or VERBOSE badge
- color-coded rows
- DEBUG/VERBOSE filter
- follow-newest toggle
- copy and clear actions
Passwords are never written to the log.
## Protocol behavior
The SSH library receives a virtual duplex socket:
- its input stream is the body of one long-lived XHTTP GET response;
- its output stream is buffered and sent as strictly ordered POST requests;
- only one POST is in flight at a time, preserving sequence order;
- bounded buffering applies backpressure instead of creating an unbounded HTTP request queue;
- control sockets are excluded from the Android VPN to prevent routing loops.
See [`docs/XHTTP_PROTOCOL.md`](docs/XHTTP_PROTOCOL.md) for the request contract.
## License
The combined sample is distributed under GPL-3.0-or-later because its VPN layer contains
GPL-covered source. Third-party components retain their own notices; see
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) and license files kept beside native sources.
+16
View File
@@ -0,0 +1,16 @@
# Third-party notices
This repository includes or derives from the following components. Their original copyright
headers and license files remain in the source tree where available.
- **Psiphon Android tunnel/VPN code** — GPL-3.0-or-later. Relevant source files retain the original notices.
- **Trilead SSH-2 for Java** — BSD-style license; copyright Trilead AG, ETH Zurich, and contributors.
- **JZlib** — BSD-style license; copyright JCraft, Inc. and contributors.
- **Bouncy Castle / Spongy Castle-derived cryptographic classes** — permissive Bouncy Castle license; original headers retained.
- **BadVPN tun2socks** — BSD 3-Clause-style license. Full text: `service/src/main/jni/badvpn/COPYING`.
- **lwIP** — BSD-style license. Full text: `service/src/main/jni/badvpn/lwip/COPYING`.
- **libancillary** — BSD-style license. Full text: `service/src/main/jni/libancillary/COPYING`.
- **Material icons and AndroidX/Material libraries** — respective Apache-2.0 and upstream licenses.
The top-level GPL license governs the combined sample without replacing the independent notices
and attribution requirements of these components.
+40
View File
@@ -0,0 +1,40 @@
plugins {
id 'com.android.application'
}
android {
namespace 'com.dragonssh.xhttpdemo'
compileSdk 36
defaultConfig {
applicationId 'com.dragonssh.xhttpdemo'
minSdk 26
targetSdk 36
versionCode 8
versionName '1.0.7'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
// tun2socks is a native executable that must be run from nativeLibraryDir,
// which is the only app-owned location Android lets you exec from (API 29+
// blocks exec out of the writable data dir under SELinux). Legacy packaging
// extracts libtun2socks.so to nativeLibraryDir at install so CustomNativeLoader
// can run it directly. Without this the loader falls back to files/libtun2socks
// and exec fails with "error=13, Permission denied".
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
}
}
dependencies {
implementation project(':service')
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.google.android.material:material:1.13.0'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
}
+1
View File
@@ -0,0 +1 @@
# Public example: no custom obfuscation rules required.
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application
android:name=".DemoApplication"
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.DragonSshXHttpDemo"
android:usesCleartextTraffic="true">
<activity
android:name=".LogsActivity"
android:exported="false" />
<activity
android:name=".VpnSettingsActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".SettingsActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,44 @@
package com.dragonssh.xhttpdemo;
final class ConfigUtils {
private ConfigUtils() { }
static String normalizePath(String path) {
if (path == null || path.trim().isEmpty()) return "";
String normalized = path.trim();
if (!normalized.startsWith("/")) normalized = "/" + normalized;
while (normalized.length() > 1 && normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
return normalized;
}
static boolean isValidPort(String value) {
try {
int port = Integer.parseInt(value);
return port >= 1 && port <= 65535;
} catch (NumberFormatException e) {
return false;
}
}
static boolean isHostPort(String value) {
if (value == null || value.trim().isEmpty()) return false;
String clean = value.trim();
int separator = clean.lastIndexOf(':');
if (separator <= 0 || separator == clean.length() - 1) return false;
return isValidPort(clean.substring(separator + 1));
}
static String displayValue(String value) {
return value == null || value.trim().isEmpty() ? "" : value.trim();
}
static String formatHostPort(String host, String port) {
String cleanHost = displayValue(host);
if (cleanHost.contains(":") && !cleanHost.startsWith("[") && !"".equals(cleanHost)) {
cleanHost = "[" + cleanHost + "]";
}
return cleanHost + ":" + displayValue(port);
}
}
@@ -0,0 +1,43 @@
package com.dragonssh.xhttpdemo;
import android.app.Application;
import android.content.SharedPreferences;
import com.dragonssh.xhttpdemo.core.XHttpSshCore;
import com.dragonssh.xhttpdemo.core.config.Settings;
public final class DemoApplication extends Application {
private static final String BOOTSTRAP_PREFS = "demo_bootstrap";
private static final String DEFAULTS_CREATED = "defaults_created";
private volatile String savedPassword = "";
private Settings settings;
@Override
public void onCreate() {
super.onCreate();
SharedPreferences prefs = getSharedPreferences(BOOTSTRAP_PREFS, MODE_PRIVATE);
if (!prefs.getBoolean(DEFAULTS_CREATED, false)) {
Settings.setDefaultConfig(this);
prefs.edit().putBoolean(DEFAULTS_CREATED, true).apply();
}
settings = new Settings(this);
savedPassword = settings.getPrivString(Settings.SSH_PASSWORD_KEY);
XHttpSshCore.init(this);
}
String getSessionPassword() {
return savedPassword;
}
void setSessionPassword(String password) {
savedPassword = password == null ? "" : password;
settings.getPrefsPrivate().edit()
.putString(Settings.SSH_PASSWORD_KEY, savedPassword)
.apply();
}
boolean hasSessionPassword() {
return savedPassword != null && !savedPassword.isEmpty();
}
}
@@ -0,0 +1,216 @@
package com.dragonssh.xhttpdemo;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.dragonssh.xhttpdemo.core.logger.LogItem;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import com.google.android.material.card.MaterialCardView;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
final class LogAdapter extends RecyclerView.Adapter<LogAdapter.LogViewHolder> {
private static final int MAX_STORED_ENTRIES = 500;
private static final int MAX_VISIBLE_ENTRIES = 250;
private final Context context;
private final List<Entry> allEntries = new ArrayList<>();
private final List<Entry> visibleEntries = new ArrayList<>();
private final SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
private boolean showDebug;
LogAdapter(Context context) {
this.context = context.getApplicationContext();
}
void replace(LogItem[] items) {
allEntries.clear();
if (items != null) {
int start = Math.max(0, items.length - MAX_STORED_ENTRIES);
for (int i = start; i < items.length; i++) {
Entry entry = toEntry(items[i]);
if (entry != null) allEntries.add(entry);
}
}
rebuildVisible();
}
void add(LogItem item) {
Entry entry = toEntry(item);
if (entry == null) return;
allEntries.add(entry);
while (allEntries.size() > MAX_STORED_ENTRIES) allEntries.remove(0);
if (isVisible(entry)) {
visibleEntries.add(entry);
notifyItemInserted(visibleEntries.size() - 1);
if (visibleEntries.size() > MAX_VISIBLE_ENTRIES) {
visibleEntries.remove(0);
notifyItemRemoved(0);
}
}
}
void setShowDebug(boolean showDebug) {
if (this.showDebug == showDebug) return;
this.showDebug = showDebug;
rebuildVisible();
}
void clear() {
int count = visibleEntries.size();
allEntries.clear();
visibleEntries.clear();
if (count > 0) notifyItemRangeRemoved(0, count);
}
boolean isEmpty() {
return visibleEntries.isEmpty();
}
String asPlainText() {
StringBuilder out = new StringBuilder();
for (Entry entry : visibleEntries) {
if (out.length() > 0) out.append('\n');
out.append(entry.time)
.append(" [")
.append(entry.level.name())
.append("] ")
.append(entry.message);
}
return out.toString();
}
@NonNull
@Override
public LogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_log, parent, false);
return new LogViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull LogViewHolder holder, int position) {
Entry entry = visibleEntries.get(position);
int accent = colorFor(entry.level);
holder.time.setText(entry.time);
holder.level.setText(entry.level.name());
holder.message.setText(entry.message);
holder.card.setStrokeColor(accent);
Drawable background = holder.level.getBackground();
if (background != null) {
Drawable wrapped = DrawableCompat.wrap(background.mutate());
DrawableCompat.setTintList(wrapped, ColorStateList.valueOf(accent));
holder.level.setBackground(wrapped);
}
}
@Override
public int getItemCount() {
return visibleEntries.size();
}
private void rebuildVisible() {
visibleEntries.clear();
for (Entry entry : allEntries) {
if (isVisible(entry)) visibleEntries.add(entry);
}
while (visibleEntries.size() > MAX_VISIBLE_ENTRIES) visibleEntries.remove(0);
notifyDataSetChanged();
}
private boolean isVisible(Entry entry) {
return showDebug
|| (entry.level != SkStatus.LogLevel.DEBUG
&& entry.level != SkStatus.LogLevel.VERBOSE);
}
private Entry toEntry(LogItem item) {
if (item == null) return null;
String raw;
try {
raw = item.getString(context);
} catch (Exception e) {
raw = item.getMessage();
}
if (raw == null) return null;
String clean = Html.fromHtml(raw, Html.FROM_HTML_MODE_LEGACY)
.toString()
.replace('\u00A0', ' ')
.trim();
if (clean.isEmpty()) return null;
long timestamp = item.getLogtime();
SkStatus.LogLevel level = item.getLogLevel() != null
? item.getLogLevel() : SkStatus.LogLevel.INFO;
return new Entry(timeFormat.format(new Date(timestamp)), level, clean);
}
private int colorFor(SkStatus.LogLevel level) {
int resource;
switch (level) {
case ERROR:
resource = R.color.log_error;
break;
case WARNING:
resource = R.color.log_warning;
break;
case DEBUG:
resource = R.color.log_debug;
break;
case VERBOSE:
resource = R.color.log_verbose;
break;
case INFO:
default:
resource = R.color.log_info;
break;
}
return ContextCompat.getColor(context, resource);
}
static final class LogViewHolder extends RecyclerView.ViewHolder {
final MaterialCardView card;
final TextView time;
final TextView level;
final TextView message;
LogViewHolder(@NonNull View itemView) {
super(itemView);
card = itemView.findViewById(R.id.log_card);
time = itemView.findViewById(R.id.log_time);
level = itemView.findViewById(R.id.log_level);
message = itemView.findViewById(R.id.log_message);
}
}
private static final class Entry {
final String time;
final SkStatus.LogLevel level;
final String message;
Entry(String time, SkStatus.LogLevel level, String message) {
this.time = time;
this.level = level;
this.message = message;
}
}
}
@@ -0,0 +1,91 @@
package com.dragonssh.xhttpdemo;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.dragonssh.xhttpdemo.core.logger.LogItem;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import com.google.android.material.switchmaterial.SwitchMaterial;
public final class LogsActivity extends AppCompatActivity implements SkStatus.LogListener {
private RecyclerView logList;
private LogAdapter logAdapter;
private SwitchMaterial followLogsSwitch;
private SwitchMaterial showDebugLogsSwitch;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_logs);
WindowInsetsHelper.apply(this, R.id.logs_root);
logList = findViewById(R.id.log_list);
followLogsSwitch = findViewById(R.id.follow_logs);
showDebugLogsSwitch = findViewById(R.id.show_debug_logs);
logAdapter = new LogAdapter(this);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setStackFromEnd(true);
logList.setLayoutManager(layoutManager);
logList.setAdapter(logAdapter);
logList.setItemAnimator(null);
findViewById(R.id.back).setOnClickListener(v -> finish());
findViewById(R.id.copy_logs).setOnClickListener(v -> copyLogs());
findViewById(R.id.clear_logs).setOnClickListener(v -> SkStatus.clearLog());
showDebugLogsSwitch.setOnCheckedChangeListener((button, checked) -> {
logAdapter.setShowDebug(checked);
if (followLogsSwitch.isChecked()) scrollLogsToBottom();
});
}
@Override
protected void onStart() {
super.onStart();
SkStatus.addLogListener(this);
logAdapter.replace(SkStatus.getlogbuffer());
scrollLogsToBottom();
}
@Override
protected void onStop() {
SkStatus.removeLogListener(this);
super.onStop();
}
@Override
public void newLog(LogItem logItem) {
runOnUiThread(() -> {
logAdapter.add(logItem);
if (followLogsSwitch.isChecked()) scrollLogsToBottom();
});
}
@Override
public void onClear() {
runOnUiThread(logAdapter::clear);
}
private void scrollLogsToBottom() {
if (logAdapter == null || logAdapter.getItemCount() == 0) return;
logList.post(() -> logList.scrollToPosition(logAdapter.getItemCount() - 1));
}
private void copyLogs() {
if (logAdapter.isEmpty()) {
Toast.makeText(this, R.string.no_logs_to_copy, Toast.LENGTH_SHORT).show();
return;
}
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText(
"DragonSSH XHTTP logs", logAdapter.asPlainText()));
Toast.makeText(this, R.string.logs_copied, Toast.LENGTH_SHORT).show();
}
}
@@ -0,0 +1,220 @@
package com.dragonssh.xhttpdemo;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.dragonssh.xhttpdemo.core.config.Settings;
import com.dragonssh.xhttpdemo.core.logger.ConnectionStatus;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import com.dragonssh.xhttpdemo.core.tunnel.TunnelManagerHelper;
import com.google.android.material.button.MaterialButton;
public final class MainActivity extends AppCompatActivity implements SkStatus.StateListener {
private static final int NOTIFICATION_PERMISSION_REQUEST = 42;
private Settings settings;
private TextView statusView;
private TextView serverValue;
private TextView usernameValue;
private TextView sniValue;
private TextView hostValue;
private TextView pathValue;
private TextView tlsModeValue;
private TextView xhttpTlsValue;
private MaterialButton startStopButton;
private boolean tunnelActive;
private final ActivityResultLauncher<Intent> settingsLauncher =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> refreshSummary());
private final ActivityResultLauncher<Intent> vpnPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
startTunnel();
} else {
Toast.makeText(this, R.string.vpn_permission_required, Toast.LENGTH_LONG).show();
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WindowInsetsHelper.apply(this, R.id.main_root);
settings = new Settings(this);
bindViews();
startStopButton.setOnClickListener(v -> {
if (tunnelActive || SkStatus.isTunnelActive()) {
TunnelManagerHelper.stopXHttpSsh(this);
} else {
requestVpnAndConnect();
}
});
findViewById(R.id.open_settings).setOnClickListener(v -> openSettings());
findViewById(R.id.open_vpn_settings).setOnClickListener(v ->
settingsLauncher.launch(new Intent(this, VpnSettingsActivity.class)));
findViewById(R.id.open_logs).setOnClickListener(v ->
startActivity(new Intent(this, LogsActivity.class)));
refreshSummary();
requestNotificationPermissionIfNeeded();
}
@Override
protected void onStart() {
super.onStart();
SkStatus.addStateListener(this);
refreshSummary();
}
@Override
protected void onStop() {
SkStatus.removeStateListener(this);
super.onStop();
}
private void bindViews() {
statusView = findViewById(R.id.status);
serverValue = findViewById(R.id.summary_server);
usernameValue = findViewById(R.id.summary_username);
sniValue = findViewById(R.id.summary_sni);
hostValue = findViewById(R.id.summary_xhttp_host);
pathValue = findViewById(R.id.summary_xhttp_path);
tlsModeValue = findViewById(R.id.summary_tls_mode);
xhttpTlsValue = findViewById(R.id.summary_xhttp_tls);
startStopButton = findViewById(R.id.start_stop);
}
private void refreshSummary() {
if (settings == null) return;
String server = settings.getPrivString(Settings.XHTTP_ENDPOINT_KEY);
String port = settings.getPrivString(Settings.XHTTP_PORT_KEY);
String username = settings.getPrivString(Settings.USUARIO_KEY);
String sni = settings.getPrivString(Settings.CUSTOM_SNI);
String host = settings.getPrivString(Settings.XHTTP_HOST_KEY);
String path = ConfigUtils.normalizePath(settings.getPrivString(Settings.XHTTP_PATH_KEY));
boolean tls = !"0".equals(settings.getPrivString(Settings.XHTTP_TLS_KEY));
serverValue.setText(ConfigUtils.formatHostPort(server, port));
usernameValue.setText(ConfigUtils.displayValue(username));
sniValue.setText(ConfigUtils.displayValue(sni));
hostValue.setText(ConfigUtils.displayValue(host));
pathValue.setText(ConfigUtils.displayValue(path));
tlsModeValue.setText(tls ? R.string.tls_mode_automatic : R.string.tls_mode_disabled);
xhttpTlsValue.setText(tls ? R.string.enabled : R.string.disabled);
}
private void openSettings() {
settingsLauncher.launch(new Intent(this, SettingsActivity.class));
}
private void requestVpnAndConnect() {
String error = validateSavedConfiguration();
if (error != null) {
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
openSettings();
return;
}
DemoApplication application = (DemoApplication) getApplication();
if (!application.hasSessionPassword()) {
Toast.makeText(this, R.string.password_needed_for_session, Toast.LENGTH_LONG).show();
openSettings();
return;
}
Intent permissionIntent = VpnService.prepare(this);
if (permissionIntent != null) {
vpnPermissionLauncher.launch(permissionIntent);
} else {
startTunnel();
}
}
private String validateSavedConfiguration() {
String server = settings.getPrivString(Settings.XHTTP_ENDPOINT_KEY).trim();
String port = settings.getPrivString(Settings.XHTTP_PORT_KEY).trim();
String username = settings.getPrivString(Settings.USUARIO_KEY).trim();
String path = ConfigUtils.normalizePath(settings.getPrivString(Settings.XHTTP_PATH_KEY));
if (server.isEmpty()) return getString(R.string.error_server_required);
if (!ConfigUtils.isValidPort(port)) return getString(R.string.error_port_invalid);
if (username.isEmpty()) return getString(R.string.error_username_required);
if (path.isEmpty()) return getString(R.string.error_path_required);
if (settings.getVpnDnsForward() && settings.getVpnDnsResolver().trim().isEmpty()) {
return getString(R.string.error_dns_primary_required);
}
if (settings.getVpnUdpForward() && !ConfigUtils.isHostPort(settings.getVpnUdpResolver())) {
return getString(R.string.error_udpgw_invalid);
}
return null;
}
private void startTunnel() {
DemoApplication application = (DemoApplication) getApplication();
String password = application.getSessionPassword();
if (password.isEmpty()) {
Toast.makeText(this, R.string.password_needed_for_session, Toast.LENGTH_LONG).show();
openSettings();
return;
}
String server = settings.getPrivString(Settings.XHTTP_ENDPOINT_KEY);
String port = settings.getPrivString(Settings.XHTTP_PORT_KEY);
String username = settings.getPrivString(Settings.USUARIO_KEY);
String sni = settings.getPrivString(Settings.CUSTOM_SNI);
String host = settings.getPrivString(Settings.XHTTP_HOST_KEY);
String path = ConfigUtils.normalizePath(settings.getPrivString(Settings.XHTTP_PATH_KEY));
boolean tls = !"0".equals(settings.getPrivString(Settings.XHTTP_TLS_KEY));
statusView.setText(R.string.status_starting);
SkStatus.logInfo("Server: " + ConfigUtils.formatHostPort(server, port));
SkStatus.logInfo("XHTTP: SNI=" + ConfigUtils.displayValue(sni)
+ ", Host=" + ConfigUtils.displayValue(host)
+ ", Path=" + ConfigUtils.displayValue(path)
+ ", TLS=" + (tls ? "on" : "off"));
SkStatus.logInfo("SSH login: " + username + " (password hidden)");
TunnelManagerHelper.startXHttpSsh(this, password);
}
@Override
public void updateState(String state, String logMessage, int localizedResId,
ConnectionStatus level, Intent intent) {
runOnUiThread(() -> {
String label;
try {
label = getString(localizedResId);
} catch (Exception e) {
label = state;
}
statusView.setText(label);
tunnelActive = level != ConnectionStatus.LEVEL_NOTCONNECTED
&& level != ConnectionStatus.LEVEL_AUTH_FAILED;
startStopButton.setText(tunnelActive ? R.string.stop : R.string.start);
});
}
private void requestNotificationPermissionIfNeeded() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.POST_NOTIFICATIONS},
NOTIFICATION_PERMISSION_REQUEST);
}
}
}
@@ -0,0 +1,132 @@
package com.dragonssh.xhttpdemo;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.dragonssh.xhttpdemo.core.config.Settings;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.android.material.textfield.TextInputLayout;
public final class SettingsActivity extends AppCompatActivity {
private Settings settings;
private EditText serverField;
private EditText portField;
private EditText usernameField;
private EditText passwordField;
private EditText sniField;
private EditText xhttpHostField;
private EditText pathField;
private SwitchMaterial xhttpTlsSwitch;
private TextInputLayout passwordLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
WindowInsetsHelper.apply(this, R.id.settings_root);
settings = new Settings(this);
bindViews();
loadConfiguration();
findViewById(R.id.back).setOnClickListener(v -> finish());
findViewById(R.id.save_settings).setOnClickListener(v -> saveConfiguration());
}
private void bindViews() {
serverField = findViewById(R.id.server);
portField = findViewById(R.id.port);
usernameField = findViewById(R.id.username);
passwordField = findViewById(R.id.password);
sniField = findViewById(R.id.sni);
xhttpHostField = findViewById(R.id.xhttp_host);
pathField = findViewById(R.id.xhttp_path);
xhttpTlsSwitch = findViewById(R.id.xhttp_tls);
passwordLayout = findViewById(R.id.password_layout);
}
private void loadConfiguration() {
serverField.setText(settings.getPrivString(Settings.XHTTP_ENDPOINT_KEY));
portField.setText(settings.getPrivString(Settings.XHTTP_PORT_KEY));
usernameField.setText(settings.getPrivString(Settings.USUARIO_KEY));
sniField.setText(settings.getPrivString(Settings.CUSTOM_SNI));
xhttpHostField.setText(settings.getPrivString(Settings.XHTTP_HOST_KEY));
pathField.setText(settings.getPrivString(Settings.XHTTP_PATH_KEY));
xhttpTlsSwitch.setChecked(!"0".equals(settings.getPrivString(Settings.XHTTP_TLS_KEY)));
DemoApplication application = (DemoApplication) getApplication();
passwordField.setText(application.getSessionPassword());
passwordLayout.setHelperText(application.hasSessionPassword()
? getString(R.string.password_loaded_for_session)
: getString(R.string.password_storage_notice));
}
private void saveConfiguration() {
String server = text(serverField);
String port = text(portField);
String username = text(usernameField);
String password = rawText(passwordField);
String path = ConfigUtils.normalizePath(text(pathField));
if (server.isEmpty()) {
showError(R.string.error_server_required);
serverField.requestFocus();
return;
}
if (!ConfigUtils.isValidPort(port)) {
showError(R.string.error_port_invalid);
portField.requestFocus();
return;
}
if (username.isEmpty()) {
showError(R.string.error_username_required);
usernameField.requestFocus();
return;
}
DemoApplication application = (DemoApplication) getApplication();
if (password.isEmpty()) {
showError(R.string.error_password_required);
passwordField.requestFocus();
return;
}
if (path.isEmpty()) {
showError(R.string.error_path_required);
pathField.requestFocus();
return;
}
SharedPreferences.Editor editor = settings.getPrefsPrivate().edit();
editor.putString(Settings.XHTTP_ENDPOINT_KEY, server)
.putString(Settings.XHTTP_PORT_KEY, port)
.putString(Settings.USUARIO_KEY, username)
.putString(Settings.PORTA_LOCAL_KEY, "1080")
.putString(Settings.CUSTOM_SNI, text(sniField))
.putString(Settings.XHTTP_HOST_KEY, text(xhttpHostField))
.putString(Settings.XHTTP_PATH_KEY, path)
.putString(Settings.XHTTP_TLS_KEY, xhttpTlsSwitch.isChecked() ? "1" : "0")
.apply();
application.setSessionPassword(password);
setResult(RESULT_OK);
Toast.makeText(this, R.string.settings_saved, Toast.LENGTH_SHORT).show();
finish();
}
private void showError(int messageRes) {
Toast.makeText(this, messageRes, Toast.LENGTH_LONG).show();
}
private static String text(EditText view) {
return view.getText() == null ? "" : view.getText().toString().trim();
}
private static String rawText(EditText view) {
return view.getText() == null ? "" : view.getText().toString();
}
}
@@ -0,0 +1,96 @@
package com.dragonssh.xhttpdemo;
import android.os.Bundle;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.dragonssh.xhttpdemo.core.config.Settings;
import com.google.android.material.switchmaterial.SwitchMaterial;
public final class VpnSettingsActivity extends AppCompatActivity {
private Settings settings;
private SwitchMaterial customDnsSwitch;
private EditText primaryDnsField;
private EditText secondaryDnsField;
private SwitchMaterial udpGatewaySwitch;
private EditText udpGatewayField;
private SwitchMaterial ipv6Switch;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vpn_settings);
WindowInsetsHelper.apply(this, R.id.vpn_settings_root);
settings = new Settings(this);
bindViews();
loadConfiguration();
customDnsSwitch.setOnCheckedChangeListener((button, checked) -> updateEnabledState());
udpGatewaySwitch.setOnCheckedChangeListener((button, checked) -> updateEnabledState());
findViewById(R.id.back).setOnClickListener(v -> finish());
findViewById(R.id.save_vpn_settings).setOnClickListener(v -> saveConfiguration());
}
private void bindViews() {
customDnsSwitch = findViewById(R.id.use_custom_dns);
primaryDnsField = findViewById(R.id.dns_primary);
secondaryDnsField = findViewById(R.id.dns_secondary);
udpGatewaySwitch = findViewById(R.id.enable_udpgw);
udpGatewayField = findViewById(R.id.udpgw_address);
ipv6Switch = findViewById(R.id.enable_ipv6);
}
private void loadConfiguration() {
customDnsSwitch.setChecked(settings.getVpnDnsForward());
primaryDnsField.setText(settings.getVpnDnsResolver());
secondaryDnsField.setText(settings.getVpnDnsResolverSecondary());
udpGatewaySwitch.setChecked(settings.getVpnUdpForward());
udpGatewayField.setText(settings.getVpnUdpResolver());
// Switch shows IPv6 ENABLED; the stored flag is the inverse (disableIpv6Tunnel).
ipv6Switch.setChecked(!settings.getDisableIpv6Tunnel());
updateEnabledState();
}
private void updateEnabledState() {
primaryDnsField.setEnabled(customDnsSwitch.isChecked());
secondaryDnsField.setEnabled(customDnsSwitch.isChecked());
udpGatewayField.setEnabled(udpGatewaySwitch.isChecked());
}
private void saveConfiguration() {
String primaryDns = text(primaryDnsField);
String secondaryDns = text(secondaryDnsField);
String udpGateway = text(udpGatewayField);
if (customDnsSwitch.isChecked() && primaryDns.isEmpty()) {
Toast.makeText(this, R.string.error_dns_primary_required, Toast.LENGTH_LONG).show();
primaryDnsField.requestFocus();
return;
}
if (udpGatewaySwitch.isChecked() && !ConfigUtils.isHostPort(udpGateway)) {
Toast.makeText(this, R.string.error_udpgw_invalid, Toast.LENGTH_LONG).show();
udpGatewayField.requestFocus();
return;
}
settings.getVpnPrefs().edit()
.putBoolean(Settings.DNSFORWARD_KEY, customDnsSwitch.isChecked())
.putString(Settings.DNSRESOLVER_KEY, primaryDns)
.putString(Settings.DNSRESOLVER_SECONDARY_KEY, secondaryDns)
.putBoolean(Settings.UDPFORWARD_KEY, udpGatewaySwitch.isChecked())
.putString(Settings.UDPRESOLVER_KEY, udpGateway)
.putBoolean(Settings.DISABLE_IPV6_TUNNEL_KEY, !ipv6Switch.isChecked())
.apply();
setResult(RESULT_OK);
Toast.makeText(this, R.string.settings_saved, Toast.LENGTH_SHORT).show();
finish();
}
private static String text(EditText view) {
return view.getText() == null ? "" : view.getText().toString().trim();
}
}
@@ -0,0 +1,39 @@
package com.dragonssh.xhttpdemo;
import android.app.Activity;
import android.view.View;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
final class WindowInsetsHelper {
private WindowInsetsHelper() { }
static void apply(Activity activity, int rootViewId) {
WindowCompat.setDecorFitsSystemWindows(activity.getWindow(), false);
View root = activity.findViewById(rootViewId);
if (root == null) return;
final int initialLeft = root.getPaddingLeft();
final int initialTop = root.getPaddingTop();
final int initialRight = root.getPaddingRight();
final int initialBottom = root.getPaddingBottom();
ViewCompat.setOnApplyWindowInsetsListener(root, (view, windowInsets) -> {
Insets systemBars = windowInsets.getInsets(
WindowInsetsCompat.Type.systemBars()
| WindowInsetsCompat.Type.displayCutout());
Insets ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
view.setPadding(
initialLeft + systemBars.left,
initialTop + systemBars.top,
initialRight + systemBars.right,
initialBottom + Math.max(systemBars.bottom, ime.bottom));
return windowInsets;
});
ViewCompat.requestApplyInsets(root);
}
}
@@ -0,0 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:fillColor="#5B5BD6" android:pathData="M0,0h108v108h-108z" />
<path android:fillColor="#FFFFFF" android:pathData="M22,29h64v12h-64zM22,48h42v12h-42zM22,67h64v12h-64z" />
</vector>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/log_surface" />
<stroke android:width="1dp" android:color="@color/log_stroke" />
<corners android:radius="12dp" />
</shape>
@@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/log_debug" />
<corners android:radius="999dp" />
</shape>
+82
View File
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/logs_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/back"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:minWidth="0dp"
android:text="@string/back_arrow"
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/logs"
android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/copy_logs"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/copy_logs" />
<com.google.android.material.button.MaterialButton
android:id="@+id/clear_logs"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/clear_logs" />
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/follow_logs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="true"
android:text="@string/follow_logs" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/show_debug_logs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/show_debug_logs" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/log_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_weight="1"
android:background="@drawable/log_background"
android:clipToPadding="false"
android:padding="8dp" />
</LinearLayout>
+227
View File
@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fillViewport="true"
android:padding="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/home_subtitle"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
<com.google.android.material.button.MaterialButton
android:id="@+id/start_stop"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginTop="32dp"
android:text="@string/start"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textStyle="bold"
app:cornerRadius="14dp" />
<TextView
android:id="@+id/status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center"
android:text="@string/status_disconnected"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:gravity="center"
android:text="@string/connection_method"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:textStyle="bold" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:cardCornerRadius="14dp"
app:strokeWidth="1dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="18dp"
android:text="@string/xhttp_ssh"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/open_settings"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/settings" />
<com.google.android.material.button.MaterialButton
android:id="@+id/open_vpn_settings"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/vpn_udpgw" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/open_logs"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/logs" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="12dp"
app:cardCornerRadius="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/current_configuration"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:text="@string/server"
android:textStyle="bold" />
<TextView
android:id="@+id/summary_server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="—"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/username"
android:textStyle="bold" />
<TextView
android:id="@+id/summary_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="—"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/sni"
android:textStyle="bold" />
<TextView
android:id="@+id/summary_sni"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="—"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/xhttp_host"
android:textStyle="bold" />
<TextView
android:id="@+id/summary_xhttp_host"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="—"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/xhttp_path"
android:textStyle="bold" />
<TextView
android:id="@+id/summary_xhttp_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="—"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/tls_mode"
android:textStyle="bold" />
<TextView
android:id="@+id/summary_tls_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tls_mode_automatic"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/xhttp_tls"
android:textStyle="bold" />
<TextView
android:id="@+id/summary_xhttp_tls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enabled"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
@@ -0,0 +1,245 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/settings_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fillViewport="true"
android:padding="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/back"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:minWidth="0dp"
android:text="@string/back_arrow"
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/settings"
android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium"
android:textStyle="bold" />
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:cardCornerRadius="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/ssh_tunnel"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textStyle="bold" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:hint="@string/server"
app:helperText="@string/server_help">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/port">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:cardCornerRadius="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/authentication"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textStyle="bold" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:hint="@string/username">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/password"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:cardCornerRadius="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/xhttp"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textStyle="bold" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:hint="@string/sni"
app:helperText="@string/sni_help_simple">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/sni"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/xhttp_host"
app:helperText="@string/xhttp_host_help_simple">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/xhttp_host"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/xhttp_path"
app:helperText="@string/xhttp_path_help_simple">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/xhttp_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/tls_mode"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tls_mode_automatic"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/xhttp_tls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/xhttp_tls" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/save_settings"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="18dp"
android:layout_marginBottom="12dp"
android:text="@string/save"
android:textStyle="bold"
app:cornerRadius="14dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
@@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/vpn_settings_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fillViewport="true"
android:padding="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/back"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:minWidth="0dp"
android:text="@string/back_arrow"
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/vpn_udpgw"
android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium"
android:textStyle="bold" />
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:cardCornerRadius="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dns"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/use_custom_dns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/use_custom_dns" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/dns_1">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dns_primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/dns_2">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dns_secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:cardCornerRadius="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/udpgw"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_udpgw"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/enable_udpgw" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/udpgw_address"
app:helperText="@string/udpgw_help_simple">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/udpgw_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:cardCornerRadius="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/ipv6"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_ipv6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/enable_ipv6" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/ipv6_help"
android:textAppearance="@style/TextAppearance.Material3.BodySmall" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/save_vpn_settings"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="18dp"
android:layout_marginBottom="12dp"
android:text="@string/save"
android:textStyle="bold"
app:cornerRadius="14dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/log_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
app:cardBackgroundColor="@color/log_row_surface"
app:cardCornerRadius="10dp"
app:cardElevation="0dp"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/log_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="monospace"
android:textColor="@color/log_secondary_text"
android:textSize="11sp" />
<TextView
android:id="@+id/log_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/log_level_badge"
android:fontFamily="sans"
android:paddingStart="8dp"
android:paddingTop="2dp"
android:paddingEnd="8dp"
android:paddingBottom="2dp"
android:textColor="@android:color/white"
android:textSize="10sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/log_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:fontFamily="monospace"
android:lineSpacingExtra="2dp"
android:textColor="@color/log_primary_text"
android:textIsSelectable="true"
android:textSize="12sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
+14
View File
@@ -0,0 +1,14 @@
<resources>
<color name="seed">#159FE6</color>
<color name="log_surface">#101216</color>
<color name="log_row_surface">#171A20</color>
<color name="preview_surface">#171A20</color>
<color name="log_stroke">#343945</color>
<color name="log_primary_text">#F2F4F8</color>
<color name="log_secondary_text">#AEB6C4</color>
<color name="log_info">#3F8CFF</color>
<color name="log_warning">#D68A00</color>
<color name="log_error">#D94A4A</color>
<color name="log_debug">#687386</color>
<color name="log_verbose">#7C5BD6</color>
</resources>
+70
View File
@@ -0,0 +1,70 @@
<resources>
<string name="app_name">DragonSSH XHTTP Example</string>
<string name="home_subtitle">Minimal XHTTP + SSH client with UDPGW.</string>
<string name="start">START</string>
<string name="stop">STOP</string>
<string name="connection_method">Connection Method</string>
<string name="xhttp_ssh">XHTTP + SSH</string>
<string name="settings">Settings</string>
<string name="vpn_udpgw">VPN / UDPGW</string>
<string name="logs">Logs</string>
<string name="current_configuration">Current configuration</string>
<string name="ssh_tunnel">SSH Tunnel</string>
<string name="authentication">Authentication</string>
<string name="xhttp">XHTTP</string>
<string name="server">Server</string>
<string name="server_help">The XHTTP proxy IP or hostname.</string>
<string name="port">Port</string>
<string name="username">User name</string>
<string name="password">Password</string>
<string name="password_storage_notice">The password will be saved in private app storage.</string>
<string name="password_loaded_for_session">The saved password is shown masked. Edit it to replace it.</string>
<string name="sni">SNI</string>
<string name="sni_help_simple">TLS hostname, for example app.example.com.</string>
<string name="xhttp_host">XHTTP Host</string>
<string name="xhttp_host_help_simple">Host used by your XHTTP server or CDN.</string>
<string name="xhttp_path">XHTTP Path</string>
<string name="xhttp_path_help_simple">Example: /ssh</string>
<string name="tls_mode">TLS MODE</string>
<string name="tls_mode_automatic">Automatic (TLS 1.3 preferred)</string>
<string name="tls_mode_disabled">Disabled</string>
<string name="xhttp_tls">XHTTP TLS</string>
<string name="enabled">Enabled</string>
<string name="disabled">Disabled</string>
<string name="save">SAVE</string>
<string name="back_arrow"></string>
<string name="dns">DNS</string>
<string name="use_custom_dns">Use custom DNS</string>
<string name="dns_1">DNS 1</string>
<string name="dns_2">DNS 2</string>
<string name="udpgw">UDPGW</string>
<string name="enable_udpgw">Enable UDPGW</string>
<string name="udpgw_address">UDPGW host:port</string>
<string name="udpgw_help_simple">Example: 127.0.0.1:7300</string>
<string name="ipv6">IPv6</string>
<string name="enable_ipv6">Enable IPv6 tunnel</string>
<string name="ipv6_help">Route IPv6 through the tunnel. Leave off if your server or network has no working IPv6.</string>
<string name="copy_logs">Copy</string>
<string name="clear_logs">Clear</string>
<string name="follow_logs">Follow newest entries</string>
<string name="show_debug_logs">Show DEBUG and VERBOSE</string>
<string name="logs_copied">Logs copied to the clipboard.</string>
<string name="no_logs_to_copy">There are no logs to copy.</string>
<string name="settings_saved">Settings saved.</string>
<string name="status_disconnected">Disconnected</string>
<string name="status_starting">Starting…</string>
<string name="vpn_permission_required">VPN permission is required to connect.</string>
<string name="password_needed_for_session">Open Settings and save the SSH password.</string>
<string name="error_server_required">Enter the Server. This is the XHTTP proxy IP or hostname.</string>
<string name="error_port_invalid">Enter a valid port from 1 to 65535.</string>
<string name="error_username_required">Enter the SSH user name.</string>
<string name="error_password_required">Enter the SSH password.</string>
<string name="error_path_required">Enter the XHTTP Path.</string>
<string name="error_dns_primary_required">Enter DNS 1 or disable custom DNS.</string>
<string name="error_udpgw_invalid">Enter UDPGW as host:port.</string>
</resources>
+11
View File
@@ -0,0 +1,11 @@
<resources>
<style name="Theme.DragonSshXHttpDemo" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/seed</item>
<item name="android:fontFamily">sans</item>
<item name="android:windowBackground">@android:color/black</item>
<item name="android:statusBarColor">@android:color/black</item>
<item name="android:navigationBarColor">@android:color/black</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
</resources>
+4
View File
@@ -0,0 +1,4 @@
plugins {
id 'com.android.application' version '8.13.2' apply false
id 'com.android.library' version '8.13.2' apply false
}
+58
View File
@@ -0,0 +1,58 @@
# XHTTP-SSH request contract
## Public configuration names
The app exposes these names:
- **Server** — the XHTTP proxy address that receives the TCP connection.
- **Port** — the XHTTP listener port.
- **SNI** — the hostname sent in the TLS handshake for CDN/fronting routing.
- **XHTTP Host** — the HTTP `Host` value used for CDN or reverse-proxy routing.
- **XHTTP Path** — the base request path.
- **User name / Password** — SSH authentication inside the XHTTP stream.
The **Server is the XHTTP proxy**. There is no separate client-side SSH destination because the XHTTP session itself carries the raw SSH byte stream.
XHTTP TLS uses a trust-all certificate manager and disables hostname verification, matching SocksRevive VOID. The configured SNI is still sent, but certificates are not validated.
## Session creation and downlink
For each SSH transport connection, the client generates a random hexadecimal session ID and opens one long-lived request:
```text
GET {basePath}/{sessionId}
Host: {xhttpHost}
Accept: */*
```
With TLS enabled, the URL host is the configured SNI. A custom DNS implementation pins that URL host to the configured Server, so the socket still connects to the XHTTP proxy address. The response body is consumed continuously as the server-to-client SSH stream.
## Uplink
Client-to-server SSH bytes use monotonically increasing POST sequence numbers:
```text
POST {basePath}/{sessionId}/0
POST {basePath}/{sessionId}/1
POST {basePath}/{sessionId}/2
...
Content-Type: application/octet-stream
```
Only one POST is in flight at a time. The client waits for a successful response before sending the next sequence, preserving order and preventing an unbounded reorder queue. Pending writes are coalesced up to 900 KiB per POST and use an 8 MiB bounded backpressure buffer.
## Failure and reconnect
A failed GET stream, failed or timed-out POST, SSH ping failure, or VPN-service loss closes the current XHTTP/SSH transport. For transient transport failures, the local SOCKS relay and Android TUN remain alive while the client creates a new XHTTP session and SSH connection.
## Server expectations
A compatible server must:
1. map GET and POST requests by session ID;
2. stream server-to-client bytes immediately in the GET response body;
3. accept sequence-numbered POST bodies and append them in numeric order;
4. return success only after a POST body has been accepted for delivery;
5. close the session when either direction is unusable;
6. avoid website-style request rate limits on this path, because the POST requests are VPN tunnel packets rather than API traffic.
+4
View File
@@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=true
org.gradle.configuration-cache=false
+13
View File
@@ -0,0 +1,13 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/658299a896470fbb3103ba3a430ee227/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e55dccbfe27cb97945148c61a39c89c5/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/dbd05c4936d573642f94cd149e1356c8/redirect
toolchainVendor=JETBRAINS
toolchainVersion=21
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
#Tue Jun 22 23:40:05 BRT 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Vendored
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
Vendored
+90
View File
@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+1
View File
@@ -0,0 +1 @@
/build
+43
View File
@@ -0,0 +1,43 @@
plugins {
id 'com.android.library'
}
android {
namespace 'com.dragonssh.xhttpdemo.core'
compileSdk 36
ndkVersion '27.0.12077973'
defaultConfig {
minSdk 26
targetSdk 36
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
buildFeatures {
aidl false
buildConfig true
}
externalNativeBuild {
ndkBuild {
path file('src/main/jni/Android.mk')
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.core:core:1.17.0'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'org.conscrypt:conscrypt-android:2.5.3'
api 'net.i2p.crypto:eddsa:0.3.0'
implementation 'org.connectbot:jbcrypt:1.0.2'
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:/tools/adt-bundle-windows-x86_64-20131030/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-keep class go.** { *; }
-keep class libv2ray.** { *; }
-keep class com.service.** { *; }
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application>
<receiver
android:name="com.dragonssh.xhttpdemo.core.MainReceiver"
android:exported="false" />
<service
android:name="com.dragonssh.xhttpdemo.core.XHttpSshService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:stopWithTask="false" />
<service
android:name="com.dragonssh.xhttpdemo.core.tunnel.vpn.TunnelVpnService"
android:enabled="true"
android:exported="false"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
</application>
</manifest>
@@ -0,0 +1,37 @@
package com.dragonssh.xhttpdemo.core;
import android.content.Context;
import android.content.Intent;
import android.content.BroadcastReceiver;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.dragonssh.xhttpdemo.core.tunnel.TunnelManagerHelper;
public class MainReceiver extends BroadcastReceiver
{
public static final String ACTION_SERVICE_RESTART = "sshTunnelServiceRestsrt",
ACTION_SERVICE_STOP = "sshtunnelservicestop";
@Override
public void onReceive(Context context, Intent intent)
{
String acao = intent.getAction();
if (acao == null) {
return;
}
switch (acao) {
case ACTION_SERVICE_STOP:
TunnelManagerHelper.stopXHttpSsh(context);
break;
case ACTION_SERVICE_RESTART:
Intent restartTunnel = new Intent(XHttpSshService.TUNNEL_SSH_RESTART_SERVICE);
LocalBroadcastManager.getInstance(context)
.sendBroadcast(restartTunnel);
break;
}
}
}
@@ -0,0 +1,282 @@
package com.dragonssh.xhttpdemo.core;
import android.net.TrafficStats;
import android.os.SystemClock;
import java.util.ArrayList;
public class StatisticGraphData
{
private static StatisticGraphData statisticData;
private boolean m_displayDataTransferStats;
private DataTransferStats m_dataTransferStats;
private StatisticGraphData()
{
m_dataTransferStats = new DataTransferStats();
m_displayDataTransferStats = false;
}
public static synchronized StatisticGraphData getStatisticData()
{
if (statisticData == null)
{
statisticData = new StatisticGraphData();
}
return statisticData;
}
public synchronized void setDisplayDataTransferStats(boolean displayDataTransferStats)
{
m_displayDataTransferStats = displayDataTransferStats;
}
public synchronized boolean getDisplayDataTransferStats()
{
return m_displayDataTransferStats;
}
public synchronized DataTransferStats getDataTransferStats()
{
return m_dataTransferStats;
}
public class DataTransferStats
{
private boolean m_isConnected;
private long m_connectedTime;
private long m_totalBytesSent;
private long m_totalBytesReceived;
public final static long SLOW_BUCKET_PERIOD_MILLISECONDS = 5*60*1000;
public final static long FAST_BUCKET_PERIOD_MILLISECONDS = 1000;
public final static int MAX_BUCKETS = 24 * 60 / 5;
private long mLastSent;
private long mSent;
private long mLastReceived;
private long mReceived;
private class Bucket
{
public long m_bytesSent = 0;
public long m_bytesReceived = 0;
}
private ArrayList<Bucket> m_slowBuckets;
private long m_slowBucketsLastStartTime;
private ArrayList<Bucket> m_fastBuckets;
private long m_fastBucketsLastStartTime;
DataTransferStats()
{
m_totalBytesSent = 0;
m_totalBytesReceived = 0;
stop();
}
public synchronized void startSession()
{
resetBytesTransferred();
}
public synchronized void startConnected()
{
this.m_isConnected = true;
this.m_connectedTime = SystemClock.elapsedRealtime();
}
public synchronized void stop()
{
this.m_totalBytesReceived = 0;
this.m_totalBytesSent = 0;
this.m_isConnected = false;
this.m_connectedTime = 0;
resetBytesTransferred();
}
private void resetBytesTransferred()
{
long now = SystemClock.elapsedRealtime();
this.m_slowBucketsLastStartTime = bucketStartTime(now, SLOW_BUCKET_PERIOD_MILLISECONDS);
this.m_slowBuckets = newBuckets();
this.m_fastBucketsLastStartTime = bucketStartTime(now, FAST_BUCKET_PERIOD_MILLISECONDS);
this.m_fastBuckets = newBuckets();
}
public synchronized void addBytesSent(long bytes)
{
this.m_totalBytesSent += bytes;
manageBuckets();
addSentToBuckets(bytes);
}
public synchronized void addBytesReceived(long bytes)
{
this.m_totalBytesReceived += bytes;
manageBuckets();
addReceivedToBuckets(bytes);
}
private long bucketStartTime(long now, long period)
{
return period*(now/period);
}
private ArrayList<Bucket> newBuckets()
{
ArrayList<Bucket> buckets = new ArrayList<Bucket>();
for (int i = 0; i < MAX_BUCKETS; i++)
{
buckets.add(new Bucket());
}
return buckets;
}
private void shiftBuckets(long diff, long period, ArrayList<Bucket> buckets)
{
for (int i = 0; i < diff/period + 1; i++)
{
buckets.add(buckets.size(), new Bucket());
if (buckets.size() >= MAX_BUCKETS)
{
buckets.remove(0);
}
}
}
private void manageBuckets()
{
long now = SystemClock.elapsedRealtime();
long diff = now - this.m_slowBucketsLastStartTime;
if (diff >= SLOW_BUCKET_PERIOD_MILLISECONDS)
{
shiftBuckets(diff, SLOW_BUCKET_PERIOD_MILLISECONDS, m_slowBuckets);
this.m_slowBucketsLastStartTime = bucketStartTime(now, SLOW_BUCKET_PERIOD_MILLISECONDS);
}
diff = now - this.m_fastBucketsLastStartTime;
if (diff >= FAST_BUCKET_PERIOD_MILLISECONDS)
{
shiftBuckets(diff, FAST_BUCKET_PERIOD_MILLISECONDS, m_fastBuckets);
this.m_fastBucketsLastStartTime = bucketStartTime(now, FAST_BUCKET_PERIOD_MILLISECONDS);
}
}
private ArrayList<Long> getSentSeries(ArrayList<Bucket> buckets)
{
ArrayList<Long> series = new ArrayList<Long>();
for (int i = 0; i < buckets.size(); i++)
{
series.add(buckets.get(i).m_bytesSent);
}
return series;
}
private ArrayList<Long> getReceivedSeries(ArrayList<Bucket> buckets)
{
ArrayList<Long> series = new ArrayList<Long>();
for (int i = 0; i < buckets.size(); i++)
{
series.add(buckets.get(i).m_bytesReceived);
}
return series;
}
private void addSentToBuckets(long bytes)
{
this.m_slowBuckets.get(this.m_slowBuckets.size()-1).m_bytesSent += bytes;
this.m_fastBuckets.get(this.m_fastBuckets.size()-1).m_bytesSent += bytes;
}
private void addReceivedToBuckets(long bytes)
{
this.m_slowBuckets.get(this.m_slowBuckets.size()-1).m_bytesReceived += bytes;
this.m_fastBuckets.get(this.m_fastBuckets.size()-1).m_bytesReceived += bytes;
}
public synchronized boolean isConnected()
{
return this.m_isConnected;
}
public synchronized long getElapsedTime()
{
long now = SystemClock.elapsedRealtime();
return now - this.m_connectedTime;
}
public synchronized long getTotalBytesSent()
{
return this.m_totalBytesSent;
}
public synchronized long getTotalBytesReceived()
{
return this.m_totalBytesReceived;
}
public synchronized ArrayList<Long> getSlowSentSeries()
{
manageBuckets();
return getSentSeries(this.m_slowBuckets);
}
public synchronized ArrayList<Long> getSlowReceivedSeries()
{
manageBuckets();
return getReceivedSeries(this.m_slowBuckets);
}
public synchronized ArrayList<Long> getFastSentSeries()
{
manageBuckets();
return getSentSeries(this.m_fastBuckets);
}
public synchronized ArrayList<Long> getFastReceivedSeries()
{
manageBuckets();
return getReceivedSeries(this.m_fastBuckets);
}
public String byteCountToDisplaySize(long bytes, boolean si)
{
// http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java/3758880#3758880
int unit = si ? 1000 : 1024;
if (bytes < unit) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
public synchronized long getBytesSent() {
long xSent = TrafficStats.getTotalTxBytes();
mSent = xSent - mLastSent;
mLastSent = xSent;
return this.mSent;
}
public synchronized long getBytesReceived()
{
long xReceived = TrafficStats.getTotalRxBytes();
mReceived = xReceived - mLastReceived;
mLastReceived = xReceived;
return this.mReceived;
}
}
}
@@ -0,0 +1,25 @@
package com.dragonssh.xhttpdemo.core;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
/** Creates the quiet foreground-service channel used by the sample. */
public final class XHttpSshCore {
private XHttpSshCore() {}
public static void init(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
NotificationManager manager = context.getSystemService(NotificationManager.class);
if (manager == null) return;
NotificationChannel channel = new NotificationChannel(
XHttpSshService.NOTIFICATION_CHANNEL_ID,
context.getString(R.string.channel_name_background),
NotificationManager.IMPORTANCE_LOW);
channel.setDescription(context.getString(R.string.channel_description_background));
channel.setSound(null, null);
channel.enableVibration(false);
manager.createNotificationChannel(channel);
}
}
@@ -0,0 +1,168 @@
package com.dragonssh.xhttpdemo.core;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.dragonssh.xhttpdemo.core.logger.ConnectionStatus;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import com.dragonssh.xhttpdemo.core.tunnel.TunnelManagerThread;
/** Foreground service that owns the SSH/XHTTP transport. */
public final class XHttpSshService extends Service implements SkStatus.StateListener {
public static final String NOTIFICATION_CHANNEL_ID = "xhttp_ssh_status";
public static final int NOTIFICATION_ID = 7001;
public static final String EXTRA_SSH_PASSWORD = "ssh_password";
public static final String TUNNEL_SSH_RESTART_SERVICE =
XHttpSshService.class.getName() + ".RESTART";
public static final String TUNNEL_SSH_STOP_SERVICE =
XHttpSshService.class.getName() + ".STOP";
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private TunnelManagerThread tunnelManager;
private Thread tunnelThread;
private boolean receiverRegistered;
@Override
public void onCreate() {
super.onCreate();
XHttpSshCore.init(this);
registerControlReceiver();
SkStatus.addStateListener(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
ServiceCompat.startForeground(this, NOTIFICATION_ID,
buildNotification(getString(R.string.state_starting)),
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
if (tunnelThread == null || !tunnelThread.isAlive()) {
String password = intent != null ? intent.getStringExtra(EXTRA_SSH_PASSWORD) : null;
if (password == null || password.isEmpty()) {
SkStatus.logError("SSH password was not supplied");
stopForeground(STOP_FOREGROUND_REMOVE);
stopSelf(startId);
return START_NOT_STICKY;
}
startTunnel(password);
}
return START_NOT_STICKY;
}
private synchronized void startTunnel(String password) {
tunnelManager = new TunnelManagerThread(mainHandler, this, password);
tunnelManager.setOnStopClientListener(this::finishService);
tunnelThread = new Thread(tunnelManager, "xhttp-ssh-manager");
tunnelThread.start();
}
private synchronized void stopTunnel() {
TunnelManagerThread manager = tunnelManager;
tunnelManager = null;
if (manager != null) manager.stopAll();
Thread thread = tunnelThread;
tunnelThread = null;
if (thread != null) thread.interrupt();
}
private void finishService() {
mainHandler.post(() -> {
stopTunnel();
stopForeground(STOP_FOREGROUND_REMOVE);
stopSelf();
});
}
@Override
public void onDestroy() {
stopTunnel();
if (receiverRegistered) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(controlReceiver);
receiverRegistered = false;
}
SkStatus.removeStateListener(this);
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private Notification buildNotification(String text) {
Intent open = getPackageManager().getLaunchIntentForPackage(getPackageName());
PendingIntent contentIntent = null;
if (open != null) {
open.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) flags |= PendingIntent.FLAG_IMMUTABLE;
contentIntent = PendingIntent.getActivity(this, 1, open, flags);
}
Intent stop = new Intent(this, MainReceiver.class).setAction(MainReceiver.ACTION_SERVICE_STOP);
int actionFlags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) actionFlags |= PendingIntent.FLAG_IMMUTABLE;
PendingIntent stopIntent = PendingIntent.getBroadcast(this, 2, stop, actionFlags);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_cloud_black_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(text)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setSilent(true)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.addAction(R.drawable.ic_power_settings_new_black_24dp,
getString(R.string.stop), stopIntent);
if (contentIntent != null) builder.setContentIntent(contentIntent);
return builder.build();
}
@Override
public void updateState(String state, String logMessage, int localizedResId,
ConnectionStatus level, Intent intent) {
String text;
try {
text = getString(localizedResId);
} catch (Exception ignored) {
text = state;
}
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (nm != null) nm.notify(NOTIFICATION_ID, buildNotification(text));
}
private void registerControlReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(TUNNEL_SSH_STOP_SERVICE);
filter.addAction(TUNNEL_SSH_RESTART_SERVICE);
LocalBroadcastManager.getInstance(this).registerReceiver(controlReceiver, filter);
receiverRegistered = true;
}
private final BroadcastReceiver controlReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null) return;
if (TUNNEL_SSH_STOP_SERVICE.equals(intent.getAction())) {
finishService();
} else if (TUNNEL_SSH_RESTART_SERVICE.equals(intent.getAction())) {
TunnelManagerThread manager = tunnelManager;
if (manager != null) new Thread(manager::reconnectSSH, "xhttp-manual-reconnect").start();
}
}
};
}
@@ -0,0 +1,111 @@
package com.dragonssh.xhttpdemo.core.config;
import android.content.Context;
import android.content.SharedPreferences;
/**
* Minimal local configuration store for the public sample.
* No online panel, config encryption, import/export, HWID, OTP, or paid-app settings.
*/
public final class Settings implements SettingsConstants {
private final SharedPreferences privatePrefs;
private final SharedPreferences vpnPrefs;
public Settings(Context context) {
Context app = context.getApplicationContext();
privatePrefs = app.getSharedPreferences("xhttp_demo_private", Context.MODE_PRIVATE);
vpnPrefs = app.getSharedPreferences("xhttp_demo_vpn", Context.MODE_PRIVATE);
}
public String getPrivString(String key) {
String fallback = "";
if (PORTA_LOCAL_KEY.equals(key)) fallback = "1080";
if (XHTTP_PATH_KEY.equals(key)) fallback = "/ssh";
if (XHTTP_TLS_KEY.equals(key)) fallback = "1";
return privatePrefs.getString(key, fallback);
}
public SharedPreferences getPrefsPrivate() {
return privatePrefs;
}
public SharedPreferences getVpnPrefs() {
return vpnPrefs;
}
public int getMaximoThreadsSocks() {
return Math.max(1, vpnPrefs.getInt(MAXIMO_THREADS_KEY, 8));
}
public int getSSHPinger() {
return Math.max(0, vpnPrefs.getInt(PINGER_KEY, 3));
}
public boolean ssh_compression() {
return vpnPrefs.getBoolean(SSH_COMPRESSION, false);
}
public boolean getIsDisabledDelaySSH() {
return true;
}
public boolean getDisableIpv6Tunnel() {
return vpnPrefs.getBoolean(DISABLE_IPV6_TUNNEL_KEY, true);
}
public boolean getVpnDnsForward() {
return vpnPrefs.getBoolean(DNSFORWARD_KEY, true);
}
public String getVpnDnsResolver() {
return vpnPrefs.getString(DNSRESOLVER_KEY, "1.1.1.1");
}
public String getVpnDnsResolverSecondary() {
return vpnPrefs.getString(DNSRESOLVER_SECONDARY_KEY, "1.0.0.1");
}
public boolean getVpnUdpForward() {
return vpnPrefs.getBoolean(UDPFORWARD_KEY, true);
}
public String getVpnUdpResolver() {
return vpnPrefs.getString(UDPRESOLVER_KEY, "127.0.0.1:7300");
}
public String[] getVpnBypassList() {
return new String[0];
}
public boolean getIsFilterBypassMode() {
return false;
}
public String[] getFilterApps() {
return new String[0];
}
public boolean getIsTetheringSubnet() {
return false;
}
public static void setDefaultConfig(Context context) {
Settings settings = new Settings(context);
settings.privatePrefs.edit()
.putString(PORTA_LOCAL_KEY, "1080")
.putString(XHTTP_PATH_KEY, "/ssh")
.putString(XHTTP_TLS_KEY, "1")
.apply();
settings.vpnPrefs.edit()
.putBoolean(UDPFORWARD_KEY, true)
.putString(UDPRESOLVER_KEY, "127.0.0.1:7300")
.putBoolean(DNSFORWARD_KEY, true)
.putString(DNSRESOLVER_KEY, "1.1.1.1")
.putString(DNSRESOLVER_SECONDARY_KEY, "1.0.0.1")
.putBoolean(DISABLE_IPV6_TUNNEL_KEY, true)
.putInt(MAXIMO_THREADS_KEY, 8)
.putInt(PINGER_KEY, 3)
.apply();
}
}
@@ -0,0 +1,33 @@
package com.dragonssh.xhttpdemo.core.config;
/** Keys used by the public XHTTP + SSH example. */
public interface SettingsConstants {
// Legacy preference values are kept so upgrades preserve saved configurations.
String XHTTP_ENDPOINT_KEY = "sshServer";
String XHTTP_PORT_KEY = "sshPort";
/** @deprecated Use {@link #XHTTP_ENDPOINT_KEY}. */
@Deprecated String SERVIDOR_KEY = XHTTP_ENDPOINT_KEY;
/** @deprecated Use {@link #XHTTP_PORT_KEY}. */
@Deprecated String SERVIDOR_PORTA_KEY = XHTTP_PORT_KEY;
String USUARIO_KEY = "sshUser";
String SSH_PASSWORD_KEY = "sshPass";
String PORTA_LOCAL_KEY = "localSocksPort";
String SSH_HOST_KEY_PREFIX = "sshHostKey.";
String CUSTOM_SNI = "xhttpSni";
String XHTTP_PATH_KEY = "xhttpPath";
String XHTTP_HOST_KEY = "xhttpHost";
String XHTTP_TLS_KEY = "xhttpTls";
String TLS12 = "tls12";
String DNSFORWARD_KEY = "dnsForward";
String DNSRESOLVER_KEY = "dnsResolver";
String DNSRESOLVER_SECONDARY_KEY = "dnsResolverSecondary";
String UDPFORWARD_KEY = "udpForward";
String UDPRESOLVER_KEY = "udpResolver";
String DISABLE_IPV6_TUNNEL_KEY = "disableIpv6Tunnel";
String MAXIMO_THREADS_KEY = "numberMaxThreadSocks";
String PINGER_KEY = "pingerSSH";
String SSH_COMPRESSION = "sshCompression";
}
@@ -0,0 +1,39 @@
package com.dragonssh.xhttpdemo.core.logger;
import android.os.Parcel;
import android.os.Parcelable;
public enum ConnectionStatus implements Parcelable {
LEVEL_CONNECTED,
LEVEL_CONNECTING_SERVER_REPLIED,
LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
LEVEL_NONETWORK,
LEVEL_NOTCONNECTED,
LEVEL_START,
LEVEL_AUTH_FAILED,
LEVEL_WAITING_FOR_USER_INPUT,
UNKNOWN_LEVEL;
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ordinal());
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<ConnectionStatus> CREATOR = new Creator<ConnectionStatus>() {
@Override
public ConnectionStatus createFromParcel(Parcel in) {
return ConnectionStatus.values()[in.readInt()];
}
@Override
public ConnectionStatus[] newArray(int size) {
return new ConnectionStatus[size];
}
};
}
@@ -0,0 +1,107 @@
package com.dragonssh.xhttpdemo.core.logger;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Locale;
/** A small, application-neutral log record for the public example. */
public final class LogItem implements Parcelable {
private final Object[] args;
private final String message;
private final int resourceId;
private final SkStatus.LogLevel level;
private final long logTime;
public LogItem(int resourceId, Object... args) {
this(SkStatus.LogLevel.INFO, resourceId, args);
}
public LogItem(SkStatus.LogLevel level, int verbosityLevel, String message) {
this(level, message);
}
public LogItem(SkStatus.LogLevel level, int resourceId, Object... args) {
this.level = level;
this.resourceId = resourceId;
this.args = args;
this.message = null;
this.logTime = System.currentTimeMillis();
}
public LogItem(SkStatus.LogLevel level, String message) {
this.level = level;
this.message = message;
this.resourceId = 0;
this.args = null;
this.logTime = System.currentTimeMillis();
}
public LogItem(SkStatus.LogLevel level, int resourceId) {
this(level, resourceId, (Object[]) null);
}
private LogItem(Parcel in) {
args = in.readArray(Object.class.getClassLoader());
message = in.readString();
resourceId = in.readInt();
int levelValue = in.readInt();
SkStatus.LogLevel parsed = SkStatus.LogLevel.getEnumByValue(levelValue);
level = parsed != null ? parsed : SkStatus.LogLevel.INFO;
logTime = in.readLong();
}
public SkStatus.LogLevel getLogLevel() {
return level;
}
public long getLogtime() {
return logTime;
}
public String getMessage() {
return message;
}
public String getString(Context context) {
if (message != null) {
return message;
}
if (context == null || resourceId == 0) {
return String.format(Locale.US, "Log resource %d", resourceId);
}
return args == null ? context.getString(resourceId) : context.getString(resourceId, args);
}
@Override
public String toString() {
return getString(null);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeArray(args);
dest.writeString(message);
dest.writeInt(resourceId);
dest.writeInt(level.getInt());
dest.writeLong(logTime);
}
public static final Creator<LogItem> CREATOR = new Creator<LogItem>() {
@Override
public LogItem createFromParcel(Parcel in) {
return new LogItem(in);
}
@Override
public LogItem[] newArray(int size) {
return new LogItem[size];
}
};
}
@@ -0,0 +1,394 @@
package com.dragonssh.xhttpdemo.core.logger;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.Vector;
import com.dragonssh.xhttpdemo.core.R;
import android.content.Intent;
import android.content.Context;
import java.util.Locale;
public class SkStatus
{
private static final LinkedList<LogItem> logbuffer;
private static Vector<LogListener> logListener;
private static Vector<StateListener> stateListener;
private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
private static String mLaststatemsg = "";
private static String mLaststate = "NOPROCESS";
private static int mLastStateresid = R.string.state_noprocess;
private static Intent mLastIntent = null;
static final int MAXLOGENTRIES = 10000;
public static boolean isTunnelActive() {
return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED);
}
public static String getLastState() {
return mLaststate;
}
public static String getLastCleanLogMessage(Context c) {
String message = mLaststatemsg;
switch (mLastLevel) {
case LEVEL_CONNECTED:
String[] parts = mLaststatemsg.split(",");
/*
(a) the integer unix date/time,
(b) the state name,
0 (c) optional descriptive string (used mostly on RECONNECTING
and EXITING to show the reason for the disconnect),
1 (d) optional TUN/TAP local IPv4 address
2 (e) optional address of remote server,
3 (f) optional port of remote server,
4 (g) optional local address,
5 (h) optional local port, and
6 (i) optional TUN/TAP local IPv6 address.
*/
// Return only the assigned IP addresses in the UI
if (parts.length >= 7)
message = String.format(Locale.US, "%s %s", parts[1], parts[6]);
break;
}
while (message.endsWith(","))
message = message.substring(0, message.length() - 1);
String status = mLaststate;
if (status.equals("NOPROCESS"))
return message;
if (mLastStateresid == R.string.state_waitconnectretry) {
return c.getString(R.string.state_waitconnectretry, mLaststatemsg);
}
String prefix = c.getString(mLastStateresid);
if (mLastStateresid == R.string.unknown_state)
message = status + message;
if (message.length() > 0)
prefix += ": ";
return prefix + message;
}
public static enum LogLevel {
INFO(2),
ERROR(-2),
WARNING(1),
VERBOSE(3),
DEBUG(4);
protected int mValue;
LogLevel(int value) {
mValue = value;
}
public int getInt() {
return mValue;
}
public static LogLevel getEnumByValue(int value) {
switch (value) {
case 2:
return INFO;
case -2:
return ERROR;
case 1:
return WARNING;
case 3:
return VERBOSE;
case 4:
return DEBUG;
default:
return null;
}
}
}
static {
logbuffer = new LinkedList<>();
logListener = new Vector<>();
stateListener = new Vector<>();
logInformation();
}
public synchronized static void clearLog() {
logbuffer.clear();
for (LogListener li : logListener) {
li.onClear();
}
// Add the header after listeners clear their UI so it remains visible.
logInformation();
}
public synchronized static LogItem[] getlogbuffer() {
// The stoned way of java to return an array from a vector
// brought to you by eclipse auto complete
return logbuffer.toArray(new LogItem[logbuffer.size()]);
}
private static void logInformation() {
logInfo("DragonSSH XHTTP public example");
}
/**
* Listeners
*/
public interface LogListener {
void newLog(LogItem logItem);
void onClear();
}
public interface StateListener {
void updateState(String state, String logMessage, int localizedResId, ConnectionStatus level, Intent intent);
}
public synchronized static void addLogListener(LogListener ll) {
if (!logListener.contains(ll)) {
logListener.add(ll);
}
}
public synchronized static void removeLogListener(LogListener ll) {
if (logListener.contains(ll)) {
logListener.remove(ll);
}
}
public synchronized static void addStateListener(StateListener sl) {
if (!stateListener.contains(sl)) {
stateListener.add(sl);
if (mLaststate != null)
sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel, mLastIntent);
}
}
public synchronized static void removeStateListener(StateListener sl) {
if (stateListener.contains(sl)) {
stateListener.remove(sl);
}
}
/**
* State
*/
public static final String
SSH_CONECTANDO = "CONECTANDO",
SSH_AGUARDANDO_REDE = "AGUARDANDO",
SSH_AUTENTICANDO = "AUTENTICANDO",
SSH_CONECTADO = "CONECTADO",
SSH_DESCONECTADO = "DESCONECTADO",
SSH_RECONECTANDO = "RECONECTANDO",
SSH_INICIANDO = "INICIANDO",
SSH_PARANDO = "PARANDO";
public static int getLocalizedState(String state) {
switch (state) {
case SSH_CONECTANDO:
return R.string.state_connecting;
case SSH_AGUARDANDO_REDE:
return R.string.state_nonetwork;
case SSH_AUTENTICANDO:
return R.string.state_auth;
case "GET_CONFIG":
return R.string.state_get_config;
case "ASSIGN_IP":
return R.string.state_assign_ip;
case "ADD_ROUTES":
return R.string.state_add_routes;
case SSH_CONECTADO:
return R.string.state_connected;
case SSH_DESCONECTADO:
return R.string.state_disconnected;
case SSH_RECONECTANDO:
return R.string.state_reconnecting;
case SSH_INICIANDO:
return R.string.state_starting;
case SSH_PARANDO:
return R.string.state_stopping;
case "RESOLVE":
return R.string.state_resolve;
case "TCP_CONNECT":
return R.string.state_tcp_connect;
case "AUTH_PENDING":
return R.string.state_auth_pending;
default:
return R.string.unknown_state;
}
}
private static ConnectionStatus getLevel(String state) {
String[] noreplyet = {SSH_INICIANDO, SSH_CONECTANDO, SSH_AGUARDANDO_REDE, SSH_RECONECTANDO, "RESOLVE", "TCP_CONNECT"};
String[] reply = {SSH_AUTENTICANDO, "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES", "AUTH_PENDING"};
String[] connected = {SSH_CONECTADO};
String[] notconnected = {SSH_DESCONECTADO};
for (String x : noreplyet)
if (state.equals(x))
return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
for (String x : reply)
if (state.equals(x))
return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;
for (String x : connected)
if (state.equals(x))
return ConnectionStatus.LEVEL_CONNECTED;
for (String x : notconnected)
if (state.equals(x))
return ConnectionStatus.LEVEL_NOTCONNECTED;
return ConnectionStatus.UNKNOWN_LEVEL;
}
public static void updateStateString(String state, String msg) {
int rid = getLocalizedState(state);
ConnectionStatus level = getLevel(state);
updateStateString(state, msg, rid, level);
}
public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level)
{
updateStateString(state, msg, resid, level, null);
}
public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level, Intent intent) {
// Workound for OpenVPN doing AUTH and wait and being connected
// Simply ignore these state
if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED &&
(state.equals(SSH_AUTENTICANDO))) {
newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring XHTTP SSH status in CONNECTED state (%s->%s): %s", state, level.toString(), msg)));
return;
}
mLaststate = state;
mLaststatemsg = msg;
mLastStateresid = resid;
mLastLevel = level;
mLastIntent = intent;
for (StateListener sl : stateListener) {
sl.updateState(state, msg, resid, level, intent);
}
// State transition: (%s->%s): %s",state,level.toString(),msg)));
}
/**
* NewLog
*/
static void newLogItem(LogItem logItem) {
newLogItem(logItem, false);
}
synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
if (cachedLine) {
logbuffer.addFirst(logItem);
} else {
logbuffer.addLast(logItem);
}
// Keep a bounded history without deleting a large block of logs at once.
// The previous 1.5x threshold made thousands of older entries disappear
// suddenly, which looked like the log had been cropped.
while (logbuffer.size() > MAXLOGENTRIES)
logbuffer.removeFirst();
for (LogListener ll : logListener) {
ll.newLog(logItem);
}
}
/**
* Logger static methods
*/
public static void logException(String context, Exception e) {
logException(LogLevel.ERROR, context, e);
}
public static void logException(LogLevel ll, String context, Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
LogItem li;
if (context != null)
li = new LogItem(ll, String.format("%s: %s, %s", context, e.getMessage(), sw.toString()));
else
li = new LogItem(ll, String.format("Error: %s, %s", e.getMessage(), sw.toString()));
newLogItem(li);
}
public static void logException(Exception e) {
logException(LogLevel.ERROR, null, e);
}
public static void logInfo(String message) {
newLogItem(new LogItem(LogLevel.INFO, message));
}
public static void logDebug(String message) {
newLogItem(new LogItem(LogLevel.DEBUG, message));
}
public static void logInfo(int resourceId, Object... args) {
newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
}
public static void logDebug(int resourceId, Object... args) {
newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
}
public static void logError(String msg) {
newLogItem(new LogItem(LogLevel.ERROR, msg));
}
public static void logWarning(int resourceId, Object... args) {
newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
}
public static void logWarning(String msg) {
newLogItem(new LogItem(LogLevel.WARNING, msg));
}
public static void logError(int resourceId) {
newLogItem(new LogItem(LogLevel.ERROR, resourceId));
}
public static void logError(int resourceId, Object... args) {
newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
}
}
@@ -0,0 +1,296 @@
package com.dragonssh.xhttpdemo.core.tunnel;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Stable loopback TCP relay used between tun2socks and Trilead's dynamic SOCKS
* forwarder.
*
* <p>The Android VPN always points to this relay. During an SSH/XHTTP reconnect,
* only the relay's backend port changes. Keeping the public SOCKS listener alive
* prevents tun2socks from treating a transient SSH outage as a fatal local proxy
* failure and tearing down the VPN process.</p>
*/
final class PersistentSocksRelay {
private static final String LOOPBACK_HOST = "127.0.0.1";
private static final int ACCEPT_BACKLOG = 128;
private static final int MAX_CLIENTS = 256;
private static final int BACKEND_CONNECT_TIMEOUT_MS = 1500;
private static final int BACKEND_RETRY_DELAY_MS = 150;
private static final long BACKEND_WAIT_TIMEOUT_MS = 90_000L;
private static final int COPY_BUFFER_SIZE = 32 * 1024;
private final int listenPort;
private final Object backendLock = new Object();
private final Semaphore clientSlots = new Semaphore(MAX_CLIENTS);
private final Set<Socket> openSockets = Collections.synchronizedSet(new HashSet<Socket>());
private volatile boolean running;
private volatile int backendPort = -1;
private ServerSocket serverSocket;
private Thread acceptThread;
PersistentSocksRelay(int listenPort) {
this.listenPort = listenPort;
}
synchronized void start() throws IOException {
if (running) {
return;
}
ServerSocket server = new ServerSocket();
server.setReuseAddress(true);
server.bind(new InetSocketAddress(LOOPBACK_HOST, listenPort), ACCEPT_BACKLOG);
serverSocket = server;
running = true;
acceptThread = new Thread(this::acceptLoop, "vpn-socks-relay-accept");
acceptThread.setDaemon(true);
acceptThread.start();
}
boolean isRunning() {
return running;
}
int getListenPort() {
return listenPort;
}
void setBackendPort(int port) {
synchronized (backendLock) {
backendPort = port > 0 ? port : -1;
backendLock.notifyAll();
}
}
void clearBackendPort() {
setBackendPort(-1);
}
synchronized void stop() {
if (!running) {
return;
}
running = false;
clearBackendPort();
ServerSocket server = serverSocket;
serverSocket = null;
closeQuietly(server);
synchronized (openSockets) {
for (Socket socket : openSockets) {
closeQuietly(socket);
}
openSockets.clear();
}
Thread thread = acceptThread;
acceptThread = null;
if (thread != null) {
thread.interrupt();
}
}
private void acceptLoop() {
while (running) {
Socket client = null;
try {
ServerSocket server = serverSocket;
if (server == null) {
break;
}
client = server.accept();
client.setTcpNoDelay(true);
client.setKeepAlive(true);
if (!clientSlots.tryAcquire(1, TimeUnit.SECONDS)) {
closeQuietly(client);
continue;
}
final Socket accepted = client;
openSockets.add(accepted);
Thread worker = new Thread(() -> handleClient(accepted), "vpn-socks-relay-client");
worker.setDaemon(true);
worker.start();
} catch (SocketException e) {
if (running) {
SkStatus.logDebug("SOCKS relay accept failed: " + e.getMessage());
}
break;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
closeQuietly(client);
break;
} catch (IOException e) {
closeQuietly(client);
if (running) {
SkStatus.logDebug("SOCKS relay accept failed: " + e.getMessage());
}
}
}
}
private void handleClient(Socket client) {
Socket backend = null;
try {
backend = connectBackend();
if (backend == null) {
return;
}
openSockets.add(backend);
relayBidirectional(client, backend);
} catch (IOException ignored) {
// A backend channel closing during reconnect is expected.
} finally {
closeQuietly(client);
closeQuietly(backend);
openSockets.remove(client);
if (backend != null) {
openSockets.remove(backend);
}
clientSlots.release();
}
}
private Socket connectBackend() throws IOException {
final long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(BACKEND_WAIT_TIMEOUT_MS);
IOException lastError = null;
while (running && System.nanoTime() < deadline) {
int port = awaitBackendPort(deadline);
if (port <= 0) {
continue;
}
Socket socket = new Socket();
try {
socket.setTcpNoDelay(true);
socket.setKeepAlive(true);
socket.connect(new InetSocketAddress(LOOPBACK_HOST, port),
BACKEND_CONNECT_TIMEOUT_MS);
return socket;
} catch (IOException e) {
lastError = e;
closeQuietly(socket);
try {
Thread.sleep(BACKEND_RETRY_DELAY_MS);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
return null;
}
}
}
if (lastError != null) {
throw lastError;
}
return null;
}
private int awaitBackendPort(long deadlineNanos) {
synchronized (backendLock) {
while (running && backendPort <= 0) {
long remainingNanos = deadlineNanos - System.nanoTime();
if (remainingNanos <= 0) {
return -1;
}
long waitMillis = Math.max(1L,
Math.min(1000L, TimeUnit.NANOSECONDS.toMillis(remainingNanos)));
try {
backendLock.wait(waitMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return -1;
}
}
return backendPort;
}
}
private static void relayBidirectional(Socket left, Socket right) throws IOException {
final IOException[] upstreamError = new IOException[1];
Thread upstream = new Thread(() -> {
try {
copy(left, right);
} catch (IOException e) {
upstreamError[0] = e;
} finally {
closeQuietly(left);
closeQuietly(right);
}
}, "vpn-socks-relay-up");
upstream.setDaemon(true);
upstream.start();
try {
copy(right, left);
} finally {
closeQuietly(left);
closeQuietly(right);
try {
upstream.join(1000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (upstreamError[0] != null) {
throw upstreamError[0];
}
}
private static void copy(Socket source, Socket destination) throws IOException {
InputStream in = source.getInputStream();
OutputStream out = destination.getOutputStream();
byte[] buffer = new byte[COPY_BUFFER_SIZE];
int read;
while ((read = in.read(buffer)) >= 0) {
if (read == 0) {
continue;
}
out.write(buffer, 0, read);
}
}
private static void closeQuietly(Socket socket) {
if (socket == null) {
return;
}
try {
socket.close();
} catch (IOException ignored) {
}
}
private static void closeQuietly(ServerSocket socket) {
if (socket == null) {
return;
}
try {
socket.close();
} catch (IOException ignored) {
}
}
}
@@ -0,0 +1,138 @@
package com.dragonssh.xhttpdemo.core.tunnel;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import android.annotation.SuppressLint;
import android.content.Context;
import com.dragonssh.xhttpdemo.core.config.Settings;
import com.dragonssh.xhttpdemo.core.tunnel.vpn.SocketProtector;
import org.conscrypt.Conscrypt;
import java.security.SecureRandom;
import java.util.Objects;
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
static {
try {
Security.insertProviderAt(Conscrypt.newProvider(), 1);
} catch (NoClassDefFoundError e) {
e.printStackTrace();
}}
public SSLContext sslctx;
private Settings mConfig;
public TLSSocketFactory(Context context) throws KeyManagementException, NoSuchAlgorithmException {
mConfig = new Settings(context);
// For easier debugging purpose, trust all certificates
//dsp = ApplicationBase.getDefSharedPreferences();
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@SuppressLint({"TrustAllX509TrustManager"})
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@SuppressLint({"TrustAllX509TrustManager"})
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
sslctx = SSLContext.getInstance("TLS");
sslctx.init(null, trustAllCerts, new SecureRandom());
internalSSLSocketFactory = sslctx.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
Socket socket = enableTLSOnSocket(internalSSLSocketFactory.createSocket());
SocketProtector.protect(socket);
return socket;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
Socket socket = createSocket();
socket.connect(new InetSocketAddress(host, port));
return socket;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
Socket socket = createSocket();
socket.bind(new InetSocketAddress(localHost, localPort));
socket.connect(new InetSocketAddress(host, port));
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
Socket socket = createSocket();
socket.connect(new InetSocketAddress(host, port));
return socket;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
Socket socket = createSocket();
socket.bind(new InetSocketAddress(localAddress, localPort));
socket.connect(new InetSocketAddress(address, port));
return socket;
}
private Socket enableTLSOnSocket(Socket socket) {
if (socket instanceof SSLSocket) {
if (Objects.equals(mConfig.getPrivString(Settings.TLS12), "1")){
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"});
}else{
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"});
}
// ((SSLSocket) socket).setEnabledProtocols(((SSLSocket) socket).getSupportedProtocols());
}
return socket;
}
}
@@ -0,0 +1,29 @@
package com.dragonssh.xhttpdemo.core.tunnel;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.dragonssh.xhttpdemo.core.XHttpSshService;
/** Small public API used by the sample activity. */
public final class TunnelManagerHelper {
private TunnelManagerHelper() {}
public static void startXHttpSsh(Context context, String password) {
Intent intent = new Intent(context, XHttpSshService.class)
.putExtra(XHttpSshService.EXTRA_SSH_PASSWORD, password);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
public static void stopXHttpSsh(Context context) {
LocalBroadcastManager.getInstance(context).sendBroadcast(
new Intent(XHttpSshService.TUNNEL_SSH_STOP_SERVICE));
}
}
@@ -0,0 +1,29 @@
package com.dragonssh.xhttpdemo.core.tunnel;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Build;
/** Network check used by the connection and reconnect loops. */
public final class TunnelUtils {
private TunnelUtils() {}
public static boolean isNetworkOnline(Context context) {
ConnectivityManager manager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (manager == null) return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = manager.getActiveNetwork();
if (network == null) return false;
NetworkCapabilities capabilities = manager.getNetworkCapabilities(network);
return capabilities != null
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
android.net.NetworkInfo info = manager.getActiveNetworkInfo();
return info != null && info.isConnected();
}
}
@@ -0,0 +1,73 @@
package com.dragonssh.xhttpdemo.core.tunnel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* A virtual duplex {@link Socket} handed to the Trilead SSH client by
* {@link XHttpProxy}. Its input stream is the XHTTP downlink (the HTTP/2 GET
* response body) and its output stream is the XHTTP uplink (fed into the HTTP/2
* POST request body). The underlying HTTP/2 connection is managed by OkHttp, so
* this class only bridges the two streams and cleans up on close.
*
* <p>Trilead's {@code TransportManager} only calls {@link #getInputStream()},
* {@link #getOutputStream()}, {@link #close()} and — via {@code Connection} —
* {@link #setTcpNoDelay(boolean)} / {@link #setSoTimeout(int)}. The latter two
* are no-ops here because OkHttp owns the real sockets (read timeout is disabled
* for the streaming tunnel; a stalled SSH handshake is aborted by Trilead's own
* kex-timeout watchdog, which closes this socket).</p>
*/
final class XHttpBridgeSocket extends Socket {
private final InputStream in;
private final OutputStream out;
private final Runnable onClose;
private volatile boolean closed = false;
XHttpBridgeSocket(InputStream in, OutputStream out, Runnable onClose) {
this.in = in;
this.out = out;
this.onClose = onClose;
}
@Override
public InputStream getInputStream() {
return in;
}
@Override
public OutputStream getOutputStream() {
return out;
}
@Override
public void setTcpNoDelay(boolean on) { /* managed by OkHttp */ }
@Override
public void setSoTimeout(int timeout) { /* streaming tunnel: no read timeout */ }
@Override
public boolean isConnected() {
return !closed;
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public synchronized void close() throws IOException {
if (closed) {
return;
}
closed = true;
try { out.close(); } catch (Exception ignored) {}
try { in.close(); } catch (Exception ignored) {}
if (onClose != null) {
try { onClose.run(); } catch (Exception ignored) {}
}
}
}
@@ -0,0 +1,538 @@
package com.dragonssh.xhttpdemo.core.tunnel;
import android.content.Context;
import com.dragonssh.xhttpdemo.core.BuildConfig;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import com.dragonssh.xhttpdemo.core.tunnel.vpn.SocketProtector;
import com.trilead.ssh2.ProxyData;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.net.SocketFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.Call;
import okhttp3.Dispatcher;
import okhttp3.Dns;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* XHTTP (SplitHTTP) transport for the SSH tunnel, over <b>HTTP/2</b>.
*
* <p>Implements {@link ProxyData} and returns a
* {@link Socket} whose duplex byte stream is consumed by the Trilead SSH client.
* The SSH stream is carried over the server's native XHTTP inbound as a real
* Xray-style XHTTP client would:</p>
*
* <ul>
* <li><b>Downlink</b> — {@code GET {path}/{session}} : one long-lived streamed
* HTTP/2 response body = server&rarr;client SSH stream (stream-down).</li>
* <li><b>Uplink</b> — {@code POST {path}/{session}/{seq}} : one discrete POST
* per SSH packet, sequence-numbered (packet-up). The server reassembles by
* {@code seq}.</li>
* </ul>
*
* <p><b>Why this exact shape:</b> CDNs (e.g. Azion) buffer HTTP/1.1 and buffer
* streaming request bodies, but pass HTTP/2 responses and discrete POSTs through
* in real time. So the download must be HTTP/2 stream-down and the upload must be
* packet-up — anything else stalls the SSH handshake behind the CDN.</p>
*
* <p><b>Domain fronting:</b> OkHttp derives the TLS SNI from the URL host and the
* HTTP routing host from the request {@code Host} header, so the URL host
* is the SNI (e.g. a carrier "bug" domain) while {@code Host} is the CDN edge,
* and a custom {@link Dns} pins the connection to the configured server IP.</p>
*/
public class XHttpProxy implements ProxyData {
private static final String USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36";
private static final MediaType OCTET = MediaType.parse("application/octet-stream");
private static final long UPLINK_READ_TIMEOUT_SECONDS = 20;
private static final long UPLINK_CALL_TIMEOUT_SECONDS = 30;
private final String server; // IP/host to actually connect to (the "proxy")
private final int port;
private final boolean useTls;
private final String sni; // TLS SNI (URL host)
private final String path;
private final String host; // XHTTP Host used by the CDN/reverse proxy
private final Context mContext;
private volatile XHttpBridgeSocket bridge;
public XHttpProxy(String server, int port, boolean useTls, String sni, String path, String host, Context context) {
this.server = server;
this.port = port;
this.useTls = useTls;
this.sni = trimOrNull(sni);
this.path = path;
this.host = trimOrNull(host);
this.mContext = context;
}
@Override
public Socket openConnection(String hostname, int port, int connectTimeout, int readTimeout) throws IOException {
final String sessionId = UUID.randomUUID().toString().replace("-", "");
final String urlHost = (useTls && sni != null) ? sni : stripBrackets(server);
final String hostHeader = hostHeader();
final String reqPath = buildPath(sessionId);
final String scheme = useTls ? "https" : "http";
final HttpUrl sessionUrl = new HttpUrl.Builder()
.scheme(scheme)
.host(urlHost)
.port(this.port)
.encodedPath(reqPath)
.build();
if (BuildConfig.DEBUG) {
SkStatus.logInfo("XHTTP(h2): connect=" + server + ":" + this.port
+ " sni=" + (sni != null ? sni : "(none)") + " host=" + hostHeader
+ " url=" + sessionUrl);
} else {
SkStatus.logInfo(useTls
? "Iniciando XHTTP (TLS)..."
: "Iniciando XHTTP (sem TLS)...");
}
final OkHttpClient client = buildClient(connectTimeout);
try {
// --- Downlink: GET, streamed HTTP/2 response body (server -> client) ---
Request getReq = new Request.Builder()
.url(sessionUrl)
.header("Host", hostHeader)
.header("User-Agent", USER_AGENT)
.header("Accept", "*/*")
.get()
.build();
Response dlResp = client.newCall(getReq).execute();
if (!dlResp.isSuccessful() || dlResp.body() == null) {
int code = dlResp.code();
dlResp.close();
throw new IOException("XHTTP download rejected: HTTP " + code);
}
final Response downResp = dlResp;
final InputStream rawDownIn = dlResp.body().byteStream();
final InputStream downIn = new FilterInputStream(rawDownIn) {
private boolean terminationLogged;
private void logTermination(String detail) {
if (!terminationLogged) {
terminationLogged = true;
SkStatus.logError("XHTTP downlink stopped: " + detail);
}
}
@Override public int read() throws IOException {
try {
int value = super.read();
if (value < 0) logTermination("EOF");
return value;
} catch (IOException e) {
logTermination(e.getMessage() != null ? e.getMessage() : e.toString());
throw e;
}
}
@Override public int read(byte[] b, int off, int len) throws IOException {
try {
int count = super.read(b, off, len);
if (count < 0) logTermination("EOF");
return count;
} catch (IOException e) {
logTermination(e.getMessage() != null ? e.getMessage() : e.toString());
throw e;
}
}
};
// h2 is required to traverse CDNs; surface the negotiated protocol so a
// silent fallback to HTTP/1.1 (which CDNs buffer) is visible in the log.
SkStatus.logInfo("XHTTP proto=" + dlResp.protocol());
// --- Uplink: packet-up (one POST per SSH packet) ---
final PacketUpOutputStream upOut =
new PacketUpOutputStream(client, sessionUrl, hostHeader);
Runnable onClose = new Runnable() {
@Override public void run() {
upOut.markClosed();
try { downResp.close(); } catch (Exception ignored) {}
}
};
XHttpBridgeSocket b = new XHttpBridgeSocket(downIn, upOut, onClose);
upOut.setBridge(b);
this.bridge = b;
return b;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("XHTTP connect failed: " + e);
}
}
private OkHttpClient buildClient(int connectTimeout) throws IOException {
// Pin every lookup to the configured server IP (the "proxy"); the URL host
// is used only for SNI / routing, never for DNS.
final InetAddress[] pinned;
try {
pinned = InetAddress.getAllByName(stripBrackets(server));
} catch (UnknownHostException e) {
throw new IOException("XHTTP: cannot resolve server " + server + ": " + e);
}
Dns dns = new Dns() {
@Override public List<InetAddress> lookup(String hostname) {
return Arrays.asList(pinned);
}
};
// This client only needs one stream-down GET plus one packet-up POST at a
// time. Keep a small bounded dispatcher so a transport bug cannot create an
// unbounded HTTP queue inside the VPN process.
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(8);
dispatcher.setMaxRequestsPerHost(8);
OkHttpClient.Builder b = new OkHttpClient.Builder()
.dns(dns)
.dispatcher(dispatcher)
.socketFactory(new ProtectedSocketFactory())
.connectTimeout(connectTimeout > 0 ? connectTimeout : 15000, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS) // streaming download: no read timeout
.writeTimeout(30, TimeUnit.SECONDS)
.callTimeout(0, TimeUnit.MILLISECONDS)
.pingInterval(25, TimeUnit.SECONDS) // keep the h2 connection alive
.retryOnConnectionFailure(false);
if (useTls) {
X509TrustManager trustAll = new X509TrustManager() {
@Override public void checkClientTrusted(java.security.cert.X509Certificate[] c, String a) {}
@Override public void checkServerTrusted(java.security.cert.X509Certificate[] c, String a) {}
@Override public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
};
try {
b.sslSocketFactory(new TLSSocketFactory(mContext), trustAll);
} catch (Exception e) {
throw new IOException("XHTTP: TLS setup failed: " + e);
}
b.hostnameVerifier((h, s) -> true);
b.protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1));
} else {
// Plaintext is only for direct (no-CDN) testing, where HTTP/1.1 streams
// fine; h2 is only required to defeat CDN buffering over TLS.
b.protocols(Arrays.asList(Protocol.HTTP_1_1));
}
return b.build();
}
/** SocketFactory that excludes control sockets from the VPN (avoids routing loops). */
private static final class ProtectedSocketFactory extends SocketFactory {
@Override public Socket createSocket() throws IOException {
Socket s = new Socket();
boolean vpnReady = SocketProtector.isVpnServiceReady();
boolean protectedOk = SocketProtector.protect(s);
/*
* Some Samsung/vendor builds return false here for an unconnected
* socket even though the VPN is valid. Do not destroy the reconnect:
* the active VPN was established with this package UID excluded, so
* this socket is already outside the TUN at the routing-policy layer.
*/
if (vpnReady && !protectedOk && !SocketProtector.isProcessBypassConfigured()) {
try { s.close(); } catch (IOException ignored) {}
throw new IOException("XHTTP control socket has no VPN bypass");
}
if (vpnReady && !protectedOk) {
SkStatus.logDebug("XHTTP protect(Socket) returned false; using package UID VPN bypass");
}
return s;
}
@Override public Socket createSocket(String host, int port) { throw new UnsupportedOperationException(); }
@Override public Socket createSocket(String host, int port, InetAddress lh, int lp) { throw new UnsupportedOperationException(); }
@Override public Socket createSocket(InetAddress host, int port) { throw new UnsupportedOperationException(); }
@Override public Socket createSocket(InetAddress host, int port, InetAddress lh, int lp) { throw new UnsupportedOperationException(); }
}
/**
* Uplink as sequence-numbered POSTs (packet-up): {@code POST {session}/{seq}}.
*
* <p>Sent <b>strictly in order, single-flight</b> — one POST fully completes
* before the next starts. The server sends its {@code 200} only after pushing
* each packet to the SSH reader, so waiting for each response guarantees the
* server receives seq 0,1,2,… in order and its reorder buffer never grows
* (concurrent/out-of-order POSTs used to overflow it and kill the tunnel).</p>
*
* <p>SSH writes just append to a buffer; a single worker thread drains it,
* coalescing whatever accumulated during the previous round-trip into one POST
* (up to {@link #MAX_POST}) so throughput isn't limited to one packet per RTT.
* Writers block once the buffer hits {@link #MAX_BUFFER} (backpressure).</p>
*/
private static final class PacketUpOutputStream extends OutputStream {
private static final int MAX_POST = 900 * 1024; // stay under server per-POST cap (1MB)
private static final int MAX_BUFFER = 8 * 1024 * 1024; // backpressure ceiling
private static final long MAX_BACKPRESSURE_WAIT_MS = 35_000;
private final OkHttpClient uplinkClient;
private final HttpUrl base; // .../{session}
private final String hostHeader;
private final Object lock = new Object();
private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
private long seq = 0; // touched only by the worker thread
private volatile IOException error;
private volatile boolean closed;
private volatile XHttpBridgeSocket bridge;
private volatile Call activeCall;
private final Thread worker;
PacketUpOutputStream(OkHttpClient client, HttpUrl base, String hostHeader) {
// The stream-down call must have no read/call timeout, but packet-up POSTs
// absolutely must. A CDN can keep a POST open forever without returning an
// error; with the old shared no-timeout client the SSH writer blocked, the
// ping thread blocked behind it, and automatic reconnect never ran.
this.uplinkClient = client.newBuilder()
.readTimeout(UPLINK_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.callTimeout(UPLINK_CALL_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.build();
this.base = base;
this.hostHeader = hostHeader;
this.worker = new Thread(this::runUplink, "xhttp-uplink");
this.worker.setDaemon(true);
this.worker.start();
}
void setBridge(XHttpBridgeSocket b) { this.bridge = b; }
void markClosed() {
Call call;
synchronized (lock) {
closed = true;
buf.reset();
call = activeCall;
lock.notifyAll();
}
if (call != null) {
call.cancel();
}
}
@Override public void write(int b) throws IOException {
write(new byte[]{(byte) b}, 0, 1);
}
@Override public void write(byte[] b, int off, int len) throws IOException {
if (len <= 0) {
return;
}
IOException timeoutFailure = null;
synchronized (lock) {
checkState();
// Backpressure: don't let the buffer grow unbounded if SSH out-runs the link.
long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(MAX_BACKPRESSURE_WAIT_MS);
while (buf.size() >= MAX_BUFFER && error == null && !closed) {
long remainingNanos = deadlineNanos - System.nanoTime();
if (remainingNanos <= 0) {
timeoutFailure = new IOException("XHTTP uplink backpressure timed out");
break;
}
waitOn(remainingNanos);
}
if (timeoutFailure == null) {
checkState();
buf.write(b, off, len);
lock.notifyAll();
}
}
if (timeoutFailure != null) {
fail(timeoutFailure);
throw timeoutFailure;
}
}
@Override public void flush() throws IOException {
synchronized (lock) {
checkState();
lock.notifyAll(); // the worker already sends promptly; nudge it
}
}
@Override public void close() {
markClosed();
}
private void runUplink() {
while (true) {
byte[] data;
synchronized (lock) {
while (buf.size() == 0 && !closed && error == null) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
fail(new IOException("XHTTP uplink worker interrupted", e));
return;
}
}
if (error != null || closed) {
return;
}
byte[] all = buf.toByteArray();
buf.reset();
if (all.length > MAX_POST) {
data = Arrays.copyOfRange(all, 0, MAX_POST);
buf.write(all, MAX_POST, all.length - MAX_POST); // keep remainder for next round
} else {
data = all;
}
lock.notifyAll(); // release any writer blocked on backpressure
}
if (!sendPacket(data)) {
return; // error already recorded and bridge closed
}
}
}
/** Sends one packet POST synchronously (single-flight) so ordering is guaranteed. */
private boolean sendPacket(byte[] data) {
if (closed) {
return false;
}
long s = seq++;
HttpUrl u = base.newBuilder().addPathSegment(Long.toString(s)).build();
Request req = new Request.Builder()
.url(u)
.header("Host", hostHeader)
.header("User-Agent", USER_AGENT)
.post(RequestBody.create(OCTET, data))
.build();
Call call = uplinkClient.newCall(req);
activeCall = call;
if (closed) {
call.cancel();
activeCall = null;
return false;
}
try (Response r = call.execute()) {
if (!r.isSuccessful()) {
fail(new IOException("XHTTP uplink seq=" + s + " HTTP " + r.code()));
return false;
}
return true;
} catch (IOException e) {
if (closed) {
return false;
}
fail(new IOException("XHTTP uplink seq=" + s + " failed: " + e.getMessage(), e));
return false;
} finally {
if (activeCall == call) {
activeCall = null;
}
}
}
private void fail(IOException e) {
boolean first;
synchronized (lock) {
first = error == null;
if (first) {
error = e;
}
lock.notifyAll();
}
if (first) {
SkStatus.logError("XHTTP uplink stopped: " + e.getMessage());
}
XHttpBridgeSocket b = bridge;
if (b != null) {
try { b.close(); } catch (Exception ignored) {}
}
}
private void waitOn(long remainingNanos) throws IOException {
try {
long waitMillis = remainingNanos / 1_000_000L;
int waitNanos = (int) (remainingNanos % 1_000_000L);
lock.wait(waitMillis, waitNanos);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("XHTTP uplink interrupted");
}
}
private void checkState() throws IOException {
if (error != null) {
throw error;
}
if (closed) {
throw new IOException("XHTTP uplink closed");
}
}
}
private String buildPath(String sessionId) {
String p = (path == null || path.trim().isEmpty()) ? "/" : path.trim();
if (!p.startsWith("/")) {
p = "/" + p;
}
while (p.endsWith("/")) {
p = p.substring(0, p.length() - 1);
}
return p + "/" + sessionId;
}
private String hostHeader() {
if (host != null) {
return host;
}
if (sni != null) {
return sni;
}
return stripBrackets(server);
}
@Override
public void close() {
XHttpBridgeSocket b = this.bridge;
if (b != null) {
try { b.close(); } catch (IOException ignored) {}
}
}
private static String trimOrNull(String s) {
if (s == null) return null;
s = s.trim();
return s.isEmpty() ? null : s;
}
private static String stripBrackets(String h) {
if (h == null) return null;
if (h.startsWith("[") && h.endsWith("]")) {
return h.substring(1, h.length() - 1);
}
return h;
}
}
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2012-2016 Arne Schwabe
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
*/
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import java.net.InetAddress;
import java.util.Locale;
public class CIDRIP {
String mIp;
int len;
public CIDRIP(String address, int prefix_length) {
len = prefix_length;
mIp = address;
}
public CIDRIP(String address, long prefix_length) {
len = (int) prefix_length;
mIp = address;
}
@Override
public String toString() {
if (InetAddressUtils.isIPv4Address(mIp)) {
return String.format(Locale.ENGLISH, "%s/%d", mIp, len);
} else if (InetAddressUtils.isIPv6Address(mIp)) {
return String.format(Locale.ENGLISH, "%s/%d", mIp, (long) len);
} else {
return mIp;
}
}
static class InetAddressUtils {
private static final int INET4_ADDRESS_LENGTH = 4;
private static final int INET6_ADDRESS_LENGTH = 16;
public static boolean isIPv4Address(String input) {
try {
InetAddress inetAddress = InetAddress.getByName(input);
return inetAddress.getAddress().length == INET4_ADDRESS_LENGTH;
} catch (Exception e) {
return false;
}
}
public static boolean isIPv6Address(String input) {
try {
InetAddress inetAddress = InetAddress.getByName(input);
return inetAddress.getAddress().length == INET6_ADDRESS_LENGTH;
} catch (Exception e) {
return false;
}
}
}
static long getInt(String ipaddr) {
try {
InetAddress inetAddress = InetAddress.getByName(ipaddr);
byte[] addressBytes = inetAddress.getAddress();
long ip = 0;
if (InetAddressUtils.isIPv4Address(ipaddr)) {
ip += (addressBytes[0] & 0xFFL) << 24;
ip += (addressBytes[1] & 0xFFL) << 16;
ip += (addressBytes[2] & 0xFFL) << 8;
ip += addressBytes[3] & 0xFFL;
} else if (InetAddressUtils.isIPv6Address(ipaddr)) {
for (byte b : addressBytes) {
ip = (ip << 8) | (b & 0xFFL);
}
}
return ip;
} catch (Exception e) {
return 0;
}
}
public long getInt() {
return getInt(mIp);
}
}
@@ -0,0 +1,369 @@
/*
* Copyright (c) 2012-2016 Arne Schwabe
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
*/
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import android.os.Build;
import androidx.annotation.NonNull;
import java.math.BigInteger;
import java.net.Inet6Address;
import java.util.Collection;
import java.util.Locale;
import java.util.PriorityQueue;
import java.util.TreeSet;
import java.util.Vector;
import com.dragonssh.xhttpdemo.core.BuildConfig;
public class NetworkSpace {
static void assertTrue(boolean f)
{
if (!f)
throw new IllegalStateException();
}
public static class IpAddress implements Comparable<IpAddress> {
private BigInteger netAddress;
public int networkMask;
private boolean included;
private boolean isV4;
private BigInteger firstAddress;
private BigInteger lastAddress;
/**
* sorts the networks with following criteria:
* 1. compares first 1 of the network
* 2. smaller networks are returned as smaller
*/
@Override
public int compareTo(@NonNull IpAddress another) {
int comp = getFirstAddress().compareTo(another.getFirstAddress());
if (comp != 0)
return comp;
if (networkMask > another.networkMask)
return -1;
else if (another.networkMask == networkMask)
return 0;
else
return 1;
}
/**
* Warning ignores the included integer
*
* @param o the object to compare this instance with.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof IpAddress))
return super.equals(o);
IpAddress on = (IpAddress) o;
return (networkMask == on.networkMask) && on.getFirstAddress().equals(getFirstAddress());
}
public IpAddress(CIDRIP ip, boolean include) {
included = include;
netAddress = BigInteger.valueOf(ip.getInt());
networkMask = ip.len;
isV4 = true;
}
public IpAddress(Inet6Address address, int mask, boolean include) {
networkMask = mask;
included = include;
int s = 128;
netAddress = BigInteger.ZERO;
for (byte b : address.getAddress()) {
s -= 8;
netAddress = netAddress.add(BigInteger.valueOf((b & 0xFF)).shiftLeft(s));
}
}
public BigInteger getLastAddress() {
if (lastAddress == null)
lastAddress = getMaskedAddress(true);
return lastAddress;
}
public BigInteger getFirstAddress() {
if (firstAddress == null)
firstAddress = getMaskedAddress(false);
return firstAddress;
}
private BigInteger getMaskedAddress(boolean one) {
BigInteger numAddress = netAddress;
int numBits;
if (isV4) {
numBits = 32 - networkMask;
} else {
numBits = 128 - networkMask;
}
for (int i = 0; i < numBits; i++) {
if (one)
numAddress = numAddress.setBit(i);
else
numAddress = numAddress.clearBit(i);
}
return numAddress;
}
@Override
public String toString() {
//String in = included ? "+" : "-";
if (isV4)
return String.format(Locale.US, "%s/%d", getIPv4Address(), networkMask);
else
return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask);
}
IpAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) {
this.netAddress = baseAddress;
this.networkMask = mask;
this.included = included;
this.isV4 = isV4;
}
public IpAddress[] split() {
IpAddress firstHalf = new IpAddress(getFirstAddress(), networkMask + 1, included, isV4);
IpAddress secondHalf = new IpAddress(firstHalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4);
if (BuildConfig.DEBUG)
assertTrue(secondHalf.getLastAddress().equals(getLastAddress()));
return new IpAddress[]{firstHalf, secondHalf};
}
public String getIPv4Address() {
if (BuildConfig.DEBUG) {
assertTrue(isV4);
assertTrue(netAddress.longValue() <= 0xffffffffl);
assertTrue(netAddress.longValue() >= 0);
}
long ip = netAddress.longValue();
return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256);
}
public String getIPv6Address() {
if (BuildConfig.DEBUG) assertTrue(!isV4);
BigInteger r = netAddress;
String ipv6str = null;
boolean lastPart = true;
while (r.compareTo(BigInteger.ZERO) == 1) {
long part = r.mod(BigInteger.valueOf(0x10000)).longValue();
if (ipv6str != null || part != 0) {
if (ipv6str == null && !lastPart)
ipv6str = ":";
if (lastPart)
ipv6str = String.format(Locale.US, "%x", part, ipv6str);
else
ipv6str = String.format(Locale.US, "%x:%s", part, ipv6str);
}
r = r.shiftRight(16);
lastPart = false;
}
if (ipv6str == null)
return "::";
return ipv6str;
}
public boolean containsNet(IpAddress network) {
// this.first >= net.first && this.last <= net.last
BigInteger ourFirst = getFirstAddress();
BigInteger ourLast = getLastAddress();
BigInteger netFirst = network.getFirstAddress();
BigInteger netLast = network.getLastAddress();
boolean a = ourFirst.compareTo(netFirst) != 1;
boolean b = ourLast.compareTo(netLast) != -1;
return a && b;
}
}
TreeSet<IpAddress> mIpAddresses = new TreeSet<IpAddress>();
public Collection<IpAddress> getNetworks(boolean included) {
Vector<IpAddress> ips = new Vector<IpAddress>();
for (IpAddress ip : mIpAddresses) {
if (ip.included == included)
ips.add(ip);
}
return ips;
}
public void clear() {
mIpAddresses.clear();
}
public void addIP(CIDRIP cidrIp, boolean include) {
mIpAddresses.add(new IpAddress(cidrIp, include));
}
public void addIPSplit(CIDRIP cidrIp, boolean include) {
IpAddress newIP = new IpAddress(cidrIp, include);
IpAddress[] splitIps = newIP.split();
for (IpAddress split : splitIps)
mIpAddresses.add(split);
}
public void addIPv6(Inet6Address address, int mask, boolean included) {
mIpAddresses.add(new IpAddress(address, mask, included));
}
TreeSet<IpAddress> generateIPList() {
PriorityQueue<IpAddress> networks = new PriorityQueue<IpAddress>(mIpAddresses);
TreeSet<IpAddress> ipsDone = new TreeSet<IpAddress>();
IpAddress currentNet = networks.poll();
if (currentNet == null)
return ipsDone;
while (currentNet != null) {
// Check if it and the next of it are compatible
IpAddress nextNet = networks.poll();
if (BuildConfig.DEBUG) assertTrue(currentNet!=null);
if (nextNet == null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) {
// Everything good, no overlapping nothing to do
ipsDone.add(currentNet);
currentNet = nextNet;
} else {
// This network is smaller or equal to the next but has the same base address
if (currentNet.getFirstAddress().equals(nextNet.getFirstAddress()) && currentNet.networkMask >= nextNet.networkMask) {
if (currentNet.included == nextNet.included) {
// Included in the next next and same type
// Simply forget our current network
currentNet = nextNet;
} else {
// our currentNet is included in next and types differ. Need to split the next network
IpAddress[] newNets = nextNet.split();
// TODO: The contains method of the Priority is stupid linear search
// First add the second half to keep the order in networks
if (!networks.contains(newNets[1]))
networks.add(newNets[1]);
if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) {
if (BuildConfig.DEBUG)
assertTrue(newNets[0].networkMask == currentNet.networkMask);
// Don't add the lower half that would conflict with currentNet
} else {
if (!networks.contains(newNets[0]))
networks.add(newNets[0]);
}
// Keep currentNet as is
}
} else {
if (BuildConfig.DEBUG) {
assertTrue(currentNet.networkMask < nextNet.networkMask);
assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1);
assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1);
}
// This network is bigger than the next and last ip of current >= next
//noinspection StatementWithEmptyBody
if (currentNet.included == nextNet.included) {
// Next network is in included in our network with the same type,
// simply ignore the next and move on
} else {
// We need to split our network
IpAddress[] newNets = currentNet.split();
if (newNets[1].networkMask == nextNet.networkMask) {
if (BuildConfig.DEBUG) {
assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress()));
assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress()));
// split second equal the next network, do not add it
}
networks.add(nextNet);
} else {
// Add the smaller network first
networks.add(newNets[1]);
networks.add(nextNet);
}
currentNet = newNets[0];
}
}
}
}
return ipsDone;
}
public Collection<IpAddress> getPositiveIPList() {
TreeSet<IpAddress> ipsSorted = generateIPList();
Vector<IpAddress> ips = new Vector<IpAddress>();
for (IpAddress ia : ipsSorted) {
if (ia.included)
ips.add(ia);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
// Include postive routes from the original set under < 4.4 since these might overrule the local
// network but only if no smaller negative route exists
for (IpAddress origIp : mIpAddresses) {
if (!origIp.included)
continue;
// The netspace exists
if (ipsSorted.contains(origIp))
continue;
boolean skipIp = false;
// If there is any smaller net that is excluded we may not add the positive route back
for (IpAddress calculatedIp : ipsSorted) {
if (!calculatedIp.included && origIp.containsNet(calculatedIp)) {
skipIp = true;
break;
}
}
if (skipIp)
continue;
// It is safe to include the IP
ips.add(origIp);
}
}
return ips;
}
}
@@ -0,0 +1,143 @@
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import java.io.File;
import android.content.Context;
import java.io.FileNotFoundException;
import java.io.IOException;
import com.dragonssh.xhttpdemo.core.R;
import com.dragonssh.xhttpdemo.core.util.StreamGobbler;
import com.dragonssh.xhttpdemo.core.util.FileUtils;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import com.dragonssh.xhttpdemo.core.util.CustomNativeLoader;
public class Pdnsd extends Thread {
private final static String TAG = "PdnsdThread";
private final static String PDNSD_SERVER = "server {\n label= \"%1$s\";\n ip = %2$s;\n port = %3$d;\n uptest = none;\n }\n";
private final static String PDNSD_SERVER_TEST = "server {\n label= \"%1$s\";\n ip = %2$s;\n port = %3$d;\n reject = ::/0;\n reject_policy = negate;\n reject_recursively = on;\n timeout = 5;\n }\n";
private final static String PDNSD_BIN = "libpdnsd";
private OnPdnsdListener mListener;
public interface OnPdnsdListener {
public void onStart();
public void onStop();
}
private Process mProcess;
private File filePdnsd;
private Context mContext;
private String[] mDnsHosts;
private int mDnsPort;
private String mPdnsdHost;
private int mPdnsdPort;
public Pdnsd(Context context, String[] dnsHosts, int dnsPort, String pdnsdHost, int pdnsdPort) {
mContext = context;
mDnsHosts = dnsHosts;
mDnsPort = dnsPort;
mPdnsdHost = pdnsdHost;
mPdnsdPort = pdnsdPort;
}
@Override
public void run() {
if (mListener != null) {
mListener.onStart();
}
try {
filePdnsd = CustomNativeLoader.loadNativeBinary(mContext, PDNSD_BIN, new File(mContext.getFilesDir(), PDNSD_BIN));
if (filePdnsd == null) {
throw new IOException("Pdnsd binary not found");
}
File fileConf = makePdnsdConf(mContext.getFilesDir(), mDnsHosts, mDnsPort, mPdnsdHost, mPdnsdPort);
String cmdString = filePdnsd.getCanonicalPath() + " -v9 -c " + fileConf.toString();
mProcess = Runtime.getRuntime().exec(cmdString);
StreamGobbler.OnLineListener onLineListener = new StreamGobbler.OnLineListener(){
@Override
public void onLine(String log){
SkStatus.logDebug("Pdnsd: " + log);
}
};
StreamGobbler stdoutGobbler = new StreamGobbler(mProcess.getInputStream(), onLineListener);
StreamGobbler stderrGobbler = new StreamGobbler(mProcess.getErrorStream(), onLineListener);
stdoutGobbler.start();
stderrGobbler.start();
mProcess.waitFor();
} catch (IOException e) {
SkStatus.logException("Pdnsd Error", e);
} catch(Exception e){
SkStatus.logDebug("Pdnsd Error: " + e);
}
mProcess = null;
if (mListener != null) {
mListener.onStop();
}
}
@Override
public synchronized void interrupt()
{
super.interrupt();
if (mProcess != null)
mProcess.destroy();
try {
if (filePdnsd != null)
VpnUtils.killProcess(filePdnsd);
} catch(Exception e) {}
mProcess = null;
filePdnsd = null;
}
private File makePdnsdConf(File fileDir, String[] dnsHosts, int dnsPort, String pdnsdHost, int pdnsdPort) throws FileNotFoundException, IOException {
String content = FileUtils.readFromRaw(mContext, R.raw.pdnsd_local);
// dns servers
StringBuilder server_dns = new StringBuilder();
for (int i = 0; i < dnsHosts.length; i++){
String dnsHost = dnsHosts[i];
server_dns.append(String.format(PDNSD_SERVER, "server" + Integer.toString(i+1), dnsHost, dnsPort));
}
String conf = String.format(content, server_dns.toString(), fileDir.getCanonicalPath(), pdnsdHost, pdnsdPort);
File f = new File(fileDir,"pdnsd.conf");
if (f.exists()) {
f.delete();
}
FileUtils.saveTextFile(f, conf);
File cache = new File(fileDir,"pdnsd.cache");
if (!cache.exists()) {
try {
cache.createNewFile();
} catch (Exception e) {}
}
return f;
}
public void setOnPdnsdListener(OnPdnsdListener listener){
this.mListener = listener;
}
}
@@ -0,0 +1,167 @@
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import android.net.VpnService;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.net.DatagramSocket;
import java.net.Socket;
/**
* Centralized helper to ensure control sockets (SSH / proxy / TLS) are excluded
* from the VPN routing.
*
* <p>When a VPN is established and a control socket is not protected, Android
* will route that socket into the VPN itself, creating a loop (VPN -> SOCKS ->
* VPN). This is especially visible with IPv6, where ::/0 routing is commonly
* installed.
*/
public final class SocketProtector {
private static volatile VpnService sVpnService;
// True only after the active VPN interface has been established with this
// application UID excluded through Builder.addDisallowedApplication().
// In that mode every control-plane socket created by this process already
// uses the underlying network, even on devices where protect(Socket)
// incorrectly returns false during a reconnect.
private static volatile boolean sProcessBypassConfigured;
// Sockets created before the VpnService instance is available.
// We keep weak refs to avoid leaking in edge cases.
private static final Object sLock = new Object();
private static final List<WeakReference<Object>> sPending = new ArrayList<>();
private SocketProtector() {
}
public static void setVpnService(VpnService vpnService) {
sVpnService = vpnService;
if (vpnService != null) {
flushPending(vpnService);
}
}
/**
* Clears the active VpnService only if it is still the same owner.
* This prevents a stopped XRAY service from clearing the SSH VPN service
* after the user switches modes.
*/
public static void clearVpnService(VpnService vpnService) {
if (vpnService == null) {
return;
}
synchronized (sLock) {
if (sVpnService == vpnService) {
sVpnService = null;
sProcessBypassConfigured = false;
sPending.clear();
}
}
}
public static boolean isVpnServiceReady() {
return sVpnService != null;
}
public static void setProcessBypassConfigured(boolean configured) {
sProcessBypassConfigured = configured;
}
public static boolean isProcessBypassConfigured() {
return sProcessBypassConfigured;
}
public static boolean protect(Socket socket) {
if (socket == null) return false;
final VpnService svc = sVpnService;
if (svc == null) {
enqueue(socket);
return false;
}
return doProtect(svc, socket);
}
public static boolean protect(DatagramSocket socket) {
if (socket == null) return false;
final VpnService svc = sVpnService;
if (svc == null) {
enqueue(socket);
return false;
}
return doProtect(svc, socket);
}
/**
* Protect a raw native socket file descriptor.
*
* Use this from gomobile/JNI callbacks before the native code connects
* the socket. Raw FDs cannot be safely queued: the native side may connect
* or close/reuse the descriptor before the VPN service becomes available.
*/
public static boolean protectFd(int fd) {
if (fd < 0) return false;
final VpnService svc = sVpnService;
if (svc == null) return false;
return doProtectFd(svc, fd);
}
private static void enqueue(Object socket) {
synchronized (sLock) {
// Avoid unlimited growth if protect() is called repeatedly.
if (sPending.size() > 128) {
// Drop oldest.
sPending.remove(0);
}
sPending.add(new WeakReference<>(socket));
}
}
private static void flushPending(VpnService svc) {
final List<Object> toProtect = new ArrayList<>();
synchronized (sLock) {
for (WeakReference<Object> ref : sPending) {
Object obj = ref.get();
if (obj != null) {
toProtect.add(obj);
}
}
sPending.clear();
}
for (Object obj : toProtect) {
if (obj instanceof Socket) {
doProtect(svc, (Socket) obj);
} else if (obj instanceof DatagramSocket) {
doProtect(svc, (DatagramSocket) obj);
}
}
}
private static boolean doProtect(VpnService svc, Socket socket) {
try {
return svc.protect(socket);
} catch (Throwable ignored) {
return false;
}
}
private static boolean doProtect(VpnService svc, DatagramSocket socket) {
try {
return svc.protect(socket);
} catch (Throwable ignored) {
return false;
}
}
private static boolean doProtectFd(VpnService svc, int fd) {
try {
return svc.protect(fd);
} catch (Throwable ignored) {
return false;
}
}
}
@@ -0,0 +1,293 @@
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import android.os.ParcelFileDescriptor;
import java.io.IOException;
import java.io.File;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import java.io.FileDescriptor;
import android.content.Context;
import com.dragonssh.xhttpdemo.core.util.StreamGobbler;
import androidx.core.content.ContextCompat;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import com.dragonssh.xhttpdemo.core.util.CustomNativeLoader;
public class Tun2Socks extends Thread implements StreamGobbler.OnLineListener {
private static final String TAG = Tun2Socks.class.getSimpleName();
private static final String TUN2SOCKS_BIN = "libtun2socks";
private OnTun2SocksListener mListener;
public interface OnTun2SocksListener {
public void onStart();
public void onStop();
}
private Process tun2SocksProcess;
private ParcelFileDescriptor mVpnInterfaceFileDescriptor;
private int mVpnInterfaceMTU;
/**
* IPv4 address assigned to the VPN interface. Historically only a single
* IPv4 address was supported. To support IPv6 natively, a separate
* {@code mVpnIp6Address} field has been added. Existing code that
* constructs {@link Tun2Socks} without specifying an IPv6 address will
* continue to work the IPv6 address will simply remain {@code null} and
* no {@code --netif-ip6addr} argument will be passed to the underlying
* tun2socks binary.
*/
private String mVpnIpAddress;
/**
* IPv6 address assigned to the VPN interface. This may be {@code null}
* when only IPv4 connectivity is required. When provided, the
* constructor will append a {@code --netif-ip6addr} flag when spawning
* the tun2socks process.
*/
private String mVpnIp6Address;
private String mVpnNetMask;
private String mSocksServerAddress;
private String mUdpgwServerAddress;
private String mDnsResolverAddress;
private boolean mUdpgwTransparentDNS;
private Context mContext;
private File fileTun2Socks;
/**
* Construct a {@link Tun2Socks} instance. This constructor has been
* updated to accept an optional IPv6 address. If the provided
* {@code vpnIp6Address} is {@code null} or empty, only an IPv4 address will
* be configured on the tun interface. To maintain backwards
* compatibility with existing callers, an overloaded constructor without
* the {@code vpnIp6Address} parameter is provided below.
*
* @param context the context used to resolve resources
* @param vpnInterfaceFileDescriptor file descriptor of the VPN interface
* @param vpnInterfaceMTU MTU of the VPN interface
* @param vpnIpAddress IPv4 address assigned to the interface
* @param vpnIp6Address IPv6 address assigned to the interface (may be null)
* @param vpnNetMask network mask for the IPv4 address
* @param socksServerAddress address of the SOCKS proxy
* @param udpgwServerAddress address of the UDP gateway (may be null)
* @param dnsResolverAddress address of the DNS resolver (may be null)
* @param udpgwTransparentDNS whether to enable transparent DNS over UDP
*/
public Tun2Socks(Context context, ParcelFileDescriptor vpnInterfaceFileDescriptor, int vpnInterfaceMTU,
String vpnIpAddress, String vpnIp6Address, String vpnNetMask,
String socksServerAddress, String udpgwServerAddress,
String dnsResolverAddress, boolean udpgwTransparentDNS) {
mContext = context;
mVpnInterfaceFileDescriptor = vpnInterfaceFileDescriptor;
mVpnInterfaceMTU = vpnInterfaceMTU;
mVpnIpAddress = vpnIpAddress;
mVpnIp6Address = vpnIp6Address;
mVpnNetMask = vpnNetMask;
mSocksServerAddress = socksServerAddress;
mUdpgwServerAddress = udpgwServerAddress;
mDnsResolverAddress = dnsResolverAddress;
mUdpgwTransparentDNS = udpgwTransparentDNS;
}
/**
* Backwards compatible constructor. Invokes the full constructor without
* assigning an IPv6 address.
*/
public Tun2Socks(Context context, ParcelFileDescriptor vpnInterfaceFileDescriptor, int vpnInterfaceMTU,
String vpnIpAddress, String vpnNetMask, String socksServerAddress,
String udpgwServerAddress, String dnsResolverAddress, boolean udpgwTransparentDNS) {
this(context, vpnInterfaceFileDescriptor, vpnInterfaceMTU, vpnIpAddress, null, vpnNetMask,
socksServerAddress, udpgwServerAddress, dnsResolverAddress, udpgwTransparentDNS);
}
@Override
public void run() {
if (mListener != null) {
mListener.onStart();
}
try {
StringBuilder cmd = new StringBuilder();
//File fileTun2Socks = CustomNativeLoader.loadExecutableBinary(mContext, "libtun2socks.so");
fileTun2Socks = CustomNativeLoader.loadNativeBinary(mContext, TUN2SOCKS_BIN, new File(mContext.getFilesDir(),TUN2SOCKS_BIN));
if (fileTun2Socks == null){
throw new IOException("Tun2Socks binary not found");
}
if (mVpnInterfaceFileDescriptor != null){
File file_path = new File(ContextCompat.getDataDir(mContext), "sock_path");
try {
if (!file_path.exists())
file_path.createNewFile();
} catch(IOException e){
throw new IOException("Failed to create socket file: " + file_path.getCanonicalPath());
}
cmd.append(fileTun2Socks.getCanonicalPath());
// Always configure the IPv4 address on the tun interface
cmd.append(" --netif-ipaddr " + mVpnIpAddress);
// Optionally configure an IPv6 address when provided. When
// mVpnIp6Address is null or empty, no IPv6 address is passed
// to the tun2socks binary. This allows clients to continue
// operating in IPv4-only environments while enabling native
// dual-stack operation when possible.
if (mVpnIp6Address != null && !mVpnIp6Address.isEmpty()) {
cmd.append(" --netif-ip6addr " + mVpnIp6Address);
}
cmd.append(" --netif-netmask " + mVpnNetMask);
cmd.append(" --socks-server-addr " + mSocksServerAddress);
cmd.append(" --tunmtu " + Integer.toString(mVpnInterfaceMTU));
cmd.append(" --tunfd " + mVpnInterfaceFileDescriptor.getFd());
cmd.append(" --sock " + file_path.getAbsolutePath());
cmd.append(" --loglevel " + Integer.toString(3));
if (mUdpgwServerAddress != null) {
if (mUdpgwTransparentDNS) {
cmd.append(" --udpgw-transparent-dns");
}
String udpgwAddr = mUdpgwServerAddress;
// Normalize IPv6 host:port into [host]:port for badvpn-tun2socks parser.
// Accept user input as:
// - host:port (IPv4/hostname)
// - ipv6 (no port) -> default 7300
// - ipv6:port (no brackets) -> brackets will be added
// - [ipv6]:port (already OK)
if (udpgwAddr != null) {
udpgwAddr = udpgwAddr.trim();
if (!udpgwAddr.isEmpty()) {
// If it's a bare IPv6 without brackets and has multiple ':' then bracket it.
boolean looksIpv6 = udpgwAddr.contains(":") && udpgwAddr.indexOf(':') != udpgwAddr.lastIndexOf(':');
if (looksIpv6 && !udpgwAddr.startsWith("[")) {
// Split last ':' as port if present, otherwise default.
int lastColon = udpgwAddr.lastIndexOf(':');
String hostPart = udpgwAddr;
String portPart = "7300";
if (lastColon > 0 && lastColon < udpgwAddr.length() - 1) {
hostPart = udpgwAddr.substring(0, lastColon);
String maybePort = udpgwAddr.substring(lastColon + 1);
if (maybePort.matches("\\d+")) {
portPart = maybePort;
} else {
hostPart = udpgwAddr; // treat as no-port
}
}
udpgwAddr = "[" + hostPart + "]:" + portPart;
} else if (udpgwAddr.matches("^[0-9a-fA-F:]+$")) {
// Pure IPv6 without port
udpgwAddr = "[" + udpgwAddr + "]:7300";
}
}
}
cmd.append(" --udpgw-remote-server-addr " + udpgwAddr);
}
if (mDnsResolverAddress != null) {
cmd.append(" --dnsgw " + mDnsResolverAddress);
}
// Start the native tun2socks process.
tun2SocksProcess = Runtime.getRuntime().exec(cmd.toString());
StreamGobbler stdoutGobbler = new StreamGobbler(tun2SocksProcess.getInputStream(), this);
StreamGobbler stderrGobbler = new StreamGobbler(tun2SocksProcess.getErrorStream(), this);
stdoutGobbler.start();
stderrGobbler.start();
// send Fd
if (!sendFd(mVpnInterfaceFileDescriptor, file_path)) {
throw new IOException("Failed to pass the TUN file descriptor to tun2socks");
}
tun2SocksProcess.waitFor();
}
} catch (IOException e) {
SkStatus.logException("Tun2Socks Error", e);
} catch (Exception e) {
SkStatus.logDebug("Tun2Socks Error: " + e.getMessage());
}
tun2SocksProcess = null;
if (mListener != null) {
mListener.onStop();
}
}
@Override
public synchronized void interrupt()
{
// TODO: Implement this method
super.interrupt();
//net.typeblog.socks.System.jniclose(mVpnInterfaceFileDescriptor.getFd());
if (tun2SocksProcess != null)
tun2SocksProcess.destroy();
try {
if (fileTun2Socks != null)
VpnUtils.killProcess(fileTun2Socks);
} catch (Exception e) {}
tun2SocksProcess = null;
fileTun2Socks = null;
}
public void setOnTun2SocksListener(OnTun2SocksListener listener){
this.mListener = listener;
}
/**
* StreamGobbler OnLine Listener
* implementation
*/
@Override
public void onLine(String log){
SkStatus.logDebug("Tun2Socks: " + log);
}
//----------------------------------------------------------------------------
// Utils
//----------------------------------------------------------------------------
private boolean sendFd(ParcelFileDescriptor fileDescriptor, File toFile) throws InterruptedException {
SkStatus.logDebug("Enviando Fd para sock");
for (int tries = 10; tries >= 0; tries--) {
try {
LocalSocket localSocket = new LocalSocket();
localSocket.connect(new LocalSocketAddress(toFile.getAbsolutePath(), LocalSocketAddress.Namespace.FILESYSTEM));
localSocket.setFileDescriptorsForSend(new FileDescriptor[]{
fileDescriptor.getFileDescriptor()
});
localSocket.getOutputStream().write(42);
localSocket.shutdownOutput();
localSocket.close();
return true;
} catch(IOException unused) {
Thread.sleep(500);
}
/*if (net.typeblog.socks.System.sendfd(fileDescriptor.getFd(), toFile.getAbsolutePath()) != -1) {
return true;
}
try {
Thread.sleep(500);
} catch(InterruptedException e){}*/
}
return false;
}
}
@@ -0,0 +1,479 @@
/*
* Copyright (c) 2015, Psiphon Inc.
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.VpnService;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.dragonssh.xhttpdemo.core.config.Settings;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
/**
* Baseado no Cordova Plugin Tun2Socks
*
* @author dFiR30n
*/
public class Tunnel {
public interface HostService {
public String getAppName();
public Context getContext();
// Object must be a VpnService; Android < 4 cannot reference this class name
public Object getVpnService();
// Object must be a VpnService.Builder;
// Android < 4 cannot reference this class name
public Object newVpnServiceBuilder();
public void onDiagnosticMessage(String message);
public void onTunnelConnected();
public void onVpnEstablished();
public void onTunnelStopped();
}
private final HostService mHostService;
private VpnUtils.PrivateAddress mPrivateAddress;
private AtomicReference<ParcelFileDescriptor> mTunFd;
private AtomicBoolean mRoutingThroughTunnel;
private Tun2Socks mTun2Socks;
private Pdnsd mPdnsd;
private NetworkSpace mRoutes;
private Settings mConfig;
// Only StartNET VPNService instance may exist at a time, as the underlying
// tun2socks implementation contains global state.
private static Tunnel mTunnel;
public static synchronized Tunnel newTunnel(HostService hostService) {
if (mTunnel != null) {
mTunnel.stop();
}
mTunnel = new Tunnel(hostService);
return mTunnel;
}
private Tunnel(HostService hostService) {
mHostService = hostService;
mTunFd = new AtomicReference<ParcelFileDescriptor>();
mRoutingThroughTunnel = new AtomicBoolean(false);
mRoutes = new NetworkSpace();
//org.uproxy.tun2socks.Tun2SocksJni.init();
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
//----------------------------------------------------------------------------------------------
// Public API
//----------------------------------------------------------------------------------------------
// To start, call in sequence: startRouting(), then startTunneling(). After startRouting()
// succeeds, the caller must call stop() to clean up.
// Returns true when the VPN routing is established; returns false if the VPN could not
// be started due to lack of prepare or revoked permissions (called should re-prepare and
// try again); throws exception for other error conditions.
public synchronized boolean startRouting(TunnelVpnSettings settings) throws java.lang.Exception {
return startVpn(settings.mDnsForward, settings.mDnsResolver, settings.mExcludeIps,
settings.mEnableFilterApps, settings.mFilterBypassMode, settings.mFilterApps, settings.mTetheringSubnet, settings.mDisableIpv6Tunnel);
}
// Starts tun2socks. Returns true on success.
public synchronized boolean startTunneling(String socksServerAddress, String[] dnsResolver, boolean forwardDns, String udpResolver, boolean udpDnsRelay)
throws Exception {
return routeThroughTunnel(socksServerAddress, dnsResolver, forwardDns, udpResolver, udpDnsRelay);
}
// Stops routing traffic through the tunnel by stopping tun2socks.
// The VPN is unaffected by this method.
public synchronized void stopTunneling() {
stopRoutingThroughTunnel();
}
// Note: to avoid deadlock, do not call directly from a HostService callback;
// instead post to a Handler if necessary to trigger from a HostService callback.
public synchronized void stop() {
stopVpn();
}
//----------------------------------------------------------------------------
// VPN Routing
//----------------------------------------------------------------------------
private static final String VPN_INTERFACE_NETMASK = "255.255.255.0";
// IPv6 addressing for the TUN interface and the internal tun2socks router.
// Using an RFC4193 ULA prefix avoids collisions with real networks.
private static final String VPN_INTERFACE_IPV6_ADDRESS = "fd00:1:fd00:1::1";
private static final int VPN_INTERFACE_IPV6_PREFIX_LENGTH = 64;
private static final String VPN_ROUTER_IPV6_ADDRESS = "fd00:1:fd00:1::2";
private static final String DNS_RESOLVER_IP = "8.8.8.8";
private static final int DNS_RESOLVER_PORT = 53;
//private String dns6 = "0";
private int mMtu = 1500;
// Note: Atomic variables used for getting/setting local proxy port, routing flag, and
// tun fd, as these functions may be called via callbacks. Do not use
// synchronized functions as stop() is synchronized and a deadlock is possible as callbacks
// can be called while stop holds the lock.
//
private boolean startVpn(boolean forwardDns, String[] dnsResolver, String[] excludeIps,
boolean enabledFilter, boolean filterBypassMode, String[] filterApps, boolean enableTethering, boolean disableIpv6Tunnel) throws java.lang.Exception {
mPrivateAddress = VpnUtils.selectPrivateAddress();
// Make sure control sockets (SSH/proxy/TLS) are excluded from the VPN.
// This prevents routing loops, especially when IPv6 ::/0 is installed.
SocketProtector.setVpnService((VpnService) mHostService.getVpnService());
// routes list
// For IPv4 we use NetworkSpace to compute a minimal set of routes excluding the server.
// For IPv6, Android does not provide a direct "exclude this single address" API when
// installing ::/0. As a defensive fallback (in addition to VpnService.protect), we build
// a set of IPv6 prefixes that cover all IPv6 addresses *except* the excluded server /128.
// This avoids the control connection being routed into the VPN when the server is IPv6.
String excludedServerIpv6 = null;
for (String ip : excludeIps) {
if (ip != null && ip.contains(":")) {
excludedServerIpv6 = ip;
SkStatus.logInfo("IPV6 DETECTED");
} else {
SkStatus.logInfo("IPV4 DETECTED");
mRoutes.addIP(new CIDRIP(ip, 32), false);
}
}
Locale previousLocale = Locale.getDefault();
final String errorMessage = "startVpn failed";
try {
// Workaround for https://code.google.com/p/android/issues/detail?id=61096
Locale.setDefault(new Locale("en"));
ParcelFileDescriptor tunFd = null;
// Create the Android VPN builder.
VpnService.Builder builder = ((VpnService.Builder) mHostService.newVpnServiceBuilder())
.addAddress(mPrivateAddress.mIpAddress, mPrivateAddress.mPrefixLength)
.allowFamily(AF_INET);
/*
* Keep the VPN implementation's own UID outside the TUN at the Android
* policy layer. All SSH, TLS, proxy and XHTTP control sockets are created
* by this package. Excluding the package prevents those reconnect sockets
* from ever entering tun2socks, even on vendor ROMs where
* VpnService.protect(Socket) returns false for a fresh socket.
*
* The traffic of other applications is still routed through the VPN.
*/
try {
builder.addDisallowedApplication(mHostService.getContext().getPackageName());
} catch (PackageManager.NameNotFoundException e) {
throw new Exception("Unable to exclude VPN control process from TUN", e);
}
if (!disableIpv6Tunnel) {
builder.allowFamily(AF_INET6);
// Configure an IPv6 address on the VPN interface so IPv6 flows can be
// routed through tun2socks. Without an IPv6 address on the interface,
// Android will not reliably deliver IPv6 packets to the TUN device.
builder.addAddress(VPN_INTERFACE_IPV6_ADDRESS, VPN_INTERFACE_IPV6_PREFIX_LENGTH);
if (excludedServerIpv6 != null) {
SkStatus.logInfo("Excluding server IPv6 from VPN routes: " + excludedServerIpv6);
addIpv6RoutesExcludingSingleAddress(builder, excludedServerIpv6);
} else {
builder.addRoute("::", 0);
}
} else {
SkStatus.logInfo("IPv6 tunnel disabled by config");
}
mRoutes.addIP(new CIDRIP("0.0.0.0", 0), true);
mRoutes.addIP(new CIDRIP("10.0.0.0", 8), false);
mRoutes.addIP(new CIDRIP(mPrivateAddress.mSubnet, mPrivateAddress.mPrefixLength), false);
// ainda pra testar, subnet routing pesquisar "allow lan"
if (enableTethering) {
// USB tethering 192.168.42.x
// Wi-Fi tethering 192.168.43.x
mRoutes.addIP(new CIDRIP("192.168.42.0", 23), false);
// Bluetooth tethering 192.168.44.x
mRoutes.addIP(new CIDRIP("192.168.44.0", 24), false);
// Wi-Fi direct 192.168.49.x
mRoutes.addIP(new CIDRIP("192.168.49.0", 24), false);
}
// Add Dns
for (String dns : dnsResolver) {
try {
builder.addDnsServer(dns);
if (CIDRIP.InetAddressUtils.isIPv4Address(dns)) {
SkStatus.logInfo("DNS IPV4 ON");
mRoutes.addIP(new CIDRIP(dns, 32), forwardDns);
}
if (CIDRIP.InetAddressUtils.isIPv6Address(Arrays.toString(excludeIps))) {
SkStatus.logInfo("DNS IPV6 ON");
builder.addDnsServer("2606:4700:4700::1111");
builder.addDnsServer("2606:4700:4700::1001");
}
} catch (IllegalArgumentException iae) {
mHostService.onDiagnosticMessage(String.format("Failed to add DNS server %s: %s", dns, iae.getLocalizedMessage()));
}
}
// set MTU
String release = Build.VERSION.RELEASE;
if ((Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && !release.startsWith("4.4.3")
&& !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6"))
&& mMtu < 1280) {
SkStatus.logInfo(String.format(Locale.US, "Forcing MTU to 1280 instead of %d to workaround Android Bug #70916", mMtu));
mMtu = 1280;
}
builder.setMtu(mMtu);
// loop routes
NetworkSpace.IpAddress multicastRange = new NetworkSpace.IpAddress(new CIDRIP("224.0.0.0", 3), true);
for (NetworkSpace.IpAddress route : mRoutes.getPositiveIPList()) {
try {
if (multicastRange.containsNet(route))
SkStatus.logDebug("VPN: Ignoring multicast route: " + route.toString());
else
builder.addRoute(route.getIPv4Address(), route.networkMask);
} catch (IllegalArgumentException ia) {
//mHostService.onDiagnosticMessage("Route rejeitada: " + route + " " + ia.getLocalizedMessage());
}
}
// The UID bypass is considered active only after establish() succeeds.
SocketProtector.setProcessBypassConfigured(false);
// TunFd
tunFd = builder
.setSession(mHostService.getAppName())
.establish();
if (tunFd == null) {
// As per http://developer.android.com/reference/android/net/VpnService.Builder.html#establish%28%29,
// this application is no longer prepared or was revoked.
return false;
}
mTunFd.set(tunFd);
SocketProtector.setProcessBypassConfigured(true);
mRoutingThroughTunnel.set(false);
mHostService.onVpnEstablished();
mRoutes.clear();
} catch (IllegalArgumentException e) {
throw new Exception("IllegalArgumentException", e);
} catch (SecurityException e) {
throw new Exception("IllegalArgumentException", e);
} catch (IllegalStateException e) {
throw new Exception("IllegalStateException", e);
} finally {
// Restore the original locale.
Locale.setDefault(previousLocale);
}
return true;
}
private boolean routeThroughTunnel(final String socksServerAddress, final String[] dnsResolver, boolean forwardDns, final String udpResolver, final boolean transparentDns) {
if (!mRoutingThroughTunnel.compareAndSet(false, true)) {
return false;
}
final ParcelFileDescriptor tunFd = mTunFd.get();
if (tunFd == null) {
return false;
}
// Pdnsd
String dnsgwRelay = null;
if (forwardDns) {
int pdnsdPort = VpnUtils.findAvailablePort(8091, 10);
String[] mServidorDNS = dnsResolver;
dnsgwRelay = String.format("%s:%d", mPrivateAddress.mIpAddress, pdnsdPort);
mPdnsd = new Pdnsd(mHostService.getContext(), mServidorDNS, DNS_RESOLVER_PORT,
mPrivateAddress.mIpAddress, pdnsdPort);
mPdnsd.setOnPdnsdListener(new Pdnsd.OnPdnsdListener() {
@Override
public void onStart() {
}
@Override
public void onStop() {
stop();
mHostService.onTunnelStopped();
}
});
mPdnsd.start();
}
// Tun2socks
boolean disableIpv6Tunnel = new Settings(mHostService.getContext()).getDisableIpv6Tunnel();
mTun2Socks = new Tun2Socks(mHostService.getContext(), tunFd, mMtu,
mPrivateAddress.mRouter, disableIpv6Tunnel ? null : VPN_ROUTER_IPV6_ADDRESS, VPN_INTERFACE_NETMASK,
socksServerAddress, udpResolver, dnsgwRelay, transparentDns);
mTun2Socks.setOnTun2SocksListener(new Tun2Socks.OnTun2SocksListener() {
@Override
public void onStart() {
}
@Override
public void onStop() {
stop();
mHostService.onTunnelStopped();
}
});
mTun2Socks.start();
mHostService.onTunnelConnected();
// TODO: should double-check tunnel routing; see:
// https://bitbucket.org/psiphon/psiphon-circumvention-system/src/1dc5e4257dca99790109f3bf374e8ab3a0ead4d7/Android/PsiphonAndroidLibrary/src/com/psiphon3/psiphonlibrary/TunnelCore.java?at=default#cl-779
return true;
}
/**
* Adds IPv6 routes that cover the entire IPv6 space except for a single excluded /128.
*
* <p>This is a defensive fallback for cases where {@link VpnService#protect(java.net.Socket)}
* is not effective on a given device/ROM. For a single excluded address, the complement can be
* represented by at most 128 prefixes.
*/
private static void addIpv6RoutesExcludingSingleAddress(VpnService.Builder builder, String excludedIpv6) {
if (builder == null || excludedIpv6 == null) {
return;
}
try {
java.net.InetAddress ia = java.net.InetAddress.getByName(excludedIpv6);
if (!(ia instanceof java.net.Inet6Address)) {
builder.addRoute("::", 0);
return;
}
final byte[] excluded = ia.getAddress(); // 16 bytes
// For each bit position i, add a prefix (i+1) that matches the excluded address for the
// first i bits and has the i-th bit flipped. The union of these prefixes equals the
// full IPv6 space minus the excluded /128.
for (int i = 0; i < 128; i++) {
byte[] prefix = excluded.clone();
// Flip the i-th bit (MSB-first).
int byteIndex = i / 8;
int bitIndex = 7 - (i % 8);
prefix[byteIndex] = (byte) (prefix[byteIndex] ^ (1 << bitIndex));
// Zero all bits after i to create a proper network prefix address.
int nextBit = i + 1;
int zeroFromByte = nextBit / 8;
int zeroFromBit = 7 - (nextBit % 8);
// If nextBit is byte-aligned, zero whole bytes from there.
if (nextBit % 8 == 0) {
for (int b = zeroFromByte; b < 16; b++) {
prefix[b] = 0;
}
} else {
// Mask the current byte to keep only the prefix bits, then zero subsequent bytes.
int mask = 0xFF << (zeroFromBit + 1);
prefix[zeroFromByte] = (byte) (prefix[zeroFromByte] & mask);
for (int b = zeroFromByte + 1; b < 16; b++) {
prefix[b] = 0;
}
}
String routeAddr = java.net.InetAddress.getByAddress(prefix).getHostAddress();
builder.addRoute(routeAddr, i + 1);
}
} catch (Throwable t) {
// If anything goes wrong, fall back to routing all IPv6 through the VPN.
try {
builder.addRoute("::", 0);
} catch (Throwable ignored) {
}
}
}
private void stopRoutingThroughTunnel() {
if (mTun2Socks != null && mTun2Socks.isAlive()) {
mTun2Socks.interrupt();
}
mTun2Socks = null;
if (mPdnsd != null && mPdnsd.isAlive()) {
mPdnsd.interrupt();
}
mPdnsd = null;
}
private void stopVpn() {
stopRoutingThroughTunnel();
SocketProtector.setProcessBypassConfigured(false);
// Clear only this VPN service instance. A different transport mode may
// already have installed a newer protector owner.
SocketProtector.clearVpnService((VpnService) mHostService.getVpnService());
ParcelFileDescriptor tunFd = mTunFd.getAndSet(null);
if (tunFd != null) {
try {
tunFd.close();
} catch (IOException e) {
}
}
}
}
@@ -0,0 +1,40 @@
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
// Singleton class to maintain state related to VPN Tunnel service.
public class TunnelState {
private static TunnelState m_tunnelState;
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public static synchronized TunnelState getTunnelState() {
if (m_tunnelState == null) {
m_tunnelState = new TunnelState();
}
return m_tunnelState;
}
private TunnelVpnManager m_tunnelManager = null;
private boolean m_startingTunnelManager = false;
private TunnelState() {}
public synchronized void setTunnelManager(TunnelVpnManager tunnelManager) {
m_tunnelManager = tunnelManager;
m_startingTunnelManager = false;
}
public synchronized TunnelVpnManager getTunnelManager() {
return m_tunnelManager;
}
public synchronized void setStartingTunnelManager() {
m_startingTunnelManager = true;
}
public synchronized boolean getStartingTunnelManager() {
return m_startingTunnelManager;
}
};
@@ -0,0 +1,294 @@
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
/*
* Copyright (c) 2016, Psiphon Inc.
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import android.annotation.TargetApi;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.VpnService;
import android.os.Build;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import com.dragonssh.xhttpdemo.core.R;
import com.dragonssh.xhttpdemo.core.config.Settings;
public class TunnelVpnManager implements Tunnel.HostService
{
private static final String TAG = "TunnelManager";
public static final String VPN_SETTINGS = "vpnSettings";
private TunnelVpnService m_parentService = null;
private CountDownLatch m_tunnelThreadStopSignal;
private Thread m_tunnelThread;
private AtomicBoolean m_isStopping;
private Tunnel m_tunnel = null;
private AtomicBoolean m_isReconnecting;
private TunnelVpnSettings mSettings;
public interface ManagerListener {
public void onLog(String msg);
}
public TunnelVpnManager(TunnelVpnService parentService)
{
m_parentService = parentService;
m_isStopping = new AtomicBoolean(false);
m_isReconnecting = new AtomicBoolean(false);
m_tunnel = Tunnel.newTunnel(this);
}
// Implementation of android.app.Service.onStartCommand
public int onStartCommand(Intent intent, int flags, int startId)
{
Log.i(TAG, "onStartCommand");
if (intent == null) {
Log.e(TAG, "Failed to receive intent");
m_parentService.broadcastVpnStart(false);
return Service.START_NOT_STICKY;
}
mSettings = intent.getParcelableExtra(VPN_SETTINGS);
if (mSettings == null) {
Log.e(TAG, "Failed to receive the Vpn Settings.");
m_parentService.broadcastVpnStart(false);
return Service.START_NOT_STICKY;
}
if (mSettings.mSocksServer == null)
{
Log.e(TAG, "Failed to receive the socks server address.");
m_parentService.broadcastVpnStart(false);
return Service.START_NOT_STICKY;
}
if (mSettings.mDnsResolver == null)
{
Log.e(TAG, "Failed to receive the dns resolvers.");
m_parentService.broadcastVpnStart(false);
return Service.START_NOT_STICKY;
}
try
{
if (!m_tunnel.startRouting(mSettings))
{
Log.e(TAG, "Failed to establish VPN");
m_parentService.broadcastVpnStart(false);
}
}
catch (Exception e)
{
Log.e(TAG, String.format("Failed to establish VPN: %s", e.getMessage()));
m_parentService.broadcastVpnStart(false);
}
return Service.START_REDELIVER_INTENT;
}
// Implementation of android.app.Service.onDestroy
public void onDestroy()
{
if (m_tunnelThread == null)
{
return;
}
// signalStopService should have been called, but in case is was not, call here.
// If signalStopService was not already called, the join may block the calling
// thread for some time.
signalStopService();
try
{
m_tunnelThread.join();
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
m_tunnelThreadStopSignal = null;
m_tunnelThread = null;
}
// Signals the runTunnel thread to stop. The thread will self-stop the service.
// This is the preferred method for stopping the tunnel service:
// 1. VpnService doesn't respond to stopService calls
// 2. The UI will not block while waiting for stopService to return
public void signalStopService()
{
if (m_tunnelThreadStopSignal != null)
{
m_tunnelThreadStopSignal.countDown();
}
}
// Stops the tunnel thread and restarts it with |socksServerAddress|.
public void restartTunnel(final String socksServerAddress)
{
Log.i(TAG, "Restarting tunnel.");
if (socksServerAddress == null ||
socksServerAddress.equals(mSettings.mSocksServer))
{
// Don't reconnect if the socks server address hasn't changed.
m_parentService.broadcastVpnStart(true);
return;
}
mSettings.mSocksServer = socksServerAddress;
m_isReconnecting.set(true);
// Signaling stop to the tunnel thread with the reconnect flag set causes
// the thread to stop the tunnel (but not the VPN or the service) and send
// the new SOCKS server address to the DNS resolver before exiting itself.
// When the DNS broadcasts its local address, the tunnel will restart.
signalStopService();
}
private void startTunnel()
{
m_tunnelThreadStopSignal = new CountDownLatch(1);
m_tunnelThread =
new Thread(
new Runnable() {
@Override
public void run()
{
runTunnel(mSettings.mSocksServer, mSettings.mDnsResolver, mSettings.mDnsForward, mSettings.mUdpResolver, mSettings.mUdpDnsRelay);
}
});
m_tunnelThread.start();
}
private void runTunnel(String socksServerAddress, String[] dnsResolver, boolean forwardDns, String udpResolver, boolean udpDnsRelay)
{
m_isStopping.set(false);
try
{
if (!m_tunnel.startTunneling(socksServerAddress, dnsResolver, forwardDns, udpResolver, udpDnsRelay))
{
throw new Exception("application is not prepared or revoked");
}
Log.i(TAG, "VPN service running");
m_parentService.broadcastVpnStart(true);
try
{
m_tunnelThreadStopSignal.await();
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
m_isStopping.set(true);
}
catch (Exception e)
{
Log.e(TAG, String.format("Start tunnel failed: %s", e.getMessage()));
m_parentService.broadcastVpnStart(false);
}
finally
{
if (m_isReconnecting.get())
{
// Stop tunneling only, not VPN, if reconnecting.
Log.i(TAG, "Stopping tunnel.");
m_tunnel.stopTunneling();
}
else
{
// Stop VPN tunnel and service only if not reconnecting.
Log.i(TAG, "Stopping VPN and tunnel.");
m_tunnel.stop();
m_parentService.stopForeground(true);
m_parentService.stopSelf();
}
m_isReconnecting.set(false);
}
}
//----------------------------------------------------------------------------
// Tunnel.HostService
//----------------------------------------------------------------------------
@Override
public String getAppName()
{
return getContext().getString(R.string.app_name);
}
@Override
public Context getContext()
{
return m_parentService;
}
@Override
public VpnService getVpnService()
{
return m_parentService;
}
@Override
public VpnService.Builder newVpnServiceBuilder()
{
return m_parentService.newBuilder();
}
@Override
public void onDiagnosticMessage(String message)
{
SkStatus.logInfo(message);
}
@Override
public void onTunnelConnected()
{
}
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onVpnEstablished()
{
//SkStatus.logInfo("<strong>VPN Estabelecida</strong>");
startTunnel();
}
@Override
public void onTunnelStopped()
{
if (!m_isStopping.get() && !m_isReconnecting.get()) {
Log.w(TAG, "VPN tunnel process stopped unexpectedly");
SkStatus.logInfo("<strong>VPN tunnel stopped. Reconnecting...</strong>");
m_parentService.broadcastVpnDisconnect();
}
// Release runTunnel() so the VpnService can clean up instead of hanging
// forever while the main SSH service starts a fresh connection.
signalStopService();
}
}
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2016, Psiphon Inc.
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import android.annotation.TargetApi;
import android.content.Intent;
import android.net.VpnService;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class TunnelVpnService extends VpnService {
private static final String LOG_TAG = "TunnelVpnService";
public static final String TUNNEL_VPN_DISCONNECT_BROADCAST =
"tunnelVpnDisconnectBroadcast";
public static final String TUNNEL_VPN_START_BROADCAST =
"tunnelVpnStartBroadcast";
public static final String TUNNEL_VPN_START_SUCCESS_EXTRA =
"tunnelVpnStartSuccessExtra";
private TunnelVpnManager m_tunnelManager = new TunnelVpnManager(this);
public class LocalBinder extends Binder {
public TunnelVpnService getService() {
return TunnelVpnService.this;
}
}
private final IBinder m_binder = new LocalBinder();
@Override
public IBinder onBind(Intent intent) {
String action = intent.getAction();
if (action != null && action.equals(SERVICE_INTERFACE)) {
return super.onBind(intent);
}
return m_binder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Log.d(LOG_TAG, "on start");
return m_tunnelManager.onStartCommand(intent, flags, startId);
}
@Override
public void onCreate() {
//Log.d(LOG_TAG, "on create");
SocketProtector.setVpnService(this);
TunnelState.getTunnelState().setTunnelManager(m_tunnelManager);
}
@Override
public void onDestroy() {
SkStatus.logInfo("<strong>VPN service destroyed</strong>");
// Keep the SSH service and the Android VPN state synchronized. If Android
// kills or destroys only the VpnService, the main tunnel manager must be
// notified so it can reconnect instead of leaving the app marked connected
// while the phone VPN interface is gone.
broadcastVpnDisconnect();
TunnelState.getTunnelState().setTunnelManager(null);
SocketProtector.clearVpnService(this);
m_tunnelManager.onDestroy();
super.onDestroy();
}
@Override
public void onRevoke() {
SkStatus.logInfo("<strong>VPN service revoked</strong>");
broadcastVpnDisconnect();
// stopSelf will trigger onDestroy in the main thread.
stopSelf();
}
public VpnService.Builder newBuilder() {
return new VpnService.Builder();
}
// Broadcast non-user-initiated VPN disconnect.
public void broadcastVpnDisconnect() {
dispatchBroadcast(new Intent(TUNNEL_VPN_DISCONNECT_BROADCAST));
}
// Broadcast VPN start. |success| is true if the VPN and tunnel were started
// successfully, and false otherwise.
public void broadcastVpnStart(boolean success) {
Intent vpnStart = new Intent(TUNNEL_VPN_START_BROADCAST);
vpnStart.putExtra(TUNNEL_VPN_START_SUCCESS_EXTRA, success);
dispatchBroadcast(vpnStart);
}
private void dispatchBroadcast(final Intent broadcast) {
LocalBroadcastManager.getInstance(TunnelVpnService.this)
.sendBroadcast(broadcast);
}
}
@@ -0,0 +1,87 @@
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import android.os.Parcelable;
import android.os.Parcel;
public class TunnelVpnSettings
implements Parcelable {
public String mSocksServer;
public boolean mDnsForward;
public String[] mDnsResolver;
public String mUdpResolver;
public String[] mExcludeIps;
public boolean mUdpDnsRelay;
public boolean mEnableFilterApps;
public boolean mFilterBypassMode;
public String[] mFilterApps;
public boolean mTetheringSubnet;
public boolean mDisableIpv6Tunnel;
public TunnelVpnSettings(String socksServer, boolean dnsForward, String[] dnsResolver,
boolean udpDnsRelay, String udpResolver, String[] excludeIps,
boolean enableFilterApps, boolean filterBypassMode, String[] filterApps,
boolean enableTethering, boolean disableIpv6Tunnel)
{
mSocksServer = socksServer;
mDnsForward = dnsForward;
mUdpDnsRelay = udpDnsRelay;
mDnsResolver = dnsResolver;
mUdpResolver = udpResolver;
mExcludeIps = excludeIps;
mEnableFilterApps = enableFilterApps;
mFilterBypassMode = filterBypassMode;
mFilterApps = filterApps;
mTetheringSubnet = enableTethering;
mDisableIpv6Tunnel = disableIpv6Tunnel;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mSocksServer);
dest.writeInt(mDnsForward ? 1 : 0);
dest.writeInt(mUdpDnsRelay ? 1 : 0);
dest.writeStringArray(mDnsResolver);
dest.writeString(mUdpResolver);
dest.writeStringArray(mExcludeIps);
dest.writeInt(mFilterBypassMode ? 1 : 0);
dest.writeStringArray(mFilterApps);
dest.writeInt(mEnableFilterApps ? 1 : 0);
dest.writeInt(mTetheringSubnet ? 1 : 0);
dest.writeInt(mDisableIpv6Tunnel ? 1 : 0);
}
public TunnelVpnSettings(Parcel in) {
mSocksServer = in.readString();
mDnsForward = in.readInt() == 1;
mUdpDnsRelay = in.readInt() == 1;
mDnsResolver = in.createStringArray();
mUdpResolver = in.readString();
mExcludeIps = in.createStringArray();
mFilterBypassMode = in.readInt() == 1;
mFilterApps = in.createStringArray();
mEnableFilterApps = in.readInt() == 1;
mTetheringSubnet = in.readInt() == 1;
mDisableIpv6Tunnel = in.readInt() == 1;
}
public static final Creator<TunnelVpnSettings> CREATOR
= new Creator<TunnelVpnSettings>() {
public TunnelVpnSettings createFromParcel(Parcel in) {
return new TunnelVpnSettings(in);
}
public TunnelVpnSettings[] newArray(int size) {
return new TunnelVpnSettings[size];
}
};
}
@@ -0,0 +1,373 @@
package com.dragonssh.xhttpdemo.core.tunnel.vpn;
import android.content.Context;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import static java.lang.Runtime.getRuntime;
import java.util.Map;
import java.util.List;
import java.net.NetworkInterface;
import java.util.Collections;
import java.net.SocketException;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.util.HashMap;
import android.annotation.TargetApi;
import android.os.Build;
import java.util.Collection;
import java.util.ArrayList;
import android.net.ConnectivityManager;
import java.lang.reflect.Method;
import android.net.LinkProperties;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.regex.Pattern;
import com.dragonssh.xhttpdemo.core.logger.SkStatus;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
public class VpnUtils {
public static int findProcessId(String command) throws IOException {
String[] cmds = {"ps -ef","ps -A","toolbox ps"};
for (int i = 0; i < cmds.length;i++) {
Process procPs = getRuntime().exec(cmds[i]);
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (!line.contains("PID") && line.contains(command)) {
String[] lineParts = line.split("\\s+");
try {
return Integer.parseInt(lineParts[1]); //for most devices it is the second
} catch (NumberFormatException e) {
return Integer.parseInt(lineParts[0]); //but for samsungs it is the first
} finally {
try {
procPs.destroy();
} catch (Exception e) {
}
}
}
}
}
return -1;
}
public static void killProcess(File fileProcBin) throws Exception {
killProcess(fileProcBin, "-9"); // this is -KILL
}
public static int killProcess(File fileProcBin, String signal) throws Exception {
int procId = -1;
int killAttempts = 0;
while ((procId = findProcessId(fileProcBin.getName())) != -1) {
killAttempts++;
String pidString = String.valueOf(procId);
String[] cmds = {"","busybox ","toolbox "};
for (int i = 0; i < cmds.length;i++) {
try {
getRuntime().exec(cmds[i] + "killall " + signal + " " + fileProcBin.getName
());
} catch (IOException ioe) {
}
try {
getRuntime().exec(cmds[i] + "killall " + signal + " " + fileProcBin.getCanonicalPath());
} catch (IOException ioe) {
}
}
killProcess(pidString, signal);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignored
}
if (killAttempts > 4)
throw new Exception("Cannot kill: " + fileProcBin.getAbsolutePath());
}
return procId;
}
public static void killProcess(String pidString, String signal) throws Exception {
String[] cmds = {"","toolbox ","busybox "};
for (int i = 0; i < cmds.length;i++) {
try {
getRuntime().exec(cmds[i] + "kill " + signal + " " + pidString);
} catch (IOException ioe) {
SkStatus.logDebug(String.format("error killing process: %s, %s", pidString, ioe.getMessage()));
}
}
}
public static List<String> getActiveNetworkDnsResolver(Context context) throws Exception {
Collection<InetAddress> dnsResolvers = getActiveNetworkDnsResolvers(context);
if (!dnsResolvers.isEmpty()) {
ArrayList<String> lista = new ArrayList<String>();
int max = 2;
Iterator it = dnsResolvers.iterator();
while (it.hasNext()) {
String dnsResolver = it.next().toString();
// strip the leading slash e.g., "/192.168.1.1"
if (dnsResolver.startsWith("/")) {
dnsResolver = dnsResolver.substring(1);
}
// Accept both IPv4 and IPv6 DNS resolvers. Previously IPv6
// addresses were filtered out to avoid issues on devices
// lacking dual-stack support. With native IPv6 forwarding,
// clients may be connected to an IPv6-only network, so we
// no longer discard resolvers containing a colon (':').
lista.add(dnsResolver);
max -= 1;
if (max <= 0) break;
}
return lista;
}
else throw new Exception("no active network DNS resolver");
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static Collection<InetAddress> getActiveNetworkDnsResolvers(Context context)
throws Exception {
final String errorMessage = "getActiveNetworkDnsResolvers failed";
ArrayList<InetAddress> dnsAddresses = new ArrayList<InetAddress>();
try {
// Hidden API
// - only available in Android 4.0+
// - no guarantee will be available beyond 4.2, or on all vendor devices
ConnectivityManager connectivityManager =
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties", new Class []{});
Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
if (linkProperties != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Method getDnsesMethod = LinkPropertiesClass.getMethod("getDnses", new Class []{});
Collection<?> dnses = (Collection<?>)getDnsesMethod.invoke(linkProperties);
for (Object dns : dnses) {
dnsAddresses.add((InetAddress)dns);
}
} else {
// LinkProperties is public in API 21 (and the DNS function signature has changed)
for (InetAddress dns : ((LinkProperties)linkProperties).getDnsServers()) {
dnsAddresses.add(dns);
}
}
}
} catch (ClassNotFoundException e) {
throw new Exception(errorMessage, e);
} catch (NoSuchMethodException e) {
throw new Exception(errorMessage, e);
} catch (IllegalArgumentException e) {
throw new Exception(errorMessage, e);
} catch (IllegalAccessException e) {
throw new Exception(errorMessage, e);
} catch (InvocationTargetException e) {
throw new Exception(errorMessage, e);
} catch (NullPointerException e) {
throw new Exception(errorMessage, e);
}
return dnsAddresses;
}
private final static String DEFAULT_PRIMARY_DNS_SERVER = "8.8.4.4";
private final static String DEFAULT_SECONDARY_DNS_SERVER = "8.8.8.8";
public static List<String> getNetworkDnsServer(Context context) {
List<String> dnsResolver = new ArrayList<String>();
try {
dnsResolver = VpnUtils.getActiveNetworkDnsResolver(context);
} catch (Exception e) {
dnsResolver.add(DEFAULT_PRIMARY_DNS_SERVER);
dnsResolver.add(DEFAULT_SECONDARY_DNS_SERVER);
}
return dnsResolver;
}
/**
* Utils ip
*/
private static final Pattern IPV4_PATTERN =
Pattern.compile(
"^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
private static final Pattern IPV6_STD_PATTERN =
Pattern.compile(
"^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
private static final Pattern IPV6_HEX_COMPRESSED_PATTERN =
Pattern.compile(
"^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");
public static boolean isIPv4Address(final String input) {
return IPV4_PATTERN.matcher(input).matches();
}
public static boolean isIPv6StdAddress(final String input) {
return IPV6_STD_PATTERN.matcher(input).matches();
}
public static boolean isIPv6HexCompressedAddress(final String input) {
return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches();
}
public static boolean isIPv6Address(final String input) {
return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input);
}
//----------------------------------------------------------------------------
// Implementation: Network Utils
//----------------------------------------------------------------------------
public static class PrivateAddress
{
public final String mIpAddress;
public final String mSubnet;
public final int mPrefixLength;
public final String mRouter;
public PrivateAddress(String ipAddress, String subnet, int prefixLength, String router)
{
mIpAddress = ipAddress;
mSubnet = subnet;
mPrefixLength = prefixLength;
mRouter = router;
}
}
public static PrivateAddress selectPrivateAddress() throws Exception
{
// Select one of 10.0.0.1, 172.16.0.1, or 192.168.0.1 depending on
// which private address range isn't in use.
Map<String, PrivateAddress> candidates = new HashMap<String, PrivateAddress>();
candidates.put("10", new PrivateAddress("10.0.0.1", "10.0.0.0", 8, "10.0.0.2"));
candidates.put("172", new PrivateAddress("172.16.0.1", "172.16.0.0", 12, "172.16.0.2"));
candidates.put("192", new PrivateAddress("192.168.0.1", "192.168.0.0", 16, "192.168.0.2"));
candidates.put("169", new PrivateAddress("169.254.1.1", "169.254.1.0", 24, "169.254.1.2"));
List<NetworkInterface> netInterfaces;
try
{
netInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
}
catch (SocketException e)
{
e.printStackTrace();
throw new Exception("selectPrivateAddress failed", e);
}
for (NetworkInterface netInterface : netInterfaces)
{
for (InetAddress inetAddress : Collections.list(netInterface.getInetAddresses()))
{
if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address)
{
String ipAddress = inetAddress.getHostAddress();
if (ipAddress.startsWith("10."))
{
candidates.remove("10");
}
else if (ipAddress.length() >= 6
&& ipAddress.substring(0, 6).compareTo("172.16") >= 0
&& ipAddress.substring(0, 6).compareTo("172.31") <= 0)
{
candidates.remove("172");
}
else if (ipAddress.startsWith("192.168"))
{
candidates.remove("192");
}
}
}
}
if (candidates.size() > 0)
{
return candidates.values().iterator().next();
}
throw new Exception("no private address available");
}
private static boolean isPortAvailable(int port)
{
Socket socket = new Socket();
SocketAddress sockaddr = new InetSocketAddress("127.0.0.1", port);
try
{
socket.connect(sockaddr, 1000);
// The connect succeeded, so there is already something running on that port
return false;
}
catch (SocketTimeoutException e)
{
// The socket is in use, but the server didn't respond quickly enough
return false;
}
catch (IOException e)
{
// The connect failed, so the port is available
return true;
}
finally
{
if (socket != null)
{
try
{
socket.close();
}
catch (IOException e)
{
/* should not be thrown */
}
}
}
}
public static int findAvailablePort(int start_port, int max_increment)
{
for(int port = start_port; port < (start_port + max_increment); port++)
{
if (isPortAvailable(port))
{
return port;
}
}
return 0;
}
}
@@ -0,0 +1,163 @@
package com.dragonssh.xhttpdemo.core.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.List;
import java.util.ArrayList;
import android.text.TextUtils;
public class CustomNativeLoader {
private final static String TAG = "CNL";
private static boolean loadFromZip(Context context, String libname, File destLocalFile, String arch) {
ZipFile zipFile = null;
InputStream stream = null;
try {
zipFile = new ZipFile(context.getApplicationInfo().sourceDir);
ZipEntry entry = zipFile.getEntry("lib/" + arch + "/" + libname + ".so");
if (entry == null) {
entry = zipFile.getEntry("jni/" + arch + "/" + libname + ".so");
if (entry == null)
throw new Exception("Unable to find file in apk:" + "lib/" + arch + "/" + libname);
}
//how we wrap this in another stream because the native .so is zipped itself
stream = zipFile.getInputStream(entry);
OutputStream out = new FileOutputStream(destLocalFile);
byte[] buf = new byte[4096];
int len;
while ((len = stream.read(buf)) > 0) {
Thread.yield();
out.write(buf, 0, len);
}
out.close();
destLocalFile.setReadable(true, false);
destLocalFile.setExecutable(true, false);
destLocalFile.setWritable(true);
return true;
} catch (Exception e) {
Log.e(TAG, e.getMessage());
} finally {
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
if (zipFile != null) {
try {
zipFile.close();
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
}
return false;
}
public static File loadNativeBinary(Context context, String libname, File destLocalFile) {
try {
File fileNativeBin = new File(getNativeLibraryDir(context), libname + ".so");
if (fileNativeBin.exists())
{
if (fileNativeBin.canExecute())
return fileNativeBin;
else
{
setExecutable(fileNativeBin);
if (fileNativeBin.canExecute())
return fileNativeBin;
}
}
else if (destLocalFile.exists()) {
if (destLocalFile.canExecute())
return destLocalFile;
else
{
setExecutable(destLocalFile);
if (destLocalFile.canExecute())
return destLocalFile;
}
}
List<String> abisList = new ArrayList<>();
if (Build.VERSION.SDK_INT >= 21) {
String[] abis = Build.SUPPORTED_ABIS;
if (abis != null) {
for (String abi : abis) {
if (!TextUtils.isEmpty(abi)) {
abisList.add(abi);
}
}
}
}
else {
String[] abis = new String[]{
Build.CPU_ABI,
Build.CPU_ABI2
};
for (String abi : abis) {
if (!TextUtils.isEmpty(abi)) {
abisList.add(abi);
}
}
}
for (String folder : abisList) {
String javaArch = System.getProperty("os.arch");
if (javaArch != null && javaArch.contains("686")) {
folder = "x86";
}
if (loadFromZip(context, libname, destLocalFile, folder)) {
return destLocalFile;
}
}
} catch (Throwable e) {
Log.e(TAG, e.getMessage(), e);
}
return null;
}
private static void setExecutable(File fileBin) {
fileBin.setReadable(true);
fileBin.setExecutable(true);
fileBin.setWritable(false);
fileBin.setWritable(true, true);
}
// Return Full path to the directory where native JNI libraries are stored.
private static String getNativeLibraryDir(Context context) {
ApplicationInfo appInfo = context.getApplicationInfo();
return appInfo.nativeLibraryDir;
}
}
@@ -0,0 +1,76 @@
package com.dragonssh.xhttpdemo.core.util;
import android.content.Context;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;
public class FileUtils {
private static final String TAG = "FileUtils";
public static void copiarArquivo(InputStream in, OutputStream out) throws IOException {
byte[] buf = new byte[8192];
int len;
try {
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.flush();
} finally {
try {
in.close();
} finally {
out.close();
}
}
}
public static String readFromRaw(Context context, int resId) {
InputStream in = context.getResources().openRawResource(resId);
Scanner scanner = new Scanner(in, "UTF-8").useDelimiter("\\A");
StringBuilder sb = new StringBuilder();
while (scanner.hasNext()) {
sb.append(scanner.next());
}
scanner.close();
return sb.toString();
}
public static String readTextFile(File f) {
StringBuilder text = new StringBuilder();
try {
BufferedReader br = new BufferedReader(new FileReader(f));
String st;
while ((st = br.readLine()) != null) {
text.append(st).append('\n');
}
br.close();
return text.toString();
} catch (Exception e) {
return null;
}
}
public static boolean saveTextFile(File file, String contents) {
try {
if (!file.exists()) {
file.createNewFile();
}
FileWriter writer = new FileWriter(file, false);
writer.write(contents);
writer.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
@@ -0,0 +1,54 @@
package com.dragonssh.xhttpdemo.core.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
public class StreamGobbler extends Thread {
private final BufferedReader reader;
private List<String> writer;
private StreamGobbler.OnLineListener listener;
public StreamGobbler(InputStream inputStream, List<String> outputList) {
this.reader = new BufferedReader(new InputStreamReader(inputStream));
this.writer = outputList;
}
public StreamGobbler(InputStream inputStream, StreamGobbler.OnLineListener onLineListener) {
this.reader = new BufferedReader(new InputStreamReader(inputStream));
this.listener = onLineListener;
}
public void run() {
while(true) {
try {
String line;
if ((line = this.reader.readLine()) != null) {
if (this.writer != null) {
this.writer.add(line);
}
if (this.listener != null) {
this.listener.onLine(line);
}
continue;
}
} catch (IOException var3) {}
try {
this.reader.close();
} catch (IOException var2) {}
return;
}
}
public interface OnLineListener {
void onLine(String var1);
}
}
@@ -0,0 +1,139 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final public class Adler32 implements Checksum {
// largest prime smaller than 65536
static final private int BASE=65521;
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
static final private int NMAX=5552;
private long s1=1L;
private long s2=0L;
public void reset(long init){
s1=init&0xffff;
s2=(init>>16)&0xffff;
}
public void reset(){
s1=1L;
s2=0L;
}
public long getValue(){
return ((s2<<16)|s1);
}
public void update(byte[] buf, int index, int len){
if(len==1){
s1+=buf[index++]&0xff; s2+=s1;
s1%=BASE;
s2%=BASE;
return;
}
int len1 = len/NMAX;
int len2 = len%NMAX;
while(len1-->0) {
int k=NMAX;
len-=k;
while(k-->0){
s1+=buf[index++]&0xff; s2+=s1;
}
s1%=BASE;
s2%=BASE;
}
int k=len2;
len-=k;
while(k-->0){
s1+=buf[index++]&0xff; s2+=s1;
}
s1%=BASE;
s2%=BASE;
}
public Adler32 copy(){
Adler32 foo = new Adler32();
foo.s1 = this.s1;
foo.s2 = this.s2;
return foo;
}
// The following logic has come from zlib.1.2.
static long combine(long adler1, long adler2, long len2){
long BASEL = (long)BASE;
long sum1;
long sum2;
long rem; // unsigned int
rem = len2 % BASEL;
sum1 = adler1 & 0xffffL;
sum2 = rem * sum1;
sum2 %= BASEL; // MOD(sum2);
sum1 += (adler2 & 0xffffL) + BASEL - 1;
sum2 += ((adler1 >> 16) & 0xffffL) + ((adler2 >> 16) & 0xffffL) + BASEL - rem;
if (sum1 >= BASEL) sum1 -= BASEL;
if (sum1 >= BASEL) sum1 -= BASEL;
if (sum2 >= (BASEL << 1)) sum2 -= (BASEL << 1);
if (sum2 >= BASEL) sum2 -= BASEL;
return sum1 | (sum2 << 16);
}
/*
private java.util.zip.Adler32 adler=new java.util.zip.Adler32();
public void update(byte[] buf, int index, int len){
if(buf==null) {adler.reset();}
else{adler.update(buf, index, len);}
}
public void reset(){
adler.reset();
}
public void reset(long init){
if(init==1L){
adler.reset();
}
else{
System.err.println("unsupported operation");
}
}
public long getValue(){
return adler.getValue();
}
*/
}
@@ -0,0 +1,179 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final public class CRC32 implements Checksum {
/*
* The following logic has come from RFC1952.
*/
private int v = 0;
private static int[] crc_table = null;
static {
crc_table = new int[256];
for (int n = 0; n < 256; n++) {
int c = n;
for (int k = 8; --k >= 0; ) {
if ((c & 1) != 0)
c = 0xedb88320 ^ (c >>> 1);
else
c = c >>> 1;
}
crc_table[n] = c;
}
}
public void update (byte[] buf, int index, int len) {
int c = ~v;
while (--len >= 0)
c = crc_table[(c^buf[index++])&0xff]^(c >>> 8);
v = ~c;
}
public void reset(){
v = 0;
}
public void reset(long vv){
v = (int)(vv&0xffffffffL);
}
public long getValue(){
return (long)(v&0xffffffffL);
}
// The following logic has come from zlib.1.2.
private static final int GF2_DIM = 32;
static long combine(long crc1, long crc2, long len2){
long row;
long[] even = new long[GF2_DIM];
long[] odd = new long[GF2_DIM];
// degenerate case (also disallow negative lengths)
if (len2 <= 0)
return crc1;
// put operator for one zero bit in odd
odd[0] = 0xedb88320L; // CRC-32 polynomial
row = 1;
for (int n = 1; n < GF2_DIM; n++) {
odd[n] = row;
row <<= 1;
}
// put operator for two zero bits in even
gf2_matrix_square(even, odd);
// put operator for four zero bits in odd
gf2_matrix_square(odd, even);
// apply len2 zeros to crc1 (first square will put the operator for one
// zero byte, eight zero bits, in even)
do {
// apply zeros operator for this bit of len2
gf2_matrix_square(even, odd);
if ((len2 & 1)!=0)
crc1 = gf2_matrix_times(even, crc1);
len2 >>= 1;
// if no more bits set, then done
if (len2 == 0)
break;
// another iteration of the loop with odd and even swapped
gf2_matrix_square(odd, even);
if ((len2 & 1)!=0)
crc1 = gf2_matrix_times(odd, crc1);
len2 >>= 1;
// if no more bits set, then done
} while (len2 != 0);
/* return combined crc */
crc1 ^= crc2;
return crc1;
}
private static long gf2_matrix_times(long[] mat, long vec){
long sum = 0;
int index = 0;
while (vec!=0) {
if ((vec & 1)!=0)
sum ^= mat[index];
vec >>= 1;
index++;
}
return sum;
}
static final void gf2_matrix_square(long[] square, long[] mat) {
for (int n = 0; n < GF2_DIM; n++)
square[n] = gf2_matrix_times(mat, mat[n]);
}
/*
private java.util.zip.CRC32 crc32 = new java.util.zip.CRC32();
public void update(byte[] buf, int index, int len){
if(buf==null) {crc32.reset();}
else{crc32.update(buf, index, len);}
}
public void reset(){
crc32.reset();
}
public void reset(long init){
if(init==0L){
crc32.reset();
}
else{
System.err.println("unsupported operation");
}
}
public long getValue(){
return crc32.getValue();
}
*/
public CRC32 copy(){
CRC32 foo = new CRC32();
foo.v = this.v;
return foo;
}
public static int[] getCRC32Table(){
int[] tmp = new int[crc_table.length];
System.arraycopy(crc_table, 0, tmp, 0, tmp.length);
return tmp;
}
}
@@ -0,0 +1,43 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
interface Checksum {
void update(byte[] buf, int index, int len);
void reset();
void reset(long init);
long getValue();
Checksum copy();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,171 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final public class Deflater extends ZStream{
static final private int MAX_WBITS=15; // 32K LZ77 window
static final private int DEF_WBITS=MAX_WBITS;
static final private int Z_NO_FLUSH=0;
static final private int Z_PARTIAL_FLUSH=1;
static final private int Z_SYNC_FLUSH=2;
static final private int Z_FULL_FLUSH=3;
static final private int Z_FINISH=4;
static final private int MAX_MEM_LEVEL=9;
static final private int Z_OK=0;
static final private int Z_STREAM_END=1;
static final private int Z_NEED_DICT=2;
static final private int Z_ERRNO=-1;
static final private int Z_STREAM_ERROR=-2;
static final private int Z_DATA_ERROR=-3;
static final private int Z_MEM_ERROR=-4;
static final private int Z_BUF_ERROR=-5;
static final private int Z_VERSION_ERROR=-6;
private boolean finished = false;
public Deflater(){
super();
}
public Deflater(int level) throws GZIPException {
this(level, MAX_WBITS);
}
public Deflater(int level, boolean nowrap) throws GZIPException {
this(level, MAX_WBITS, nowrap);
}
public Deflater(int level, int bits) throws GZIPException {
this(level, bits, false);
}
public Deflater(int level, int bits, boolean nowrap) throws GZIPException {
super();
int ret = init(level, bits, nowrap);
if(ret!=Z_OK)
throw new GZIPException(ret+": "+msg);
}
public Deflater(int level, int bits, int memlevel, JZlib.WrapperType wrapperType) throws GZIPException {
super();
int ret = init(level, bits, memlevel, wrapperType);
if(ret!=Z_OK)
throw new GZIPException(ret+": "+msg);
}
public Deflater(int level, int bits, int memlevel) throws GZIPException {
super();
int ret = init(level, bits, memlevel);
if(ret!=Z_OK)
throw new GZIPException(ret+": "+msg);
}
public int init(int level){
return init(level, MAX_WBITS);
}
public int init(int level, boolean nowrap){
return init(level, MAX_WBITS, nowrap);
}
public int init(int level, int bits){
return init(level, bits, false);
}
public int init(int level, int bits, int memlevel, JZlib.WrapperType wrapperType){
if(bits < 9 || bits > 15){
return Z_STREAM_ERROR;
}
if(wrapperType == JZlib.W_NONE) {
bits *= -1;
}
else if(wrapperType == JZlib.W_GZIP) {
bits += 16;
}
else if(wrapperType == JZlib.W_ANY) {
return Z_STREAM_ERROR;
}
else if(wrapperType == JZlib.W_ZLIB) {
}
return init(level, bits, memlevel);
}
public int init(int level, int bits, int memlevel){
finished = false;
dstate=new Deflate(this);
return dstate.deflateInit(level, bits, memlevel);
}
public int init(int level, int bits, boolean nowrap){
finished = false;
dstate=new Deflate(this);
return dstate.deflateInit(level, nowrap?-bits:bits);
}
public int deflate(int flush){
if(dstate==null){
return Z_STREAM_ERROR;
}
int ret = dstate.deflate(flush);
if(ret == Z_STREAM_END)
finished = true;
return ret;
}
public int end(){
finished = true;
if(dstate==null) return Z_STREAM_ERROR;
int ret=dstate.deflateEnd();
dstate=null;
free();
return ret;
}
public int params(int level, int strategy){
if(dstate==null) return Z_STREAM_ERROR;
return dstate.deflateParams(level, strategy);
}
public int setDictionary (byte[] dictionary, int dictLength){
if(dstate == null)
return Z_STREAM_ERROR;
return dstate.deflateSetDictionary(dictionary, dictLength);
}
public boolean finished(){
return finished;
}
public int copy(Deflater src){
this.finished = src.finished;
return Deflate.deflateCopy(this, src);
}
}
@@ -0,0 +1,181 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jzlib;
import java.io.*;
public class DeflaterOutputStream extends FilterOutputStream {
protected final Deflater deflater;
protected byte[] buffer;
private boolean closed = false;
private boolean syncFlush = false;
private final byte[] buf1 = new byte[1];
protected boolean mydeflater = false;
private boolean close_out = true;
protected static final int DEFAULT_BUFSIZE = 512;
public DeflaterOutputStream(OutputStream out) throws IOException {
this(out,
new Deflater(JZlib.Z_DEFAULT_COMPRESSION),
DEFAULT_BUFSIZE, true);
mydeflater = true;
}
public DeflaterOutputStream(OutputStream out, Deflater def) throws IOException {
this(out, def, DEFAULT_BUFSIZE, true);
}
public DeflaterOutputStream(OutputStream out,
Deflater deflater,
int size) throws IOException {
this(out, deflater, size, true);
}
public DeflaterOutputStream(OutputStream out,
Deflater deflater,
int size,
boolean close_out) throws IOException {
super(out);
if (out == null || deflater == null) {
throw new NullPointerException();
}
else if (size <= 0) {
throw new IllegalArgumentException("buffer size must be greater than 0");
}
this.deflater = deflater;
buffer = new byte[size];
this.close_out = close_out;
}
public void write(int b) throws IOException {
buf1[0] = (byte)(b & 0xff);
write(buf1, 0, 1);
}
public void write(byte[] b, int off, int len) throws IOException {
if (deflater.finished()) {
throw new IOException("finished");
}
else if (off<0 | len<0 | off+len>b.length) {
throw new IndexOutOfBoundsException();
}
else if (len == 0) {
return;
}
else {
int flush = syncFlush ? JZlib.Z_SYNC_FLUSH : JZlib.Z_NO_FLUSH;
deflater.setInput(b, off, len, true);
while (deflater.avail_in>0) {
int err = deflate(flush);
if (err == JZlib.Z_STREAM_END)
break;
}
}
}
public void finish() throws IOException {
while (!deflater.finished()) {
deflate(JZlib.Z_FINISH);
}
}
public void close() throws IOException {
if (!closed) {
finish();
if (mydeflater){
deflater.end();
}
if(close_out)
out.close();
closed = true;
}
}
protected int deflate(int flush) throws IOException {
deflater.setOutput(buffer, 0, buffer.length);
int err = deflater.deflate(flush);
switch(err) {
case JZlib.Z_OK:
case JZlib.Z_STREAM_END:
break;
case JZlib.Z_BUF_ERROR:
if(deflater.avail_in<=0 && flush!=JZlib.Z_FINISH){
// flush() without any data
break;
}
default:
throw new IOException("failed to deflate: error="+err+" avail_out="+deflater.avail_out);
}
int len = deflater.next_out_index;
if (len > 0) {
out.write(buffer, 0, len);
}
return err;
}
public void flush() throws IOException {
if (syncFlush && !deflater.finished()) {
while (true) {
int err = deflate(JZlib.Z_SYNC_FLUSH);
if (deflater.next_out_index < buffer.length)
break;
if (err == JZlib.Z_STREAM_END)
break;
}
}
out.flush();
}
public long getTotalIn() {
return deflater.getTotalIn();
}
public long getTotalOut() {
return deflater.getTotalOut();
}
public void setSyncFlush(boolean syncFlush){
this.syncFlush = syncFlush;
}
public boolean getSyncFlush(){
return this.syncFlush;
}
public Deflater getDeflater(){
return deflater;
}
}
@@ -0,0 +1,44 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
public class GZIPException extends java.io.IOException {
public GZIPException() {
super();
}
public GZIPException(String s) {
super(s);
}
}
@@ -0,0 +1,214 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
import java.io.UnsupportedEncodingException;
/**
* @see "http://www.ietf.org/rfc/rfc1952.txt"
*/
public class GZIPHeader implements Cloneable {
public static final byte OS_MSDOS = (byte) 0x00;
public static final byte OS_AMIGA = (byte) 0x01;
public static final byte OS_VMS = (byte) 0x02;
public static final byte OS_UNIX = (byte) 0x03;
public static final byte OS_ATARI = (byte) 0x05;
public static final byte OS_OS2 = (byte) 0x06;
public static final byte OS_MACOS = (byte) 0x07;
public static final byte OS_TOPS20 = (byte) 0x0a;
public static final byte OS_WIN32 = (byte) 0x0b;
public static final byte OS_VMCMS = (byte) 0x04;
public static final byte OS_ZSYSTEM = (byte) 0x08;
public static final byte OS_CPM = (byte) 0x09;
public static final byte OS_QDOS = (byte) 0x0c;
public static final byte OS_RISCOS = (byte) 0x0d;
public static final byte OS_UNKNOWN = (byte) 0xff;
boolean text = false;
private boolean fhcrc = false;
long time;
int xflags;
int os = 255;
byte[] extra;
byte[] name;
byte[] comment;
int hcrc;
long crc;
boolean done = false;
long mtime = 0;
public void setModifiedTime(long mtime) {
this.mtime = mtime;
}
public long getModifiedTime() {
return mtime;
}
public void setOS(int os) {
if((0<=os && os <=13) || os==255)
this.os=os;
else
throw new IllegalArgumentException("os: "+os);
}
public int getOS(){
return os;
}
public void setName(String name) {
try{
this.name=name.getBytes("ISO-8859-1");
}
catch(UnsupportedEncodingException e){
throw new IllegalArgumentException("name must be in ISO-8859-1 "+name);
}
}
public String getName(){
if(name==null) return "";
try {
return new String(name, "ISO-8859-1");
}
catch (UnsupportedEncodingException e) {
throw new InternalError(e.toString());
}
}
public void setComment(String comment) {
try{
this.comment=comment.getBytes("ISO-8859-1");
}
catch(UnsupportedEncodingException e){
throw new IllegalArgumentException("comment must be in ISO-8859-1 "+name);
}
}
public String getComment(){
if(comment==null) return "";
try {
return new String(comment, "ISO-8859-1");
}
catch (UnsupportedEncodingException e) {
throw new InternalError(e.toString());
}
}
public void setCRC(long crc){
this.crc = crc;
}
public long getCRC(){
return crc;
}
void put(Deflate d){
int flag = 0;
if(text){
flag |= 1; // FTEXT
}
if(fhcrc){
flag |= 2; // FHCRC
}
if(extra!=null){
flag |= 4; // FEXTRA
}
if(name!=null){
flag |= 8; // FNAME
}
if(comment!=null){
flag |= 16; // FCOMMENT
}
int xfl = 0;
if(d.level == JZlib.Z_BEST_SPEED){
xfl |= 4;
}
else if (d.level == JZlib.Z_BEST_COMPRESSION){
xfl |= 2;
}
d.put_short((short)0x8b1f); // ID1 ID2
d.put_byte((byte)8); // CM(Compression Method)
d.put_byte((byte)flag);
d.put_byte((byte)mtime);
d.put_byte((byte)(mtime>>8));
d.put_byte((byte)(mtime>>16));
d.put_byte((byte)(mtime>>24));
d.put_byte((byte)xfl);
d.put_byte((byte)os);
if(extra!=null){
d.put_byte((byte)extra.length);
d.put_byte((byte)(extra.length>>8));
d.put_byte(extra, 0, extra.length);
}
if(name!=null){
d.put_byte(name, 0, name.length);
d.put_byte((byte)0);
}
if(comment!=null){
d.put_byte(comment, 0, comment.length);
d.put_byte((byte)0);
}
}
@Override
public Object clone() throws CloneNotSupportedException {
GZIPHeader gheader = (GZIPHeader)super.clone();
byte[] tmp;
if(gheader.extra!=null){
tmp=new byte[gheader.extra.length];
System.arraycopy(gheader.extra, 0, tmp, 0, tmp.length);
gheader.extra = tmp;
}
if(gheader.name!=null){
tmp=new byte[gheader.name.length];
System.arraycopy(gheader.name, 0, tmp, 0, tmp.length);
gheader.name = tmp;
}
if(gheader.comment!=null){
tmp=new byte[gheader.comment.length];
System.arraycopy(gheader.comment, 0, tmp, 0, tmp.length);
gheader.comment = tmp;
}
return gheader;
}
}
@@ -0,0 +1,145 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jzlib;
import java.io.*;
public class GZIPInputStream extends InflaterInputStream {
public GZIPInputStream(InputStream in) throws IOException {
this(in, DEFAULT_BUFSIZE, true);
}
public GZIPInputStream(InputStream in,
int size,
boolean close_in) throws IOException {
this(in, new Inflater(15+16), size, close_in);
myinflater = true;
}
public GZIPInputStream(InputStream in,
Inflater inflater,
int size,
boolean close_in) throws IOException {
super(in, inflater, size, close_in);
}
public long getModifiedtime() {
return inflater.istate.getGZIPHeader().getModifiedTime();
}
public int getOS() {
return inflater.istate.getGZIPHeader().getOS();
}
public String getName() {
return inflater.istate.getGZIPHeader().getName();
}
public String getComment() {
return inflater.istate.getGZIPHeader().getComment();
}
public long getCRC() throws GZIPException {
if(inflater.istate.mode != 12 /*DONE*/)
throw new GZIPException("checksum is not calculated yet.");
return inflater.istate.getGZIPHeader().getCRC();
}
public void readHeader() throws IOException {
byte[] empty = "".getBytes();
inflater.setOutput(empty, 0, 0);
inflater.setInput(empty, 0, 0, false);
byte[] b = new byte[10];
int n = fill(b);
if(n!=10){
if(n>0){
inflater.setInput(b, 0, n, false);
//inflater.next_in_index = n;
inflater.next_in_index = 0;
inflater.avail_in = n;
}
throw new IOException("no input");
}
inflater.setInput(b, 0, n, false);
byte[] b1 = new byte[1];
do{
if(inflater.avail_in<=0){
int i = in.read(b1);
if(i<=0)
throw new IOException("no input");
inflater.setInput(b1, 0, 1, true);
}
int err = inflater.inflate(JZlib.Z_NO_FLUSH);
if(err!=0/*Z_OK*/){
int len = 2048-inflater.next_in.length;
if(len>0){
byte[] tmp = new byte[len];
n = fill(tmp);
if(n>0){
inflater.avail_in += inflater.next_in_index;
inflater.next_in_index = 0;
inflater.setInput(tmp, 0, n, true);
}
}
//inflater.next_in_index = inflater.next_in.length;
inflater.avail_in += inflater.next_in_index;
inflater.next_in_index = 0;
throw new IOException(inflater.msg);
}
}
while(inflater.istate.inParsingHeader());
}
private int fill(byte[] buf) {
int len = buf.length;
int n = 0;
do{
int i = -1;
try {
i = in.read(buf, n, buf.length - n);
}
catch(IOException e){
}
if(i == -1){
break;
}
n+=i;
}
while(n<len);
return n;
}
}
@@ -0,0 +1,90 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jzlib;
import java.io.*;
public class GZIPOutputStream extends DeflaterOutputStream {
public GZIPOutputStream(OutputStream out) throws IOException {
this(out, DEFAULT_BUFSIZE);
}
public GZIPOutputStream(OutputStream out, int size) throws IOException {
this(out, size, true);
}
public GZIPOutputStream(OutputStream out,
int size,
boolean close_out) throws IOException {
this(out,
new Deflater(JZlib.Z_DEFAULT_COMPRESSION, 15+16),
size, close_out);
mydeflater=true;
}
public GZIPOutputStream(OutputStream out,
Deflater deflater,
int size,
boolean close_out) throws IOException{
super(out, deflater, size, close_out);
}
private void check() throws GZIPException {
if(deflater.dstate.status != 42 /*INIT_STATUS*/)
throw new GZIPException("header is already written.");
}
public void setModifiedTime(long mtime) throws GZIPException {
check();
deflater.dstate.getGZIPHeader().setModifiedTime(mtime);
}
public void setOS(int os) throws GZIPException {
check();
deflater.dstate.getGZIPHeader().setOS(os);
}
public void setName(String name) throws GZIPException {
check();
deflater.dstate.getGZIPHeader().setName(name);
}
public void setComment(String comment) throws GZIPException {
check();
deflater.dstate.getGZIPHeader().setComment(comment);
}
public long getCRC() throws GZIPException {
if(deflater.dstate.status != 666 /*FINISH_STATE*/)
throw new GZIPException("checksum is not calculated yet.");
return deflater.dstate.getGZIPHeader().getCRC();
}
}
@@ -0,0 +1,614 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final class InfBlocks{
static final private int MANY=1440;
// And'ing with mask[n] masks the lower n bits
static final private int[] inflate_mask = {
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f,
0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff,
0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff,
0x00007fff, 0x0000ffff
};
// Table for deflate from PKZIP's appnote.txt.
static final int[] border = { // Order of the bit length code lengths
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
};
static final private int Z_OK=0;
static final private int Z_STREAM_END=1;
static final private int Z_NEED_DICT=2;
static final private int Z_ERRNO=-1;
static final private int Z_STREAM_ERROR=-2;
static final private int Z_DATA_ERROR=-3;
static final private int Z_MEM_ERROR=-4;
static final private int Z_BUF_ERROR=-5;
static final private int Z_VERSION_ERROR=-6;
static final private int TYPE=0; // get type bits (3, including end bit)
static final private int LENS=1; // get lengths for stored
static final private int STORED=2;// processing stored block
static final private int TABLE=3; // get table lengths
static final private int BTREE=4; // get bit lengths tree for a dynamic block
static final private int DTREE=5; // get length, distance trees for a dynamic block
static final private int CODES=6; // processing fixed or dynamic block
static final private int DRY=7; // output remaining window bytes
static final private int DONE=8; // finished last block, done
static final private int BAD=9; // ot a data error--stuck here
int mode; // current inflate_block mode
int left; // if STORED, bytes left to copy
int table; // table lengths (14 bits)
int index; // index into blens (or border)
int[] blens; // bit lengths of codes
int[] bb=new int[1]; // bit length tree depth
int[] tb=new int[1]; // bit length decoding tree
int[] bl=new int[1];
int[] bd=new int[1];
int[][] tl=new int[1][];
int[][] td=new int[1][];
int[] tli=new int[1]; // tl_index
int[] tdi=new int[1]; // td_index
private final InfCodes codes; // if CODES, current state
int last; // true if this block is the last block
// mode independent information
int bitk; // bits in bit buffer
int bitb; // bit buffer
int[] hufts; // single malloc for tree space
byte[] window; // sliding window
int end; // one byte after sliding window
int read; // window read pointer
int write; // window write pointer
private boolean check;
private final InfTree inftree=new InfTree();
private final ZStream z;
InfBlocks(ZStream z, int w){
this.z=z;
this.codes=new InfCodes(this.z, this);
hufts=new int[MANY*3];
window=new byte[w];
end=w;
this.check = (z.istate.wrap==0) ? false : true;
mode = TYPE;
reset();
}
void reset(){
if(mode==BTREE || mode==DTREE){
}
if(mode==CODES){
codes.free(z);
}
mode=TYPE;
bitk=0;
bitb=0;
read=write=0;
if(check){
z.adler.reset();
}
}
int proc(int r){
int t; // temporary storage
int b; // bit buffer
int k; // bits in bit buffer
int p; // input data pointer
int n; // bytes available there
int q; // output window write pointer
int m; // bytes to end of window or read pointer
// copy input/output information to locals (UPDATE macro restores)
{p=z.next_in_index;n=z.avail_in;b=bitb;k=bitk;}
{q=write;m=(int)(q<read?read-q-1:end-q);}
// process input based on current state
while(true){
switch (mode){
case TYPE:
while(k<(3)){
if(n!=0){
r=Z_OK;
}
else{
bitb=b; bitk=k;
z.avail_in=n;
z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
};
n--;
b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
t = (int)(b & 7);
last = t & 1;
switch (t >>> 1){
case 0: // stored
{b>>>=(3);k-=(3);}
t = k & 7; // go to byte boundary
{b>>>=(t);k-=(t);}
mode = LENS; // get length of stored block
break;
case 1: // fixed
InfTree.inflate_trees_fixed(bl, bd, tl, td, z);
codes.init(bl[0], bd[0], tl[0], 0, td[0], 0);
{b>>>=(3);k-=(3);}
mode = CODES;
break;
case 2: // dynamic
{b>>>=(3);k-=(3);}
mode = TABLE;
break;
case 3: // illegal
{b>>>=(3);k-=(3);}
mode = BAD;
z.msg = "invalid block type";
r = Z_DATA_ERROR;
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
break;
case LENS:
while(k<(32)){
if(n!=0){
r=Z_OK;
}
else{
bitb=b; bitk=k;
z.avail_in=n;
z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
};
n--;
b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
if ((((~b) >>> 16) & 0xffff) != (b & 0xffff)){
mode = BAD;
z.msg = "invalid stored block lengths";
r = Z_DATA_ERROR;
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
left = (b & 0xffff);
b = k = 0; // dump bits
mode = left!=0 ? STORED : (last!=0 ? DRY : TYPE);
break;
case STORED:
if (n == 0){
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
if(m==0){
if(q==end&&read!=0){
q=0; m=(int)(q<read?read-q-1:end-q);
}
if(m==0){
write=q;
r=inflate_flush(r);
q=write;m=(int)(q<read?read-q-1:end-q);
if(q==end&&read!=0){
q=0; m=(int)(q<read?read-q-1:end-q);
}
if(m==0){
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
}
}
r=Z_OK;
t = left;
if(t>n) t = n;
if(t>m) t = m;
System.arraycopy(z.next_in, p, window, q, t);
p += t; n -= t;
q += t; m -= t;
if ((left -= t) != 0)
break;
mode = last!=0 ? DRY : TYPE;
break;
case TABLE:
while(k<(14)){
if(n!=0){
r=Z_OK;
}
else{
bitb=b; bitk=k;
z.avail_in=n;
z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
};
n--;
b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
table = t = (b & 0x3fff);
if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29)
{
mode = BAD;
z.msg = "too many length or distance symbols";
r = Z_DATA_ERROR;
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f);
if(blens==null || blens.length<t){
blens=new int[t];
}
else{
for(int i=0; i<t; i++){blens[i]=0;}
}
{b>>>=(14);k-=(14);}
index = 0;
mode = BTREE;
case BTREE:
while (index < 4 + (table >>> 10)){
while(k<(3)){
if(n!=0){
r=Z_OK;
}
else{
bitb=b; bitk=k;
z.avail_in=n;
z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
};
n--;
b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
blens[border[index++]] = b&7;
{b>>>=(3);k-=(3);}
}
while(index < 19){
blens[border[index++]] = 0;
}
bb[0] = 7;
t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z);
if (t != Z_OK){
r = t;
if (r == Z_DATA_ERROR){
blens=null;
mode = BAD;
}
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
index = 0;
mode = DTREE;
case DTREE:
while (true){
t = table;
if(!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))){
break;
}
int[] h;
int i, j, c;
t = bb[0];
while(k<(t)){
if(n!=0){
r=Z_OK;
}
else{
bitb=b; bitk=k;
z.avail_in=n;
z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
};
n--;
b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
if(tb[0]==-1){
//System.err.println("null...");
}
t=hufts[(tb[0]+(b&inflate_mask[t]))*3+1];
c=hufts[(tb[0]+(b&inflate_mask[t]))*3+2];
if (c < 16){
b>>>=(t);k-=(t);
blens[index++] = c;
}
else { // c == 16..18
i = c == 18 ? 7 : c - 14;
j = c == 18 ? 11 : 3;
while(k<(t+i)){
if(n!=0){
r=Z_OK;
}
else{
bitb=b; bitk=k;
z.avail_in=n;
z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
};
n--;
b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
b>>>=(t);k-=(t);
j += (b & inflate_mask[i]);
b>>>=(i);k-=(i);
i = index;
t = table;
if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) ||
(c == 16 && i < 1)){
blens=null;
mode = BAD;
z.msg = "invalid bit length repeat";
r = Z_DATA_ERROR;
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
c = c == 16 ? blens[i-1] : 0;
do{
blens[i++] = c;
}
while (--j!=0);
index = i;
}
}
tb[0]=-1;
{
bl[0] = 9; // must be <= 9 for lookahead assumptions
bd[0] = 6; // must be <= 9 for lookahead assumptions
t = table;
t = inftree.inflate_trees_dynamic(257 + (t & 0x1f),
1 + ((t >> 5) & 0x1f),
blens, bl, bd, tli, tdi, hufts, z);
if (t != Z_OK){
if (t == Z_DATA_ERROR){
blens=null;
mode = BAD;
}
r = t;
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
codes.init(bl[0], bd[0], hufts, tli[0], hufts, tdi[0]);
}
mode = CODES;
case CODES:
bitb=b; bitk=k;
z.avail_in=n; z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
if ((r = codes.proc(r)) != Z_STREAM_END){
return inflate_flush(r);
}
r = Z_OK;
codes.free(z);
p=z.next_in_index; n=z.avail_in;b=bitb;k=bitk;
q=write;m=(int)(q<read?read-q-1:end-q);
if (last==0){
mode = TYPE;
break;
}
mode = DRY;
case DRY:
write=q;
r=inflate_flush(r);
q=write; m=(int)(q<read?read-q-1:end-q);
if (read != write){
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
mode = DONE;
case DONE:
r = Z_STREAM_END;
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
case BAD:
r = Z_DATA_ERROR;
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
default:
r = Z_STREAM_ERROR;
bitb=b; bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
write=q;
return inflate_flush(r);
}
}
}
void free(){
reset();
window=null;
hufts=null;
//ZFREE(z, s);
}
void set_dictionary(byte[] d, int start, int n){
System.arraycopy(d, start, window, 0, n);
read = write = n;
}
// Returns true if inflate is currently at the end of a block generated
// by Z_SYNC_FLUSH or Z_FULL_FLUSH.
int sync_point(){
return mode == LENS ? 1 : 0;
}
// copy as much as possible from the sliding window to the output area
int inflate_flush(int r){
int n;
int p;
int q;
// local copies of source and destination pointers
p = z.next_out_index;
q = read;
// compute number of bytes to copy as far as end of window
n = (int)((q <= write ? write : end) - q);
if(n > z.avail_out) n = z.avail_out;
if(n!=0 && r == Z_BUF_ERROR) r = Z_OK;
// update counters
z.avail_out -= n;
z.total_out += n;
// update check information
if(check && n>0){
z.adler.update(window, q, n);
}
// copy as far as end of window
System.arraycopy(window, q, z.next_out, p, n);
p += n;
q += n;
// see if more to copy at beginning of window
if (q == end){
// wrap pointers
q = 0;
if (write == end)
write = 0;
// compute bytes to copy
n = write - q;
if (n > z.avail_out) n = z.avail_out;
if (n!=0 && r == Z_BUF_ERROR) r = Z_OK;
// update counters
z.avail_out -= n;
z.total_out += n;
// update check information
if(check && n>0){
z.adler.update(window, q, n);
}
// copy
System.arraycopy(window, q, z.next_out, p, n);
p += n;
q += n;
}
// update pointers
z.next_out_index = p;
read = q;
// done
return r;
}
}
@@ -0,0 +1,610 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final class InfCodes{
static final private int[] inflate_mask = {
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f,
0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff,
0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff,
0x00007fff, 0x0000ffff
};
static final private int Z_OK=0;
static final private int Z_STREAM_END=1;
static final private int Z_NEED_DICT=2;
static final private int Z_ERRNO=-1;
static final private int Z_STREAM_ERROR=-2;
static final private int Z_DATA_ERROR=-3;
static final private int Z_MEM_ERROR=-4;
static final private int Z_BUF_ERROR=-5;
static final private int Z_VERSION_ERROR=-6;
// waiting for "i:"=input,
// "o:"=output,
// "x:"=nothing
static final private int START=0; // x: set up for LEN
static final private int LEN=1; // i: get length/literal/eob next
static final private int LENEXT=2; // i: getting length extra (have base)
static final private int DIST=3; // i: get distance next
static final private int DISTEXT=4;// i: getting distance extra
static final private int COPY=5; // o: copying bytes in window, waiting for space
static final private int LIT=6; // o: got literal, waiting for output space
static final private int WASH=7; // o: got eob, possibly still output waiting
static final private int END=8; // x: got eob and all data flushed
static final private int BADCODE=9;// x: got error
int mode; // current inflate_codes mode
// mode dependent information
int len;
int[] tree; // pointer into tree
int tree_index=0;
int need; // bits needed
int lit;
// if EXT or COPY, where and how much
int get; // bits to get for extra
int dist; // distance back to copy from
byte lbits; // ltree bits decoded per branch
byte dbits; // dtree bits decoder per branch
int[] ltree; // literal/length/eob tree
int ltree_index; // literal/length/eob tree
int[] dtree; // distance tree
int dtree_index; // distance tree
private final ZStream z;
private final InfBlocks s;
InfCodes(ZStream z, InfBlocks s){
this.z=z;
this.s=s;
}
void init(int bl, int bd,
int[] tl, int tl_index,
int[] td, int td_index){
mode=START;
lbits=(byte)bl;
dbits=(byte)bd;
ltree=tl;
ltree_index=tl_index;
dtree = td;
dtree_index=td_index;
tree=null;
}
int proc(int r){
int j; // temporary storage
int[] t; // temporary pointer
int tindex; // temporary pointer
int e; // extra bits or operation
int b=0; // bit buffer
int k=0; // bits in bit buffer
int p=0; // input data pointer
int n; // bytes available there
int q; // output window write pointer
int m; // bytes to end of window or read pointer
int f; // pointer to copy strings from
// copy input/output information to locals (UPDATE macro restores)
p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk;
q=s.write;m=q<s.read?s.read-q-1:s.end-q;
// process input and output based on current state
while (true){
switch (mode){
// waiting for "i:"=input, "o:"=output, "x:"=nothing
case START: // x: set up for LEN
if (m >= 258 && n >= 10){
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
r = inflate_fast(lbits, dbits,
ltree, ltree_index,
dtree, dtree_index,
s, z);
p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk;
q=s.write;m=q<s.read?s.read-q-1:s.end-q;
if (r != Z_OK){
mode = r == Z_STREAM_END ? WASH : BADCODE;
break;
}
}
need = lbits;
tree = ltree;
tree_index=ltree_index;
mode = LEN;
case LEN: // i: get length/literal/eob next
j = need;
while(k<(j)){
if(n!=0)r=Z_OK;
else{
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
}
n--;
b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
tindex=(tree_index+(b&inflate_mask[j]))*3;
b>>>=(tree[tindex+1]);
k-=(tree[tindex+1]);
e=tree[tindex];
if(e == 0){ // literal
lit = tree[tindex+2];
mode = LIT;
break;
}
if((e & 16)!=0 ){ // length
get = e & 15;
len = tree[tindex+2];
mode = LENEXT;
break;
}
if ((e & 64) == 0){ // next table
need = e;
tree_index = tindex/3+tree[tindex+2];
break;
}
if ((e & 32)!=0){ // end of block
mode = WASH;
break;
}
mode = BADCODE; // invalid code
z.msg = "invalid literal/length code";
r = Z_DATA_ERROR;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
case LENEXT: // i: getting length extra (have base)
j = get;
while(k<(j)){
if(n!=0)r=Z_OK;
else{
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
}
n--; b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
len += (b & inflate_mask[j]);
b>>=j;
k-=j;
need = dbits;
tree = dtree;
tree_index=dtree_index;
mode = DIST;
case DIST: // i: get distance next
j = need;
while(k<(j)){
if(n!=0)r=Z_OK;
else{
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
}
n--; b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
tindex=(tree_index+(b & inflate_mask[j]))*3;
b>>=tree[tindex+1];
k-=tree[tindex+1];
e = (tree[tindex]);
if((e & 16)!=0){ // distance
get = e & 15;
dist = tree[tindex+2];
mode = DISTEXT;
break;
}
if ((e & 64) == 0){ // next table
need = e;
tree_index = tindex/3 + tree[tindex+2];
break;
}
mode = BADCODE; // invalid code
z.msg = "invalid distance code";
r = Z_DATA_ERROR;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
case DISTEXT: // i: getting distance extra
j = get;
while(k<(j)){
if(n!=0)r=Z_OK;
else{
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
}
n--; b|=(z.next_in[p++]&0xff)<<k;
k+=8;
}
dist += (b & inflate_mask[j]);
b>>=j;
k-=j;
mode = COPY;
case COPY: // o: copying bytes in window, waiting for space
f = q - dist;
while(f < 0){ // modulo window size-"while" instead
f += s.end; // of "if" handles invalid distances
}
while (len!=0){
if(m==0){
if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;}
if(m==0){
s.write=q; r=s.inflate_flush(r);
q=s.write;m=q<s.read?s.read-q-1:s.end-q;
if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;}
if(m==0){
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
}
}
}
s.window[q++]=s.window[f++]; m--;
if (f == s.end)
f = 0;
len--;
}
mode = START;
break;
case LIT: // o: got literal, waiting for output space
if(m==0){
if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;}
if(m==0){
s.write=q; r=s.inflate_flush(r);
q=s.write;m=q<s.read?s.read-q-1:s.end-q;
if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;}
if(m==0){
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
}
}
}
r=Z_OK;
s.window[q++]=(byte)lit; m--;
mode = START;
break;
case WASH: // o: got eob, possibly more output
if (k > 7){ // return unused byte, if any
k -= 8;
n++;
p--; // can always return one
}
s.write=q; r=s.inflate_flush(r);
q=s.write;m=q<s.read?s.read-q-1:s.end-q;
if (s.read != s.write){
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
}
mode = END;
case END:
r = Z_STREAM_END;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
case BADCODE: // x: got error
r = Z_DATA_ERROR;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
default:
r = Z_STREAM_ERROR;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return s.inflate_flush(r);
}
}
}
void free(ZStream z){
// ZFREE(z, c);
}
// Called with number of bytes left to write in window at least 258
// (the maximum string length) and number of input bytes available
// at least ten. The ten bytes are six bytes for the longest length/
// distance pair plus four bytes for overloading the bit buffer.
int inflate_fast(int bl, int bd,
int[] tl, int tl_index,
int[] td, int td_index,
InfBlocks s, ZStream z){
int t; // temporary pointer
int[] tp; // temporary pointer
int tp_index; // temporary pointer
int e; // extra bits or operation
int b; // bit buffer
int k; // bits in bit buffer
int p; // input data pointer
int n; // bytes available there
int q; // output window write pointer
int m; // bytes to end of window or read pointer
int ml; // mask for literal/length tree
int md; // mask for distance tree
int c; // bytes to copy
int d; // distance back to copy from
int r; // copy source pointer
int tp_index_t_3; // (tp_index+t)*3
// load input, output, bit values
p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk;
q=s.write;m=q<s.read?s.read-q-1:s.end-q;
// initialize masks
ml = inflate_mask[bl];
md = inflate_mask[bd];
// do until not enough input or output space for fast loop
do { // assume called with m >= 258 && n >= 10
// get literal/length code
while(k<(20)){ // max bits for literal/length code
n--;
b|=(z.next_in[p++]&0xff)<<k;k+=8;
}
t= b&ml;
tp=tl;
tp_index=tl_index;
tp_index_t_3=(tp_index+t)*3;
if ((e = tp[tp_index_t_3]) == 0){
b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]);
s.window[q++] = (byte)tp[tp_index_t_3+2];
m--;
continue;
}
do {
b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]);
if((e&16)!=0){
e &= 15;
c = tp[tp_index_t_3+2] + ((int)b & inflate_mask[e]);
b>>=e; k-=e;
// decode distance base of block to copy
while(k<(15)){ // max bits for distance code
n--;
b|=(z.next_in[p++]&0xff)<<k;k+=8;
}
t= b&md;
tp=td;
tp_index=td_index;
tp_index_t_3=(tp_index+t)*3;
e = tp[tp_index_t_3];
do {
b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]);
if((e&16)!=0){
// get extra bits to add to distance base
e &= 15;
while(k<(e)){ // get extra bits (up to 13)
n--;
b|=(z.next_in[p++]&0xff)<<k;k+=8;
}
d = tp[tp_index_t_3+2] + (b&inflate_mask[e]);
b>>=(e); k-=(e);
// do the copy
m -= c;
if (q >= d){ // offset before dest
// just copy
r=q-d;
if(q-r>0 && 2>(q-r)){
s.window[q++]=s.window[r++]; // minimum count is three,
s.window[q++]=s.window[r++]; // so unroll loop a little
c-=2;
}
else{
System.arraycopy(s.window, r, s.window, q, 2);
q+=2; r+=2; c-=2;
}
}
else{ // else offset after destination
r=q-d;
do{
r+=s.end; // force pointer in window
}while(r<0); // covers invalid distances
e=s.end-r;
if(c>e){ // if source crosses,
c-=e; // wrapped copy
if(q-r>0 && e>(q-r)){
do{s.window[q++] = s.window[r++];}
while(--e!=0);
}
else{
System.arraycopy(s.window, r, s.window, q, e);
q+=e; r+=e; e=0;
}
r = 0; // copy rest from start of window
}
}
// copy all or what's left
if(q-r>0 && c>(q-r)){
do{s.window[q++] = s.window[r++];}
while(--c!=0);
}
else{
System.arraycopy(s.window, r, s.window, q, c);
q+=c; r+=c; c=0;
}
break;
}
else if((e&64)==0){
t+=tp[tp_index_t_3+2];
t+=(b&inflate_mask[e]);
tp_index_t_3=(tp_index+t)*3;
e=tp[tp_index_t_3];
}
else{
z.msg = "invalid distance code";
c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return Z_DATA_ERROR;
}
}
while(true);
break;
}
if((e&64)==0){
t+=tp[tp_index_t_3+2];
t+=(b&inflate_mask[e]);
tp_index_t_3=(tp_index+t)*3;
if((e=tp[tp_index_t_3])==0){
b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]);
s.window[q++]=(byte)tp[tp_index_t_3+2];
m--;
break;
}
}
else if((e&32)!=0){
c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return Z_STREAM_END;
}
else{
z.msg="invalid literal/length code";
c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return Z_DATA_ERROR;
}
}
while(true);
}
while(m>=258 && n>= 10);
// not enough input or output--restore pointers and return
c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3;
s.bitb=b;s.bitk=k;
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;
s.write=q;
return Z_OK;
}
}
@@ -0,0 +1,518 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final class InfTree{
static final private int MANY=1440;
static final private int Z_OK=0;
static final private int Z_STREAM_END=1;
static final private int Z_NEED_DICT=2;
static final private int Z_ERRNO=-1;
static final private int Z_STREAM_ERROR=-2;
static final private int Z_DATA_ERROR=-3;
static final private int Z_MEM_ERROR=-4;
static final private int Z_BUF_ERROR=-5;
static final private int Z_VERSION_ERROR=-6;
static final int fixed_bl = 9;
static final int fixed_bd = 5;
static final int[] fixed_tl = {
96,7,256, 0,8,80, 0,8,16, 84,8,115,
82,7,31, 0,8,112, 0,8,48, 0,9,192,
80,7,10, 0,8,96, 0,8,32, 0,9,160,
0,8,0, 0,8,128, 0,8,64, 0,9,224,
80,7,6, 0,8,88, 0,8,24, 0,9,144,
83,7,59, 0,8,120, 0,8,56, 0,9,208,
81,7,17, 0,8,104, 0,8,40, 0,9,176,
0,8,8, 0,8,136, 0,8,72, 0,9,240,
80,7,4, 0,8,84, 0,8,20, 85,8,227,
83,7,43, 0,8,116, 0,8,52, 0,9,200,
81,7,13, 0,8,100, 0,8,36, 0,9,168,
0,8,4, 0,8,132, 0,8,68, 0,9,232,
80,7,8, 0,8,92, 0,8,28, 0,9,152,
84,7,83, 0,8,124, 0,8,60, 0,9,216,
82,7,23, 0,8,108, 0,8,44, 0,9,184,
0,8,12, 0,8,140, 0,8,76, 0,9,248,
80,7,3, 0,8,82, 0,8,18, 85,8,163,
83,7,35, 0,8,114, 0,8,50, 0,9,196,
81,7,11, 0,8,98, 0,8,34, 0,9,164,
0,8,2, 0,8,130, 0,8,66, 0,9,228,
80,7,7, 0,8,90, 0,8,26, 0,9,148,
84,7,67, 0,8,122, 0,8,58, 0,9,212,
82,7,19, 0,8,106, 0,8,42, 0,9,180,
0,8,10, 0,8,138, 0,8,74, 0,9,244,
80,7,5, 0,8,86, 0,8,22, 192,8,0,
83,7,51, 0,8,118, 0,8,54, 0,9,204,
81,7,15, 0,8,102, 0,8,38, 0,9,172,
0,8,6, 0,8,134, 0,8,70, 0,9,236,
80,7,9, 0,8,94, 0,8,30, 0,9,156,
84,7,99, 0,8,126, 0,8,62, 0,9,220,
82,7,27, 0,8,110, 0,8,46, 0,9,188,
0,8,14, 0,8,142, 0,8,78, 0,9,252,
96,7,256, 0,8,81, 0,8,17, 85,8,131,
82,7,31, 0,8,113, 0,8,49, 0,9,194,
80,7,10, 0,8,97, 0,8,33, 0,9,162,
0,8,1, 0,8,129, 0,8,65, 0,9,226,
80,7,6, 0,8,89, 0,8,25, 0,9,146,
83,7,59, 0,8,121, 0,8,57, 0,9,210,
81,7,17, 0,8,105, 0,8,41, 0,9,178,
0,8,9, 0,8,137, 0,8,73, 0,9,242,
80,7,4, 0,8,85, 0,8,21, 80,8,258,
83,7,43, 0,8,117, 0,8,53, 0,9,202,
81,7,13, 0,8,101, 0,8,37, 0,9,170,
0,8,5, 0,8,133, 0,8,69, 0,9,234,
80,7,8, 0,8,93, 0,8,29, 0,9,154,
84,7,83, 0,8,125, 0,8,61, 0,9,218,
82,7,23, 0,8,109, 0,8,45, 0,9,186,
0,8,13, 0,8,141, 0,8,77, 0,9,250,
80,7,3, 0,8,83, 0,8,19, 85,8,195,
83,7,35, 0,8,115, 0,8,51, 0,9,198,
81,7,11, 0,8,99, 0,8,35, 0,9,166,
0,8,3, 0,8,131, 0,8,67, 0,9,230,
80,7,7, 0,8,91, 0,8,27, 0,9,150,
84,7,67, 0,8,123, 0,8,59, 0,9,214,
82,7,19, 0,8,107, 0,8,43, 0,9,182,
0,8,11, 0,8,139, 0,8,75, 0,9,246,
80,7,5, 0,8,87, 0,8,23, 192,8,0,
83,7,51, 0,8,119, 0,8,55, 0,9,206,
81,7,15, 0,8,103, 0,8,39, 0,9,174,
0,8,7, 0,8,135, 0,8,71, 0,9,238,
80,7,9, 0,8,95, 0,8,31, 0,9,158,
84,7,99, 0,8,127, 0,8,63, 0,9,222,
82,7,27, 0,8,111, 0,8,47, 0,9,190,
0,8,15, 0,8,143, 0,8,79, 0,9,254,
96,7,256, 0,8,80, 0,8,16, 84,8,115,
82,7,31, 0,8,112, 0,8,48, 0,9,193,
80,7,10, 0,8,96, 0,8,32, 0,9,161,
0,8,0, 0,8,128, 0,8,64, 0,9,225,
80,7,6, 0,8,88, 0,8,24, 0,9,145,
83,7,59, 0,8,120, 0,8,56, 0,9,209,
81,7,17, 0,8,104, 0,8,40, 0,9,177,
0,8,8, 0,8,136, 0,8,72, 0,9,241,
80,7,4, 0,8,84, 0,8,20, 85,8,227,
83,7,43, 0,8,116, 0,8,52, 0,9,201,
81,7,13, 0,8,100, 0,8,36, 0,9,169,
0,8,4, 0,8,132, 0,8,68, 0,9,233,
80,7,8, 0,8,92, 0,8,28, 0,9,153,
84,7,83, 0,8,124, 0,8,60, 0,9,217,
82,7,23, 0,8,108, 0,8,44, 0,9,185,
0,8,12, 0,8,140, 0,8,76, 0,9,249,
80,7,3, 0,8,82, 0,8,18, 85,8,163,
83,7,35, 0,8,114, 0,8,50, 0,9,197,
81,7,11, 0,8,98, 0,8,34, 0,9,165,
0,8,2, 0,8,130, 0,8,66, 0,9,229,
80,7,7, 0,8,90, 0,8,26, 0,9,149,
84,7,67, 0,8,122, 0,8,58, 0,9,213,
82,7,19, 0,8,106, 0,8,42, 0,9,181,
0,8,10, 0,8,138, 0,8,74, 0,9,245,
80,7,5, 0,8,86, 0,8,22, 192,8,0,
83,7,51, 0,8,118, 0,8,54, 0,9,205,
81,7,15, 0,8,102, 0,8,38, 0,9,173,
0,8,6, 0,8,134, 0,8,70, 0,9,237,
80,7,9, 0,8,94, 0,8,30, 0,9,157,
84,7,99, 0,8,126, 0,8,62, 0,9,221,
82,7,27, 0,8,110, 0,8,46, 0,9,189,
0,8,14, 0,8,142, 0,8,78, 0,9,253,
96,7,256, 0,8,81, 0,8,17, 85,8,131,
82,7,31, 0,8,113, 0,8,49, 0,9,195,
80,7,10, 0,8,97, 0,8,33, 0,9,163,
0,8,1, 0,8,129, 0,8,65, 0,9,227,
80,7,6, 0,8,89, 0,8,25, 0,9,147,
83,7,59, 0,8,121, 0,8,57, 0,9,211,
81,7,17, 0,8,105, 0,8,41, 0,9,179,
0,8,9, 0,8,137, 0,8,73, 0,9,243,
80,7,4, 0,8,85, 0,8,21, 80,8,258,
83,7,43, 0,8,117, 0,8,53, 0,9,203,
81,7,13, 0,8,101, 0,8,37, 0,9,171,
0,8,5, 0,8,133, 0,8,69, 0,9,235,
80,7,8, 0,8,93, 0,8,29, 0,9,155,
84,7,83, 0,8,125, 0,8,61, 0,9,219,
82,7,23, 0,8,109, 0,8,45, 0,9,187,
0,8,13, 0,8,141, 0,8,77, 0,9,251,
80,7,3, 0,8,83, 0,8,19, 85,8,195,
83,7,35, 0,8,115, 0,8,51, 0,9,199,
81,7,11, 0,8,99, 0,8,35, 0,9,167,
0,8,3, 0,8,131, 0,8,67, 0,9,231,
80,7,7, 0,8,91, 0,8,27, 0,9,151,
84,7,67, 0,8,123, 0,8,59, 0,9,215,
82,7,19, 0,8,107, 0,8,43, 0,9,183,
0,8,11, 0,8,139, 0,8,75, 0,9,247,
80,7,5, 0,8,87, 0,8,23, 192,8,0,
83,7,51, 0,8,119, 0,8,55, 0,9,207,
81,7,15, 0,8,103, 0,8,39, 0,9,175,
0,8,7, 0,8,135, 0,8,71, 0,9,239,
80,7,9, 0,8,95, 0,8,31, 0,9,159,
84,7,99, 0,8,127, 0,8,63, 0,9,223,
82,7,27, 0,8,111, 0,8,47, 0,9,191,
0,8,15, 0,8,143, 0,8,79, 0,9,255
};
static final int[] fixed_td = {
80,5,1, 87,5,257, 83,5,17, 91,5,4097,
81,5,5, 89,5,1025, 85,5,65, 93,5,16385,
80,5,3, 88,5,513, 84,5,33, 92,5,8193,
82,5,9, 90,5,2049, 86,5,129, 192,5,24577,
80,5,2, 87,5,385, 83,5,25, 91,5,6145,
81,5,7, 89,5,1537, 85,5,97, 93,5,24577,
80,5,4, 88,5,769, 84,5,49, 92,5,12289,
82,5,13, 90,5,3073, 86,5,193, 192,5,24577
};
// Tables for deflate from PKZIP's appnote.txt.
static final int[] cplens = { // Copy lengths for literal codes 257..285
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
};
// see note #13 above about 258
static final int[] cplext = { // Extra bits for literal codes 257..285
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid
};
static final int[] cpdist = { // Copy offsets for distance codes 0..29
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577
};
static final int[] cpdext = { // Extra bits for distance codes
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 13, 13};
// If BMAX needs to be larger than 16, then h and x[] should be uLong.
static final int BMAX=15; // maximum bit length of any code
int[] hn = null; // hufts used in space
int[] v = null; // work area for huft_build
int[] c = null; // bit length count table
int[] r = null; // table entry for structure assignment
int[] u = null; // table stack
int[] x = null; // bit offsets, then code stack
private int huft_build(int[] b, // code lengths in bits (all assumed <= BMAX)
int bindex,
int n, // number of codes (assumed <= 288)
int s, // number of simple-valued codes (0..s-1)
int[] d, // list of base values for non-simple codes
int[] e, // list of extra bits for non-simple codes
int[] t, // result: starting table
int[] m, // maximum lookup bits, returns actual
int[] hp,// space for trees
int[] hn,// hufts used in space
int[] v // working area: values in order of bit length
){
// Given a list of code lengths and a maximum table size, make a set of
// tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR
// if the given code set is incomplete (the tables are still built in this
// case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of
// lengths), or Z_MEM_ERROR if not enough memory.
int a; // counter for codes of length k
int f; // i repeats in table every f entries
int g; // maximum code length
int h; // table level
int i; // counter, current code
int j; // counter
int k; // number of bits in current code
int l; // bits per table (returned in m)
int mask; // (1 << w) - 1, to avoid cc -O bug on HP
int p; // pointer into c[], b[], or v[]
int q; // points to current table
int w; // bits before this table == (l * h)
int xp; // pointer into x
int y; // number of dummy codes added
int z; // number of entries in current table
// Generate counts for each bit length
p = 0; i = n;
do {
c[b[bindex+p]]++; p++; i--; // assume all entries <= BMAX
}while(i!=0);
if(c[0] == n){ // null input--all zero length codes
t[0] = -1;
m[0] = 0;
return Z_OK;
}
// Find minimum and maximum length, bound *m by those
l = m[0];
for (j = 1; j <= BMAX; j++)
if(c[j]!=0) break;
k = j; // minimum code length
if(l < j){
l = j;
}
for (i = BMAX; i!=0; i--){
if(c[i]!=0) break;
}
g = i; // maximum code length
if(l > i){
l = i;
}
m[0] = l;
// Adjust last length count to fill out codes, if needed
for (y = 1 << j; j < i; j++, y <<= 1){
if ((y -= c[j]) < 0){
return Z_DATA_ERROR;
}
}
if ((y -= c[i]) < 0){
return Z_DATA_ERROR;
}
c[i] += y;
// Generate starting offsets into the value table for each length
x[1] = j = 0;
p = 1; xp = 2;
while (--i!=0) { // note that i == g from above
x[xp] = (j += c[p]);
xp++;
p++;
}
// Make a table of values in order of bit lengths
i = 0; p = 0;
do {
if ((j = b[bindex+p]) != 0){
v[x[j]++] = i;
}
p++;
}
while (++i < n);
n = x[g]; // set n to length of v
// Generate the Huffman codes and for each, make the table entries
x[0] = i = 0; // first Huffman code is zero
p = 0; // grab values in bit order
h = -1; // no tables yet--level -1
w = -l; // bits decoded == (l * h)
u[0] = 0; // just to keep compilers happy
q = 0; // ditto
z = 0; // ditto
// go through the bit lengths (k already is bits in shortest code)
for (; k <= g; k++){
a = c[k];
while (a--!=0){
// here i is the Huffman code of length k bits for value *p
// make tables up to required level
while (k > w + l){
h++;
w += l; // previous table always l bits
// compute minimum size table less than or equal to l bits
z = g - w;
z = (z > l) ? l : z; // table size upper limit
if((f=1<<(j=k-w))>a+1){ // try a k-w bit table
// too few codes for k-w bit table
f -= a + 1; // deduct codes from patterns left
xp = k;
if(j < z){
while (++j < z){ // try smaller tables up to z bits
if((f <<= 1) <= c[++xp])
break; // enough codes to use up j bits
f -= c[xp]; // else deduct codes from patterns
}
}
}
z = 1 << j; // table entries for j-bit table
// allocate new table
if (hn[0] + z > MANY){ // (note: doesn't matter for fixed)
return Z_DATA_ERROR; // overflow of MANY
}
u[h] = q = /*hp+*/ hn[0]; // DEBUG
hn[0] += z;
// connect to last table, if there is one
if(h!=0){
x[h]=i; // save pattern for backing up
r[0]=(byte)j; // bits in this table
r[1]=(byte)l; // bits to dump before this table
j=i>>>(w - l);
r[2] = (int)(q - u[h-1] - j); // offset to this table
System.arraycopy(r, 0, hp, (u[h-1]+j)*3, 3); // connect to last table
}
else{
t[0] = q; // first table is returned result
}
}
// set up table entry in r
r[1] = (byte)(k - w);
if (p >= n){
r[0] = 128 + 64; // out of values--invalid code
}
else if (v[p] < s){
r[0] = (byte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block
r[2] = v[p++]; // simple code is just the value
}
else{
r[0]=(byte)(e[v[p]-s]+16+64); // non-simple--look up in lists
r[2]=d[v[p++] - s];
}
// fill code-like entries with r
f=1<<(k-w);
for (j=i>>>w;j<z;j+=f){
System.arraycopy(r, 0, hp, (q+j)*3, 3);
}
// backwards increment the k-bit code i
for (j = 1 << (k - 1); (i & j)!=0; j >>>= 1){
i ^= j;
}
i ^= j;
// backup over finished tables
mask = (1 << w) - 1; // needed on HP, cc -O bug
while ((i & mask) != x[h]){
h--; // don't need to update q
w -= l;
mask = (1 << w) - 1;
}
}
}
// Return Z_BUF_ERROR if we were given an incomplete table
return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK;
}
int inflate_trees_bits(int[] c, // 19 code lengths
int[] bb, // bits tree desired/actual depth
int[] tb, // bits tree result
int[] hp, // space for trees
ZStream z // for messages
){
int result;
initWorkArea(19);
hn[0]=0;
result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v);
if(result == Z_DATA_ERROR){
z.msg = "oversubscribed dynamic bit lengths tree";
}
else if(result == Z_BUF_ERROR || bb[0] == 0){
z.msg = "incomplete dynamic bit lengths tree";
result = Z_DATA_ERROR;
}
return result;
}
int inflate_trees_dynamic(int nl, // number of literal/length codes
int nd, // number of distance codes
int[] c, // that many (total) code lengths
int[] bl, // literal desired/actual bit depth
int[] bd, // distance desired/actual bit depth
int[] tl, // literal/length tree result
int[] td, // distance tree result
int[] hp, // space for trees
ZStream z // for messages
){
int result;
// build literal/length tree
initWorkArea(288);
hn[0]=0;
result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v);
if (result != Z_OK || bl[0] == 0){
if(result == Z_DATA_ERROR){
z.msg = "oversubscribed literal/length tree";
}
else if (result != Z_MEM_ERROR){
z.msg = "incomplete literal/length tree";
result = Z_DATA_ERROR;
}
return result;
}
// build distance tree
initWorkArea(288);
result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v);
if (result != Z_OK || (bd[0] == 0 && nl > 257)){
if (result == Z_DATA_ERROR){
z.msg = "oversubscribed distance tree";
}
else if (result == Z_BUF_ERROR) {
z.msg = "incomplete distance tree";
result = Z_DATA_ERROR;
}
else if (result != Z_MEM_ERROR){
z.msg = "empty distance tree with lengths";
result = Z_DATA_ERROR;
}
return result;
}
return Z_OK;
}
static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth
int[] bd, //distance desired/actual bit depth
int[][] tl,//literal/length tree result
int[][] td,//distance tree result
ZStream z //for memory allocation
){
bl[0]=fixed_bl;
bd[0]=fixed_bd;
tl[0]=fixed_tl;
td[0]=fixed_td;
return Z_OK;
}
private void initWorkArea(int vsize){
if(hn==null){
hn=new int[1];
v=new int[vsize];
c=new int[BMAX+1];
r=new int[3];
u=new int[BMAX];
x=new int[BMAX+1];
}
if(v.length<vsize){ v=new int[vsize]; }
for(int i=0; i<vsize; i++){v[i]=0;}
for(int i=0; i<BMAX+1; i++){c[i]=0;}
for(int i=0; i<3; i++){r[i]=0;}
System.arraycopy(c, 0, u, 0, BMAX);
System.arraycopy(c, 0, x, 0, BMAX+1);
}
}
@@ -0,0 +1,751 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final class Inflate{
static final private int MAX_WBITS=15; // 32K LZ77 window
// preset dictionary flag in zlib header
static final private int PRESET_DICT=0x20;
static final int Z_NO_FLUSH=0;
static final int Z_PARTIAL_FLUSH=1;
static final int Z_SYNC_FLUSH=2;
static final int Z_FULL_FLUSH=3;
static final int Z_FINISH=4;
static final private int Z_DEFLATED=8;
static final private int Z_OK=0;
static final private int Z_STREAM_END=1;
static final private int Z_NEED_DICT=2;
static final private int Z_ERRNO=-1;
static final private int Z_STREAM_ERROR=-2;
static final private int Z_DATA_ERROR=-3;
static final private int Z_MEM_ERROR=-4;
static final private int Z_BUF_ERROR=-5;
static final private int Z_VERSION_ERROR=-6;
static final private int METHOD=0; // waiting for method byte
static final private int FLAG=1; // waiting for flag byte
static final private int DICT4=2; // four dictionary check bytes to go
static final private int DICT3=3; // three dictionary check bytes to go
static final private int DICT2=4; // two dictionary check bytes to go
static final private int DICT1=5; // one dictionary check byte to go
static final private int DICT0=6; // waiting for inflateSetDictionary
static final private int BLOCKS=7; // decompressing blocks
static final private int CHECK4=8; // four check bytes to go
static final private int CHECK3=9; // three check bytes to go
static final private int CHECK2=10; // two check bytes to go
static final private int CHECK1=11; // one check byte to go
static final private int DONE=12; // finished check, done
static final private int BAD=13; // got an error--stay here
static final private int HEAD=14;
static final private int LENGTH=15;
static final private int TIME=16;
static final private int OS=17;
static final private int EXLEN=18;
static final private int EXTRA=19;
static final private int NAME=20;
static final private int COMMENT=21;
static final private int HCRC=22;
static final private int FLAGS=23;
static final int INFLATE_ANY=0x40000000;
int mode; // current inflate mode
// mode dependent information
int method; // if FLAGS, method byte
// if CHECK, check values to compare
long was = -1; // computed check value
long need; // stream check value
// if BAD, inflateSync's marker bytes count
int marker;
// mode independent information
int wrap; // flag for no wrapper
// 0: no wrapper
// 1: zlib header
// 2: gzip header
// 4: auto detection
int wbits; // log2(window size) (8..15, defaults to 15)
InfBlocks blocks; // current inflate_blocks state
private final ZStream z;
private int flags;
private int need_bytes = -1;
private byte[] crcbuf=new byte[4];
GZIPHeader gheader = null;
int inflateReset(){
if(z == null) return Z_STREAM_ERROR;
z.total_in = z.total_out = 0;
z.msg = null;
this.mode = HEAD;
this.need_bytes = -1;
this.blocks.reset();
return Z_OK;
}
int inflateEnd(){
if(blocks != null){
blocks.free();
}
return Z_OK;
}
Inflate(ZStream z){
this.z=z;
}
int inflateInit(int w){
z.msg = null;
blocks = null;
// handle undocumented wrap option (no zlib header or check)
wrap = 0;
if(w < 0){
w = - w;
}
else if((w&INFLATE_ANY) != 0){
wrap = 4;
w &= ~INFLATE_ANY;
if(w < 48)
w &= 15;
}
else if((w & ~31) != 0) { // for example, DEF_WBITS + 32
wrap = 4; // zlib and gzip wrapped data should be accepted.
w &= 15;
}
else {
wrap = (w >> 4) + 1;
if(w < 48)
w &= 15;
}
if(w<8 ||w>15){
inflateEnd();
return Z_STREAM_ERROR;
}
if(blocks != null && wbits != w){
blocks.free();
blocks=null;
}
// set window size
wbits=w;
this.blocks=new InfBlocks(z, 1<<w);
// reset state
inflateReset();
return Z_OK;
}
int inflate(int f){
int hold = 0;
int r;
int b;
if(z == null || z.next_in == null){
if(f == Z_FINISH && this.mode==HEAD)
return Z_OK;
return Z_STREAM_ERROR;
}
f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK;
r = Z_BUF_ERROR;
while (true){
switch (this.mode){
case HEAD:
if(wrap==0){
this.mode = BLOCKS;
break;
}
try { r=readBytes(2, r, f); }
catch(Return e){ return e.r; }
if((wrap == 4 || (wrap&2)!=0) &&
this.need == 0x8b1fL) { // gzip header
if(wrap == 4){
wrap = 2;
}
z.adler=new CRC32();
checksum(2, this.need);
if(gheader==null)
gheader=new GZIPHeader();
this.mode = FLAGS;
break;
}
if((wrap&2) != 0){
this.mode = BAD;
z.msg = "incorrect header check";
break;
}
flags = 0;
this.method = ((int)this.need)&0xff;
b=((int)(this.need>>8))&0xff;
if(((wrap&1)==0 || // check if zlib header allowed
(((this.method << 8)+b) % 31)!=0) &&
(this.method&0xf)!=Z_DEFLATED){
if(wrap == 4){
z.next_in_index -= 2;
z.avail_in += 2;
z.total_in -= 2;
wrap = 0;
this.mode = BLOCKS;
break;
}
this.mode = BAD;
z.msg = "incorrect header check";
// since zlib 1.2, it is allowted to inflateSync for this case.
/*
this.marker = 5; // can't try inflateSync
*/
break;
}
if((this.method&0xf)!=Z_DEFLATED){
this.mode = BAD;
z.msg="unknown compression method";
// since zlib 1.2, it is allowted to inflateSync for this case.
/*
this.marker = 5; // can't try inflateSync
*/
break;
}
if(wrap == 4){
wrap = 1;
}
if((this.method>>4)+8>this.wbits){
this.mode = BAD;
z.msg="invalid window size";
// since zlib 1.2, it is allowted to inflateSync for this case.
/*
this.marker = 5; // can't try inflateSync
*/
break;
}
z.adler=new Adler32();
if((b&PRESET_DICT)==0){
this.mode = BLOCKS;
break;
}
this.mode = DICT4;
case DICT4:
if(z.avail_in==0)return r;r=f;
z.avail_in--; z.total_in++;
this.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L;
this.mode=DICT3;
case DICT3:
if(z.avail_in==0)return r;r=f;
z.avail_in--; z.total_in++;
this.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L;
this.mode=DICT2;
case DICT2:
if(z.avail_in==0)return r;r=f;
z.avail_in--; z.total_in++;
this.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L;
this.mode=DICT1;
case DICT1:
if(z.avail_in==0)return r;r=f;
z.avail_in--; z.total_in++;
this.need += (z.next_in[z.next_in_index++]&0xffL);
z.adler.reset(this.need);
this.mode = DICT0;
return Z_NEED_DICT;
case DICT0:
this.mode = BAD;
z.msg = "need dictionary";
this.marker = 0; // can try inflateSync
return Z_STREAM_ERROR;
case BLOCKS:
r = this.blocks.proc(r);
if(r == Z_DATA_ERROR){
this.mode = BAD;
this.marker = 0; // can try inflateSync
break;
}
if(r == Z_OK){
r = f;
}
if(r != Z_STREAM_END){
return r;
}
r = f;
this.was=z.adler.getValue();
this.blocks.reset();
if(this.wrap==0){
this.mode=DONE;
break;
}
this.mode=CHECK4;
case CHECK4:
if(z.avail_in==0)return r;r=f;
z.avail_in--; z.total_in++;
this.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L;
this.mode=CHECK3;
case CHECK3:
if(z.avail_in==0)return r;r=f;
z.avail_in--; z.total_in++;
this.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L;
this.mode = CHECK2;
case CHECK2:
if(z.avail_in==0)return r;r=f;
z.avail_in--; z.total_in++;
this.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L;
this.mode = CHECK1;
case CHECK1:
if(z.avail_in==0)return r;r=f;
z.avail_in--; z.total_in++;
this.need+=(z.next_in[z.next_in_index++]&0xffL);
if(flags!=0){ // gzip
this.need = ((this.need&0xff000000)>>24 |
(this.need&0x00ff0000)>>8 |
(this.need&0x0000ff00)<<8 |
(this.need&0x0000ffff)<<24)&0xffffffffL;
}
if(((int)(this.was)) != ((int)(this.need))){
z.msg = "incorrect data check";
// chack is delayed
/*
this.mode = BAD;
this.marker = 5; // can't try inflateSync
break;
*/
}
else if(flags!=0 && gheader!=null){
gheader.crc = this.need;
}
this.mode = LENGTH;
case LENGTH:
if (wrap!=0 && flags!=0) {
try { r=readBytes(4, r, f); }
catch(Return e){ return e.r; }
if(z.msg!=null && z.msg.equals("incorrect data check")){
this.mode = BAD;
this.marker = 5; // can't try inflateSync
break;
}
if (this.need != (z.total_out & 0xffffffffL)) {
z.msg = "incorrect length check";
this.mode = BAD;
break;
}
z.msg = null;
}
else {
if(z.msg!=null && z.msg.equals("incorrect data check")){
this.mode = BAD;
this.marker = 5; // can't try inflateSync
break;
}
}
this.mode = DONE;
case DONE:
return Z_STREAM_END;
case BAD:
return Z_DATA_ERROR;
case FLAGS:
try { r=readBytes(2, r, f); }
catch(Return e){ return e.r; }
flags = ((int)this.need)&0xffff;
if ((flags & 0xff) != Z_DEFLATED) {
z.msg = "unknown compression method";
this.mode = BAD;
break;
}
if ((flags & 0xe000)!=0) {
z.msg = "unknown header flags set";
this.mode = BAD;
break;
}
if ((flags & 0x0200)!=0){
checksum(2, this.need);
}
this.mode = TIME;
case TIME:
try { r=readBytes(4, r, f); }
catch(Return e){ return e.r; }
if(gheader!=null)
gheader.time = this.need;
if ((flags & 0x0200)!=0){
checksum(4, this.need);
}
this.mode = OS;
case OS:
try { r=readBytes(2, r, f); }
catch(Return e){ return e.r; }
if(gheader!=null){
gheader.xflags = ((int)this.need)&0xff;
gheader.os = (((int)this.need)>>8)&0xff;
}
if ((flags & 0x0200)!=0){
checksum(2, this.need);
}
this.mode = EXLEN;
case EXLEN:
if ((flags & 0x0400)!=0) {
try { r=readBytes(2, r, f); }
catch(Return e){ return e.r; }
if(gheader!=null){
gheader.extra = new byte[((int)this.need)&0xffff];
}
if ((flags & 0x0200)!=0){
checksum(2, this.need);
}
}
else if(gheader!=null){
gheader.extra=null;
}
this.mode = EXTRA;
case EXTRA:
if ((flags & 0x0400)!=0) {
try {
r=readBytes(r, f);
if(gheader!=null){
byte[] foo = tmp_string.toByteArray();
tmp_string=null;
if(foo.length == gheader.extra.length){
System.arraycopy(foo, 0, gheader.extra, 0, foo.length);
}
else{
z.msg = "bad extra field length";
this.mode = BAD;
break;
}
}
}
catch(Return e){ return e.r; }
}
else if(gheader!=null){
gheader.extra=null;
}
this.mode = NAME;
case NAME:
if ((flags & 0x0800)!=0) {
try {
r=readString(r, f);
if(gheader!=null){
gheader.name=tmp_string.toByteArray();
}
tmp_string=null;
}
catch(Return e){ return e.r; }
}
else if(gheader!=null){
gheader.name=null;
}
this.mode = COMMENT;
case COMMENT:
if ((flags & 0x1000)!=0) {
try {
r=readString(r, f);
if(gheader!=null){
gheader.comment=tmp_string.toByteArray();
}
tmp_string=null;
}
catch(Return e){ return e.r; }
}
else if(gheader!=null){
gheader.comment=null;
}
this.mode = HCRC;
case HCRC:
if ((flags & 0x0200)!=0) {
try { r=readBytes(2, r, f); }
catch(Return e){ return e.r; }
if(gheader!=null){
gheader.hcrc=(int)(this.need&0xffff);
}
if(this.need != (z.adler.getValue()&0xffffL)){
this.mode = BAD;
z.msg = "header crc mismatch";
this.marker = 5; // can't try inflateSync
break;
}
}
z.adler = new CRC32();
this.mode = BLOCKS;
break;
default:
return Z_STREAM_ERROR;
}
}
}
int inflateSetDictionary(byte[] dictionary, int dictLength){
if(z==null || (this.mode != DICT0 && this.wrap != 0)){
return Z_STREAM_ERROR;
}
int index=0;
int length = dictLength;
if(this.mode==DICT0){
long adler_need=z.adler.getValue();
z.adler.reset();
z.adler.update(dictionary, 0, dictLength);
if(z.adler.getValue()!=adler_need){
return Z_DATA_ERROR;
}
}
z.adler.reset();
if(length >= (1<<this.wbits)){
length = (1<<this.wbits)-1;
index=dictLength - length;
}
this.blocks.set_dictionary(dictionary, index, length);
this.mode = BLOCKS;
return Z_OK;
}
static private byte[] mark = {(byte)0, (byte)0, (byte)0xff, (byte)0xff};
int inflateSync(){
int n; // number of bytes to look at
int p; // pointer to bytes
int m; // number of marker bytes found in a row
long r, w; // temporaries to save total_in and total_out
// set up
if(z == null)
return Z_STREAM_ERROR;
if(this.mode != BAD){
this.mode = BAD;
this.marker = 0;
}
if((n=z.avail_in)==0)
return Z_BUF_ERROR;
p=z.next_in_index;
m=this.marker;
// search
while (n!=0 && m < 4){
if(z.next_in[p] == mark[m]){
m++;
}
else if(z.next_in[p]!=0){
m = 0;
}
else{
m = 4 - m;
}
p++; n--;
}
// restore
z.total_in += p-z.next_in_index;
z.next_in_index = p;
z.avail_in = n;
this.marker = m;
// return no joy or set up to restart on a new block
if(m != 4){
return Z_DATA_ERROR;
}
r=z.total_in; w=z.total_out;
inflateReset();
z.total_in=r; z.total_out = w;
this.mode = BLOCKS;
return Z_OK;
}
// Returns true if inflate is currently at the end of a block generated
// by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
// implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH
// but removes the length bytes of the resulting empty stored block. When
// decompressing, PPP checks that at the end of input packet, inflate is
// waiting for these length bytes.
int inflateSyncPoint(){
if(z == null || this.blocks == null)
return Z_STREAM_ERROR;
return this.blocks.sync_point();
}
private int readBytes(int n, int r, int f) throws Return{
if(need_bytes == -1){
need_bytes=n;
this.need=0;
}
while(need_bytes>0){
if(z.avail_in==0){ throw new Return(r); }; r=f;
z.avail_in--; z.total_in++;
this.need = this.need |
((z.next_in[z.next_in_index++]&0xff)<<((n-need_bytes)*8));
need_bytes--;
}
if(n==2){
this.need&=0xffffL;
}
else if(n==4) {
this.need&=0xffffffffL;
}
need_bytes=-1;
return r;
}
class Return extends Exception{
int r;
Return(int r){this.r=r; }
}
private java.io.ByteArrayOutputStream tmp_string = null;
private int readString(int r, int f) throws Return{
if(tmp_string == null){
tmp_string=new java.io.ByteArrayOutputStream();
}
int b=0;
do {
if(z.avail_in==0){ throw new Return(r); }; r=f;
z.avail_in--; z.total_in++;
b = z.next_in[z.next_in_index];
if(b!=0) tmp_string.write(z.next_in, z.next_in_index, 1);
z.adler.update(z.next_in, z.next_in_index, 1);
z.next_in_index++;
}while(b!=0);
return r;
}
private int readBytes(int r, int f) throws Return{
if(tmp_string == null){
tmp_string=new java.io.ByteArrayOutputStream();
}
int b=0;
while(this.need>0){
if(z.avail_in==0){ throw new Return(r); }; r=f;
z.avail_in--; z.total_in++;
b = z.next_in[z.next_in_index];
tmp_string.write(z.next_in, z.next_in_index, 1);
z.adler.update(z.next_in, z.next_in_index, 1);
z.next_in_index++;
this.need--;
}
return r;
}
private void checksum(int n, long v){
for(int i=0; i<n; i++){
crcbuf[i]=(byte)(v&0xff);
v>>=8;
}
z.adler.update(crcbuf, 0, n);
}
public GZIPHeader getGZIPHeader(){
return gheader;
}
boolean inParsingHeader(){
switch(mode){
case HEAD:
case DICT4:
case DICT3:
case DICT2:
case DICT1:
case FLAGS:
case TIME:
case OS:
case EXLEN:
case EXTRA:
case NAME:
case COMMENT:
case HCRC:
return true;
default:
return false;
}
}
}
@@ -0,0 +1,168 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final public class Inflater extends ZStream{
static final private int MAX_WBITS=15; // 32K LZ77 window
static final private int DEF_WBITS=MAX_WBITS;
static final private int Z_NO_FLUSH=0;
static final private int Z_PARTIAL_FLUSH=1;
static final private int Z_SYNC_FLUSH=2;
static final private int Z_FULL_FLUSH=3;
static final private int Z_FINISH=4;
static final private int MAX_MEM_LEVEL=9;
static final private int Z_OK=0;
static final private int Z_STREAM_END=1;
static final private int Z_NEED_DICT=2;
static final private int Z_ERRNO=-1;
static final private int Z_STREAM_ERROR=-2;
static final private int Z_DATA_ERROR=-3;
static final private int Z_MEM_ERROR=-4;
static final private int Z_BUF_ERROR=-5;
static final private int Z_VERSION_ERROR=-6;
public Inflater() {
super();
init();
}
public Inflater(JZlib.WrapperType wrapperType) throws GZIPException {
this(DEF_WBITS, wrapperType);
}
public Inflater(int w, JZlib.WrapperType wrapperType) throws GZIPException {
super();
int ret = init(w, wrapperType);
if(ret!=Z_OK)
throw new GZIPException(ret+": "+msg);
}
public Inflater(int w) throws GZIPException {
this(w, false);
}
public Inflater(boolean nowrap) throws GZIPException {
this(DEF_WBITS, nowrap);
}
public Inflater(int w, boolean nowrap) throws GZIPException {
super();
int ret = init(w, nowrap);
if(ret!=Z_OK)
throw new GZIPException(ret+": "+msg);
}
private boolean finished = false;
public int init(){
return init(DEF_WBITS);
}
public int init(JZlib.WrapperType wrapperType){
return init(DEF_WBITS, wrapperType);
}
public int init(int w, JZlib.WrapperType wrapperType) {
boolean nowrap = false;
if(wrapperType == JZlib.W_NONE){
nowrap = true;
}
else if(wrapperType == JZlib.W_GZIP) {
w += 16;
}
else if(wrapperType == JZlib.W_ANY) {
w |= Inflate.INFLATE_ANY;
}
else if(wrapperType == JZlib.W_ZLIB) {
}
return init(w, nowrap);
}
public int init(boolean nowrap){
return init(DEF_WBITS, nowrap);
}
public int init(int w){
return init(w, false);
}
public int init(int w, boolean nowrap){
finished = false;
istate=new Inflate(this);
return istate.inflateInit(nowrap?-w:w);
}
public int inflate(int f){
if(istate==null) return Z_STREAM_ERROR;
int ret = istate.inflate(f);
if(ret == Z_STREAM_END)
finished = true;
return ret;
}
public int end(){
finished = true;
if(istate==null) return Z_STREAM_ERROR;
int ret=istate.inflateEnd();
// istate = null;
return ret;
}
public int sync(){
if(istate == null)
return Z_STREAM_ERROR;
return istate.inflateSync();
}
public int syncPoint(){
if(istate == null)
return Z_STREAM_ERROR;
return istate.inflateSyncPoint();
}
public int setDictionary(byte[] dictionary, int dictLength){
if(istate == null)
return Z_STREAM_ERROR;
return istate.inflateSetDictionary(dictionary, dictLength);
}
public boolean finished(){
return istate.mode==12 /*DONE*/;
}
}
@@ -0,0 +1,247 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jzlib;
import java.io.*;
public class InflaterInputStream extends FilterInputStream {
protected final Inflater inflater;
protected byte[] buf;
private boolean closed = false;
private boolean eof = false;
private boolean close_in = true;
protected static final int DEFAULT_BUFSIZE = 512;
public InflaterInputStream(InputStream in) throws IOException {
this(in, false);
}
public InflaterInputStream(InputStream in, boolean nowrap) throws IOException {
this(in, new Inflater(nowrap));
myinflater = true;
}
public InflaterInputStream(InputStream in, Inflater inflater) throws IOException {
this(in, inflater, DEFAULT_BUFSIZE);
}
public InflaterInputStream(InputStream in,
Inflater inflater, int size) throws IOException {
this(in, inflater, size, true);
}
public InflaterInputStream(InputStream in,
Inflater inflater,
int size, boolean close_in) throws IOException {
super(in);
if (in == null || inflater == null) {
throw new NullPointerException();
}
else if (size <= 0) {
throw new IllegalArgumentException("buffer size must be greater than 0");
}
this.inflater = inflater;
buf = new byte[size];
this.close_in = close_in;
}
protected boolean myinflater = false;
private byte[] byte1 = new byte[1];
public int read() throws IOException {
if (closed) { throw new IOException("Stream closed"); }
return read(byte1, 0, 1) == -1 ? -1 : byte1[0] & 0xff;
}
public int read(byte[] b, int off, int len) throws IOException {
if (closed) { throw new IOException("Stream closed"); }
if (b == null) {
throw new NullPointerException();
}
else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
else if (len == 0) {
return 0;
}
else if (eof) {
return -1;
}
int n = 0;
inflater.setOutput(b, off, len);
while(!eof) {
if(inflater.avail_in==0)
fill();
int err = inflater.inflate(JZlib.Z_NO_FLUSH);
n += inflater.next_out_index - off;
off = inflater.next_out_index;
switch(err) {
case JZlib.Z_DATA_ERROR:
throw new IOException(inflater.msg);
case JZlib.Z_STREAM_END:
case JZlib.Z_NEED_DICT:
eof = true;
if(err == JZlib.Z_NEED_DICT)
return -1;
break;
default:
}
if(inflater.avail_out==0)
break;
}
return n;
}
public int available() throws IOException {
if (closed) { throw new IOException("Stream closed"); }
if (eof) {
return 0;
}
else {
return 1;
}
}
private byte[] b = new byte[512];
public long skip(long n) throws IOException {
if (n < 0) {
throw new IllegalArgumentException("negative skip length");
}
if (closed) { throw new IOException("Stream closed"); }
int max = (int)Math.min(n, Integer.MAX_VALUE);
int total = 0;
while (total < max) {
int len = max - total;
if (len > b.length) {
len = b.length;
}
len = read(b, 0, len);
if (len == -1) {
eof = true;
break;
}
total += len;
}
return total;
}
public void close() throws IOException {
if (!closed) {
if (myinflater)
inflater.end();
if(close_in)
in.close();
closed = true;
}
}
protected void fill() throws IOException {
if (closed) { throw new IOException("Stream closed"); }
int len = in.read(buf, 0, buf.length);
if (len == -1) {
if(inflater.istate.wrap == 0 &&
!inflater.finished()){
buf[0]=0;
len=1;
}
else if(inflater.istate.was != -1){ // in reading trailer
throw new IOException("footer is not found");
}
else{
throw new EOFException("Unexpected end of ZLIB input stream");
}
}
inflater.setInput(buf, 0, len, true);
}
public boolean markSupported() {
return false;
}
public synchronized void mark(int readlimit) {
}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public long getTotalIn() {
return inflater.getTotalIn();
}
public long getTotalOut() {
return inflater.getTotalOut();
}
public byte[] getAvailIn() {
if(inflater.avail_in<=0)
return null;
byte[] tmp = new byte[inflater.avail_in];
System.arraycopy(inflater.next_in, inflater.next_in_index,
tmp, 0, inflater.avail_in);
return tmp;
}
public void readHeader() throws IOException {
byte[] empty = "".getBytes();
inflater.setInput(empty, 0, 0, false);
inflater.setOutput(empty, 0, 0);
int err = inflater.inflate(JZlib.Z_NO_FLUSH);
if(!inflater.istate.inParsingHeader()){
return;
}
byte[] b1 = new byte[1];
do{
int i = in.read(b1);
if(i<=0)
throw new IOException("no input");
inflater.setInput(b1);
err = inflater.inflate(JZlib.Z_NO_FLUSH);
if(err!=0/*Z_OK*/)
throw new IOException(inflater.msg);
}
while(inflater.istate.inParsingHeader());
}
public Inflater getInflater(){
return inflater;
}
}
@@ -0,0 +1,92 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final public class JZlib{
private static final String version="1.1.0";
public static String version(){return version;}
static final public int MAX_WBITS=15; // 32K LZ77 window
static final public int DEF_WBITS=MAX_WBITS;
public enum WrapperType {
NONE, ZLIB, GZIP, ANY
}
public static final WrapperType W_NONE = WrapperType.NONE;
public static final WrapperType W_ZLIB = WrapperType.ZLIB;
public static final WrapperType W_GZIP = WrapperType.GZIP;
public static final WrapperType W_ANY = WrapperType.ANY;
// compression levels
static final public int Z_NO_COMPRESSION=0;
static final public int Z_BEST_SPEED=1;
static final public int Z_BEST_COMPRESSION=9;
static final public int Z_DEFAULT_COMPRESSION=(-1);
// compression strategy
static final public int Z_FILTERED=1;
static final public int Z_HUFFMAN_ONLY=2;
static final public int Z_DEFAULT_STRATEGY=0;
static final public int Z_NO_FLUSH=0;
static final public int Z_PARTIAL_FLUSH=1;
static final public int Z_SYNC_FLUSH=2;
static final public int Z_FULL_FLUSH=3;
static final public int Z_FINISH=4;
static final public int Z_OK=0;
static final public int Z_STREAM_END=1;
static final public int Z_NEED_DICT=2;
static final public int Z_ERRNO=-1;
static final public int Z_STREAM_ERROR=-2;
static final public int Z_DATA_ERROR=-3;
static final public int Z_MEM_ERROR=-4;
static final public int Z_BUF_ERROR=-5;
static final public int Z_VERSION_ERROR=-6;
// The three kinds of block type
static final public byte Z_BINARY = 0;
static final public byte Z_ASCII = 1;
static final public byte Z_UNKNOWN = 2;
public static long adler32_combine(long adler1, long adler2, long len2){
return Adler32.combine(adler1, adler2, len2);
}
public static long crc32_combine(long crc1, long crc2, long len2){
return CRC32.combine(crc1, crc2, len2);
}
}
@@ -0,0 +1,148 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final class StaticTree{
static final private int MAX_BITS=15;
static final private int BL_CODES=19;
static final private int D_CODES=30;
static final private int LITERALS=256;
static final private int LENGTH_CODES=29;
static final private int L_CODES=(LITERALS+1+LENGTH_CODES);
// Bit length codes must not exceed MAX_BL_BITS bits
static final int MAX_BL_BITS=7;
static final short[] static_ltree = {
12, 8, 140, 8, 76, 8, 204, 8, 44, 8,
172, 8, 108, 8, 236, 8, 28, 8, 156, 8,
92, 8, 220, 8, 60, 8, 188, 8, 124, 8,
252, 8, 2, 8, 130, 8, 66, 8, 194, 8,
34, 8, 162, 8, 98, 8, 226, 8, 18, 8,
146, 8, 82, 8, 210, 8, 50, 8, 178, 8,
114, 8, 242, 8, 10, 8, 138, 8, 74, 8,
202, 8, 42, 8, 170, 8, 106, 8, 234, 8,
26, 8, 154, 8, 90, 8, 218, 8, 58, 8,
186, 8, 122, 8, 250, 8, 6, 8, 134, 8,
70, 8, 198, 8, 38, 8, 166, 8, 102, 8,
230, 8, 22, 8, 150, 8, 86, 8, 214, 8,
54, 8, 182, 8, 118, 8, 246, 8, 14, 8,
142, 8, 78, 8, 206, 8, 46, 8, 174, 8,
110, 8, 238, 8, 30, 8, 158, 8, 94, 8,
222, 8, 62, 8, 190, 8, 126, 8, 254, 8,
1, 8, 129, 8, 65, 8, 193, 8, 33, 8,
161, 8, 97, 8, 225, 8, 17, 8, 145, 8,
81, 8, 209, 8, 49, 8, 177, 8, 113, 8,
241, 8, 9, 8, 137, 8, 73, 8, 201, 8,
41, 8, 169, 8, 105, 8, 233, 8, 25, 8,
153, 8, 89, 8, 217, 8, 57, 8, 185, 8,
121, 8, 249, 8, 5, 8, 133, 8, 69, 8,
197, 8, 37, 8, 165, 8, 101, 8, 229, 8,
21, 8, 149, 8, 85, 8, 213, 8, 53, 8,
181, 8, 117, 8, 245, 8, 13, 8, 141, 8,
77, 8, 205, 8, 45, 8, 173, 8, 109, 8,
237, 8, 29, 8, 157, 8, 93, 8, 221, 8,
61, 8, 189, 8, 125, 8, 253, 8, 19, 9,
275, 9, 147, 9, 403, 9, 83, 9, 339, 9,
211, 9, 467, 9, 51, 9, 307, 9, 179, 9,
435, 9, 115, 9, 371, 9, 243, 9, 499, 9,
11, 9, 267, 9, 139, 9, 395, 9, 75, 9,
331, 9, 203, 9, 459, 9, 43, 9, 299, 9,
171, 9, 427, 9, 107, 9, 363, 9, 235, 9,
491, 9, 27, 9, 283, 9, 155, 9, 411, 9,
91, 9, 347, 9, 219, 9, 475, 9, 59, 9,
315, 9, 187, 9, 443, 9, 123, 9, 379, 9,
251, 9, 507, 9, 7, 9, 263, 9, 135, 9,
391, 9, 71, 9, 327, 9, 199, 9, 455, 9,
39, 9, 295, 9, 167, 9, 423, 9, 103, 9,
359, 9, 231, 9, 487, 9, 23, 9, 279, 9,
151, 9, 407, 9, 87, 9, 343, 9, 215, 9,
471, 9, 55, 9, 311, 9, 183, 9, 439, 9,
119, 9, 375, 9, 247, 9, 503, 9, 15, 9,
271, 9, 143, 9, 399, 9, 79, 9, 335, 9,
207, 9, 463, 9, 47, 9, 303, 9, 175, 9,
431, 9, 111, 9, 367, 9, 239, 9, 495, 9,
31, 9, 287, 9, 159, 9, 415, 9, 95, 9,
351, 9, 223, 9, 479, 9, 63, 9, 319, 9,
191, 9, 447, 9, 127, 9, 383, 9, 255, 9,
511, 9, 0, 7, 64, 7, 32, 7, 96, 7,
16, 7, 80, 7, 48, 7, 112, 7, 8, 7,
72, 7, 40, 7, 104, 7, 24, 7, 88, 7,
56, 7, 120, 7, 4, 7, 68, 7, 36, 7,
100, 7, 20, 7, 84, 7, 52, 7, 116, 7,
3, 8, 131, 8, 67, 8, 195, 8, 35, 8,
163, 8, 99, 8, 227, 8
};
static final short[] static_dtree = {
0, 5, 16, 5, 8, 5, 24, 5, 4, 5,
20, 5, 12, 5, 28, 5, 2, 5, 18, 5,
10, 5, 26, 5, 6, 5, 22, 5, 14, 5,
30, 5, 1, 5, 17, 5, 9, 5, 25, 5,
5, 5, 21, 5, 13, 5, 29, 5, 3, 5,
19, 5, 11, 5, 27, 5, 7, 5, 23, 5
};
static StaticTree static_l_desc =
new StaticTree(static_ltree, Tree.extra_lbits,
LITERALS+1, L_CODES, MAX_BITS);
static StaticTree static_d_desc =
new StaticTree(static_dtree, Tree.extra_dbits,
0, D_CODES, MAX_BITS);
static StaticTree static_bl_desc =
new StaticTree(null, Tree.extra_blbits,
0, BL_CODES, MAX_BL_BITS);
short[] static_tree; // static tree or null
int[] extra_bits; // extra bits for each code or null
int extra_base; // base index for extra_bits
int elems; // max number of elements in the tree
int max_length; // max bit length for the codes
private StaticTree(short[] static_tree,
int[] extra_bits,
int extra_base,
int elems,
int max_length){
this.static_tree=static_tree;
this.extra_bits=extra_bits;
this.extra_base=extra_base;
this.elems=elems;
this.max_length=max_length;
}
}
@@ -0,0 +1,367 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
final class Tree{
static final private int MAX_BITS=15;
static final private int BL_CODES=19;
static final private int D_CODES=30;
static final private int LITERALS=256;
static final private int LENGTH_CODES=29;
static final private int L_CODES=(LITERALS+1+LENGTH_CODES);
static final private int HEAP_SIZE=(2*L_CODES+1);
// Bit length codes must not exceed MAX_BL_BITS bits
static final int MAX_BL_BITS=7;
// end of block literal code
static final int END_BLOCK=256;
// repeat previous bit length 3-6 times (2 bits of repeat count)
static final int REP_3_6=16;
// repeat a zero length 3-10 times (3 bits of repeat count)
static final int REPZ_3_10=17;
// repeat a zero length 11-138 times (7 bits of repeat count)
static final int REPZ_11_138=18;
// extra bits for each length code
static final int[] extra_lbits={
0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0
};
// extra bits for each distance code
static final int[] extra_dbits={
0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13
};
// extra bits for each bit length code
static final int[] extra_blbits={
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7
};
static final byte[] bl_order={
16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15};
// The lengths of the bit length codes are sent in order of decreasing
// probability, to avoid transmitting the lengths for unused bit
// length codes.
static final int Buf_size=8*2;
// see definition of array dist_code below
static final int DIST_CODE_LEN=512;
static final byte[] _dist_code = {
0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8,
8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10,
10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17,
18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22,
23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27,
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
};
static final byte[] _length_code={
0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12,
13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16,
17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19,
19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
};
static final int[] base_length = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
64, 80, 96, 112, 128, 160, 192, 224, 0
};
static final int[] base_dist = {
0, 1, 2, 3, 4, 6, 8, 12, 16, 24,
32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576
};
// Mapping from a distance to a distance code. dist is the distance - 1 and
// must not have side effects. _dist_code[256] and _dist_code[257] are never
// used.
static int d_code(int dist){
return ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>>7)]);
}
short[] dyn_tree; // the dynamic tree
int max_code; // largest code with non zero frequency
StaticTree stat_desc; // the corresponding static tree
// Compute the optimal bit lengths for a tree and update the total bit length
// for the current block.
// IN assertion: the fields freq and dad are set, heap[heap_max] and
// above are the tree nodes sorted by increasing frequency.
// OUT assertions: the field len is set to the optimal bit length, the
// array bl_count contains the frequencies for each bit length.
// The length opt_len is updated; static_len is also updated if stree is
// not null.
void gen_bitlen(Deflate s){
short[] tree = dyn_tree;
short[] stree = stat_desc.static_tree;
int[] extra = stat_desc.extra_bits;
int base = stat_desc.extra_base;
int max_length = stat_desc.max_length;
int h; // heap index
int n, m; // iterate over the tree elements
int bits; // bit length
int xbits; // extra bits
short f; // frequency
int overflow = 0; // number of elements with bit length too large
for (bits = 0; bits <= MAX_BITS; bits++) s.bl_count[bits] = 0;
// In a first pass, compute the optimal bit lengths (which may
// overflow in the case of the bit length tree).
tree[s.heap[s.heap_max]*2+1] = 0; // root of the heap
for(h=s.heap_max+1; h<HEAP_SIZE; h++){
n = s.heap[h];
bits = tree[tree[n*2+1]*2+1] + 1;
if (bits > max_length){ bits = max_length; overflow++; }
tree[n*2+1] = (short)bits;
// We overwrite tree[n*2+1] which is no longer needed
if (n > max_code) continue; // not a leaf node
s.bl_count[bits]++;
xbits = 0;
if (n >= base) xbits = extra[n-base];
f = tree[n*2];
s.opt_len += f * (bits + xbits);
if (stree!=null) s.static_len += f * (stree[n*2+1] + xbits);
}
if (overflow == 0) return;
// This happens for example on obj2 and pic of the Calgary corpus
// Find the first bit length which could increase:
do {
bits = max_length-1;
while(s.bl_count[bits]==0) bits--;
s.bl_count[bits]--; // move one leaf down the tree
s.bl_count[bits+1]+=2; // move one overflow item as its brother
s.bl_count[max_length]--;
// The brother of the overflow item also moves one step up,
// but this does not affect bl_count[max_length]
overflow -= 2;
}
while (overflow > 0);
for (bits = max_length; bits != 0; bits--) {
n = s.bl_count[bits];
while (n != 0) {
m = s.heap[--h];
if (m > max_code) continue;
if (tree[m*2+1] != bits) {
s.opt_len += ((long)bits - (long)tree[m*2+1])*(long)tree[m*2];
tree[m*2+1] = (short)bits;
}
n--;
}
}
}
// Construct one Huffman tree and assigns the code bit strings and lengths.
// Update the total bit length for the current block.
// IN assertion: the field freq is set for all tree elements.
// OUT assertions: the fields len and code are set to the optimal bit length
// and corresponding code. The length opt_len is updated; static_len is
// also updated if stree is not null. The field max_code is set.
void build_tree(Deflate s){
short[] tree=dyn_tree;
short[] stree=stat_desc.static_tree;
int elems=stat_desc.elems;
int n, m; // iterate over heap elements
int max_code=-1; // largest code with non zero frequency
int node; // new node being created
// Construct the initial heap, with least frequent element in
// heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
// heap[0] is not used.
s.heap_len = 0;
s.heap_max = HEAP_SIZE;
for(n=0; n<elems; n++) {
if(tree[n*2] != 0) {
s.heap[++s.heap_len] = max_code = n;
s.depth[n] = 0;
}
else{
tree[n*2+1] = 0;
}
}
// The pkzip format requires that at least one distance code exists,
// and that at least one bit should be sent even if there is only one
// possible code. So to avoid special checks later on we force at least
// two codes of non zero frequency.
while (s.heap_len < 2) {
node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
tree[node*2] = 1;
s.depth[node] = 0;
s.opt_len--; if (stree!=null) s.static_len -= stree[node*2+1];
// node is 0 or 1 so it does not have extra bits
}
this.max_code = max_code;
// The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
// establish sub-heaps of increasing lengths:
for(n=s.heap_len/2;n>=1; n--)
s.pqdownheap(tree, n);
// Construct the Huffman tree by repeatedly combining the least two
// frequent nodes.
node=elems; // next internal node of the tree
do{
// n = node of least frequency
n=s.heap[1];
s.heap[1]=s.heap[s.heap_len--];
s.pqdownheap(tree, 1);
m=s.heap[1]; // m = node of next least frequency
s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency
s.heap[--s.heap_max] = m;
// Create a new node father of n and m
tree[node*2] = (short)(tree[n*2] + tree[m*2]);
s.depth[node] = (byte)(Math.max(s.depth[n],s.depth[m])+1);
tree[n*2+1] = tree[m*2+1] = (short)node;
// and insert the new node in the heap
s.heap[1] = node++;
s.pqdownheap(tree, 1);
}
while(s.heap_len>=2);
s.heap[--s.heap_max] = s.heap[1];
// At this point, the fields freq and dad are set. We can now
// generate the bit lengths.
gen_bitlen(s);
// The field len is now set, we can generate the bit codes
gen_codes(tree, max_code, s.bl_count, s.next_code);
}
// Generate the codes for a given tree and bit counts (which need not be
// optimal).
// IN assertion: the array bl_count contains the bit length statistics for
// the given tree and the field len is set for all tree elements.
// OUT assertion: the field code is set for all tree elements of non
// zero code length.
private final static void gen_codes(
short[] tree, // the tree to decorate
int max_code, // largest code with non zero frequency
short[] bl_count, // number of codes at each bit length
short[] next_code){
short code = 0; // running code value
int bits; // bit index
int n; // code index
// The distribution counts are first used to generate the code values
// without bit reversal.
next_code[0]=0;
for (bits = 1; bits <= MAX_BITS; bits++) {
next_code[bits] = code = (short)((code + bl_count[bits-1]) << 1);
}
// Check that the bit counts in bl_count are consistent. The last code
// must be all ones.
//Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
// "inconsistent bit counts");
//Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
for (n = 0; n <= max_code; n++) {
int len = tree[n*2+1];
if (len == 0) continue;
// Now reverse the bits
tree[n*2] = (short)(bi_reverse(next_code[len]++, len));
}
}
// Reverse the first len bits of a code, using straightforward code (a faster
// method would use a table)
// IN assertion: 1 <= len <= 15
private final static int bi_reverse(
int code, // the value to invert
int len // its bit length
){
int res = 0;
do{
res|=code&1;
code>>>=1;
res<<=1;
}
while(--len>0);
return res>>>1;
}
}
@@ -0,0 +1,126 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jzlib;
import java.io.*;
/**
* ZInputStream
*
* @deprecated use DeflaterOutputStream or InflaterInputStream
*/
@Deprecated
public class ZInputStream extends FilterInputStream {
protected int flush=JZlib.Z_NO_FLUSH;
protected boolean compress;
protected InputStream in=null;
protected Deflater deflater;
protected InflaterInputStream iis;
public ZInputStream(InputStream in) throws IOException {
this(in, false);
}
public ZInputStream(InputStream in, boolean nowrap) throws IOException {
super(in);
iis = new InflaterInputStream(in, nowrap);
compress=false;
}
public ZInputStream(InputStream in, int level) throws IOException {
super(in);
this.in=in;
deflater = new Deflater();
deflater.init(level);
compress=true;
}
private byte[] buf1 = new byte[1];
public int read() throws IOException {
if(read(buf1, 0, 1)==-1) return -1;
return(buf1[0]&0xFF);
}
private byte[] buf = new byte[512];
public int read(byte[] b, int off, int len) throws IOException {
if(compress){
deflater.setOutput(b, off, len);
while(true){
int datalen = in.read(buf, 0, buf.length);
if(datalen == -1) return -1;
deflater.setInput(buf, 0, datalen, true);
int err = deflater.deflate(flush);
if(deflater.next_out_index>0)
return deflater.next_out_index;
if(err == JZlib.Z_STREAM_END)
return 0;
if(err == JZlib.Z_STREAM_ERROR ||
err == JZlib.Z_DATA_ERROR){
throw new ZStreamException("deflating: "+deflater.msg);
}
}
}
else{
return iis.read(b, off, len);
}
}
public long skip(long n) throws IOException {
int len=512;
if(n<len)
len=(int)n;
byte[] tmp=new byte[len];
return((long)read(tmp));
}
public int getFlushMode() {
return flush;
}
public void setFlushMode(int flush) {
this.flush=flush;
}
public long getTotalIn() {
if(compress) return deflater.total_in;
else return iis.getTotalIn();
}
public long getTotalOut() {
if(compress) return deflater.total_out;
else return iis.getTotalOut();
}
public void close() throws IOException{
if(compress) deflater.end();
else iis.close();
}
}
@@ -0,0 +1,159 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jzlib;
import java.io.*;
/**
* ZOutputStream
*
* @deprecated use DeflaterOutputStream or InflaterInputStream
*/
@Deprecated
public class ZOutputStream extends FilterOutputStream {
protected int bufsize=512;
protected int flush=JZlib.Z_NO_FLUSH;
protected byte[] buf=new byte[bufsize];
protected boolean compress;
protected OutputStream out;
private boolean end=false;
private DeflaterOutputStream dos;
private Inflater inflater;
public ZOutputStream(OutputStream out) throws IOException {
super(out);
this.out=out;
inflater = new Inflater();
inflater.init();
compress=false;
}
public ZOutputStream(OutputStream out, int level) throws IOException {
this(out, level, false);
}
public ZOutputStream(OutputStream out, int level, boolean nowrap) throws IOException {
super(out);
this.out=out;
Deflater deflater = new Deflater(level, nowrap);
dos = new DeflaterOutputStream(out, deflater);
compress=true;
}
private byte[] buf1 = new byte[1];
public void write(int b) throws IOException {
buf1[0]=(byte)b;
write(buf1, 0, 1);
}
public void write(byte b[], int off, int len) throws IOException {
if(len==0) return;
if(compress){
dos.write(b, off, len);
}
else {
inflater.setInput(b, off, len, true);
int err = JZlib.Z_OK;
while(inflater.avail_in>0){
inflater.setOutput(buf, 0, buf.length);
err = inflater.inflate(flush);
if(inflater.next_out_index>0)
out.write(buf, 0, inflater.next_out_index);
if(err != JZlib.Z_OK)
break;
}
if(err != JZlib.Z_OK)
throw new ZStreamException("inflating: "+inflater.msg);
return;
}
}
public int getFlushMode() {
return flush;
}
public void setFlushMode(int flush) {
this.flush=flush;
}
public void finish() throws IOException {
int err;
if(compress){
int tmp = flush;
int flush = JZlib.Z_FINISH;
try{
write("".getBytes(), 0, 0);
}
finally { flush = tmp; }
}
else{
dos.finish();
}
flush();
}
public synchronized void end() {
if(end) return;
if(compress){
try { dos.finish(); } catch(Exception e){}
}
else{
inflater.end();
}
end=true;
}
public void close() throws IOException {
try{
try{finish();}
catch (IOException ignored) {}
}
finally{
end();
out.close();
out=null;
}
}
public long getTotalIn() {
if(compress) return dos.getTotalIn();
else return inflater.total_in;
}
public long getTotalOut() {
if(compress) return dos.getTotalOut();
else return inflater.total_out;
}
public void flush() throws IOException {
out.flush();
}
}
@@ -0,0 +1,377 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
/**
* ZStream
*
* @deprecated Not for public use in the future.
*/
@Deprecated
public class ZStream{
static final private int MAX_WBITS=15; // 32K LZ77 window
static final private int DEF_WBITS=MAX_WBITS;
static final private int Z_NO_FLUSH=0;
static final private int Z_PARTIAL_FLUSH=1;
static final private int Z_SYNC_FLUSH=2;
static final private int Z_FULL_FLUSH=3;
static final private int Z_FINISH=4;
static final private int MAX_MEM_LEVEL=9;
static final private int Z_OK=0;
static final private int Z_STREAM_END=1;
static final private int Z_NEED_DICT=2;
static final private int Z_ERRNO=-1;
static final private int Z_STREAM_ERROR=-2;
static final private int Z_DATA_ERROR=-3;
static final private int Z_MEM_ERROR=-4;
static final private int Z_BUF_ERROR=-5;
static final private int Z_VERSION_ERROR=-6;
public byte[] next_in; // next input byte
public int next_in_index;
public int avail_in; // number of bytes available at next_in
public long total_in; // total nb of input bytes read so far
public byte[] next_out; // next output byte should be put there
public int next_out_index;
public int avail_out; // remaining free space at next_out
public long total_out; // total nb of bytes output so far
public String msg;
Deflate dstate;
Inflate istate;
int data_type; // best guess about the data type: ascii or binary
Checksum adler;
public ZStream(){
this(new Adler32());
}
public ZStream(Checksum adler){
this.adler=adler;
}
public int inflateInit(){
return inflateInit(DEF_WBITS);
}
public int inflateInit(boolean nowrap){
return inflateInit(DEF_WBITS, nowrap);
}
public int inflateInit(int w){
return inflateInit(w, false);
}
public int inflateInit(JZlib.WrapperType wrapperType) {
return inflateInit(DEF_WBITS, wrapperType);
}
public int inflateInit(int w, JZlib.WrapperType wrapperType) {
boolean nowrap = false;
if(wrapperType == JZlib.W_NONE){
nowrap = true;
}
else if(wrapperType == JZlib.W_GZIP) {
w += 16;
}
else if(wrapperType == JZlib.W_ANY) {
w |= Inflate.INFLATE_ANY;
}
else if(wrapperType == JZlib.W_ZLIB) {
}
return inflateInit(w, nowrap);
}
public int inflateInit(int w, boolean nowrap){
istate=new Inflate(this);
return istate.inflateInit(nowrap?-w:w);
}
public int inflate(int f){
if(istate==null) return Z_STREAM_ERROR;
return istate.inflate(f);
}
public int inflateEnd(){
if(istate==null) return Z_STREAM_ERROR;
int ret=istate.inflateEnd();
// istate = null;
return ret;
}
public int inflateSync(){
if(istate == null)
return Z_STREAM_ERROR;
return istate.inflateSync();
}
public int inflateSyncPoint(){
if(istate == null)
return Z_STREAM_ERROR;
return istate.inflateSyncPoint();
}
public int inflateSetDictionary(byte[] dictionary, int dictLength){
if(istate == null)
return Z_STREAM_ERROR;
return istate.inflateSetDictionary(dictionary, dictLength);
}
public boolean inflateFinished(){
return istate.mode==12 /*DONE*/;
}
public int deflateInit(int level){
return deflateInit(level, MAX_WBITS);
}
public int deflateInit(int level, boolean nowrap){
return deflateInit(level, MAX_WBITS, nowrap);
}
public int deflateInit(int level, int bits){
return deflateInit(level, bits, false);
}
public int deflateInit(int level, int bits, int memlevel, JZlib.WrapperType wrapperType){
if(bits < 9 || bits > 15){
return Z_STREAM_ERROR;
}
if(wrapperType == JZlib.W_NONE) {
bits *= -1;
}
else if(wrapperType == JZlib.W_GZIP) {
bits += 16;
}
else if(wrapperType == JZlib.W_ANY) {
return Z_STREAM_ERROR;
}
else if(wrapperType == JZlib.W_ZLIB) {
}
return this.deflateInit(level, bits, memlevel);
}
public int deflateInit(int level, int bits, int memlevel){
dstate=new Deflate(this);
return dstate.deflateInit(level, bits, memlevel);
}
public int deflateInit(int level, int bits, boolean nowrap){
dstate=new Deflate(this);
return dstate.deflateInit(level, nowrap?-bits:bits);
}
public int deflate(int flush){
if(dstate==null){
return Z_STREAM_ERROR;
}
return dstate.deflate(flush);
}
public int deflateEnd(){
if(dstate==null) return Z_STREAM_ERROR;
int ret=dstate.deflateEnd();
dstate=null;
return ret;
}
public int deflateParams(int level, int strategy){
if(dstate==null) return Z_STREAM_ERROR;
return dstate.deflateParams(level, strategy);
}
public int deflateSetDictionary (byte[] dictionary, int dictLength){
if(dstate == null)
return Z_STREAM_ERROR;
return dstate.deflateSetDictionary(dictionary, dictLength);
}
// Flush as much pending output as possible. All deflate() output goes
// through this function so some applications may wish to modify it
// to avoid allocating a large strm->next_out buffer and copying into it.
// (See also read_buf()).
void flush_pending(){
int len=dstate.pending;
if(len>avail_out) len=avail_out;
if(len==0) return;
if(dstate.pending_buf.length<=dstate.pending_out ||
next_out.length<=next_out_index ||
dstate.pending_buf.length<(dstate.pending_out+len) ||
next_out.length<(next_out_index+len)){
//System.out.println(dstate.pending_buf.length+", "+dstate.pending_out+
// ", "+next_out.length+", "+next_out_index+", "+len);
//System.out.println("avail_out="+avail_out);
}
System.arraycopy(dstate.pending_buf, dstate.pending_out,
next_out, next_out_index, len);
next_out_index+=len;
dstate.pending_out+=len;
total_out+=len;
avail_out-=len;
dstate.pending-=len;
if(dstate.pending==0){
dstate.pending_out=0;
}
}
// Read a new buffer from the current input stream, update the adler32
// and total number of bytes read. All deflate() input goes through
// this function so some applications may wish to modify it to avoid
// allocating a large strm->next_in buffer and copying from it.
// (See also flush_pending()).
int read_buf(byte[] buf, int start, int size) {
int len=avail_in;
if(len>size) len=size;
if(len==0) return 0;
avail_in-=len;
if(dstate.wrap!=0) {
adler.update(next_in, next_in_index, len);
}
System.arraycopy(next_in, next_in_index, buf, start, len);
next_in_index += len;
total_in += len;
return len;
}
public long getAdler(){
return adler.getValue();
}
public void free(){
next_in=null;
next_out=null;
msg=null;
}
public void setOutput(byte[] buf){
setOutput(buf, 0, buf.length);
}
public void setOutput(byte[] buf, int off, int len){
next_out = buf;
next_out_index = off;
avail_out = len;
}
public void setInput(byte[] buf){
setInput(buf, 0, buf.length, false);
}
public void setInput(byte[] buf, boolean append){
setInput(buf, 0, buf.length, append);
}
public void setInput(byte[] buf, int off, int len, boolean append){
if(len<=0 && append && next_in!=null) return;
if(avail_in>0 && append){
byte[] tmp = new byte[avail_in+len];
System.arraycopy(next_in, next_in_index, tmp, 0, avail_in);
System.arraycopy(buf, off, tmp, avail_in, len);
next_in=tmp;
next_in_index=0;
avail_in+=len;
}
else{
next_in=buf;
next_in_index=off;
avail_in=len;
}
}
public byte[] getNextIn(){
return next_in;
}
public void setNextIn(byte[] next_in){
this.next_in = next_in;
}
public int getNextInIndex(){
return next_in_index;
}
public void setNextInIndex(int next_in_index){
this.next_in_index = next_in_index;
}
public int getAvailIn(){
return avail_in;
}
public void setAvailIn(int avail_in){
this.avail_in = avail_in;
}
public byte[] getNextOut(){
return next_out;
}
public void setNextOut(byte[] next_out){
this.next_out = next_out;
}
public int getNextOutIndex(){
return next_out_index;
}
public void setNextOutIndex(int next_out_index){
this.next_out_index = next_out_index;
}
public int getAvailOut(){
return avail_out;
}
public void setAvailOut(int avail_out){
this.avail_out = avail_out;
}
public long getTotalOut(){
return total_out;
}
public long getTotalIn(){
return total_in;
}
public String getMessage(){
return msg;
}
/**
* Those methods are expected to be override by Inflater and Deflater.
* In the future, they will become abstract methods.
*/
public int end(){ return Z_OK; }
public boolean finished(){ return false; }
}
@@ -0,0 +1,44 @@
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program is based on zlib-1.1.3, so all credit should go authors
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
* and contributors of zlib.
*/
package com.jcraft.jzlib;
public class ZStreamException extends java.io.IOException {
public ZStreamException() {
super();
}
public ZStreamException(String s) {
super(s);
}
}
@@ -0,0 +1,61 @@
package com.trilead.ssh2;
/**
* Contains constants that can be used to specify what conditions to wait for on
* a SSH-2 channel (e.g., represented by a {@link Session}).
*
* @see Session#waitForCondition(int, long)
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: ChannelCondition.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
*/
public abstract interface ChannelCondition
{
/**
* A timeout has occurred, none of your requested conditions is fulfilled.
* However, other conditions may be true - therefore, NEVER use the "=="
* operator to test for this (or any other) condition. Always use
* something like <code>((cond &amp; ChannelCondition.CLOSED) != 0)</code>.
*/
public static final int TIMEOUT = 1;
/**
* The underlying SSH-2 channel, however not necessarily the whole connection,
* has been closed. This implies <code>EOF</code>. Note that there may still
* be unread stdout or stderr data in the local window, i.e, <code>STDOUT_DATA</code>
* or/and <code>STDERR_DATA</code> may be set at the same time.
*/
public static final int CLOSED = 2;
/**
* There is stdout data available that is ready to be consumed.
*/
public static final int STDOUT_DATA = 4;
/**
* There is stderr data available that is ready to be consumed.
*/
public static final int STDERR_DATA = 8;
/**
* EOF on has been reached, no more _new_ stdout or stderr data will arrive
* from the remote server. However, there may be unread stdout or stderr
* data, i.e, <code>STDOUT_DATA</code> or/and <code>STDERR_DATA</code>
* may be set at the same time.
*/
public static final int EOF = 16;
/**
* The exit status of the remote process is available.
* Some servers never send the exist status, or occasionally "forget" to do so.
*/
public static final int EXIT_STATUS = 32;
/**
* The exit signal of the remote process is available.
*/
public static final int EXIT_SIGNAL = 64;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
package com.trilead.ssh2;
/**
* In most cases you probably do not need the information contained in here.
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: ConnectionInfo.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
*/
public class ConnectionInfo
{
/**
* The used key exchange (KEX) algorithm in the latest key exchange.
*/
public String keyExchangeAlgorithm;
/**
* The currently used crypto algorithm for packets from to the client to the
* server.
*/
public String clientToServerCryptoAlgorithm;
/**
* The currently used crypto algorithm for packets from to the server to the
* client.
*/
public String serverToClientCryptoAlgorithm;
/**
* The currently used MAC algorithm for packets from to the client to the
* server.
*/
public String clientToServerMACAlgorithm;
/**
* The currently used MAC algorithm for packets from to the server to the
* client.
*/
public String serverToClientMACAlgorithm;
/**
* The type of the server host key
*/
public String serverHostKeyAlgorithm;
/**
* The server host key that was sent during the latest key exchange.
*/
public byte[] serverHostKey;
/**
* Number of kex exchanges performed on this connection so far.
*/
public int keyExchangeCounter = 0;
}
@@ -0,0 +1,38 @@
package com.trilead.ssh2;
/**
* A <code>ConnectionMonitor</code> is used to get notified when the
* underlying socket of a connection is closed.
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: ConnectionMonitor.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
*/
public interface ConnectionMonitor
{
public static final int SERVER_BANNER = 101;
/**
* This method is called after the connection's underlying
* socket has been closed. E.g., due to the {@link Connection#close()} request of the
* user, if the peer closed the connection, due to a fatal error during connect()
* (also if the socket cannot be established) or if a fatal error occured on
* an established connection.
* <p>
* This is an experimental feature.
* <p>
* You MUST NOT make any assumption about the thread that invokes this method.
* <p>
* <b>Please note: if the connection is not connected (e.g., there was no successful
* connect() call), then the invocation of {@link Connection#close()} will NOT trigger
* this method.</b>
*
* @see Connection#addConnectionMonitor(ConnectionMonitor)
*
* @param reason Includes an indication why the socket was closed.
*/
public void connectionLost(Throwable reason);
public void onReceiveInfo(int infoId, String infoMsg);
}
@@ -0,0 +1,121 @@
package com.trilead.ssh2;
/**
* A <code>DHGexParameters</code> object can be used to specify parameters for
* the diffie-hellman group exchange.
* <p>
* Depending on which constructor is used, either the use of a
* <code>SSH_MSG_KEX_DH_GEX_REQUEST</code> or <code>SSH_MSG_KEX_DH_GEX_REQUEST_OLD</code>
* can be forced.
*
* @see Connection#setDHGexParameters(DHGexParameters)
* @author Christian Plattner, plattner@trilead.com
* @version $Id: DHGexParameters.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
*/
public class DHGexParameters
{
private final int min_group_len;
private final int pref_group_len;
private final int max_group_len;
private static final int MIN_ALLOWED = 1024;
private static final int MAX_ALLOWED = 8192;
/**
* Same as calling {@link #DHGexParameters(int, int, int) DHGexParameters(1024, 2048, 4096)}.
* This is also the default used by the Connection class.
*
*/
public DHGexParameters()
{
this(1024, 2048, 4096);
}
/**
* This constructor can be used to force the sending of a
* <code>SSH_MSG_KEX_DH_GEX_REQUEST_OLD</code> request.
* Internally, the minimum and maximum group lengths will
* be set to zero.
*
* @param pref_group_len has to be &gt;= 1024 and &lt;= 8192
*/
public DHGexParameters(int pref_group_len)
{
if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED))
throw new IllegalArgumentException("pref_group_len out of range!");
this.pref_group_len = pref_group_len;
this.min_group_len = 0;
this.max_group_len = 0;
}
/**
* This constructor can be used to force the sending of a
* <code>SSH_MSG_KEX_DH_GEX_REQUEST</code> request.
* <p>
* Note: older OpenSSH servers don't understand this request, in which
* case you should use the {@link #DHGexParameters(int)} constructor.
* <p>
* All values have to be &gt;= 1024 and &lt;= 8192. Furthermore,
* min_group_len &lt;= pref_group_len &lt;= max_group_len.
*
* @param min_group_len min_group_len
* @param pref_group_len pref_group_len
* @param max_group_len max_group_len
*/
public DHGexParameters(int min_group_len, int pref_group_len, int max_group_len)
{
if ((min_group_len < MIN_ALLOWED) || (min_group_len > MAX_ALLOWED))
throw new IllegalArgumentException("min_group_len out of range!");
if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED))
throw new IllegalArgumentException("pref_group_len out of range!");
if ((max_group_len < MIN_ALLOWED) || (max_group_len > MAX_ALLOWED))
throw new IllegalArgumentException("max_group_len out of range!");
if ((pref_group_len < min_group_len) || (pref_group_len > max_group_len))
throw new IllegalArgumentException("pref_group_len is incompatible with min and max!");
if (max_group_len < min_group_len)
throw new IllegalArgumentException("max_group_len must not be smaller than min_group_len!");
this.min_group_len = min_group_len;
this.pref_group_len = pref_group_len;
this.max_group_len = max_group_len;
}
/**
* Get the maximum group length.
*
* @return the maximum group length, may be <code>zero</code> if
* SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested
*/
public int getMax_group_len()
{
return max_group_len;
}
/**
* Get the minimum group length.
*
* @return minimum group length, may be <code>zero</code> if
* SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested
*/
public int getMin_group_len()
{
return min_group_len;
}
/**
* Get the preferred group length.
*
* @return the preferred group length
*/
public int getPref_group_len()
{
return pref_group_len;
}
}
@@ -0,0 +1,25 @@
package com.trilead.ssh2;
/**
* An interface which needs to be implemented if you
* want to capture debugging messages.
*
* @see Connection#enableDebugging(boolean, DebugLogger)
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: DebugLogger.java,v 1.1 2008/03/03 07:01:36 cplattne Exp $
* @deprecated
* Logging all goes to JDK java.util.logging
*/
public interface DebugLogger
{
/**
* Log a debug message.
*
* @param level 0-99, 99 is a the most verbose level
* @param className the class that generated the message
* @param message the debug message
*/
public void log(int level, String className, String message);
}
@@ -0,0 +1,70 @@
/*
* ConnectBot: simple, powerful, open-source SSH client for Android
* Copyright 2007 Kenny Root, Jeffrey Sharkey
*
* 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 com.trilead.ssh2;
import java.io.IOException;
import java.net.InetSocketAddress;
import com.trilead.ssh2.channel.ChannelManager;
import com.trilead.ssh2.channel.DynamicAcceptThread;
/**
* A <code>DynamicPortForwarder</code> forwards TCP/IP connections to a local
* port via the secure tunnel to another host which is selected via the SOCKS
* protocol. Checkout {@link Connection#createDynamicPortForwarder(int)} on how
* to create one.
*
* @author Kenny Root
* @version $Id: $
*/
public class DynamicPortForwarder {
ChannelManager cm;
DynamicAcceptThread dat;
DynamicPortForwarder(ChannelManager cm, InetSocketAddress addr, int maxThreads)
throws IOException {
this.cm = cm;
dat = new DynamicAcceptThread(cm, addr, maxThreads);
dat.setDaemon(true);
dat.start();
}
DynamicPortForwarder(ChannelManager cm, int local_port, int maxThreads) throws IOException {
this.cm = cm;
dat = new DynamicAcceptThread(cm, local_port, maxThreads);
dat.setDaemon(true);
dat.start();
}
/** Returns the actual loopback port. Useful when the forwarder was bound to port 0. */
public int getLocalPort() {
return dat.getLocalPort();
}
/**
* Stop TCP/IP forwarding of newly arriving connections.
*
* @throws IOException
*/
public void close() throws IOException {
dat.stopWorking();
}
}
@@ -0,0 +1,220 @@
package com.trilead.ssh2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import com.dragonssh.xhttpdemo.core.tunnel.vpn.SocketProtector;
import com.trilead.ssh2.crypto.Base64;
import com.trilead.ssh2.transport.ClientServerHello;
import java.net.UnknownHostException;
import com.trilead.ssh2.transport.TransportManager;
/**
* A <code>HTTPProxyData</code> object is used to specify the needed connection data
* to connect through a HTTP proxy.
*
* @see Connection#setProxyData(ProxyData)
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: HTTPProxyData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
*/
public class HTTPProxyData implements ProxyData
{
private final String proxyHost;
private final int proxyPort;
private final String proxyUser;
private final String proxyPass;
private final String[] requestHeaderLines;
private Socket sock;
/**
* Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, <code>null</code>, <code>null</code>)}
*
* @param proxyHost Proxy hostname.
* @param proxyPort Proxy port.
*/
public HTTPProxyData(String proxyHost, int proxyPort)
{
this(proxyHost, proxyPort, null, null);
}
/**
* Same as calling {@link #HTTPProxyData(String, int, String, String, String[]) HTTPProxyData(proxyHost, proxyPort, <code>null</code>, <code>null</code>, <code>null</code>)}
*
* @param proxyHost Proxy hostname.
* @param proxyPort Proxy port.
* @param proxyUser Username for basic authentication (<code>null</code> if no authentication is needed).
* @param proxyPass Password for basic authentication (<code>null</code> if no authentication is needed).
*/
public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass)
{
this(proxyHost, proxyPort, proxyUser, proxyPass, null);
}
/**
* Connection data for a HTTP proxy. It is possible to specify a username and password
* if the proxy requires basic authentication. Also, additional request header lines can
* be specified (e.g., "User-Agent: CERN-LineMode/2.15 libwww/2.17b3").
* <p>
* Please note: if you want to use basic authentication, then both <code>proxyUser</code>
* and <code>proxyPass</code> must be non-null.
* <p>
* Here is an example:
* <p>
* <code>
* new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: TrileadBasedClient/1.0", "X-My-Proxy-Option: something"});
* </code>
*
* @param proxyHost Proxy hostname.
* @param proxyPort Proxy port.
* @param proxyUser Username for basic authentication (<code>null</code> if no authentication is needed).
* @param proxyPass Password for basic authentication (<code>null</code> if no authentication is needed).
* @param requestHeaderLines An array with additional request header lines (without end-of-line markers)
* that have to be sent to the server. May be <code>null</code>.
*/
public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass,
String[] requestHeaderLines)
{
if (proxyHost == null)
throw new IllegalArgumentException("proxyHost must be non-null");
if (proxyPort < 0)
throw new IllegalArgumentException("proxyPort must be non-negative");
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
this.proxyUser = proxyUser;
this.proxyPass = proxyPass;
this.requestHeaderLines = requestHeaderLines;
}
@Override
public Socket openConnection(String hostname, int port, int connectTimeout,int readTimeout) throws IOException {
sock = new Socket();
// Ensure the proxy socket bypasses the VPN to prevent routing loops.
SocketProtector.protect(sock);
InetAddress addr = TransportManager.createInetAddress(proxyHost);
sock.connect(new InetSocketAddress(addr, proxyPort), connectTimeout);
sock.setSoTimeout(readTimeout);
/* OK, now tell the proxy where we actually want to connect to */
StringBuffer sb = new StringBuffer();
sb.append("CONNECT ");
sb.append(hostname);
sb.append(':');
sb.append(port);
sb.append(" HTTP/1.0\r\n");
if ((proxyUser != null) && (proxyPass != null))
{
String credentials = proxyUser + ":" + proxyPass;
char[] encoded;
try {
encoded = Base64.encode(credentials.getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
encoded = Base64.encode(credentials.getBytes());
}
sb.append("Proxy-Authorization: Basic ");
sb.append(encoded);
sb.append("\r\n");
}
if (requestHeaderLines != null)
{
for (int i = 0; i < requestHeaderLines.length; i++)
{
if (requestHeaderLines[i] != null)
{
sb.append(requestHeaderLines[i]);
sb.append("\r\n");
}
}
}
sb.append("\r\n");
OutputStream out = sock.getOutputStream();
try {
out.write(sb.toString().getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
out.write(sb.toString().getBytes());
}
out.flush();
/* Now parse the HTTP response */
byte[] buffer = new byte[1024];
InputStream in = sock.getInputStream();
int len = ClientServerHello.readLineRN(in, buffer);
String httpReponse;
try {
httpReponse = new String(buffer, 0, len, "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
httpReponse = new String(buffer, 0, len);
}
if (!httpReponse.startsWith("HTTP/"))
throw new IOException("The proxy did not send back a valid HTTP response.");
/* "HTTP/1.X XYZ X" => 14 characters minimum */
if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' '))
throw new IOException("The proxy did not send back a valid HTTP response.");
int errorCode = 0;
try
{
errorCode = Integer.parseInt(httpReponse.substring(9, 12));
}
catch (NumberFormatException ignore)
{
throw new IOException("The proxy did not send back a valid HTTP response.");
}
if ((errorCode < 0) || (errorCode > 999))
throw new IOException("The proxy did not send back a valid HTTP response.");
if (errorCode != 200)
{
throw new HTTPProxyException(httpReponse.substring(13), errorCode);
}
/* OK, read until empty line */
while (true)
{
len = ClientServerHello.readLineRN(in, buffer);
if (len == 0)
break;
}
return sock;
}
@Override
public void close()
{
if (sock != null) {
try {
sock.close();
} catch(IOException e) {}
}
}
}
@@ -0,0 +1,29 @@
package com.trilead.ssh2;
import java.io.IOException;
/**
* May be thrown upon connect() if a HTTP proxy is being used.
*
* @see Connection#connect()
* @see Connection#setProxyData(ProxyData)
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: HTTPProxyException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
*/
public class HTTPProxyException extends IOException
{
private static final long serialVersionUID = 2241537397104426186L;
public final String httpResponse;
public final int httpErrorCode;
public HTTPProxyException(String httpResponse, int httpErrorCode)
{
super("HTTP Proxy Error (" + httpErrorCode + " " + httpResponse + ")");
this.httpResponse = httpResponse;
this.httpErrorCode = httpErrorCode;
}
}

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