Launch
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
.gradle/
|
||||||
|
.idea/
|
||||||
|
**/build/
|
||||||
|
**/.cxx/
|
||||||
|
local.properties
|
||||||
|
*.iml
|
||||||
|
captures/
|
||||||
|
.externalNativeBuild/
|
||||||
@@ -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>.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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'
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
# Public example: no custom obfuscation rules required.
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
org.gradle.configuration-cache=false
|
||||||
@@ -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
|
||||||
Vendored
BIN
Binary file not shown.
+6
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -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'
|
||||||
|
}
|
||||||
Vendored
+21
@@ -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.** { *; }
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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→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 & 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 >= 1024 and <= 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 >= 1024 and <= 8192. Furthermore,
|
||||||
|
* min_group_len <= pref_group_len <= 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
Reference in New Issue
Block a user