mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-25 09:37:37 +00:00
Initial checkin.
This commit is contained in:
commit
cb0e328709
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
MANIFEST
|
||||
/build
|
||||
/dist
|
||||
/tmp
|
||||
/doc
|
||||
*.py[cd]
|
||||
*.swp
|
||||
*.swo
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://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 <http://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
|
||||
<http://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
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
5
MANIFEST.in
Normal file
5
MANIFEST.in
Normal file
@ -0,0 +1,5 @@
|
||||
include LICENSE
|
||||
recursive-include doc *
|
||||
recursive-include test *
|
||||
recursive-include libmproxy/resources *
|
||||
recursive-exclude test *.swo *.swp *.pyc
|
38
README
Normal file
38
README
Normal file
@ -0,0 +1,38 @@
|
||||
mitmproxy is an interactive SSL-capable intercepting HTTP proxy. It lets you to
|
||||
observe, modify and replay requests and responses on the fly. The underlying
|
||||
library that mitmproxy is built on can also be used to do these things
|
||||
programmatically.
|
||||
|
||||
By default, mitmproxy starts up with a mutt-like interactive curses interface -
|
||||
the help page (which you can view by pressing "?") should tell you everything
|
||||
you need to know. Note that requests and responses are stored in-memory until
|
||||
you delete them, so leaving mitmproxy running indefinitely or requesting very
|
||||
large amounts of data through it is a bad idea.
|
||||
|
||||
mitmproxy intercepts SSL requests by simply assuming that all CONNECT requests
|
||||
are https. The connection from the browser is wrapped in SSL, and we read the
|
||||
request by pretending to be the connecting server. We then open an SSL request
|
||||
to the destination server, and replay the request.
|
||||
|
||||
Releases can be found here: http://corte.si/software
|
||||
|
||||
Source is hosted here: http://github.com/cortesi/mitmproxy
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* The curses interface relies on a current version of the
|
||||
[urwid](http://excess.org/urwid/) library.
|
||||
* The test suite uses the [pry](http://github.com/cortesi/pry) unit testing
|
||||
library.
|
||||
|
||||
You should also make sure that your console environment is set up with the
|
||||
following:
|
||||
|
||||
* EDITOR environment variable to determine the external editor.
|
||||
* PAGER environment variable to determine the external pager.
|
||||
* Appropriate entries in your mailcap files to determine external
|
||||
viewers for request and response contents.
|
||||
|
||||
|
8
doc-src/01-reset-fonts-grids-base.css
Normal file
8
doc-src/01-reset-fonts-grids-base.css
Normal file
File diff suppressed because one or more lines are too long
95
doc-src/02-docstyle.css
Normal file
95
doc-src/02-docstyle.css
Normal file
@ -0,0 +1,95 @@
|
||||
body {
|
||||
-x-system-font:none;
|
||||
font-family: Helvetica,Arial,Tahoma,Verdana,Sans-Serif;
|
||||
color: #555555;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3F8ED8;
|
||||
}
|
||||
|
||||
#hd {
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
#hd h1 {
|
||||
letter-spacing: 3px;
|
||||
font-size: 2.5em;
|
||||
line-height: 100%;
|
||||
margin: 0.3em 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#bd {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#bd h1 {
|
||||
font-size: 1.6em;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#bd h2 {
|
||||
font-size: 1.2em;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#ft {
|
||||
color: #aaa;
|
||||
border-top: 1px solid #aaa;
|
||||
clear: both;
|
||||
margin: 0 0 2em 0;
|
||||
font-size: 0.8em;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.pageindex {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.pageindex ul {
|
||||
list-style-image:none;
|
||||
list-style-position:outside;
|
||||
list-style-type:none;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.pageindex li {
|
||||
list-style-image:none;
|
||||
list-style-position:outside;
|
||||
list-style-type:none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pageindex li.active {
|
||||
padding-left: 4px;
|
||||
border-left: 5px solid #ff0000;
|
||||
}
|
||||
|
||||
.pageindex li.inactive{
|
||||
border-left: none;
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
.pageindex li li a {
|
||||
display: block;
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.pageindex ul ul {
|
||||
margin-left: 20px;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
|
||||
.faq .question {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
16
doc-src/_layout.html
Normal file
16
doc-src/_layout.html
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="yui-t2" id="doc3">
|
||||
<div style="" id="hd">
|
||||
$!head!$
|
||||
</div>
|
||||
<div id="bd">
|
||||
<div id="yui-main">
|
||||
<div style="" class="yui-b">$!body!$</div>
|
||||
</div>
|
||||
<div style="" class="yui-b">
|
||||
<div>@!sidebar!@</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="" id="ft">
|
||||
<p>@!copyright!@</p>
|
||||
</div>
|
||||
</div>
|
14
doc-src/admin.html
Normal file
14
doc-src/admin.html
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
<h2>Contact</h2>
|
||||
|
||||
<p> Please send any comments, suggestions and bug reports to
|
||||
<a href="mailto:$!docMaintainerEmail!$">$!docMaintainerEmail!$</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<h2>License</h2>
|
||||
|
||||
<pre>
|
||||
@!license!@
|
||||
</pre>
|
||||
|
17
doc-src/faq.html
Normal file
17
doc-src/faq.html
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
<div class="faq">
|
||||
|
||||
<p class="question">On some sites I see a lot of "Connection from.."
|
||||
entries that never complete.</p>
|
||||
|
||||
<p> This is probably because the page requests resources from SSL-protected
|
||||
domains. These requests are intercepted by mitmproxy, but because we're
|
||||
using a bogus certificate, the browser-side of the connection hangs. The
|
||||
browser doesn't prompt you to add a certificate trust exception for remote
|
||||
page components, only for the primary domain being visited. </p>
|
||||
|
||||
<p> To solve this, use something like FireBug to find out which page
|
||||
components are hanging. Visit the relevant domains using your browser, and
|
||||
add a certificate trust exception for each one. </p>
|
||||
|
||||
</div>
|
3
doc-src/index.html
Normal file
3
doc-src/index.html
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
@!index_contents!@
|
||||
|
26
doc-src/index.py
Normal file
26
doc-src/index.py
Normal file
@ -0,0 +1,26 @@
|
||||
import countershape
|
||||
from countershape import Page, Directory, PythonModule
|
||||
import countershape.grok
|
||||
|
||||
this.layout = countershape.Layout("_layout.html")
|
||||
this.markup = "markdown"
|
||||
ns.docTitle = "mitmproxy"
|
||||
ns.docMaintainer = "Aldo Cortesi"
|
||||
ns.docMaintainerEmail = "aldo@corte.si"
|
||||
ns.copyright = "Aldo Cortesi 2010"
|
||||
ns.head = countershape.template.Template(None, "<h1> @!docTitle!@ - @!this.title!@ </h1>")
|
||||
ns.sidebar = countershape.widgets.SiblingPageIndex(
|
||||
'/index.html',
|
||||
exclude=['countershape']
|
||||
)
|
||||
|
||||
ns.license = file("../LICENSE").read()
|
||||
ns.index_contents = file("../README").read()
|
||||
ns.example = file("../examples/stickycookies.py").read()
|
||||
|
||||
pages = [
|
||||
Page("index.html", "introduction"),
|
||||
Page("library.html", "library"),
|
||||
Page("faq.html", "faq"),
|
||||
Page("admin.html", "administrivia")
|
||||
]
|
15
doc-src/library.html
Normal file
15
doc-src/library.html
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
All of mitmproxy's basic functionality is exposed through the __libmproxy__
|
||||
library. The example below shows a simple implementation of the "sticky cookie"
|
||||
functionality included in the interactive mitmproxy program. Traffic is
|
||||
monitored for __cookie__ and __set-cookie__ headers, and requests are rewritten
|
||||
to include a previously seen cookie if they don't already have one. In effect,
|
||||
this lets you log in to a site using your browser, and then make subsequent
|
||||
requests using a tool like __curl__, which will then seem to be part of the
|
||||
authenticated session.
|
||||
|
||||
|
||||
<!--(block |pySyntax)-->
|
||||
$!example!$
|
||||
<!--(end)-->
|
||||
|
120
doc-src/syntax.css
Normal file
120
doc-src/syntax.css
Normal file
@ -0,0 +1,120 @@
|
||||
.highlight { background: #f8f8f8; }
|
||||
.highlight .c { color: #408080; font-style: italic } /* Comment */
|
||||
.highlight .err { border: 1px solid #FF0000 } /* Error */
|
||||
.highlight .k { color: #008000; font-weight: bold } /* Keyword */
|
||||
.highlight .o { color: #666666 } /* Operator */
|
||||
.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #BC7A00 } /* Comment.Preproc */
|
||||
.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */
|
||||
.highlight .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #FF0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.highlight .go { color: #808080 } /* Generic.Output */
|
||||
.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.highlight .gt { color: #0040D0 } /* Generic.Traceback */
|
||||
.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kp { color: #008000 } /* Keyword.Pseudo */
|
||||
.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #B00040 } /* Keyword.Type */
|
||||
.highlight .m { color: #666666 } /* Literal.Number */
|
||||
.highlight .s { color: #BA2121 } /* Literal.String */
|
||||
.highlight .na { color: #7D9029 } /* Name.Attribute */
|
||||
.highlight .nb { color: #008000 } /* Name.Builtin */
|
||||
.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #880000 } /* Name.Constant */
|
||||
.highlight .nd { color: #AA22FF } /* Name.Decorator */
|
||||
.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
|
||||
.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
|
||||
.highlight .nf { color: #0000FF } /* Name.Function */
|
||||
.highlight .nl { color: #A0A000 } /* Name.Label */
|
||||
.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
|
||||
.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
|
||||
.highlight .nv { color: #19177C } /* Name.Variable */
|
||||
.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mf { color: #666666 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #666666 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #666666 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #666666 } /* Literal.Number.Oct */
|
||||
.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #BA2121 } /* Literal.String.Char */
|
||||
.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
|
||||
.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #008000 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #19177C } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
|
||||
.highlight .vc { color: #19177C } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #19177C } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #19177C } /* Name.Variable.Instance */
|
||||
.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
|
||||
.grokdoc { background: #f8f8f8; }
|
||||
.grokdoc .c { color: #408080; font-style: italic } /* Comment */
|
||||
.grokdoc .err { border: 1px solid #FF0000 } /* Error */
|
||||
.grokdoc .k { color: #008000; font-weight: bold } /* Keyword */
|
||||
.grokdoc .o { color: #666666 } /* Operator */
|
||||
.grokdoc .cm { color: #408080; font-style: italic } /* Comment.Multiline */
|
||||
.grokdoc .cp { color: #BC7A00 } /* Comment.Preproc */
|
||||
.grokdoc .c1 { color: #408080; font-style: italic } /* Comment.Single */
|
||||
.grokdoc .cs { color: #408080; font-style: italic } /* Comment.Special */
|
||||
.grokdoc .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.grokdoc .ge { font-style: italic } /* Generic.Emph */
|
||||
.grokdoc .gr { color: #FF0000 } /* Generic.Error */
|
||||
.grokdoc .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.grokdoc .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.grokdoc .go { color: #808080 } /* Generic.Output */
|
||||
.grokdoc .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||
.grokdoc .gs { font-weight: bold } /* Generic.Strong */
|
||||
.grokdoc .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.grokdoc .gt { color: #0040D0 } /* Generic.Traceback */
|
||||
.grokdoc .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
|
||||
.grokdoc .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
|
||||
.grokdoc .kp { color: #008000 } /* Keyword.Pseudo */
|
||||
.grokdoc .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
|
||||
.grokdoc .kt { color: #B00040 } /* Keyword.Type */
|
||||
.grokdoc .m { color: #666666 } /* Literal.Number */
|
||||
.grokdoc .s { color: #BA2121 } /* Literal.String */
|
||||
.grokdoc .na { color: #7D9029 } /* Name.Attribute */
|
||||
.grokdoc .nb { color: #008000 } /* Name.Builtin */
|
||||
.grokdoc .nc { color: #0000FF; font-weight: bold } /* Name.Class */
|
||||
.grokdoc .no { color: #880000 } /* Name.Constant */
|
||||
.grokdoc .nd { color: #AA22FF } /* Name.Decorator */
|
||||
.grokdoc .ni { color: #999999; font-weight: bold } /* Name.Entity */
|
||||
.grokdoc .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
|
||||
.grokdoc .nf { color: #0000FF } /* Name.Function */
|
||||
.grokdoc .nl { color: #A0A000 } /* Name.Label */
|
||||
.grokdoc .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
|
||||
.grokdoc .nt { color: #008000; font-weight: bold } /* Name.Tag */
|
||||
.grokdoc .nv { color: #19177C } /* Name.Variable */
|
||||
.grokdoc .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
|
||||
.grokdoc .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.grokdoc .mf { color: #666666 } /* Literal.Number.Float */
|
||||
.grokdoc .mh { color: #666666 } /* Literal.Number.Hex */
|
||||
.grokdoc .mi { color: #666666 } /* Literal.Number.Integer */
|
||||
.grokdoc .mo { color: #666666 } /* Literal.Number.Oct */
|
||||
.grokdoc .sb { color: #BA2121 } /* Literal.String.Backtick */
|
||||
.grokdoc .sc { color: #BA2121 } /* Literal.String.Char */
|
||||
.grokdoc .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
|
||||
.grokdoc .s2 { color: #BA2121 } /* Literal.String.Double */
|
||||
.grokdoc .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
|
||||
.grokdoc .sh { color: #BA2121 } /* Literal.String.Heredoc */
|
||||
.grokdoc .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
|
||||
.grokdoc .sx { color: #008000 } /* Literal.String.Other */
|
||||
.grokdoc .sr { color: #BB6688 } /* Literal.String.Regex */
|
||||
.grokdoc .s1 { color: #BA2121 } /* Literal.String.Single */
|
||||
.grokdoc .ss { color: #19177C } /* Literal.String.Symbol */
|
||||
.grokdoc .bp { color: #008000 } /* Name.Builtin.Pseudo */
|
||||
.grokdoc .vc { color: #19177C } /* Name.Variable.Class */
|
||||
.grokdoc .vg { color: #19177C } /* Name.Variable.Global */
|
||||
.grokdoc .vi { color: #19177C } /* Name.Variable.Instance */
|
||||
.grokdoc .il { color: #666666 } /* Literal.Number.Integer.Long */
|
35
examples/stickycookies.py
Normal file
35
examples/stickycookies.py
Normal file
@ -0,0 +1,35 @@
|
||||
from libmproxy import controller, proxy
|
||||
|
||||
proxy.config = proxy.Config(
|
||||
"~/.mitmproxy/cert.pem"
|
||||
)
|
||||
|
||||
class StickyMaster(controller.Master):
|
||||
def __init__(self, server):
|
||||
controller.Master.__init__(self, server)
|
||||
self.stickyhosts = {}
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
return controller.Master.run(self)
|
||||
except KeyboardInterrupt:
|
||||
self.shutdown()
|
||||
|
||||
def handle_request(self, msg):
|
||||
hid = (msg.host, msg.port)
|
||||
if msg.headers.has_key("cookie"):
|
||||
self.stickyhosts[hid] = msg.headers["cookie"]
|
||||
elif hid in self.stickyhosts:
|
||||
msg.headers["cookie"] = self.stickyhosts[hid]
|
||||
msg.ack()
|
||||
|
||||
def handle_response(self, msg):
|
||||
hid = (msg.request.host, msg.request.port)
|
||||
if msg.headers.has_key("set-cookie"):
|
||||
self.stickyhosts[hid] = f.response.headers["set-cookie"]
|
||||
msg.ack()
|
||||
|
||||
|
||||
server = proxy.ProxyServer(8080)
|
||||
m = StickyMaster(server)
|
||||
m.run()
|
0
libmproxy/__init__.py
Normal file
0
libmproxy/__init__.py
Normal file
1065
libmproxy/console.py
Normal file
1065
libmproxy/console.py
Normal file
File diff suppressed because it is too large
Load Diff
119
libmproxy/controller.py
Normal file
119
libmproxy/controller.py
Normal file
@ -0,0 +1,119 @@
|
||||
|
||||
# Copyright (C) 2010 Aldo Cortesi
|
||||
#
|
||||
# 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 sys
|
||||
import Queue, threading
|
||||
|
||||
#begin nocover
|
||||
|
||||
class Msg:
|
||||
def __init__(self):
|
||||
self.q = Queue.Queue()
|
||||
self.acked = False
|
||||
|
||||
def ack(self, data=None):
|
||||
self.acked = True
|
||||
self.q.put(data or self)
|
||||
|
||||
def send(self, masterq):
|
||||
self.acked = False
|
||||
masterq.put(self)
|
||||
return self.q.get()
|
||||
|
||||
|
||||
class Slave(threading.Thread):
|
||||
def __init__(self, masterq, server):
|
||||
self.masterq, self.server = masterq, server
|
||||
self.server.set_mqueue(masterq)
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
self.server.serve_forever()
|
||||
|
||||
|
||||
class Master:
|
||||
def __init__(self, server):
|
||||
self.server = server
|
||||
self._shutdown = False
|
||||
self.masterq = None
|
||||
|
||||
def tick(self, q):
|
||||
try:
|
||||
# Small timeout to prevent pegging the CPU
|
||||
msg = q.get(timeout=0.01)
|
||||
self.handle(msg)
|
||||
except Queue.Empty:
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
q = Queue.Queue()
|
||||
self.masterq = q
|
||||
slave = Slave(q, self.server)
|
||||
slave.start()
|
||||
while not self._shutdown:
|
||||
self.tick(q)
|
||||
self.shutdown()
|
||||
|
||||
def handle(self, msg):
|
||||
c = "handle_" + msg.__class__.__name__.lower()
|
||||
m = getattr(self, c, None)
|
||||
if m:
|
||||
m(msg)
|
||||
else:
|
||||
msg.ack()
|
||||
|
||||
def shutdown(self):
|
||||
if not self._shutdown:
|
||||
self._shutdown = True
|
||||
self.server.shutdown()
|
||||
|
||||
|
||||
class DumpMaster(Master):
|
||||
"""
|
||||
A simple master that just dumps to screen.
|
||||
"""
|
||||
def __init__(self, server, verbosity):
|
||||
self.verbosity = verbosity
|
||||
Master.__init__(self, server)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
return Master.run(self)
|
||||
except KeyboardInterrupt:
|
||||
self.shutdown()
|
||||
|
||||
def handle_response(self, msg):
|
||||
if 0 < self.verbosity < 3:
|
||||
print >> sys.stderr, ">>",
|
||||
print >> sys.stderr, msg.request.short()
|
||||
if self.verbosity == 1:
|
||||
print >> sys.stderr, "<<",
|
||||
print >> sys.stderr, msg.short()
|
||||
elif self.verbosity == 2:
|
||||
print >> sys.stderr, "<<"
|
||||
for i in msg.assemble().splitlines():
|
||||
print >> sys.stderr, "\t", i
|
||||
print >> sys.stderr, "<<"
|
||||
elif self.verbosity == 3:
|
||||
print >> sys.stderr, ">>"
|
||||
for i in msg.request.assemble().splitlines():
|
||||
print >> sys.stderr, "\t", i
|
||||
print >> sys.stderr, ">>"
|
||||
print >> sys.stderr, "<<"
|
||||
for i in msg.assemble().splitlines():
|
||||
print >> sys.stderr, "\t", i
|
||||
print >> sys.stderr, "<<"
|
||||
msg.ack()
|
316
libmproxy/filt.py
Normal file
316
libmproxy/filt.py
Normal file
@ -0,0 +1,316 @@
|
||||
|
||||
# Copyright (C) 2010 Aldo Cortesi
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
The following operators are understood:
|
||||
|
||||
~q Request
|
||||
~s Response
|
||||
|
||||
Headers:
|
||||
|
||||
Patterns are matched against "name: value" strings. Field names are
|
||||
all-lowercase.
|
||||
|
||||
~h rex Header line in either request or response
|
||||
~hq rex Header in request
|
||||
~hs rex Header in response
|
||||
|
||||
~b rex Expression in the body of either request or response
|
||||
~bq rex Expression in the body of request
|
||||
~bq rex Expression in the body of response
|
||||
~t rex Shortcut for content-type header.
|
||||
|
||||
~u rex URL
|
||||
~c CODE Response code.
|
||||
rex Equivalent to ~u rex
|
||||
"""
|
||||
import re, sys
|
||||
import pyparsing as pp
|
||||
|
||||
|
||||
class _Token:
|
||||
def dump(self, indent=0, fp=sys.stdout):
|
||||
print >> fp, "\t"*indent, self.__class__.__name__,
|
||||
if hasattr(self, "expr"):
|
||||
print >> fp, "(%s)"%self.expr,
|
||||
print >> fp
|
||||
|
||||
|
||||
class _Action(_Token):
|
||||
@classmethod
|
||||
def make(klass, s, loc, toks):
|
||||
return klass(*toks[1:])
|
||||
|
||||
|
||||
class FReq(_Action):
|
||||
code = "q"
|
||||
help = "Match request"
|
||||
def __call__(self, conn):
|
||||
return not conn.is_response()
|
||||
|
||||
|
||||
class FResp(_Action):
|
||||
code = "s"
|
||||
help = "Match response"
|
||||
def __call__(self, conn):
|
||||
return conn.is_response()
|
||||
|
||||
|
||||
class _Rex(_Action):
|
||||
def __init__(self, expr):
|
||||
self.expr = expr
|
||||
self.re = re.compile(self.expr)
|
||||
|
||||
|
||||
def _check_content_type(expr, o):
|
||||
val = o.headers.get("content-type")
|
||||
if val and re.search(expr, val[0]):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class FContentType(_Rex):
|
||||
code = "t"
|
||||
help = "Content-type header"
|
||||
def __call__(self, o):
|
||||
if _check_content_type(self.expr, o):
|
||||
return True
|
||||
elif o.is_response() and _check_content_type(self.expr, o.request):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class FRequestContentType(_Rex):
|
||||
code = "tq"
|
||||
help = "Request Content-Type header"
|
||||
def __call__(self, o):
|
||||
if o.is_response():
|
||||
return _check_content_type(self.expr, o.request)
|
||||
else:
|
||||
return _check_content_type(self.expr, o)
|
||||
|
||||
|
||||
class FResponseContentType(_Rex):
|
||||
code = "ts"
|
||||
help = "Request Content-Type header"
|
||||
def __call__(self, o):
|
||||
if o.is_response():
|
||||
return _check_content_type(self.expr, o)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class FHead(_Rex):
|
||||
code = "h"
|
||||
help = "Header"
|
||||
def __call__(self, o):
|
||||
val = o.headers.match_re(self.expr)
|
||||
if not val and o.is_response():
|
||||
val = o.request.headers.match_re(self.expr)
|
||||
return val
|
||||
|
||||
|
||||
class FHeadRequest(_Rex):
|
||||
code = "hq"
|
||||
help = "Request header"
|
||||
def __call__(self, o):
|
||||
if o.is_response():
|
||||
h = o.request.headers
|
||||
else:
|
||||
h = o.headers
|
||||
return h.match_re(self.expr)
|
||||
|
||||
|
||||
class FHeadResponse(_Rex):
|
||||
code = "hs"
|
||||
help = "Response header"
|
||||
def __call__(self, o):
|
||||
if not o.is_response():
|
||||
return False
|
||||
return o.headers.match_re(self.expr)
|
||||
|
||||
|
||||
class FBod(_Rex):
|
||||
code = "b"
|
||||
help = "Body"
|
||||
def __call__(self, o):
|
||||
if o.content and re.search(self.expr, o.content):
|
||||
return True
|
||||
elif o.is_response() and o.request.content and re.search(self.expr, o.request.content):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class FBodRequest(_Rex):
|
||||
code = "bq"
|
||||
help = "Request body"
|
||||
def __call__(self, o):
|
||||
if o.is_response() and o.request.content and re.search(self.expr, o.request.content):
|
||||
return True
|
||||
elif not o.is_response() and o.content and re.search(self.expr, o.content):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class FBodResponse(_Rex):
|
||||
code = "bs"
|
||||
help = "Response body"
|
||||
def __call__(self, o):
|
||||
if not o.is_response():
|
||||
return False
|
||||
elif o.content and re.search(self.expr, o.content):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class FUrl(_Rex):
|
||||
code = "u"
|
||||
help = "URL"
|
||||
# FUrl is special, because it can be "naked".
|
||||
@classmethod
|
||||
def make(klass, s, loc, toks):
|
||||
if len(toks) > 1:
|
||||
toks = toks[1:]
|
||||
return klass(*toks)
|
||||
|
||||
def __call__(self, o):
|
||||
if o.is_response():
|
||||
c = o.request
|
||||
else:
|
||||
c = o
|
||||
return re.search(self.expr, c.url())
|
||||
|
||||
|
||||
class _Int(_Action):
|
||||
def __init__(self, num):
|
||||
self.num = int(num)
|
||||
|
||||
|
||||
class FCode(_Int):
|
||||
code = "c"
|
||||
help = "HTTP response code"
|
||||
def __call__(self, o):
|
||||
if o.is_response():
|
||||
return o.code == self.num
|
||||
return False
|
||||
|
||||
|
||||
class FAnd(_Token):
|
||||
def __init__(self, lst):
|
||||
self.lst = lst
|
||||
|
||||
def dump(self, indent=0, fp=sys.stdout):
|
||||
print >> fp, "\t"*indent, self.__class__.__name__
|
||||
for i in self.lst:
|
||||
i.dump(indent+1, fp)
|
||||
|
||||
def __call__(self, o):
|
||||
return all([i(o) for i in self.lst])
|
||||
|
||||
|
||||
class FOr(_Token):
|
||||
def __init__(self, lst):
|
||||
self.lst = lst
|
||||
|
||||
def dump(self, indent=0, fp=sys.stdout):
|
||||
print >> fp, "\t"*indent, self.__class__.__name__
|
||||
for i in self.lst:
|
||||
i.dump(indent+1, fp)
|
||||
|
||||
def __call__(self, o):
|
||||
return any([i(o) for i in self.lst])
|
||||
|
||||
|
||||
class FNot(_Token):
|
||||
def __init__(self, itm):
|
||||
self.itm = itm[0]
|
||||
|
||||
def dump(self, indent=0, fp=sys.stdout):
|
||||
print >> fp, "\t"*indent, self.__class__.__name__
|
||||
self.itm.dump(indent + 1, fp)
|
||||
|
||||
def __call__(self, o):
|
||||
return not self.itm(o)
|
||||
|
||||
filt_unary = [
|
||||
FReq,
|
||||
FResp
|
||||
]
|
||||
filt_rex = [
|
||||
FHeadRequest,
|
||||
FHeadResponse,
|
||||
FHead,
|
||||
FBodRequest,
|
||||
FBodResponse,
|
||||
FBod,
|
||||
FUrl,
|
||||
FRequestContentType,
|
||||
FResponseContentType,
|
||||
FContentType,
|
||||
]
|
||||
filt_int = [
|
||||
FCode
|
||||
]
|
||||
def _make():
|
||||
# Order is important - multi-char expressions need to come before narrow
|
||||
# ones.
|
||||
parts = []
|
||||
for klass in filt_unary:
|
||||
f = pp.Literal("~%s"%klass.code)
|
||||
f.setParseAction(klass.make)
|
||||
parts.append(f)
|
||||
|
||||
simplerex = "".join([c for c in pp.printables if c not in "()~'\""])
|
||||
rex = pp.Word(simplerex) |\
|
||||
pp.QuotedString("\"", escChar='\\') |\
|
||||
pp.QuotedString("'", escChar='\\')
|
||||
for klass in filt_rex:
|
||||
f = pp.Literal("~%s"%klass.code) + rex.copy()
|
||||
f.setParseAction(klass.make)
|
||||
parts.append(f)
|
||||
|
||||
for klass in filt_int:
|
||||
f = pp.Literal("~%s"%klass.code) + pp.Word(pp.nums)
|
||||
f.setParseAction(klass.make)
|
||||
parts.append(f)
|
||||
|
||||
# A naked rex is a URL rex:
|
||||
f = rex.copy()
|
||||
f.setParseAction(FUrl.make)
|
||||
parts.append(f)
|
||||
|
||||
atom = pp.MatchFirst(parts)
|
||||
expr = pp.operatorPrecedence(
|
||||
atom,
|
||||
[
|
||||
(pp.Literal("!").suppress(), 1, pp.opAssoc.RIGHT, lambda x: FNot(*x)),
|
||||
(pp.Literal("&").suppress(), 2, pp.opAssoc.LEFT, lambda x: FAnd(*x)),
|
||||
(pp.Literal("|").suppress(), 2, pp.opAssoc.LEFT, lambda x: FOr(*x)),
|
||||
]
|
||||
)
|
||||
expr = pp.OneOrMore(expr)
|
||||
return expr.setParseAction(lambda x: FAnd(x) if len(x) != 1 else x)
|
||||
bnf = _make()
|
||||
|
||||
|
||||
def parse(s):
|
||||
try:
|
||||
return bnf.parseString(s, parseAll=True)[0]
|
||||
except pp.ParseException:
|
||||
return None
|
||||
|
374
libmproxy/proxy.py
Normal file
374
libmproxy/proxy.py
Normal file
@ -0,0 +1,374 @@
|
||||
"""
|
||||
A simple proxy server implementation, which always reads all of a server
|
||||
response into memory, performs some transformation, and then writes it back
|
||||
to the client.
|
||||
|
||||
Development started from Neil Schemenauer's munchy.py
|
||||
"""
|
||||
import sys, os, time, string, socket, urlparse, re, select, copy
|
||||
import SocketServer, ssl
|
||||
import utils, controller
|
||||
|
||||
NAME = "mitmproxy"
|
||||
config = None
|
||||
|
||||
|
||||
class ProxyError(Exception):
|
||||
def __init__(self, code, msg):
|
||||
self.code, self.msg = code, msg
|
||||
|
||||
def __str__(self):
|
||||
return "ProxyError(%s, %s)"%(self.code, self.msg)
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, pemfile):
|
||||
self.pemfile = pemfile
|
||||
|
||||
|
||||
def try_del(dict, key):
|
||||
try:
|
||||
del dict[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def parse_url(url):
|
||||
"""
|
||||
Returns a (scheme, host, port, path) tuple, or None on error.
|
||||
"""
|
||||
scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
|
||||
if not scheme:
|
||||
return None
|
||||
if ':' in netloc:
|
||||
host, port = string.split(netloc, ':')
|
||||
port = int(port)
|
||||
else:
|
||||
host = netloc
|
||||
port = 80
|
||||
path = urlparse.urlunparse(('', '', path, params, query, fragment))
|
||||
if not path:
|
||||
path = "/"
|
||||
return scheme, host, port, path
|
||||
|
||||
|
||||
def parse_proxy_request(request):
|
||||
"""
|
||||
Parse a proxy request line. Return (method, scheme, host, port, path).
|
||||
Raise ProxyError on error.
|
||||
"""
|
||||
try:
|
||||
method, url, protocol = string.split(request)
|
||||
except ValueError:
|
||||
raise ProxyError(400, "Can't parse request")
|
||||
if method in ['GET', 'HEAD', 'POST']:
|
||||
if url.startswith("/"):
|
||||
scheme, port, host, path = None, None, None, url
|
||||
else:
|
||||
parts = parse_url(url)
|
||||
if not parts:
|
||||
raise ProxyError(400, "Invalid url: %s"%url)
|
||||
scheme, host, port, path = parts
|
||||
elif method == 'CONNECT':
|
||||
scheme = None
|
||||
path = None
|
||||
host, port = url.split(":")
|
||||
port = int(port)
|
||||
else:
|
||||
raise ProxyError(501, "Unknown request method: %s" % method)
|
||||
return method, scheme, host, port, path
|
||||
|
||||
|
||||
class Request(controller.Msg):
|
||||
FMT = '%s %s HTTP/1.0\r\n%s\r\n%s'
|
||||
def __init__(self, connection, host, port, scheme, method, path, headers, content):
|
||||
self.connection = connection
|
||||
self.host, self.port, self.scheme = host, port, scheme
|
||||
self.method, self.path, self.headers, self.content = method, path, headers, content
|
||||
self.kill = False
|
||||
controller.Msg.__init__(self)
|
||||
|
||||
def copy(self):
|
||||
c = copy.copy(self)
|
||||
c.headers = self.headers.copy()
|
||||
return c
|
||||
|
||||
def url(self):
|
||||
if (self.port, self.scheme) in [(80, "http"), (443, "https")]:
|
||||
host = self.host
|
||||
else:
|
||||
host = "%s:%s"%(self.host, self.port)
|
||||
return "%s://%s%s"%(self.scheme, host, self.path)
|
||||
|
||||
def set_url(self, url):
|
||||
parts = parse_url(url)
|
||||
if not parts:
|
||||
return False
|
||||
self.scheme, self.host, self.port, self.path = parts
|
||||
return True
|
||||
|
||||
def is_response(self):
|
||||
return False
|
||||
|
||||
def short(self):
|
||||
return "%s %s"%(self.method, self.url())
|
||||
|
||||
def assemble(self):
|
||||
"""
|
||||
Assembles the request for transmission to the server. We make some
|
||||
modifications to make sure interception works properly.
|
||||
"""
|
||||
headers = self.headers.copy()
|
||||
try_del(headers, 'accept-encoding')
|
||||
try_del(headers, 'proxy-connection')
|
||||
try_del(headers, 'keep-alive')
|
||||
try_del(headers, 'connection')
|
||||
headers["connection"] = ["close"]
|
||||
data = (self.method, self.path, str(headers), self.content)
|
||||
return self.FMT%data
|
||||
|
||||
|
||||
class Response(controller.Msg):
|
||||
FMT = '%s\r\n%s\r\n%s'
|
||||
def __init__(self, request, code, proto, msg, headers, content):
|
||||
self.request = request
|
||||
self.code, self.proto, self.msg = code, proto, msg
|
||||
self.headers, self.content = headers, content
|
||||
self.kill = False
|
||||
controller.Msg.__init__(self)
|
||||
|
||||
def copy(self):
|
||||
c = copy.copy(self)
|
||||
c.headers = self.headers.copy()
|
||||
return c
|
||||
|
||||
def is_response(self):
|
||||
return True
|
||||
|
||||
def short(self):
|
||||
return "%s %s"%(self.code, self.proto)
|
||||
|
||||
def assemble(self):
|
||||
"""
|
||||
Assembles the response for transmission to the client. We make some
|
||||
modifications to make sure interception works properly.
|
||||
"""
|
||||
headers = self.headers.copy()
|
||||
try_del(headers, 'accept-encoding')
|
||||
try_del(headers, 'proxy-connection')
|
||||
try_del(headers, 'connection')
|
||||
try_del(headers, 'keep-alive')
|
||||
headers["connection"] = ["close"]
|
||||
proto = "%s %s %s"%(self.proto, self.code, self.msg)
|
||||
data = (proto, str(headers), self.content)
|
||||
return self.FMT%data
|
||||
|
||||
|
||||
class BrowserConnection(controller.Msg):
|
||||
def __init__(self, address, port):
|
||||
self.address, self.port = address, port
|
||||
controller.Msg.__init__(self)
|
||||
|
||||
def copy(self):
|
||||
return copy.copy(self)
|
||||
|
||||
|
||||
class Error(controller.Msg):
|
||||
def __init__(self, connection, msg):
|
||||
self.connection, self.msg = connection, msg
|
||||
controller.Msg.__init__(self)
|
||||
|
||||
def copy(self):
|
||||
return copy.copy(self)
|
||||
|
||||
|
||||
class FileLike:
|
||||
def __init__(self, o):
|
||||
self.o = o
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.o, attr)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def readline(self):
|
||||
result = ''
|
||||
while True:
|
||||
ch = self.read(1)
|
||||
if not ch:
|
||||
break
|
||||
else:
|
||||
result += ch
|
||||
if ch == '\n':
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
class ServerConnection:
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
self.server, self.rfile, self.wfile = None, None, None
|
||||
self.connect()
|
||||
self.send_request()
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
addr = socket.gethostbyname(self.request.host)
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
if self.request.scheme == "https":
|
||||
server = ssl.wrap_socket(server)
|
||||
server.connect((addr, self.request.port))
|
||||
except socket.error, err:
|
||||
raise ProxyError(200, 'Error connecting to "%s": %s' % (self.request.host, err))
|
||||
self.server = server
|
||||
self.rfile, self.wfile = server.makefile('rb'), server.makefile('wb')
|
||||
|
||||
def send_request(self):
|
||||
try:
|
||||
self.wfile.write(self.request.assemble())
|
||||
self.wfile.flush()
|
||||
except socket.error, err:
|
||||
raise ProxyError(500, 'Error sending data to "%s": %s' % (request.host, err))
|
||||
|
||||
def read_response(self):
|
||||
proto = self.rfile.readline()
|
||||
parts = proto.strip().split(" ", 2)
|
||||
if not len(parts) == 3:
|
||||
raise ProxyError(200, "Invalid server response.")
|
||||
proto, code, msg = parts
|
||||
code = int(code)
|
||||
headers = utils.Headers()
|
||||
headers.read(self.rfile)
|
||||
if headers.has_key("content-length"):
|
||||
content = self.rfile.read(int(headers["content-length"][0]))
|
||||
else:
|
||||
content = self.rfile.read()
|
||||
return Response(self.request, code, proto, msg, headers, content)
|
||||
|
||||
def terminate(self):
|
||||
try:
|
||||
if not self.wfile.closed:
|
||||
self.wfile.flush()
|
||||
self.server.close()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
|
||||
class ProxyHandler(SocketServer.StreamRequestHandler):
|
||||
def __init__(self, request, client_address, server, q):
|
||||
self.mqueue = q
|
||||
SocketServer.StreamRequestHandler.__init__(self, request, client_address, server)
|
||||
|
||||
def handle(self):
|
||||
server = None
|
||||
bc = BrowserConnection(*self.client_address)
|
||||
bc.send(self.mqueue)
|
||||
try:
|
||||
request = self.read_request(bc)
|
||||
request = request.send(self.mqueue)
|
||||
if request.kill:
|
||||
self.finish()
|
||||
return
|
||||
server = ServerConnection(request)
|
||||
response = server.read_response()
|
||||
response = response.send(self.mqueue)
|
||||
if response.kill:
|
||||
server.terminate()
|
||||
self.finish()
|
||||
return
|
||||
self.send_response(response)
|
||||
except IOError:
|
||||
pass
|
||||
except ProxyError, e:
|
||||
err = Error(bc, e.msg)
|
||||
err.send(self.mqueue)
|
||||
self.send_error(e.code, e.msg)
|
||||
if server:
|
||||
server.terminate()
|
||||
self.finish()
|
||||
|
||||
def read_request(self, connection):
|
||||
request = self.rfile.readline()
|
||||
method, scheme, host, port, path = parse_proxy_request(request)
|
||||
if not host:
|
||||
raise ProxyError(200, 'Invalid request: %s'%request)
|
||||
if method == "CONNECT":
|
||||
# Discard additional headers sent to the proxy. Should I expose
|
||||
# these to users?
|
||||
while 1:
|
||||
d = self.rfile.readline()
|
||||
if not d.strip():
|
||||
break
|
||||
self.wfile.write('HTTP/1.1 200 Connection established\r\n')
|
||||
self.wfile.write('Proxy-agent: %s\r\n'%NAME)
|
||||
self.wfile.write('\r\n')
|
||||
self.wfile.flush()
|
||||
self.connection = ssl.wrap_socket(
|
||||
self.connection,
|
||||
certfile = config.pemfile,
|
||||
keyfile = config.pemfile,
|
||||
server_side = True,
|
||||
ssl_version = ssl.PROTOCOL_SSLv23,
|
||||
do_handshake_on_connect = False
|
||||
)
|
||||
self.rfile = FileLike(self.connection)
|
||||
self.wfile = FileLike(self.connection)
|
||||
method, _, _, _, path = parse_proxy_request(self.rfile.readline())
|
||||
scheme = "https"
|
||||
headers = utils.Headers()
|
||||
headers.read(self.rfile)
|
||||
if method == 'POST' and not headers.has_key('content-length'):
|
||||
raise ProxyError(400, "Missing Content-Length for POST method")
|
||||
if headers.has_key("content-length"):
|
||||
content = self.rfile.read(int(headers["content-length"][0]))
|
||||
else:
|
||||
content = ""
|
||||
return Request(connection, host, port, scheme, method, path, headers, content)
|
||||
|
||||
def send_response(self, response):
|
||||
self.wfile.write(response.assemble())
|
||||
self.wfile.flush()
|
||||
|
||||
def terminate(self, connection, wfile, rfile):
|
||||
try:
|
||||
if not getattr(wfile, "closed", False):
|
||||
wfile.flush()
|
||||
connection.close()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def finish(self):
|
||||
self.terminate(self.connection, self.wfile, self.rfile)
|
||||
|
||||
def send_error(self, code, body):
|
||||
import BaseHTTPServer
|
||||
response = BaseHTTPServer.BaseHTTPRequestHandler.responses[code][0]
|
||||
self.wfile.write("HTTP/1.0 %s %s\r\n" % (code, response))
|
||||
self.wfile.write("Server: %s\r\n"%NAME)
|
||||
self.wfile.write("Content-type: text/html\r\n")
|
||||
self.wfile.write("\r\n")
|
||||
self.wfile.write('<html><head>\n<title>%d %s</title>\n</head>\n'
|
||||
'<body>\n%s\n</body>\n</html>' % (code, response, body))
|
||||
self.wfile.flush()
|
||||
self.wfile.close()
|
||||
self.rfile.close()
|
||||
|
||||
|
||||
ServerBase = SocketServer.ThreadingTCPServer
|
||||
class ProxyServer(ServerBase):
|
||||
allow_reuse_address = True
|
||||
def __init__(self, port):
|
||||
self.port = port
|
||||
ServerBase.__init__(self, ('', port), ProxyHandler)
|
||||
self.masterq = None
|
||||
|
||||
def set_mqueue(self, q):
|
||||
self.masterq = q
|
||||
|
||||
def process_request(self, request, client_address):
|
||||
return ServerBase.process_request(self, request, client_address)
|
||||
|
||||
def finish_request(self, request, client_address):
|
||||
self.RequestHandlerClass(request, client_address, self, self.masterq)
|
||||
|
3707
libmproxy/pyparsing.py
Normal file
3707
libmproxy/pyparsing.py
Normal file
File diff suppressed because it is too large
Load Diff
11
libmproxy/resources/bogus_template
Normal file
11
libmproxy/resources/bogus_template
Normal file
@ -0,0 +1,11 @@
|
||||
[ req ]
|
||||
prompt = no
|
||||
distinguished_name = req_distinguished_name
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = NZ
|
||||
ST = none
|
||||
L = none
|
||||
O = none
|
||||
OU = none
|
||||
emailAddress = none
|
277
libmproxy/utils.py
Normal file
277
libmproxy/utils.py
Normal file
@ -0,0 +1,277 @@
|
||||
# Copyright (C) 2010 Aldo Cortesi
|
||||
#
|
||||
# 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 re, os, subprocess
|
||||
|
||||
def isBin(s):
|
||||
"""
|
||||
Does this string have any non-ASCII characters?
|
||||
"""
|
||||
for i in s:
|
||||
i = ord(i)
|
||||
if i < 9:
|
||||
return True
|
||||
elif i > 13 and i < 32:
|
||||
return True
|
||||
elif i > 126:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def cleanBin(s):
|
||||
parts = []
|
||||
for i in s:
|
||||
o = ord(i)
|
||||
if o > 31 and o < 127:
|
||||
parts.append(i)
|
||||
else:
|
||||
parts.append(".")
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def hexdump(s):
|
||||
"""
|
||||
Returns a set of typles:
|
||||
(offset, hex, str)
|
||||
"""
|
||||
parts = []
|
||||
for i in range(0, len(s), 16):
|
||||
o = "%.10x"%i
|
||||
part = s[i:i+16]
|
||||
x = " ".join(["%.2x"%ord(i) for i in part])
|
||||
if len(part) < 16:
|
||||
x += " "
|
||||
x += " ".join([" " for i in range(16-len(part))])
|
||||
parts.append(
|
||||
(o, x, cleanBin(part))
|
||||
)
|
||||
return parts
|
||||
|
||||
|
||||
def isStringLike(anobj):
|
||||
try:
|
||||
# Avoid succeeding expensively if anobj is large.
|
||||
anobj[:0]+''
|
||||
except:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
def isSequenceLike(anobj):
|
||||
"""
|
||||
Is anobj a non-string sequence type (list, tuple, iterator, or
|
||||
similar)? Crude, but mostly effective.
|
||||
"""
|
||||
if not hasattr(anobj, "next"):
|
||||
if isStringLike(anobj):
|
||||
return 0
|
||||
try:
|
||||
anobj[:0]
|
||||
except:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def _caseless(s):
|
||||
return s.lower()
|
||||
|
||||
|
||||
class MultiDict:
|
||||
"""
|
||||
Simple wrapper around a dictionary to make holding multiple objects per
|
||||
key easier.
|
||||
|
||||
Note that this class assumes that keys are strings.
|
||||
|
||||
Keys have no order, but the order in which values are added to a key is
|
||||
preserved.
|
||||
"""
|
||||
# This ridiculous bit of subterfuge is needed to prevent the class from
|
||||
# treating this as a bound method.
|
||||
_helper = (str,)
|
||||
def __init__(self):
|
||||
self._d = dict()
|
||||
|
||||
def copy(self):
|
||||
m = self.__class__()
|
||||
m._d = self._d.copy()
|
||||
return m
|
||||
|
||||
def clear(self):
|
||||
return self._d.clear()
|
||||
|
||||
def get(self, key, d=None):
|
||||
key = self._helper[0](key)
|
||||
return self._d.get(key, d)
|
||||
|
||||
def __eq__(self, other):
|
||||
return dict(self) == dict(other)
|
||||
|
||||
def __delitem__(self, key):
|
||||
self._d.__delitem__(key)
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = self._helper[0](key)
|
||||
return self._d.__getitem__(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if not isSequenceLike(value):
|
||||
raise ValueError, "Cannot insert non-sequence."
|
||||
key = self._helper[0](key)
|
||||
return self._d.__setitem__(key, value)
|
||||
|
||||
def has_key(self, key):
|
||||
key = self._helper[0](key)
|
||||
return self._d.has_key(key)
|
||||
|
||||
def keys(self):
|
||||
return self._d.keys()
|
||||
|
||||
def extend(self, key, value):
|
||||
if not self.has_key(key):
|
||||
self[key] = []
|
||||
self[key].extend(value)
|
||||
|
||||
def append(self, key, value):
|
||||
self.extend(key, [value])
|
||||
|
||||
def itemPairs(self):
|
||||
"""
|
||||
Yield all possible pairs of items.
|
||||
"""
|
||||
for i in self.keys():
|
||||
for j in self[i]:
|
||||
yield (i, j)
|
||||
|
||||
|
||||
class Headers(MultiDict):
|
||||
"""
|
||||
A dictionary-like class for keeping track of HTTP headers.
|
||||
|
||||
It is case insensitive, and __repr__ formats the headers correcty for
|
||||
output to the server.
|
||||
"""
|
||||
_helper = (_caseless,)
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns a string containing a formatted header string.
|
||||
"""
|
||||
headerElements = []
|
||||
for key in self.keys():
|
||||
for val in self[key]:
|
||||
headerElements.append(key + ": " + val)
|
||||
headerElements.append("")
|
||||
return "\r\n".join(headerElements)
|
||||
|
||||
def match_re(self, expr):
|
||||
"""
|
||||
Match the regular expression against each header (key, value) pair.
|
||||
"""
|
||||
for k, v in self.itemPairs():
|
||||
s = "%s: %s"%(k, v)
|
||||
if re.search(expr, s):
|
||||
return True
|
||||
return False
|
||||
|
||||
def read(self, fp):
|
||||
"""
|
||||
Read a set of headers from a file pointer. Stop once a blank line
|
||||
is reached.
|
||||
"""
|
||||
name = ''
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line or line == '\r\n' or line == '\n':
|
||||
break
|
||||
if line[0] in ' \t':
|
||||
# continued header
|
||||
self[name][-1] = self[name][-1] + '\r\n ' + line.strip()
|
||||
else:
|
||||
i = line.find(':')
|
||||
# We're being liberal in what we accept, here.
|
||||
if i > 0:
|
||||
name = line[:i]
|
||||
value = line[i+1:].strip()
|
||||
if self.has_key(name):
|
||||
# merge value
|
||||
self.append(name, value)
|
||||
else:
|
||||
self[name] = [value]
|
||||
|
||||
|
||||
def pretty_size(size):
|
||||
suffixes = [
|
||||
("B", 2**10),
|
||||
("kB", 2**20),
|
||||
("M", 2**30),
|
||||
]
|
||||
for suf, lim in suffixes:
|
||||
if size >= lim:
|
||||
continue
|
||||
else:
|
||||
x = round(size/float(lim/2**10), 2)
|
||||
if x == int(x):
|
||||
x = int(x)
|
||||
return str(x) + suf
|
||||
|
||||
|
||||
class Data:
|
||||
def __init__(self, name):
|
||||
m = __import__(name)
|
||||
dirname, _ = os.path.split(m.__file__)
|
||||
self.dirname = os.path.abspath(dirname)
|
||||
|
||||
def path(self, path):
|
||||
"""
|
||||
Returns a path to the package data housed at 'path' under this
|
||||
module.Path can be a path to a file, or to a directory.
|
||||
|
||||
This function will raise ValueError if the path does not exist.
|
||||
"""
|
||||
fullpath = os.path.join(self.dirname, path)
|
||||
if not os.path.exists(fullpath):
|
||||
raise ValueError, "dataPath: %s does not exist."%fullpath
|
||||
return fullpath
|
||||
data = Data(__name__)
|
||||
|
||||
|
||||
def make_bogus_cert(path):
|
||||
# Generates a bogus certificate like so:
|
||||
# openssl req -config template -x509 -nodes -days 9999 -newkey rsa:1024 \
|
||||
# -keyout cert.pem -out cert.pem
|
||||
|
||||
d = os.path.dirname(path)
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d)
|
||||
|
||||
cmd = [
|
||||
"openssl",
|
||||
"req",
|
||||
"-config", data.path("resources/bogus_template"),
|
||||
"-x509" ,
|
||||
"-nodes",
|
||||
"-days", "9999",
|
||||
"-newkey", "rsa:1024",
|
||||
"-keyout", path,
|
||||
"-out", path,
|
||||
]
|
||||
subprocess.call(
|
||||
cmd,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE
|
||||
)
|
||||
|
70
mitmproxy
Executable file
70
mitmproxy
Executable file
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010 Aldo Cortesi
|
||||
#
|
||||
# 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 sys, os.path
|
||||
from libmproxy import proxy, controller, console, utils
|
||||
from optparse import OptionParser, OptionGroup
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(
|
||||
usage = "%prog [options] output",
|
||||
version="%prog 0.1",
|
||||
)
|
||||
parser.add_option(
|
||||
"-d", "--dump", action="store_true",
|
||||
dest="dump", default=False,
|
||||
help = "Just dump data to screen."
|
||||
)
|
||||
parser.add_option(
|
||||
"-c", "--cert", action="store",
|
||||
type = "str", dest="cert", default="~/.mitmproxy/cert.pem",
|
||||
help = "SSL certificate file."
|
||||
)
|
||||
parser.add_option(
|
||||
"-p", "--port", action="store",
|
||||
type = "int", dest="port", default=8080,
|
||||
help = "Port."
|
||||
)
|
||||
parser.add_option("-q", "--quiet",
|
||||
action="store_true", dest="quiet",
|
||||
help="Quiet.")
|
||||
parser.add_option("-v", "--verbose",
|
||||
action="count", dest="verbose", default=1,
|
||||
help="Increase verbosity. Can be passed multiple times.")
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if options.quiet:
|
||||
options.verbose = 0
|
||||
|
||||
certpath = os.path.expanduser(options.cert)
|
||||
|
||||
if not os.path.exists(certpath):
|
||||
print >> sys.stderr, "Creating bogus certificate at %s"%options.cert
|
||||
utils.make_bogus_cert(certpath)
|
||||
|
||||
proxy.config = proxy.Config(
|
||||
certpath
|
||||
)
|
||||
server = proxy.ProxyServer(options.port)
|
||||
if options.dump:
|
||||
m = controller.DumpMaster(server, options.verbose)
|
||||
else:
|
||||
m = console.ConsoleMaster(server, options.verbose)
|
||||
if options.verbose > 0:
|
||||
print >> sys.stderr, "Running on port %s"%options.port
|
||||
m.run()
|
97
setup.py
Normal file
97
setup.py
Normal file
@ -0,0 +1,97 @@
|
||||
from distutils.core import setup
|
||||
import fnmatch, os.path
|
||||
|
||||
def _fnmatch(name, patternList):
|
||||
for i in patternList:
|
||||
if fnmatch.fnmatch(name, i):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _splitAll(path):
|
||||
parts = []
|
||||
h = path
|
||||
while 1:
|
||||
if not h:
|
||||
break
|
||||
h, t = os.path.split(h)
|
||||
parts.append(t)
|
||||
parts.reverse()
|
||||
return parts
|
||||
|
||||
|
||||
def findPackages(path, dataExclude=[]):
|
||||
"""
|
||||
Recursively find all packages and data directories rooted at path. Note
|
||||
that only data _directories_ and their contents are returned -
|
||||
non-Python files at module scope are not, and should be manually
|
||||
included.
|
||||
|
||||
dataExclude is a list of fnmatch-compatible expressions for files and
|
||||
directories that should not be included in pakcage_data.
|
||||
|
||||
Returns a (packages, package_data) tuple, ready to be passed to the
|
||||
corresponding distutils.core.setup arguments.
|
||||
"""
|
||||
packages = []
|
||||
datadirs = []
|
||||
for root, dirs, files in os.walk(path, topdown=True):
|
||||
if "__init__.py" in files:
|
||||
p = _splitAll(root)
|
||||
packages.append(".".join(p))
|
||||
else:
|
||||
dirs[:] = []
|
||||
if packages:
|
||||
datadirs.append(root)
|
||||
|
||||
# Now we recurse into the data directories
|
||||
package_data = {}
|
||||
for i in datadirs:
|
||||
if not _fnmatch(i, dataExclude):
|
||||
parts = _splitAll(i)
|
||||
module = ".".join(parts[:-1])
|
||||
acc = package_data.get(module, [])
|
||||
for root, dirs, files in os.walk(i, topdown=True):
|
||||
sub = os.path.join(*_splitAll(root)[1:])
|
||||
if not _fnmatch(sub, dataExclude):
|
||||
for fname in files:
|
||||
path = os.path.join(sub, fname)
|
||||
if not _fnmatch(path, dataExclude):
|
||||
acc.append(path)
|
||||
else:
|
||||
dirs[:] = []
|
||||
package_data[module] = acc
|
||||
return packages, package_data
|
||||
|
||||
|
||||
|
||||
|
||||
long_description = """
|
||||
A man-in-the-middle intercepting proxy written in Python.
|
||||
|
||||
Features
|
||||
========
|
||||
|
||||
* Intercept HTTP and HTTPS traffic.
|
||||
* Modify, manipulate and replay requests and responses on the fly.
|
||||
"""
|
||||
packages, package_data = findPackages("libmproxy")
|
||||
print packages, package_data
|
||||
version = "0.1"
|
||||
setup(
|
||||
name = "mitmproxy",
|
||||
version = version,
|
||||
description = "An interactive intercepting proxy server.",
|
||||
long_description = long_description,
|
||||
author = "Aldo Cortesi",
|
||||
author_email = "aldo@corte.si",
|
||||
url = "http://corte.si/software/mitmproxy",
|
||||
packages = packages,
|
||||
package_data = package_data,
|
||||
scripts = ["mitmproxy"],
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Programming Language :: Python",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
)
|
5
test/.pry
Normal file
5
test/.pry
Normal file
@ -0,0 +1,5 @@
|
||||
base = ..
|
||||
coverage = ../libmproxy
|
||||
exclude = ../libmproxy/pyparsing.py
|
||||
.
|
||||
|
32
test/data/serverkey.pem
Normal file
32
test/data/serverkey.pem
Normal file
@ -0,0 +1,32 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQC+N+9bv1YC0GKbGdv2wMuuWTGSNwE/Hq5IIxYN1eITsvbD1GgB
|
||||
69x++XJd6KTIthnta0KCpCAtbaYbCkhUfxCVv2bP+iQt2AjwMOZlgRZ+RGJ25dBu
|
||||
AjAxQmqDJcAdS6MoRHWziomnUNfNogVrfqjpvJor+1iRnrj2q00ab9WYCwIDAQAB
|
||||
AoGBAIM7V9l2UcKzPbQ/zO+Z52urgXWcmTGQ2zBNdIOrEcQBbhmAyxi4PnEja3G6
|
||||
dSU77PtNSp+S19g/k5+IIoqY9zkGigdaPhRVRKJgBTAzFzMz+WHpQIffDojFKCnL
|
||||
gyDnzMRJY8+cnsCqbHRY4hqFiCr8Rq9sCdlynAytdtrnxzqhAkEA9bha6MO+L0JA
|
||||
6IEEbVY1vtaUO9Xg5DUDjRxQcfniSJACb/2IvF0tvxAnG7I/S8AavCXqtlDPtYkI
|
||||
WOxY5Sd62QJBAMYtKUxGka4XxwCyBK8EUNaN8m9C++mpjoHD1kFri9B1bXm91nCO
|
||||
iGWqtqdarwyEc/pAHw5UGzVyBXticPIcs4MCQQCcPvsHsZhYoq91aLyw7bXFQNsH
|
||||
ZUvYsOEuNIfuwa+i5ne2UKhG5pU1PgcwNFrNRz140D98aMx7KcS2DqvEIyOZAkBF
|
||||
6Yi4L+0Uza6WwDaGx679AfaU6byVIgv0G3JqgdZBJCwK1r3f12im9SKax5MZh2Ci
|
||||
2Bwcoe83W5IzhPbzcsyhAkBo8O2U2vig5PQWQ0BUKJrCGHLq//D/ttdLVtmc6eWc
|
||||
zqssCF3Unkk3bOq35swSKeAx8WotPPVsALWr87N2hCB+
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICsDCCAhmgAwIBAgIJANwogM9sqMHLMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTAwMTMxMDEzOTEzWhcNMTEwMTMxMDEzOTEzWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
|
||||
gQC+N+9bv1YC0GKbGdv2wMuuWTGSNwE/Hq5IIxYN1eITsvbD1GgB69x++XJd6KTI
|
||||
thnta0KCpCAtbaYbCkhUfxCVv2bP+iQt2AjwMOZlgRZ+RGJ25dBuAjAxQmqDJcAd
|
||||
S6MoRHWziomnUNfNogVrfqjpvJor+1iRnrj2q00ab9WYCwIDAQABo4GnMIGkMB0G
|
||||
A1UdDgQWBBTTnBZyw7ZZsb8+/6gvZFIHhVgtDzB1BgNVHSMEbjBsgBTTnBZyw7ZZ
|
||||
sb8+/6gvZFIHhVgtD6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
|
||||
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJANwogM9s
|
||||
qMHLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEApz428aOar0EBuAib
|
||||
I+liefRlK4I3MQQxq3tOeB1dgAIo0ivKtdVJGi1kPg8EO0KMvFfn6IRtssUmFgCp
|
||||
JBD+HoDzFxwI1bLMVni+g7OzaNSwL3nQ94lZUdpWMYDxqY4bLUv3goX1TlN9lmpG
|
||||
8FiBLYUC0RNTCCRDFGfDr/wUT/M=
|
||||
-----END CERTIFICATE-----
|
32
test/data/testkey.pem
Normal file
32
test/data/testkey.pem
Normal file
@ -0,0 +1,32 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQC+6rG6A/BGD0dI+mh2FZIqQZn82z/pGs4f3pyxbHb+ROxjjQOr
|
||||
fDCw2jc11XDxK7CXpDQAnkO6au/sQ5t50vSZ+PGhFD+t558VV2ausB5OYZsR7RRx
|
||||
gl1jsxWdde3EHGjxSK+aXRgFpVrZzPLSy6dl8tMoqUMWIBi0u1WTbmyYjwIDAQAB
|
||||
AoGBAKyqhmK9/Sjf2JDgKGnjyHX/Ls3JXVvtqk6Yfw7YEiaVH1ZJyu/lOgQ414YQ
|
||||
rDzyTpxXHdERUh/fZ24/FvZvHFgy5gWEQjQPpprIxvqCLKJhX73L2+TnXmfYDApb
|
||||
J7V/JfnTeOaK9LTpHsofB98A1s9DWX/ccOgKTtZIYMjYpdoBAkEA9hLvtixbO2A2
|
||||
ZgDcA9ftVX2WwdpRH+mYXl1G60Fem5nlO3Rl3FDoafRvSQNZiqyOlObvKbbYh/S2
|
||||
L7ihEMMNYQJBAMaeLnAc9jO/z4ApTqSBGUpM9b7ul16aSgq56saUI0VULIZcXeo3
|
||||
3BwdL2fEOOnzjNy6NpH2BW63h/+2t7lV++8CQQDK+S+1Sr0uKtx0Iv1YRkHEJMW3
|
||||
vQbxldNS8wnOf6s0GisVcZubsTkkPLWWuiaf1ln9xMc9106gRmAI2PgyRVHBAkA6
|
||||
iI+C9uYP5i1Oxd2pWWqMnRWnSUVO2gWMF7J7B1lFq0Lb7gi3Z/L0Th2UZR2oxN/0
|
||||
hORkK676LBhmYgDPG+n9AkAJOnPIFQVAEBAO9bAxFrje8z6GRt332IlgxuiTeDE3
|
||||
EAlH9tmZma4Tri4sWnhJwCsxl+5hWamI8NL4EIeXRvPw
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICsDCCAhmgAwIBAgIJAI7G7a/d5YwEMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTAwMjAyMDM0MTExWhcNMTEwMjAyMDM0MTExWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
|
||||
gQC+6rG6A/BGD0dI+mh2FZIqQZn82z/pGs4f3pyxbHb+ROxjjQOrfDCw2jc11XDx
|
||||
K7CXpDQAnkO6au/sQ5t50vSZ+PGhFD+t558VV2ausB5OYZsR7RRxgl1jsxWdde3E
|
||||
HGjxSK+aXRgFpVrZzPLSy6dl8tMoqUMWIBi0u1WTbmyYjwIDAQABo4GnMIGkMB0G
|
||||
A1UdDgQWBBS+MFJTsriCPNYsj8/4f+PympPEkzB1BgNVHSMEbjBsgBS+MFJTsriC
|
||||
PNYsj8/4f+PympPEk6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
|
||||
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAI7G7a/d
|
||||
5YwEMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAlpan/QX2fpXVRihV
|
||||
lQic2DktF4xd5unrZnFC8X8ScNX1ClU+AO79ejaobt4YGjeVYs0iQQsUL2E0G43c
|
||||
mOXfsq1b970Ep6xRS76EmZ+tTdFBd86tFTIhZJrOi67gs+twj5V2elyp3tQpg2ze
|
||||
G/jwDQS8V1X9CbfqBQriL7x5Tk4=
|
||||
-----END CERTIFICATE-----
|
25
test/handler.py
Normal file
25
test/handler.py
Normal file
@ -0,0 +1,25 @@
|
||||
import socket
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
|
||||
|
||||
class TestRequestHandler(BaseHTTPRequestHandler):
|
||||
default_request_version = "HTTP/1.1"
|
||||
def setup(self):
|
||||
self.connection = self.request
|
||||
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
|
||||
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
|
||||
|
||||
def log_message(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
data = "data: %s\npath: %s\n"%(self.headers, self.path)
|
||||
self.send_response(200)
|
||||
self.send_header("proxtest", "testing")
|
||||
self.send_header("Content-type", "text-html")
|
||||
self.send_header("Content-length", len(data))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
|
||||
|
10
test/serv.py
Normal file
10
test/serv.py
Normal file
@ -0,0 +1,10 @@
|
||||
import socket, os, cStringIO, tempfile
|
||||
from SocketServer import BaseServer
|
||||
from BaseHTTPServer import HTTPServer
|
||||
import handler
|
||||
|
||||
def make(port):
|
||||
server_address = ('', port)
|
||||
return HTTPServer(server_address, handler.TestRequestHandler)
|
||||
|
||||
|
22
test/sslserv.py
Normal file
22
test/sslserv.py
Normal file
@ -0,0 +1,22 @@
|
||||
import socket, os, cStringIO, tempfile
|
||||
from SocketServer import BaseServer
|
||||
from BaseHTTPServer import HTTPServer
|
||||
import ssl
|
||||
import handler
|
||||
|
||||
|
||||
class SecureHTTPServer(HTTPServer):
|
||||
def __init__(self, server_address, HandlerClass):
|
||||
BaseServer.__init__(self, server_address, HandlerClass)
|
||||
self.socket = ssl.wrap_socket(
|
||||
socket.socket(self.address_family, self.socket_type),
|
||||
keyfile = "data/serverkey.pem",
|
||||
certfile = "data/serverkey.pem"
|
||||
)
|
||||
self.server_bind()
|
||||
self.server_activate()
|
||||
|
||||
|
||||
def make(port):
|
||||
server_address = ('', port)
|
||||
return SecureHTTPServer(server_address, handler.TestRequestHandler)
|
269
test/test_console.py
Normal file
269
test/test_console.py
Normal file
@ -0,0 +1,269 @@
|
||||
from libmproxy import console, proxy, utils, filt
|
||||
import libpry
|
||||
|
||||
def treq(conn=None):
|
||||
if not conn:
|
||||
conn = proxy.BrowserConnection("address", 22)
|
||||
headers = utils.Headers()
|
||||
headers["header"] = ["qvalue"]
|
||||
return proxy.Request(conn, "host", 80, "http", "GET", "/path", headers, "content")
|
||||
|
||||
|
||||
def tresp(req=None):
|
||||
if not req:
|
||||
req = treq()
|
||||
headers = utils.Headers()
|
||||
headers["header_response"] = ["svalue"]
|
||||
return proxy.Response(req, 200, "HTTP/1.1", "message", headers, "content_response")
|
||||
|
||||
|
||||
def tflow():
|
||||
bc = proxy.BrowserConnection("address", 22)
|
||||
return console.Flow(bc)
|
||||
|
||||
|
||||
class uState(libpry.AutoTree):
|
||||
def test_backup(self):
|
||||
bc = proxy.BrowserConnection("address", 22)
|
||||
c = console.State()
|
||||
f = console.Flow(bc)
|
||||
c.add_browserconnect(f)
|
||||
|
||||
f.backup()
|
||||
c.revert(f)
|
||||
|
||||
def test_flow(self):
|
||||
"""
|
||||
normal flow:
|
||||
|
||||
connect -> request -> response
|
||||
"""
|
||||
bc = proxy.BrowserConnection("address", 22)
|
||||
c = console.State()
|
||||
f = console.Flow(bc)
|
||||
c.add_browserconnect(f)
|
||||
assert c.lookup(bc)
|
||||
assert c.get_focus() == (f, 0)
|
||||
|
||||
req = treq(bc)
|
||||
assert c.add_request(req)
|
||||
assert len(c.flow_list) == 1
|
||||
assert c.lookup(req)
|
||||
|
||||
newreq = treq()
|
||||
assert not c.add_request(newreq)
|
||||
assert not c.lookup(newreq)
|
||||
|
||||
resp = tresp(req)
|
||||
assert c.add_response(resp)
|
||||
assert len(c.flow_list) == 1
|
||||
assert f.waiting == False
|
||||
assert c.lookup(resp)
|
||||
|
||||
newresp = tresp()
|
||||
assert not c.add_response(newresp)
|
||||
assert not c.lookup(newresp)
|
||||
|
||||
def test_err(self):
|
||||
bc = proxy.BrowserConnection("address", 22)
|
||||
c = console.State()
|
||||
f = console.Flow(bc)
|
||||
c.add_browserconnect(f)
|
||||
e = proxy.Error(bc, "message")
|
||||
assert c.add_error(e)
|
||||
|
||||
e = proxy.Error(proxy.BrowserConnection("address", 22), "message")
|
||||
assert not c.add_error(e)
|
||||
|
||||
def test_view(self):
|
||||
c = console.State()
|
||||
|
||||
f = tflow()
|
||||
c.add_browserconnect(f)
|
||||
assert len(c.view) == 1
|
||||
c.set_limit(filt.parse("~q"))
|
||||
assert len(c.view) == 0
|
||||
c.set_limit(None)
|
||||
|
||||
|
||||
f = tflow()
|
||||
req = treq(f.connection)
|
||||
c.add_browserconnect(f)
|
||||
c.add_request(req)
|
||||
assert len(c.view) == 2
|
||||
c.set_limit(filt.parse("~q"))
|
||||
assert len(c.view) == 1
|
||||
c.set_limit(filt.parse("~s"))
|
||||
assert len(c.view) == 0
|
||||
|
||||
def test_focus(self):
|
||||
"""
|
||||
normal flow:
|
||||
|
||||
connect -> request -> response
|
||||
"""
|
||||
c = console.State()
|
||||
|
||||
bc = proxy.BrowserConnection("address", 22)
|
||||
f = console.Flow(bc)
|
||||
c.add_browserconnect(f)
|
||||
assert c.get_focus() == (f, 0)
|
||||
assert c.get_from_pos(0) == (f, 0)
|
||||
assert c.get_from_pos(1) == (None, None)
|
||||
assert c.get_next(0) == (None, None)
|
||||
|
||||
bc2 = proxy.BrowserConnection("address", 22)
|
||||
f2 = console.Flow(bc2)
|
||||
c.add_browserconnect(f2)
|
||||
assert c.get_focus() == (f, 1)
|
||||
assert c.get_next(0) == (f, 1)
|
||||
assert c.get_prev(1) == (f2, 0)
|
||||
assert c.get_next(1) == (None, None)
|
||||
|
||||
c.set_focus(0)
|
||||
assert c.get_focus() == (f2, 0)
|
||||
c.set_focus(-1)
|
||||
assert c.get_focus() == (f2, 0)
|
||||
|
||||
c.delete_flow(f2)
|
||||
assert c.get_focus() == (f, 0)
|
||||
c.delete_flow(f)
|
||||
assert c.get_focus() == (None, None)
|
||||
|
||||
def _add_request(self, state):
|
||||
f = tflow()
|
||||
state.add_browserconnect(f)
|
||||
q = treq(f.connection)
|
||||
state.add_request(q)
|
||||
return f
|
||||
|
||||
def _add_response(self, state):
|
||||
f = self._add_request(state)
|
||||
r = tresp(f.request)
|
||||
state.add_response(r)
|
||||
|
||||
def test_focus_view(self):
|
||||
c = console.State()
|
||||
self._add_request(c)
|
||||
self._add_response(c)
|
||||
self._add_request(c)
|
||||
self._add_response(c)
|
||||
self._add_request(c)
|
||||
self._add_response(c)
|
||||
c.set_limit(filt.parse("~q"))
|
||||
assert len(c.view) == 3
|
||||
assert c.focus == 2
|
||||
|
||||
def test_delete_last(self):
|
||||
c = console.State()
|
||||
f1 = tflow()
|
||||
f2 = tflow()
|
||||
c.add_browserconnect(f1)
|
||||
c.add_browserconnect(f2)
|
||||
c.set_focus(1)
|
||||
c.delete_flow(f1)
|
||||
assert c.focus == 0
|
||||
|
||||
def test_kill_flow(self):
|
||||
c = console.State()
|
||||
f = tflow()
|
||||
c.add_browserconnect(f)
|
||||
c.kill_flow(f)
|
||||
assert not c.flow_list
|
||||
|
||||
def test_clear(self):
|
||||
c = console.State()
|
||||
f = tflow()
|
||||
c.add_browserconnect(f)
|
||||
f.intercepting = True
|
||||
|
||||
c.clear()
|
||||
assert len(c.flow_list) == 1
|
||||
f.intercepting = False
|
||||
c.clear()
|
||||
assert len(c.flow_list) == 0
|
||||
|
||||
|
||||
class uFlow(libpry.AutoTree):
|
||||
def test_match(self):
|
||||
f = tflow()
|
||||
f.response = tresp()
|
||||
f.request = f.response.request
|
||||
assert not f.match(filt.parse("~b test"))
|
||||
|
||||
def test_backup(self):
|
||||
f = tflow()
|
||||
f.backup()
|
||||
f.revert()
|
||||
|
||||
def test_simple(self):
|
||||
f = tflow()
|
||||
assert f.get_text()
|
||||
|
||||
f.request = treq()
|
||||
assert f.get_text()
|
||||
|
||||
f.response = tresp()
|
||||
f.response.headers["content-type"] = ["text/html"]
|
||||
assert f.get_text()
|
||||
f.response.code = 404
|
||||
assert f.get_text()
|
||||
|
||||
f.focus = True
|
||||
assert f.get_text()
|
||||
|
||||
f.connection = console.ReplayConnection()
|
||||
assert f.get_text()
|
||||
|
||||
f.response = None
|
||||
assert f.get_text()
|
||||
|
||||
f.error = proxy.Error(200, "test")
|
||||
assert f.get_text()
|
||||
|
||||
def test_kill(self):
|
||||
f = tflow()
|
||||
f.request = treq()
|
||||
f.intercept()
|
||||
assert not f.request.acked
|
||||
f.kill()
|
||||
assert f.request.acked
|
||||
f.intercept()
|
||||
f.response = tresp()
|
||||
f.request = f.response.request
|
||||
f.request.ack()
|
||||
assert not f.response.acked
|
||||
f.kill()
|
||||
assert f.response.acked
|
||||
|
||||
def test_accept_intercept(self):
|
||||
f = tflow()
|
||||
f.request = treq()
|
||||
f.intercept()
|
||||
assert not f.request.acked
|
||||
f.accept_intercept()
|
||||
assert f.request.acked
|
||||
f.response = tresp()
|
||||
f.request = f.response.request
|
||||
f.intercept()
|
||||
f.request.ack()
|
||||
assert not f.response.acked
|
||||
f.accept_intercept()
|
||||
assert f.response.acked
|
||||
|
||||
|
||||
class uformat_keyvals(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
assert console.format_keyvals(
|
||||
[
|
||||
("aa", "bb"),
|
||||
("cc", "dd"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
tests = [
|
||||
uFlow(),
|
||||
uformat_keyvals(),
|
||||
uState()
|
||||
]
|
220
test/test_filt.py
Normal file
220
test/test_filt.py
Normal file
@ -0,0 +1,220 @@
|
||||
import cStringIO
|
||||
from libmproxy import filt, proxy, utils
|
||||
import libpry
|
||||
|
||||
|
||||
class uParsing(libpry.AutoTree):
|
||||
def _dump(self, x):
|
||||
c = cStringIO.StringIO()
|
||||
x.dump(fp=c)
|
||||
assert c.getvalue()
|
||||
|
||||
def test_simple(self):
|
||||
assert not filt.parse("~b")
|
||||
assert filt.parse("~q")
|
||||
assert filt.parse("~c 10")
|
||||
assert filt.parse("~u foobar")
|
||||
assert filt.parse("~q ~c 10")
|
||||
p = filt.parse("~q ~c 10")
|
||||
self._dump(p)
|
||||
assert len(p.lst) == 2
|
||||
|
||||
def test_naked_url(self):
|
||||
#a = filt.parse("foobar")
|
||||
#assert a.lst[0].expr == "foobar"
|
||||
|
||||
a = filt.parse("foobar ~h rex")
|
||||
assert a.lst[0].expr == "foobar"
|
||||
assert a.lst[1].expr == "rex"
|
||||
self._dump(a)
|
||||
|
||||
def test_quoting(self):
|
||||
a = filt.parse("~u 'foo ~u bar' ~u voing")
|
||||
assert a.lst[0].expr == "foo ~u bar"
|
||||
assert a.lst[1].expr == "voing"
|
||||
self._dump(a)
|
||||
|
||||
a = filt.parse("~u foobar")
|
||||
assert a.expr == "foobar"
|
||||
|
||||
a = filt.parse(r"~u 'foobar\"\''")
|
||||
assert a.expr == "foobar\"'"
|
||||
|
||||
a = filt.parse(r'~u "foo \'bar"')
|
||||
assert a.expr == "foo 'bar"
|
||||
|
||||
def test_nesting(self):
|
||||
a = filt.parse("(~u foobar & ~h voing)")
|
||||
assert a.lst[0].expr == "foobar"
|
||||
self._dump(a)
|
||||
|
||||
def test_not(self):
|
||||
a = filt.parse("!~h test")
|
||||
assert a.itm.expr == "test"
|
||||
a = filt.parse("!(~u test & ~h bar)")
|
||||
assert a.itm.lst[0].expr == "test"
|
||||
self._dump(a)
|
||||
|
||||
def test_binaryops(self):
|
||||
a = filt.parse("~u foobar | ~h voing")
|
||||
isinstance(a, filt.FOr)
|
||||
self._dump(a)
|
||||
|
||||
a = filt.parse("~u foobar & ~h voing")
|
||||
isinstance(a, filt.FAnd)
|
||||
self._dump(a)
|
||||
|
||||
def test_wideops(self):
|
||||
a = filt.parse("~hq 'header: qvalue'")
|
||||
assert isinstance(a, filt.FHeadRequest)
|
||||
self._dump(a)
|
||||
|
||||
|
||||
class uMatching(libpry.AutoTree):
|
||||
def req(self):
|
||||
conn = proxy.BrowserConnection("one", 2222)
|
||||
headers = utils.Headers()
|
||||
headers["header"] = ["qvalue"]
|
||||
return proxy.Request(
|
||||
conn,
|
||||
"host",
|
||||
80,
|
||||
"http",
|
||||
"GET",
|
||||
"/path",
|
||||
headers,
|
||||
"content_request"
|
||||
)
|
||||
|
||||
def resp(self):
|
||||
q = self.req()
|
||||
headers = utils.Headers()
|
||||
headers["header_response"] = ["svalue"]
|
||||
return proxy.Response(
|
||||
q,
|
||||
200,
|
||||
"HTTP/1.1",
|
||||
"message",
|
||||
headers,
|
||||
"content_response"
|
||||
)
|
||||
|
||||
def q(self, q, o):
|
||||
return filt.parse(q)(o)
|
||||
|
||||
def test_fcontenttype(self):
|
||||
q = self.req()
|
||||
s = self.resp()
|
||||
assert not self.q("~t content", q)
|
||||
assert not self.q("~t content", s)
|
||||
|
||||
q.headers["content-type"] = ["text/json"]
|
||||
assert self.q("~t json", q)
|
||||
assert self.q("~tq json", q)
|
||||
assert not self.q("~ts json", q)
|
||||
|
||||
s.headers["content-type"] = ["text/json"]
|
||||
assert self.q("~t json", s)
|
||||
|
||||
del s.headers["content-type"]
|
||||
s.request.headers["content-type"] = ["text/json"]
|
||||
assert self.q("~t json", s)
|
||||
assert self.q("~tq json", s)
|
||||
assert not self.q("~ts json", s)
|
||||
|
||||
def test_freq_fresp(self):
|
||||
q = self.req()
|
||||
s = self.resp()
|
||||
|
||||
assert self.q("~q", q)
|
||||
assert not self.q("~q", s)
|
||||
|
||||
assert not self.q("~s", q)
|
||||
assert self.q("~s", s)
|
||||
|
||||
def test_head(self):
|
||||
q = self.req()
|
||||
s = self.resp()
|
||||
assert not self.q("~h nonexistent", q)
|
||||
assert self.q("~h qvalue", q)
|
||||
assert self.q("~h header", q)
|
||||
assert self.q("~h 'header: qvalue'", q)
|
||||
|
||||
assert self.q("~h 'header: qvalue'", s)
|
||||
assert self.q("~h 'header_response: svalue'", s)
|
||||
|
||||
assert self.q("~hq 'header: qvalue'", s)
|
||||
assert not self.q("~hq 'header_response: svalue'", s)
|
||||
|
||||
assert self.q("~hq 'header: qvalue'", q)
|
||||
assert not self.q("~hq 'header_request: svalue'", q)
|
||||
|
||||
assert not self.q("~hs 'header: qvalue'", s)
|
||||
assert self.q("~hs 'header_response: svalue'", s)
|
||||
assert not self.q("~hs 'header: qvalue'", q)
|
||||
|
||||
def test_body(self):
|
||||
q = self.req()
|
||||
s = self.resp()
|
||||
assert not self.q("~b nonexistent", q)
|
||||
assert self.q("~b content", q)
|
||||
assert self.q("~b response", s)
|
||||
assert self.q("~b content_request", s)
|
||||
|
||||
assert self.q("~bq content", q)
|
||||
assert self.q("~bq content", s)
|
||||
assert not self.q("~bq response", q)
|
||||
assert not self.q("~bq response", s)
|
||||
|
||||
assert not self.q("~bs content", q)
|
||||
assert self.q("~bs content", s)
|
||||
assert not self.q("~bs nomatch", s)
|
||||
assert not self.q("~bs response", q)
|
||||
assert self.q("~bs response", s)
|
||||
|
||||
def test_url(self):
|
||||
q = self.req()
|
||||
s = self.resp()
|
||||
assert self.q("~u host", q)
|
||||
assert self.q("~u host/path", q)
|
||||
assert not self.q("~u moo/path", q)
|
||||
|
||||
assert self.q("~u host", s)
|
||||
assert self.q("~u host/path", s)
|
||||
assert not self.q("~u moo/path", s)
|
||||
|
||||
def test_code(self):
|
||||
q = self.req()
|
||||
s = self.resp()
|
||||
assert not self.q("~c 200", q)
|
||||
assert self.q("~c 200", s)
|
||||
assert not self.q("~c 201", s)
|
||||
|
||||
def test_and(self):
|
||||
s = self.resp()
|
||||
assert self.q("~c 200 & ~h head", s)
|
||||
assert not self.q("~c 200 & ~h nohead", s)
|
||||
assert self.q("(~c 200 & ~h head) & ~b content", s)
|
||||
assert not self.q("(~c 200 & ~h head) & ~b nonexistent", s)
|
||||
assert not self.q("(~c 200 & ~h nohead) & ~b content", s)
|
||||
|
||||
def test_or(self):
|
||||
s = self.resp()
|
||||
assert self.q("~c 200 | ~h nohead", s)
|
||||
assert self.q("~c 201 | ~h head", s)
|
||||
assert not self.q("~c 201 | ~h nohead", s)
|
||||
assert self.q("(~c 201 | ~h nohead) | ~s", s)
|
||||
assert not self.q("(~c 201 | ~h nohead) | ~q", s)
|
||||
|
||||
def test_not(self):
|
||||
s = self.resp()
|
||||
assert not self.q("! ~c 200", s)
|
||||
assert self.q("! ~c 201", s)
|
||||
assert self.q("!~c 201 !~c 202", s)
|
||||
assert not self.q("!~c 201 !~c 200", s)
|
||||
|
||||
|
||||
tests = [
|
||||
uMatching(),
|
||||
uParsing()
|
||||
]
|
259
test/test_proxy.py
Normal file
259
test/test_proxy.py
Normal file
@ -0,0 +1,259 @@
|
||||
import threading, urllib, Queue, urllib2, cStringIO
|
||||
import libpry
|
||||
import serv, sslserv
|
||||
from libmproxy import proxy, controller, utils
|
||||
import random
|
||||
|
||||
# Yes, the random ports are horrible. During development, sockets are often not
|
||||
# properly closed during error conditions, which means you have to wait until
|
||||
# you can re-bind to the same port. This is a pain in the ass, so we just pick
|
||||
# a random port and keep moving.
|
||||
PROXL_PORT = random.randint(10000, 20000)
|
||||
HTTP_PORT = random.randint(20000, 30000)
|
||||
HTTPS_PORT = random.randint(30000, 40000)
|
||||
|
||||
|
||||
class TestMaster(controller.Master):
|
||||
def __init__(self, port, testq):
|
||||
serv = proxy.ProxyServer(port)
|
||||
controller.Master.__init__(self, serv)
|
||||
self.testq = testq
|
||||
self.log = []
|
||||
|
||||
def clear(self):
|
||||
self.log = []
|
||||
|
||||
def handle(self, m):
|
||||
self.log.append(m)
|
||||
m.ack()
|
||||
|
||||
|
||||
class ProxyThread(threading.Thread):
|
||||
def __init__(self, port, testq):
|
||||
self.tmaster = TestMaster(port, testq)
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
self.tmaster.run()
|
||||
|
||||
def shutdown(self):
|
||||
self.tmaster.shutdown()
|
||||
|
||||
|
||||
class ServerThread(threading.Thread):
|
||||
def __init__(self, server):
|
||||
self.server = server
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
self.server.serve_forever()
|
||||
|
||||
def shutdown(self):
|
||||
self.server.shutdown()
|
||||
|
||||
|
||||
class _TestServers(libpry.TestContainer):
|
||||
def setUpAll(self):
|
||||
proxy.config = proxy.Config("data/testkey.pem")
|
||||
self.tqueue = Queue.Queue()
|
||||
# We don't make any concurrent requests, so we can access
|
||||
# the attributes on this object safely.
|
||||
self.proxthread = ProxyThread(PROXL_PORT, self.tqueue)
|
||||
self.threads = [
|
||||
ServerThread(serv.make(HTTP_PORT)),
|
||||
ServerThread(sslserv.make(HTTPS_PORT)),
|
||||
self.proxthread
|
||||
]
|
||||
for i in self.threads:
|
||||
i.start()
|
||||
|
||||
def setUp(self):
|
||||
self.proxthread.tmaster.clear()
|
||||
|
||||
def tearDownAll(self):
|
||||
for i in self.threads:
|
||||
i.shutdown()
|
||||
|
||||
|
||||
class _ProxTests(libpry.AutoTree):
|
||||
def log(self):
|
||||
pthread = self.findAttr("proxthread")
|
||||
return pthread.tmaster.log
|
||||
|
||||
|
||||
class uSanity(_ProxTests):
|
||||
def test_http(self):
|
||||
"""
|
||||
Just check that the HTTP server is running.
|
||||
"""
|
||||
f = urllib.urlopen("http://127.0.0.1:%s"%HTTP_PORT)
|
||||
assert f.read()
|
||||
|
||||
def test_https(self):
|
||||
"""
|
||||
Just check that the HTTPS server is running.
|
||||
"""
|
||||
f = urllib.urlopen("https://127.0.0.1:%s"%HTTPS_PORT)
|
||||
assert f.read()
|
||||
|
||||
|
||||
class uProxy(_ProxTests):
|
||||
HOST = "127.0.0.1"
|
||||
def _get(self, host=HOST):
|
||||
r = urllib2.Request("http://%s:%s"%(host, HTTP_PORT))
|
||||
r.set_proxy("127.0.0.1:%s"%PROXL_PORT, "http")
|
||||
return urllib2.urlopen(r)
|
||||
|
||||
def _sget(self, host=HOST):
|
||||
proxy_support = urllib2.ProxyHandler(
|
||||
{"https" : "https://127.0.0.1:%s"%PROXL_PORT}
|
||||
)
|
||||
opener = urllib2.build_opener(proxy_support)
|
||||
r = urllib2.Request("https://%s:%s"%(host, HTTPS_PORT))
|
||||
return opener.open(r)
|
||||
|
||||
def test_http(self):
|
||||
f = self._get()
|
||||
assert f.code == 200
|
||||
assert f.read()
|
||||
f.close()
|
||||
|
||||
l = self.log()
|
||||
assert l[0].address
|
||||
assert l[1].headers.has_key("host")
|
||||
assert l[2].code == 200
|
||||
|
||||
def test_https(self):
|
||||
f = self._sget()
|
||||
assert f.code == 200
|
||||
assert f.read()
|
||||
f.close()
|
||||
|
||||
l = self.log()
|
||||
assert l[0].address
|
||||
assert l[1].headers.has_key("host")
|
||||
assert l[2].code == 200
|
||||
|
||||
# Disable these two for now: they take a long time.
|
||||
def _test_http_nonexistent(self):
|
||||
f = self._get("nonexistent")
|
||||
assert f.code == 200
|
||||
assert "Error" in f.read()
|
||||
|
||||
def _test_https_nonexistent(self):
|
||||
f = self._sget("nonexistent")
|
||||
assert f.code == 200
|
||||
assert "Error" in f.read()
|
||||
|
||||
|
||||
|
||||
class u_parse_proxy_request(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
libpry.raises(proxy.ProxyError, proxy.parse_proxy_request, "")
|
||||
|
||||
u = "GET ... HTTP/1.1"
|
||||
libpry.raises("invalid url", proxy.parse_proxy_request, u)
|
||||
|
||||
u = "MORK / HTTP/1.1"
|
||||
libpry.raises("unknown request method", proxy.parse_proxy_request, u)
|
||||
|
||||
u = "GET http://foo.com:8888/test HTTP/1.1"
|
||||
m, s, h, po, pa = proxy.parse_proxy_request(u)
|
||||
assert m == "GET"
|
||||
assert s == "http"
|
||||
assert h == "foo.com"
|
||||
assert po == 8888
|
||||
assert pa == "/test"
|
||||
|
||||
def test_connect(self):
|
||||
u = "CONNECT host.com:443 HTTP/1.0"
|
||||
expected = ('CONNECT', None, 'host.com', 443, None)
|
||||
ret = proxy.parse_proxy_request(u)
|
||||
assert expected == ret
|
||||
|
||||
def test_inner(self):
|
||||
u = "GET / HTTP/1.1"
|
||||
assert proxy.parse_proxy_request(u) == ('GET', None, None, None, '/')
|
||||
|
||||
|
||||
class u_parse_url(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
assert not proxy.parse_url("")
|
||||
|
||||
u = "http://foo.com:8888/test"
|
||||
s, h, po, pa = proxy.parse_url(u)
|
||||
assert s == "http"
|
||||
assert h == "foo.com"
|
||||
assert po == 8888
|
||||
assert pa == "/test"
|
||||
|
||||
s, h, po, pa = proxy.parse_url("http://foo/bar")
|
||||
assert s == "http"
|
||||
assert h == "foo"
|
||||
assert po == 80
|
||||
assert pa == "/bar"
|
||||
|
||||
s, h, po, pa = proxy.parse_url("http://foo")
|
||||
assert pa == "/"
|
||||
|
||||
|
||||
class uConfig(libpry.AutoTree):
|
||||
def test_pem(self):
|
||||
c = proxy.Config(pemfile="data/testkey.pem")
|
||||
assert c.pemfile
|
||||
|
||||
|
||||
class uFileLike(libpry.AutoTree):
|
||||
def test_wrap(self):
|
||||
s = cStringIO.StringIO("foobar\nfoobar")
|
||||
s = proxy.FileLike(s)
|
||||
s.flush()
|
||||
assert s.readline() == "foobar\n"
|
||||
assert s.readline() == "foobar"
|
||||
|
||||
|
||||
class uRequest(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
h = utils.Headers()
|
||||
h["test"] = ["test"]
|
||||
c = proxy.BrowserConnection("addr", 2222)
|
||||
r = proxy.Request(c, "host", 22, "https", "GET", "/", h, "content")
|
||||
u = r.url()
|
||||
assert r.set_url(u)
|
||||
assert not r.set_url("")
|
||||
assert r.url() == u
|
||||
assert r.short()
|
||||
assert r.assemble()
|
||||
|
||||
|
||||
class uResponse(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
h = utils.Headers()
|
||||
h["test"] = ["test"]
|
||||
c = proxy.BrowserConnection("addr", 2222)
|
||||
req = proxy.Request(c, "host", 22, "https", "GET", "/", h, "content")
|
||||
resp = proxy.Response(req, 200, "HTTP", "msg", h.copy(), "content")
|
||||
assert resp.short()
|
||||
assert resp.assemble()
|
||||
|
||||
|
||||
class uProxyError(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
p = proxy.ProxyError(111, "msg")
|
||||
assert repr(p)
|
||||
|
||||
|
||||
|
||||
tests = [
|
||||
uProxyError(),
|
||||
uRequest(),
|
||||
uResponse(),
|
||||
uFileLike(),
|
||||
uConfig(),
|
||||
u_parse_proxy_request(),
|
||||
u_parse_url(),
|
||||
_TestServers(), [
|
||||
uSanity(),
|
||||
uProxy(),
|
||||
]
|
||||
]
|
221
test/test_utils.py
Normal file
221
test/test_utils.py
Normal file
@ -0,0 +1,221 @@
|
||||
import textwrap, cStringIO, os
|
||||
import libpry
|
||||
from libmproxy import utils
|
||||
|
||||
|
||||
class uisBin(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
assert not utils.isBin("testing\n\r")
|
||||
assert utils.isBin("testing\x01")
|
||||
assert utils.isBin("testing\x0e")
|
||||
assert utils.isBin("testing\x7f")
|
||||
|
||||
|
||||
class uhexdump(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
assert utils.hexdump("one\0"*10)
|
||||
|
||||
|
||||
class upretty_size(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
assert utils.pretty_size(100) == "100B"
|
||||
assert utils.pretty_size(1024) == "1kB"
|
||||
assert utils.pretty_size(1024 + (1024/2)) == "1.5kB"
|
||||
assert utils.pretty_size(1024*1024) == "1M"
|
||||
|
||||
|
||||
class uData(libpry.AutoTree):
|
||||
def test_nonexistent(self):
|
||||
libpry.raises("does not exist", utils.data.path, "nonexistent")
|
||||
|
||||
|
||||
class uMultiDict(libpry.AutoTree):
|
||||
def setUp(self):
|
||||
self.md = utils.MultiDict()
|
||||
|
||||
def test_setget(self):
|
||||
assert not self.md.has_key("foo")
|
||||
self.md.append("foo", 1)
|
||||
assert self.md["foo"] == [1]
|
||||
assert self.md.has_key("foo")
|
||||
|
||||
def test_del(self):
|
||||
self.md.append("foo", 1)
|
||||
del self.md["foo"]
|
||||
assert not self.md.has_key("foo")
|
||||
|
||||
def test_extend(self):
|
||||
self.md.append("foo", 1)
|
||||
self.md.extend("foo", [2, 3])
|
||||
assert self.md["foo"] == [1, 2, 3]
|
||||
|
||||
def test_extend_err(self):
|
||||
self.md.append("foo", 1)
|
||||
libpry.raises("not iterable", self.md.extend, "foo", 2)
|
||||
|
||||
def test_get(self):
|
||||
self.md.append("foo", 1)
|
||||
self.md.append("foo", 2)
|
||||
assert self.md.get("foo") == [1, 2]
|
||||
assert self.md.get("bar") == None
|
||||
|
||||
def test_caseSensitivity(self):
|
||||
self.md._helper = (utils._caseless,)
|
||||
self.md["foo"] = [1]
|
||||
self.md.append("FOO", 2)
|
||||
assert self.md["foo"] == [1, 2]
|
||||
assert self.md["FOO"] == [1, 2]
|
||||
assert self.md.has_key("FoO")
|
||||
|
||||
def test_dict(self):
|
||||
self.md.append("foo", 1)
|
||||
self.md.append("foo", 2)
|
||||
self.md["bar"] = [3]
|
||||
assert self.md == self.md
|
||||
assert dict(self.md) == self.md
|
||||
|
||||
def test_copy(self):
|
||||
self.md["foo"] = [1, 2]
|
||||
self.md["bar"] = [3, 4]
|
||||
md2 = self.md.copy()
|
||||
assert md2 == self.md
|
||||
assert id(md2) != id(self.md)
|
||||
|
||||
def test_clear(self):
|
||||
self.md["foo"] = [1, 2]
|
||||
self.md["bar"] = [3, 4]
|
||||
self.md.clear()
|
||||
assert not self.md.keys()
|
||||
|
||||
def test_setitem(self):
|
||||
libpry.raises(ValueError, self.md.__setitem__, "foo", "bar")
|
||||
self.md["foo"] = ["bar"]
|
||||
assert self.md["foo"] == ["bar"]
|
||||
|
||||
def test_itemPairs(self):
|
||||
self.md.append("foo", 1)
|
||||
self.md.append("foo", 2)
|
||||
self.md.append("bar", 3)
|
||||
l = list(self.md.itemPairs())
|
||||
assert len(l) == 3
|
||||
assert ("foo", 1) in l
|
||||
assert ("foo", 2) in l
|
||||
assert ("bar", 3) in l
|
||||
|
||||
|
||||
class uHeaders(libpry.AutoTree):
|
||||
def setUp(self):
|
||||
self.hd = utils.Headers()
|
||||
|
||||
def test_read_simple(self):
|
||||
data = """
|
||||
Header: one
|
||||
Header2: two
|
||||
\r\n
|
||||
"""
|
||||
data = textwrap.dedent(data)
|
||||
data = data.strip()
|
||||
s = cStringIO.StringIO(data)
|
||||
self.hd.read(s)
|
||||
assert self.hd["header"] == ["one"]
|
||||
assert self.hd["header2"] == ["two"]
|
||||
|
||||
def test_read_multi(self):
|
||||
data = """
|
||||
Header: one
|
||||
Header: two
|
||||
\r\n
|
||||
"""
|
||||
data = textwrap.dedent(data)
|
||||
data = data.strip()
|
||||
s = cStringIO.StringIO(data)
|
||||
self.hd.read(s)
|
||||
assert self.hd["header"] == ["one", "two"]
|
||||
|
||||
def test_read_continued(self):
|
||||
data = """
|
||||
Header: one
|
||||
\ttwo
|
||||
Header2: three
|
||||
\r\n
|
||||
"""
|
||||
data = textwrap.dedent(data)
|
||||
data = data.strip()
|
||||
s = cStringIO.StringIO(data)
|
||||
self.hd.read(s)
|
||||
assert self.hd["header"] == ['one\r\n two']
|
||||
|
||||
def test_dictToHeader1(self):
|
||||
self.hd.append("one", "uno")
|
||||
self.hd.append("two", "due")
|
||||
self.hd.append("two", "tre")
|
||||
expected = [
|
||||
"one: uno\r\n",
|
||||
"two: due\r\n",
|
||||
"two: tre\r\n",
|
||||
"\r\n"
|
||||
]
|
||||
out = repr(self.hd)
|
||||
for i in expected:
|
||||
assert out.find(i) >= 0
|
||||
|
||||
def test_dictToHeader2(self):
|
||||
self.hd["one"] = ["uno"]
|
||||
expected1 = "one: uno\r\n"
|
||||
expected2 = "\r\n"
|
||||
out = repr(self.hd)
|
||||
assert out.find(expected1) >= 0
|
||||
assert out.find(expected2) >= 0
|
||||
|
||||
def test_match_re(self):
|
||||
h = utils.Headers()
|
||||
h.append("one", "uno")
|
||||
h.append("two", "due")
|
||||
h.append("two", "tre")
|
||||
assert h.match_re("uno")
|
||||
assert h.match_re("two: due")
|
||||
assert not h.match_re("nonono")
|
||||
|
||||
|
||||
|
||||
class uisStringLike(libpry.AutoTree):
|
||||
def test_all(self):
|
||||
assert utils.isStringLike("foo")
|
||||
assert not utils.isStringLike([1, 2, 3])
|
||||
assert not utils.isStringLike((1, 2, 3))
|
||||
assert not utils.isStringLike(["1", "2", "3"])
|
||||
|
||||
|
||||
class uisSequenceLike(libpry.AutoTree):
|
||||
def test_all(self):
|
||||
assert utils.isSequenceLike([1, 2, 3])
|
||||
assert utils.isSequenceLike((1, 2, 3))
|
||||
assert not utils.isSequenceLike("foobar")
|
||||
assert utils.isSequenceLike(["foobar", "foo"])
|
||||
x = iter([1, 2, 3])
|
||||
assert utils.isSequenceLike(x)
|
||||
assert not utils.isSequenceLike(1)
|
||||
|
||||
|
||||
class umake_bogus_cert(libpry.AutoTree):
|
||||
def test_all(self):
|
||||
d = self.tmpdir()
|
||||
path = os.path.join(d, "foo", "cert")
|
||||
utils.make_bogus_cert(path)
|
||||
|
||||
d = open(path).read()
|
||||
assert "PRIVATE KEY" in d
|
||||
assert "CERTIFICATE" in d
|
||||
|
||||
|
||||
tests = [
|
||||
umake_bogus_cert(),
|
||||
uisBin(),
|
||||
uhexdump(),
|
||||
upretty_size(),
|
||||
uisStringLike(),
|
||||
uisSequenceLike(),
|
||||
uMultiDict(),
|
||||
uHeaders(),
|
||||
uData(),
|
||||
]
|
30
test/tserv
Executable file
30
test/tserv
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
A simple program for testing the test HTTP/S servers.
|
||||
"""
|
||||
from optparse import OptionParser, OptionGroup
|
||||
import sslserv, serv
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = OptionParser(
|
||||
usage = "%prog [options] output",
|
||||
version="%prog 0.1",
|
||||
)
|
||||
parser.add_option(
|
||||
"-s", "--ssl", action="store_true",
|
||||
dest="ssl", default=False
|
||||
)
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if options.ssl:
|
||||
port = 8443
|
||||
print "Running on port %s"%port
|
||||
s = sslserv.make(port)
|
||||
else:
|
||||
port = 8080
|
||||
print "Running on port %s"%port
|
||||
s = serv.make(port)
|
||||
try:
|
||||
s.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
17
todo
Normal file
17
todo
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
Future:
|
||||
|
||||
- Strings view.
|
||||
- Field parsing and editing.
|
||||
- On-the-fly generation of keys, signed with a CA
|
||||
- Pass-through fast-track for things that don't match filter?
|
||||
- Reading contents from file
|
||||
- Saving contents to file
|
||||
- Shortcut for viewing in pager
|
||||
- Serializing and de-serializing requests and responses.
|
||||
|
||||
|
||||
Bugs:
|
||||
|
||||
- In some circumstances, long URLs in list view are line-broken oddly.
|
||||
- Termination sometimes hangs.
|
Loading…
Reference in New Issue
Block a user