commit
5e0bee18c5
20 changed files with 6126 additions and 0 deletions
@ -0,0 +1,674 @@
@@ -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>. |
||||
@ -0,0 +1,165 @@
@@ -0,0 +1,165 @@
|
||||
GNU LESSER 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. |
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates |
||||
the terms and conditions of version 3 of the GNU General Public |
||||
License, supplemented by the additional permissions listed below. |
||||
|
||||
0. Additional Definitions. |
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser |
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU |
||||
General Public License. |
||||
|
||||
"The Library" refers to a covered work governed by this License, |
||||
other than an Application or a Combined Work as defined below. |
||||
|
||||
An "Application" is any work that makes use of an interface provided |
||||
by the Library, but which is not otherwise based on the Library. |
||||
Defining a subclass of a class defined by the Library is deemed a mode |
||||
of using an interface provided by the Library. |
||||
|
||||
A "Combined Work" is a work produced by combining or linking an |
||||
Application with the Library. The particular version of the Library |
||||
with which the Combined Work was made is also called the "Linked |
||||
Version". |
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the |
||||
Corresponding Source for the Combined Work, excluding any source code |
||||
for portions of the Combined Work that, considered in isolation, are |
||||
based on the Application, and not on the Linked Version. |
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the |
||||
object code and/or source code for the Application, including any data |
||||
and utility programs needed for reproducing the Combined Work from the |
||||
Application, but excluding the System Libraries of the Combined Work. |
||||
|
||||
1. Exception to Section 3 of the GNU GPL. |
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License |
||||
without being bound by section 3 of the GNU GPL. |
||||
|
||||
2. Conveying Modified Versions. |
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a |
||||
facility refers to a function or data to be supplied by an Application |
||||
that uses the facility (other than as an argument passed when the |
||||
facility is invoked), then you may convey a copy of the modified |
||||
version: |
||||
|
||||
a) under this License, provided that you make a good faith effort to |
||||
ensure that, in the event an Application does not supply the |
||||
function or data, the facility still operates, and performs |
||||
whatever part of its purpose remains meaningful, or |
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of |
||||
this License applicable to that copy. |
||||
|
||||
3. Object Code Incorporating Material from Library Header Files. |
||||
|
||||
The object code form of an Application may incorporate material from |
||||
a header file that is part of the Library. You may convey such object |
||||
code under terms of your choice, provided that, if the incorporated |
||||
material is not limited to numerical parameters, data structure |
||||
layouts and accessors, or small macros, inline functions and templates |
||||
(ten or fewer lines in length), you do both of the following: |
||||
|
||||
a) Give prominent notice with each copy of the object code that the |
||||
Library is used in it and that the Library and its use are |
||||
covered by this License. |
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license |
||||
document. |
||||
|
||||
4. Combined Works. |
||||
|
||||
You may convey a Combined Work under terms of your choice that, |
||||
taken together, effectively do not restrict modification of the |
||||
portions of the Library contained in the Combined Work and reverse |
||||
engineering for debugging such modifications, if you also do each of |
||||
the following: |
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that |
||||
the Library is used in it and that the Library and its use are |
||||
covered by this License. |
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license |
||||
document. |
||||
|
||||
c) For a Combined Work that displays copyright notices during |
||||
execution, include the copyright notice for the Library among |
||||
these notices, as well as a reference directing the user to the |
||||
copies of the GNU GPL and this license document. |
||||
|
||||
d) Do one of the following: |
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this |
||||
License, and the Corresponding Application Code in a form |
||||
suitable for, and under terms that permit, the user to |
||||
recombine or relink the Application with a modified version of |
||||
the Linked Version to produce a modified Combined Work, in the |
||||
manner specified by section 6 of the GNU GPL for conveying |
||||
Corresponding Source. |
||||
|
||||
1) Use a suitable shared library mechanism for linking with the |
||||
Library. A suitable mechanism is one that (a) uses at run time |
||||
a copy of the Library already present on the user's computer |
||||
system, and (b) will operate properly with a modified version |
||||
of the Library that is interface-compatible with the Linked |
||||
Version. |
||||
|
||||
e) Provide Installation Information, but only if you would otherwise |
||||
be required to provide such information under section 6 of the |
||||
GNU GPL, and only to the extent that such information is |
||||
necessary to install and execute a modified version of the |
||||
Combined Work produced by recombining or relinking the |
||||
Application with a modified version of the Linked Version. (If |
||||
you use option 4d0, the Installation Information must accompany |
||||
the Minimal Corresponding Source and Corresponding Application |
||||
Code. If you use option 4d1, you must provide the Installation |
||||
Information in the manner specified by section 6 of the GNU GPL |
||||
for conveying Corresponding Source.) |
||||
|
||||
5. Combined Libraries. |
||||
|
||||
You may place library facilities that are a work based on the |
||||
Library side by side in a single library together with other library |
||||
facilities that are not Applications and are not covered by this |
||||
License, and convey such a combined library under terms of your |
||||
choice, if you do both of the following: |
||||
|
||||
a) Accompany the combined library with a copy of the same work based |
||||
on the Library, uncombined with any other library facilities, |
||||
conveyed under the terms of this License. |
||||
|
||||
b) Give prominent notice with the combined library that part of it |
||||
is a work based on the Library, and explaining where to find the |
||||
accompanying uncombined form of the same work. |
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License. |
||||
|
||||
The Free Software Foundation may publish revised and/or new versions |
||||
of the GNU Lesser 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 |
||||
Library as you received it specifies that a certain numbered version |
||||
of the GNU Lesser General Public License "or any later version" |
||||
applies to it, you have the option of following the terms and |
||||
conditions either of that published version or of any later version |
||||
published by the Free Software Foundation. If the Library as you |
||||
received it does not specify a version number of the GNU Lesser |
||||
General Public License, you may choose any version of the GNU Lesser |
||||
General Public License ever published by the Free Software Foundation. |
||||
|
||||
If the Library as you received it specifies that a proxy can decide |
||||
whether future versions of the GNU Lesser General Public License shall |
||||
apply, that proxy's public statement of acceptance of any version is |
||||
permanent authorization for you to choose that version for the |
||||
Library. |
||||
@ -0,0 +1,536 @@
@@ -0,0 +1,536 @@
|
||||
#LyX 2.0 created this file. For more info see http://www.lyx.org/ |
||||
\lyxformat 413 |
||||
\begin_document |
||||
\begin_header |
||||
\textclass article |
||||
\use_default_options true |
||||
\maintain_unincluded_children false |
||||
\language english |
||||
\language_package default |
||||
\inputencoding auto |
||||
\fontencoding global |
||||
\font_roman default |
||||
\font_sans default |
||||
\font_typewriter default |
||||
\font_default_family default |
||||
\use_non_tex_fonts false |
||||
\font_sc false |
||||
\font_osf false |
||||
\font_sf_scale 100 |
||||
\font_tt_scale 100 |
||||
|
||||
\graphics default |
||||
\default_output_format default |
||||
\output_sync 0 |
||||
\bibtex_command default |
||||
\index_command default |
||||
\paperfontsize default |
||||
\spacing single |
||||
\use_hyperref false |
||||
\papersize default |
||||
\use_geometry false |
||||
\use_amsmath 1 |
||||
\use_esint 1 |
||||
\use_mhchem 1 |
||||
\use_mathdots 1 |
||||
\cite_engine basic |
||||
\use_bibtopic false |
||||
\use_indices false |
||||
\paperorientation portrait |
||||
\suppress_date false |
||||
\use_refstyle 1 |
||||
\index Index |
||||
\shortcut idx |
||||
\color #008000 |
||||
\end_index |
||||
\secnumdepth 3 |
||||
\tocdepth 3 |
||||
\paragraph_separation indent |
||||
\paragraph_indentation default |
||||
\quotes_language english |
||||
\papercolumns 1 |
||||
\papersides 1 |
||||
\paperpagestyle default |
||||
\tracking_changes false |
||||
\output_changes false |
||||
\html_math_output 0 |
||||
\html_css_as_file 0 |
||||
\html_be_strict false |
||||
\end_header |
||||
|
||||
\begin_body |
||||
|
||||
\begin_layout Title |
||||
Pontarius XMPP 0.1 Manual (Third Draft) |
||||
\end_layout |
||||
|
||||
\begin_layout Author |
||||
The Pontarius Project |
||||
\end_layout |
||||
|
||||
\begin_layout Date |
||||
The 6th of July, 2011 |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset CommandInset toc |
||||
LatexCommand tableofcontents |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Introduction |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Pontarius XMPP aims to be a convenient-to-use, powerful, correct, secure, |
||||
and extendable XMPP client library for Haskell. |
||||
It is written by Jon Kristensen and Mahdi Abdinejadi. |
||||
Being licensed under the GNU Lesser General Public License, Pontarius XMPP |
||||
is free and open source software. |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Features and Implementation Specifics |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Pontarius XMPP 0.1 implements the client capabilities of the XMPP Core specificat |
||||
ion (RFC 6120) |
||||
\begin_inset Foot |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
http://tools.ietf.org/html/rfc6120 |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
. |
||||
Below are the specifics of our implementation. |
||||
\end_layout |
||||
|
||||
\begin_layout Itemize |
||||
The client is always the initiating entity |
||||
\end_layout |
||||
|
||||
\begin_layout Itemize |
||||
A client-of-server connection is always exactly one TCP connection |
||||
\end_layout |
||||
|
||||
\begin_layout Itemize |
||||
TLS is supported for client-to-server confidentiality |
||||
\end_layout |
||||
|
||||
\begin_layout Itemize |
||||
Only the SCRAM authentication method is supported |
||||
\end_layout |
||||
|
||||
\begin_layout Itemize |
||||
... |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Later versions will add supports for different XMPP extensions, such as |
||||
RFC 6121 (XMPP IM), XEP-0004: Data Forms, and XEP-0077: In-Band Registration. |
||||
\begin_inset Foot |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
XMPP RFCs can be found at http://xmpp.org/xmpp-protocols/rfcs/, and the so-called |
||||
XEPs at http://xmpp.org/xmpp-protocols/xmpp-extensions/. |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Usage |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Working with Pontarius XMPP is mostly done asynchronously; Pontarius XMPP |
||||
``owns'' the XMPP thread, and calls different StateT s m a callback functions |
||||
in the client. |
||||
StateT is a monad transformer which allows the functions to be stateful |
||||
(being able to access and modify the arbitrary client-defined state of |
||||
type s) and to be executed on top of a MonadIO m monad (typically IO). |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Creating the session |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Setting up an XMPP session is done through the (blocking) session function: |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset listings |
||||
inline false |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
session :: (MonadIO m, ClientState s m) => s -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
[ClientHandler s m] -> (StateT s m ()) -> m () |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
The first parameter (of type s) is an arbitrary state that is defined by |
||||
the client. |
||||
This is the initial state, and it will be passed to the stateful client |
||||
callbacks. |
||||
It will typically be modified by the client. |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
The second parameter is the list of client handlers to deal with XMPP callbacks. |
||||
The reason why we have a list is because we want to provide a ``layered'' |
||||
system of XMPP event handlers. |
||||
For example, XMPP client developers may want to have a dedicated handler |
||||
to manage messages, implement a spam protection system, and so on. |
||||
Messages are piped through these handlers one by one, and any handler may |
||||
block the message from being sent to the next handler(s) above in the stack. |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset listings |
||||
inline false |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
data MonadIO m => ClientHandler s m = ClientHandler { |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
messageReceived :: Maybe (Message -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
StateT s m Bool), presenceReceived :: Maybe |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
(Presence -> StateT s m Bool), iqReceived :: |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
Maybe (IQ -> StateT s m Bool), |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
sessionTerminated :: Maybe (TerminationReason -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
StateT s m ()) } |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
ClientHandler is a record which specifies four callback functions. |
||||
The first three deals with the three XMPP stanzas, and are called once |
||||
an XMPP stanza is received. |
||||
These functions take the stanza in question, and are stateful with the |
||||
current client state. |
||||
The boolean value returned signals whether or not the message should be |
||||
blocked to clients further down the stack. |
||||
For example, a XEP-0030: Service Discovery handler may choose to hide disco#inf |
||||
o requests handlers above it in the stack. |
||||
The last function is the callback that is used when the XMPP session is |
||||
terminated. |
||||
All callbacks are optional. |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
The third argument to session is a callback function that will be called |
||||
when the session has been initialized. |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Any function with access to the Session object can operate with the XMPP |
||||
session, such as connecting the XMPP client or sending stanzas. |
||||
More on this below. |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Connecting the client |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Different clients connect to XMPP in different ways. |
||||
Some secure the stream with TLS, and some authenticate with the server. |
||||
Pontarius XMPP provides a flexible function to help out with this in a |
||||
convenient way: |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset listings |
||||
inline false |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
connect :: MonadIO m => Session s m -> HostName -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
PortNumber -> Maybe (Certificate, (Certificate -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
Bool)) -> Maybe (UserName, Password, Maybe |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
Resource) -> (ConnectResult -> StateT s m ()) -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
StateT s m () |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
This function simply takes the host name and port number to connect to, |
||||
an optional tuple of the certificate to use and a function evaluating certifica |
||||
tes for TLS (if Nothing is provided, the connection will not be TLS secured), |
||||
and another optional tuple with user name, password, and an optional resource |
||||
for authentication (analogously, providing Nothing here causes Pontarius |
||||
XMPP not to authenticate). |
||||
The final paramter is a callback function providing the result of the connect |
||||
action. |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
For more fine-grained control of the connection, use the openStream, secureWithT |
||||
LS, and authenticate functions. |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Managing XMPP addresses |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
There are four functions dealing with XMPP addresses (or JIDs, as they are |
||||
also called): |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset listings |
||||
inline false |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
fromString :: String -> Maybe Address |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
fromStrings :: Maybe String -> String -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
Maybe String -> Maybe Address |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
isBare :: Address -> Bool |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
isFull :: Address -> Bool |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
These functions should be pretty self-explainatory to those who know the |
||||
XMPP: Core standard. |
||||
The fromString functions takes one to three strings and tries to construct |
||||
an XMPP address. |
||||
isBare and isFull checks whether or not the bare is full (has a resource |
||||
value). |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Sending stanzas |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Sending messages is done using this function: |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset listings |
||||
inline false |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
sendMessage :: MonadIO m => Session s m -> Message -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
Maybe (Message -> StateT s m Bool) -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
Maybe (Timeout, StateT s m ()) -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
Maybe (StreamError -> StateT s m ()) -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
StateT s m () |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Like in section 3.2, the first parameter is the session object. |
||||
The second is the message (check the Message record type in the API). |
||||
The third parameter is an optional callback function to be executed if |
||||
a reply to the message is received. |
||||
The fourth parameter contains a Timeout (Integer) value, and a callback |
||||
that Pontarius XMPP will call when a reply has not been received in the |
||||
window of the timeout. |
||||
The last parameter is an optional callback that is called if a stream error |
||||
occurs. |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Presence and IQ stanzas are sent in a very similar way. |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Stanza IDs will be set for you if you leave them out. |
||||
If, however, you want to know what ID you send, you can acquire a stanza |
||||
ID by calling the getID function: |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset listings |
||||
inline false |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
getID :: MonadIO m => Session s m -> StateT s m String |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Concurrent usage |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Sometimes clients will want to perform XMPP actions from more than one thread, |
||||
or in other words, they want to perform actions from code that is not a |
||||
Pontarius XMPP callback. |
||||
For these use cases, use injectAction: |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset listings |
||||
inline false |
||||
status open |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
injectAction :: MonadIO m => Session s m -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
Maybe (StateT s m Bool) -> StateT s m () -> |
||||
\end_layout |
||||
|
||||
\begin_layout Plain Layout |
||||
|
||||
StateT s m () |
||||
\end_layout |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
The second parameter is an optional predicate callback to be executed right |
||||
before the third parameter callback is called. |
||||
If it is provided and evaluates to False, then the action will not be called. |
||||
Otherwise, the action will be called. |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Example echo server |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
We provide an example to further illustrate the Pontarius XMPP API and to |
||||
make it easier for developers to get started with the library. |
||||
The program illustrates how to connect, authenticate, set a presence, and |
||||
echo all messages received. |
||||
It only uses one client handler. |
||||
The contents of this example may be used freely, as if it is in the public |
||||
domain. |
||||
You find it in the Examples directory of the Pontarius XMPP source code. |
||||
\end_layout |
||||
|
||||
\end_body |
||||
\end_document |
||||
@ -0,0 +1,192 @@
@@ -0,0 +1,192 @@
|
||||
#LyX 2.0 created this file. For more info see http://www.lyx.org/ |
||||
\lyxformat 413 |
||||
\begin_document |
||||
\begin_header |
||||
\textclass article |
||||
\use_default_options true |
||||
\maintain_unincluded_children false |
||||
\language english |
||||
\language_package default |
||||
\inputencoding auto |
||||
\fontencoding global |
||||
\font_roman default |
||||
\font_sans default |
||||
\font_typewriter default |
||||
\font_default_family default |
||||
\use_non_tex_fonts false |
||||
\font_sc false |
||||
\font_osf false |
||||
\font_sf_scale 100 |
||||
\font_tt_scale 100 |
||||
|
||||
\graphics default |
||||
\default_output_format default |
||||
\output_sync 0 |
||||
\bibtex_command default |
||||
\index_command default |
||||
\paperfontsize default |
||||
\use_hyperref false |
||||
\papersize default |
||||
\use_geometry false |
||||
\use_amsmath 1 |
||||
\use_esint 1 |
||||
\use_mhchem 1 |
||||
\use_mathdots 1 |
||||
\cite_engine basic |
||||
\use_bibtopic false |
||||
\use_indices false |
||||
\paperorientation portrait |
||||
\suppress_date false |
||||
\use_refstyle 1 |
||||
\index Index |
||||
\shortcut idx |
||||
\color #008000 |
||||
\end_index |
||||
\secnumdepth 3 |
||||
\tocdepth 3 |
||||
\paragraph_separation indent |
||||
\paragraph_indentation default |
||||
\quotes_language english |
||||
\papercolumns 1 |
||||
\papersides 1 |
||||
\paperpagestyle default |
||||
\tracking_changes false |
||||
\output_changes false |
||||
\html_math_output 0 |
||||
\html_css_as_file 0 |
||||
\html_be_strict false |
||||
\end_header |
||||
|
||||
\begin_body |
||||
|
||||
\begin_layout Title |
||||
Software Design Description for Pontarius XMPP 1.0 (First Draft) |
||||
\end_layout |
||||
|
||||
\begin_layout Author |
||||
The Pontarius Project |
||||
\end_layout |
||||
|
||||
\begin_layout Date |
||||
15th of June, 2011 |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset CommandInset toc |
||||
LatexCommand tableofcontents |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Introduction |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Purpose |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Scope |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Definitions and acronyms |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
References |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Decomposition description |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Module decomposition |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Concurrent process decomposition |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Data decomposition |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Dependency description |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Intermodule dependencies |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Interprocess dependencies |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Data dependencies |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Interface description |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Module interface |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Module 1 description |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Module 2 description |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Process interface |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Process 1 description |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Process 2 description |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Detailed design |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Module detailed design |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Module 1 detail |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Module 2 detail |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Data detailed design |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Module 1 detail |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Module 2 detail |
||||
\end_layout |
||||
|
||||
\end_body |
||||
\end_document |
||||
@ -0,0 +1,324 @@
@@ -0,0 +1,324 @@
|
||||
#LyX 2.0 created this file. For more info see http://www.lyx.org/ |
||||
\lyxformat 413 |
||||
\begin_document |
||||
\begin_header |
||||
\textclass article |
||||
\use_default_options true |
||||
\maintain_unincluded_children false |
||||
\language english |
||||
\language_package default |
||||
\inputencoding auto |
||||
\fontencoding global |
||||
\font_roman default |
||||
\font_sans default |
||||
\font_typewriter default |
||||
\font_default_family default |
||||
\use_non_tex_fonts false |
||||
\font_sc false |
||||
\font_osf false |
||||
\font_sf_scale 100 |
||||
\font_tt_scale 100 |
||||
|
||||
\graphics default |
||||
\default_output_format default |
||||
\output_sync 0 |
||||
\bibtex_command default |
||||
\index_command default |
||||
\paperfontsize default |
||||
\use_hyperref false |
||||
\papersize default |
||||
\use_geometry false |
||||
\use_amsmath 1 |
||||
\use_esint 1 |
||||
\use_mhchem 1 |
||||
\use_mathdots 1 |
||||
\cite_engine basic |
||||
\use_bibtopic false |
||||
\use_indices false |
||||
\paperorientation portrait |
||||
\suppress_date false |
||||
\use_refstyle 1 |
||||
\index Index |
||||
\shortcut idx |
||||
\color #008000 |
||||
\end_index |
||||
\secnumdepth 3 |
||||
\tocdepth 3 |
||||
\paragraph_separation indent |
||||
\paragraph_indentation default |
||||
\quotes_language english |
||||
\papercolumns 1 |
||||
\papersides 1 |
||||
\paperpagestyle default |
||||
\tracking_changes false |
||||
\output_changes false |
||||
\html_math_output 0 |
||||
\html_css_as_file 0 |
||||
\html_be_strict false |
||||
\end_header |
||||
|
||||
\begin_body |
||||
|
||||
\begin_layout Title |
||||
Software Quality Assurance Plan for Pontarius XMPP 1.0 |
||||
\end_layout |
||||
|
||||
\begin_layout Author |
||||
Jon Kristensen |
||||
\end_layout |
||||
|
||||
\begin_layout Date |
||||
6th of June, 2011 |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
\begin_inset CommandInset toc |
||||
LatexCommand tableofcontents |
||||
|
||||
\end_inset |
||||
|
||||
|
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Purpose |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
The purpose of writing this SQAP is not only to increase the quality of |
||||
Pontarius XMPP, but also to evaluate the use of the IEEE Standard for Software |
||||
Quality Assurance Plans (IEEE Std 730-1998) standard as well as meeting |
||||
the goals of a university course in IT quality management. |
||||
For information on the intended use of the software, please consult the |
||||
Pontarius XMPP 1.0 Software Requirement Specification. |
||||
The applicable portions of the software's life cycle from its first beta |
||||
until its disposal phase. |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Reference documents |
||||
\end_layout |
||||
|
||||
\begin_layout Enumerate |
||||
IEEE Standard for Software Quality Assurance Plans (IEEE Std 730-1998) |
||||
\end_layout |
||||
|
||||
\begin_layout Enumerate |
||||
Pontarius XMPP 1.0 Software Requirement Specification |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Legal notice |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
Pontarius XMPP is a free and open source software project. |
||||
The |
||||
\begin_inset Quotes eld |
||||
\end_inset |
||||
|
||||
Pontarius project |
||||
\begin_inset Quotes erd |
||||
\end_inset |
||||
|
||||
is not a legal entity, but is like a synonym for Jon Kristensen. |
||||
Jon Kristensen does DOES NOT TAKE ANY RESPONSIBILITY OR OFFER ANY GUARANTEES |
||||
in regards to the software, its quality or this document. |
||||
Furthermore, the software is provided |
||||
\begin_inset Quotes eld |
||||
\end_inset |
||||
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
||||
or FITNESS FOR A PARTICULAR PURPOSE |
||||
\begin_inset Quotes erd |
||||
\end_inset |
||||
|
||||
. |
||||
Consult the GNU General Public License for more information. |
||||
This aspect particularly influences the next section of this document. |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Management |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
The |
||||
\begin_inset Quotes eld |
||||
\end_inset |
||||
|
||||
organization |
||||
\begin_inset Quotes erd |
||||
\end_inset |
||||
|
||||
behind the software is the project founder, project leader, and copyright |
||||
holder Jon Kristensen, which acts in a |
||||
\begin_inset Quotes eld |
||||
\end_inset |
||||
|
||||
benevolent dictator |
||||
\begin_inset Quotes erd |
||||
\end_inset |
||||
|
||||
position for the free and open source project. |
||||
The Pontarius project hopes that Pontarius XMPP will grow to become the |
||||
de-facto XMPP library for Haskell, and that we will get voluntary feedback |
||||
from multiple testers from different communities, such as the Haskell and |
||||
XMPP communities, as well as the free and open source software community |
||||
as a whole. |
||||
We will do what we can to organize and act on that feedback; however, the |
||||
only person that has currently even |
||||
\emph on |
||||
planned |
||||
\emph default |
||||
to perform software quality assurance activities is Jon Kristensen. |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
[Lifecycle of software, Sequence of tasks (with emphasis on activities), |
||||
relationships between tasks and major checkpoints] |
||||
\end_layout |
||||
|
||||
\begin_layout Standard |
||||
(Testing API, extending Pontarius XMPP with a set of RFCs and/or XEPs, unit |
||||
testing, performance testing, stress testing, code (un)coverage...) |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Documentation |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Purpose |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Minimum documentation requirements |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Software Requirements Specification |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Software Design Description |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Software Verification and Validation Plan |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
User Documentation |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Software Configuration Management Plan |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Standards, practices, conventions, and metrics |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Purpose |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Content |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Reviews and audits |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Purpose |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Minimum requirements |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Software Requirements Review |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Preliminary Design Review |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Critical Design Review |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Software Verification and Validation Plan Review |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Functional audit |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Physical audit |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
In-process audits |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Manegerial reviews |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Software Configuration Management Plan Review |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
Post-mortem review |
||||
\end_layout |
||||
|
||||
\begin_layout Subsubsection |
||||
User Documentation Review |
||||
\end_layout |
||||
|
||||
\begin_layout Subsection |
||||
Test |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Problem reporting and corrective actions |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Tools, technologies, and methodologies |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Code control |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Media control |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Supplier control |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Records collection, maintainance, and retention |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Training |
||||
\end_layout |
||||
|
||||
\begin_layout Section |
||||
Risk management |
||||
\end_layout |
||||
|
||||
\end_body |
||||
\end_document |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
{- |
||||
|
||||
Copyright © 2010-2011 Jon Kristensen. |
||||
|
||||
This file (EchoClient.hs) illustrates how to connect, authenticate, set a |
||||
presence, and echo messages with Pontarius XMPP. The contents of this file may |
||||
be used freely, as if it is in the public domain. |
||||
|
||||
In any state-aware function (function operating in the StateT monad) you can get |
||||
and set the current by writing |
||||
|
||||
@CMS.get >>= \ state -> CMS.put $ state { stateTest = 10 } ...@ |
||||
|
||||
or, if you prefer the do-notation, |
||||
|
||||
@do |
||||
state <- CMS.get |
||||
CMS.put $ state { stateTest = 10 } |
||||
...@ |
||||
|
||||
-} |
||||
|
||||
|
||||
{-# LANGUAGE MultiParamTypeClasses #-} |
||||
|
||||
|
||||
module Examples.EchoClient () where |
||||
|
||||
import Network.XMPP |
||||
|
||||
import qualified Control.Monad as CM |
||||
import qualified Control.Monad.State as CMS |
||||
import qualified Control.Monad.IO.Class as CMIC |
||||
import qualified Data.Maybe as DM |
||||
|
||||
|
||||
-- Account and server details. |
||||
|
||||
hostName = "jonkristensen.com" |
||||
userName = "pontarius" |
||||
serverIdentifier = "jonkristensen.com" |
||||
portNumber = 5222 |
||||
resource = "echo-client" |
||||
password = "" |
||||
|
||||
|
||||
-- The client state, containing the required Pontarius XMPP Session object. It |
||||
-- also contains a dummy integer value to illustrate how client states are used. |
||||
|
||||
data State = State { stateSession :: Maybe (Session State IO) |
||||
, stateTest :: Integer } |
||||
|
||||
defaultState :: State |
||||
|
||||
defaultState = State { stateSession = Nothing |
||||
, stateTest = 5 } |
||||
|
||||
|
||||
instance ClientState State IO where |
||||
putSession st se = st { stateSession = Just se } |
||||
|
||||
|
||||
-- This client defines one client handler, and only specifies the |
||||
-- messageReceived callback. |
||||
|
||||
clientHandlers = [ClientHandler { messageReceived = Just messageReceived_ |
||||
, presenceReceived = Nothing |
||||
, iqReceived = Nothing |
||||
, sessionTerminated = Nothing }] |
||||
|
||||
|
||||
-- The main function sets up the Pontarius XMPP session with the default client |
||||
-- state and client handler defined above, as well as specifying that the |
||||
-- sessionCreated function should be called when the session has been created. |
||||
|
||||
main :: IO () |
||||
|
||||
main = do |
||||
session |
||||
defaultState |
||||
clientHandlers |
||||
sessionCreated |
||||
|
||||
|
||||
-- The session has been created. Let's try to open the XMPP stream! |
||||
|
||||
sessionCreated :: CMS.StateT State IO () |
||||
|
||||
sessionCreated = do |
||||
state <- CMS.get |
||||
connect (DM.fromJust $ stateSession state) hostName portNumber |
||||
(Just ("", \ x -> True)) (Just (userName, password, Just resource)) |
||||
connectCallback |
||||
id <- getID (DM.fromJust $ stateSession state) |
||||
CMIC.liftIO $ putStrLn $ "Unique ID acquired: " ++ id |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
injectAction (DM.fromJust $ stateSession state) Nothing (do CMIC.liftIO $ putStrLn "Async action!"; return ()) |
||||
return () |
||||
|
||||
|
||||
-- We have tried to connected, TLS secured and authenticated! |
||||
|
||||
connectCallback :: ConnectResult -> CMS.StateT State IO () |
||||
|
||||
connectCallback r = do |
||||
state <- CMS.get |
||||
case r of |
||||
ConnectSuccess _ _ _ -> do |
||||
sendPresence (DM.fromJust $ stateSession state) |
||||
Presence { presenceID = Nothing |
||||
, presenceFrom = Nothing |
||||
, presenceTo = Nothing |
||||
, presenceXMLLang = Nothing |
||||
, presenceType = Available |
||||
, presencePayload = [] } |
||||
Nothing Nothing Nothing |
||||
_ -> do |
||||
CMIC.liftIO $ putStrLn "Could not connect." |
||||
return () |
||||
|
||||
|
||||
-- A message (stanza) has been received. Let's echo it! |
||||
|
||||
messageReceived_ :: Message -> CMS.StateT State IO Bool |
||||
|
||||
messageReceived_ m = do |
||||
state <- CMS.get |
||||
CMIC.liftIO $ putStrLn $ |
||||
"Received a message; echoing it! By the way: Internal state is " ++ |
||||
(show $ stateTest state) ++ "." |
||||
sendMessage (DM.fromJust $ stateSession state) |
||||
Message { messageID = messageID m |
||||
, messageFrom = Nothing |
||||
, messageTo = messageFrom m |
||||
, messageXMLLang = Nothing |
||||
, messageType = messageType m |
||||
, messagePayload = messagePayload m } |
||||
Nothing (Just (0, (do CMIC.liftIO $ putStrLn "Timeout!"; return ()))) Nothing |
||||
return True |
||||
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
Copyright © 2010-2011, Jon Kristensen. |
||||
|
||||
Pontarius XMPP is licensed under GNU Lesser General Public License, version 3. |
||||
See COPYING and COPYING.LESSER for more information. |
||||
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
{- |
||||
|
||||
Copyright © 2010-2011 Jon Kristensen. |
||||
|
||||
This file is part of Pontarius XMPP. |
||||
|
||||
Pontarius XMPP is free software: you can redistribute it and/or modify it under |
||||
the terms of the GNU Lesser General Public License as published by the Free |
||||
Software Foundation, either version 3 of the License, or (at your option) any |
||||
later version. |
||||
|
||||
Pontarius XMPP 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 Lesser General Public License for more |
||||
details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along |
||||
with Pontarius XMPP. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
-} |
||||
|
||||
-- | Module: $Header$ |
||||
-- Description: A minimalistic and easy-to-use XMPP library |
||||
-- Copyright: Copyright © 2010-2011 Jon Kristensen |
||||
-- License: LGPL-3 |
||||
-- |
||||
-- Maintainer: info@pontarius.org |
||||
-- Stability: unstable |
||||
-- Portability: portable |
||||
|
||||
-- Pontarius XMPP aims to be a secure, concurrent/event-based and easy-to-use |
||||
-- XMPP library for Haskell. It is being actively developed. |
||||
-- |
||||
-- Note that we are not recommending anyone to use Pontarius XMPP at this time |
||||
-- as it's still in an experimental stage and will have its API and data types |
||||
-- modified frequently. See the project's web site at |
||||
-- <http://www.pontarius.org/> for more information. |
||||
-- |
||||
-- This module will be documented soon. |
||||
|
||||
module Network.XMPP ( -- Network.XMPP.JID |
||||
Address (..) |
||||
, Localpart |
||||
, Serverpart |
||||
, Resourcepart |
||||
, isFull |
||||
, isBare |
||||
, fromString |
||||
, fromStrings |
||||
|
||||
-- Network.XMPP.SASL |
||||
, replyToChallenge1 |
||||
|
||||
-- Network.XMPP.Session |
||||
, Certificate |
||||
, ClientHandler (..) |
||||
, ClientState (..) |
||||
, ConnectResult (..) |
||||
, HostName |
||||
, Password |
||||
, PortNumber |
||||
, Resource |
||||
, Session |
||||
, TerminationReason |
||||
, UserName |
||||
, sendIQ |
||||
, sendPresence |
||||
, sendMessage |
||||
, connect |
||||
, openStream |
||||
, secureWithTLS |
||||
, authenticate |
||||
, session |
||||
, OpenStreamResult (..) |
||||
, SecureWithTLSResult (..) |
||||
, AuthenticateResult (..) |
||||
|
||||
-- Network.XMPP.Stanza |
||||
, StanzaID (SID) |
||||
, From |
||||
, To |
||||
, XMLLang |
||||
, MessageType (..) |
||||
, Message (..) |
||||
, PresenceType (..) |
||||
, Presence (..) |
||||
, IQ (..) |
||||
, iqPayloadNamespace |
||||
, iqPayload |
||||
|
||||
, injectAction |
||||
|
||||
-- Network.XMPP.Utilities |
||||
, elementToString |
||||
, elementsToString |
||||
, getID ) where |
||||
|
||||
import Network.XMPP.Address |
||||
import Network.XMPP.SASL |
||||
import Network.XMPP.Session |
||||
import Network.XMPP.Stanza |
||||
import Network.XMPP.Utilities |
||||
import Network.XMPP.Types |
||||
import Network.XMPP.TLS |
||||
import Network.XMPP.Stream |
||||
|
||||
@ -0,0 +1,216 @@
@@ -0,0 +1,216 @@
|
||||
{- |
||||
|
||||
Copyright © 2010-2011 Jon Kristensen. |
||||
|
||||
This file is part of Pontarius XMPP. |
||||
|
||||
Pontarius XMPP is free software: you can redistribute it and/or modify it under |
||||
the terms of the GNU Lesser General Public License as published by the Free |
||||
Software Foundation, either version 3 of the License, or (at your option) any |
||||
later version. |
||||
|
||||
Pontarius XMPP 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 Lesser General Public License for more |
||||
details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along |
||||
with Pontarius XMPP. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
-} |
||||
|
||||
|
||||
-- TODO: Move away from stringprep for all three profiles. |
||||
|
||||
-- TODO: When no longer using stringprep, do appropriate testing. (Including |
||||
-- testing addresses like a@b/c@d/e, a/b@c, a@/b, a/@b...) |
||||
|
||||
-- TODO: Unicode 3.2 should be used. |
||||
|
||||
|
||||
-- | |
||||
-- Module: $Header$ |
||||
-- Description: Data type and utility functions for XMPP addresses (JIDs) |
||||
-- Copyright: Copyright © 2010-2011 Jon Kristensen |
||||
-- License: LGPL-3 |
||||
-- |
||||
-- Maintainer: info@pontarius.org |
||||
-- Stability: unstable |
||||
-- Portability: portable |
||||
-- |
||||
-- This module deals with XMPP addresses (also known as JIDs and JabberIDs). For |
||||
-- more information on XMPP addresses, see RFC 6122: XMPP: Address Format. |
||||
-- |
||||
-- Provided hostnames may contain international characters; Pontarius XMPP will |
||||
-- try to convert such hostnames to internationalized hostnames. |
||||
|
||||
|
||||
module Network.XMPP.Address (fromString, fromStrings, isBare, isFull) where |
||||
|
||||
import Network.XMPP.Types |
||||
|
||||
import Data.Maybe (fromJust, isJust) |
||||
import Text.Parsec ((<|>), anyToken, char, eof, many, noneOf, parse) |
||||
import Text.Parsec.ByteString (GenParser) |
||||
|
||||
import Text.StringPrep (StringPrepProfile (..), a1, b1, b2, c11, c12, c21, c22, |
||||
c3, c4, c5, c6, c7, c8, c9, runStringPrep) |
||||
import Text.NamePrep (namePrepProfile) |
||||
|
||||
import Data.Text.IDNA2008 (toASCII) |
||||
|
||||
import Network.URI (isIPv4address, isIPv6address) |
||||
|
||||
import qualified Data.ByteString.Char8 as DBC (pack) |
||||
import qualified Data.Text as DT (pack, unpack) |
||||
|
||||
|
||||
-- | |
||||
-- Converts a string to an XMPP address. |
||||
|
||||
fromString :: String -> Maybe Address |
||||
|
||||
fromString s = fromStrings localpart serverpart resourcepart |
||||
where |
||||
Right (localpart, serverpart, resourcepart) = |
||||
parse addressParts "" (DBC.pack s) |
||||
|
||||
|
||||
-- | |
||||
-- Converts localpart, serverpart, and resourcepart strings to an XMPP address. |
||||
|
||||
-- Runs the appropriate stringprep profiles and validates the parts. |
||||
|
||||
fromStrings :: Maybe String -> String -> Maybe String -> Maybe Address |
||||
|
||||
fromStrings l s r |
||||
| serverpart == Nothing = Nothing |
||||
| otherwise = if validateNonServerpart localpart && |
||||
isJust serverpart' && |
||||
validateNonServerpart resourcepart |
||||
then Just (Address localpart (fromJust serverpart') resourcepart) |
||||
else Nothing |
||||
where |
||||
|
||||
-- Applies the nodeprep profile on the localpart string, if any. |
||||
localpart :: Maybe String |
||||
localpart = case l of |
||||
Just l' -> case runStringPrep nodeprepProfile (DT.pack l') of |
||||
Just l'' -> Just $ DT.unpack l'' |
||||
Nothing -> Nothing |
||||
Nothing -> Nothing |
||||
|
||||
-- Applies the nameprep profile on the serverpart string. |
||||
-- TODO: Allow unassigned? |
||||
serverpart :: Maybe String |
||||
serverpart = case runStringPrep (namePrepProfile False) (DT.pack s) of |
||||
Just s' -> Just $ DT.unpack s' |
||||
Nothing -> Nothing |
||||
|
||||
-- Applies the resourceprep profile on the resourcepart string, if any. |
||||
resourcepart :: Maybe String |
||||
resourcepart = case r of |
||||
Just r' -> case runStringPrep resourceprepProfile (DT.pack r') of |
||||
Just r'' -> Just $ DT.unpack r'' |
||||
Nothing -> Nothing |
||||
Nothing -> Nothing |
||||
|
||||
-- Returns the serverpart if it was a valid IP or if the toASCII |
||||
-- function was successful, or Nothing otherwise. |
||||
serverpart' :: Maybe String |
||||
serverpart' | isIPv4address s || isIPv6address s = Just s |
||||
| otherwise = toASCII s |
||||
|
||||
-- Validates that non-serverpart strings have an appropriate length. |
||||
validateNonServerpart :: Maybe String -> Bool |
||||
validateNonServerpart Nothing = True |
||||
validateNonServerpart (Just l) = validPartLength l |
||||
where |
||||
validPartLength :: String -> Bool |
||||
validPartLength p = length p > 0 && length p < 1024 |
||||
|
||||
|
||||
-- | Returns True if the address is `bare', and False otherwise. |
||||
|
||||
isBare :: Address -> Bool |
||||
|
||||
isBare j | resourcepart j == Nothing = True |
||||
| otherwise = False |
||||
|
||||
|
||||
-- | Returns True if the address is `full', and False otherwise. |
||||
|
||||
isFull :: Address -> Bool |
||||
|
||||
isFull jid = not $ isBare jid |
||||
|
||||
|
||||
-- Parses an address string and returns its three parts. It performs no |
||||
-- validation or transformations. We are using Parsec to parse the address. |
||||
-- There is no input for which 'addressParts' fails. |
||||
|
||||
addressParts :: GenParser Char st (Maybe String, String, Maybe String) |
||||
|
||||
addressParts = do |
||||
|
||||
-- Read until we reach an '@', a '/', or EOF. |
||||
a <- many $ noneOf ['@', '/'] |
||||
|
||||
-- Case 1: We found an '@', and thus the localpart. At least the serverpart |
||||
-- is remaining. Read the '@' and until a '/' or EOF. |
||||
do |
||||
char '@' |
||||
b <- many $ noneOf ['/'] |
||||
|
||||
-- Case 1A: We found a '/' and thus have all the address parts. Read the |
||||
-- '/' and until EOF. |
||||
do |
||||
char '/' -- Resourcepart remaining |
||||
c <- many $ anyToken -- Parse resourcepart |
||||
eof |
||||
return (Just a, b, Just c) |
||||
|
||||
-- Case 1B: We have reached EOF; the address is in the form |
||||
-- localpart@serverpart. |
||||
<|> do |
||||
eof |
||||
return (Just a, b, Nothing) |
||||
|
||||
-- Case 2: We found a '/'; the address is in the form |
||||
-- serverpart/resourcepart. |
||||
<|> do |
||||
char '/' |
||||
b <- many $ anyToken |
||||
eof |
||||
return (Nothing, a, Just b) |
||||
|
||||
-- Case 3: We have reached EOF; we have an address consisting of only a |
||||
-- serverpart. |
||||
<|> do |
||||
eof |
||||
return (Nothing, a, Nothing) |
||||
|
||||
|
||||
nodeprepProfile :: StringPrepProfile |
||||
|
||||
nodeprepProfile = Profile { maps = [b1, b2] |
||||
, shouldNormalize = True |
||||
, prohibited = [a1] ++ [c11, c12, c21, c22, c3, c4, c5, c6, c7, c8, c9] |
||||
, shouldCheckBidi = True } |
||||
|
||||
|
||||
-- These needs to be checked for after normalization. We could also look up the |
||||
-- Unicode mappings and include a list of characters in the prohibited field |
||||
-- above. Let's defer that until we know that we are going to use stringprep. |
||||
|
||||
nodeprepExtraProhibitedCharacters = ['\x22', '\x26', '\x27', '\x2F', '\x3A', |
||||
'\x3C', '\x3E', '\x40'] |
||||
|
||||
|
||||
|
||||
resourceprepProfile :: StringPrepProfile |
||||
|
||||
resourceprepProfile = Profile { maps = [b1] |
||||
, shouldNormalize = True |
||||
, prohibited = [a1] ++ [c12, c21, c22, c3, c4, c5, c6, c7, c8, c9] |
||||
, shouldCheckBidi = True } |
||||
@ -0,0 +1,215 @@
@@ -0,0 +1,215 @@
|
||||
{- |
||||
|
||||
Copyright © 2010-2011 Jon Kristensen. |
||||
|
||||
This file is part of Pontarius XMPP. |
||||
|
||||
Pontarius XMPP is free software: you can redistribute it and/or modify it under |
||||
the terms of the GNU Lesser General Public License as published by the Free |
||||
Software Foundation, either version 3 of the License, or (at your option) any |
||||
later version. |
||||
|
||||
Pontarius XMPP 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 Lesser General Public License for more |
||||
details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along |
||||
with Pontarius XMPP. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
-} |
||||
|
||||
-- TODO: Make it possible to include host. |
||||
-- TODO: Host is assumed to be ISO 8859-1; make list of assumptions. |
||||
-- TODO: Can it contain newline characters? |
||||
|
||||
module Network.XMPP.SASL (replyToChallenge1) where |
||||
|
||||
import Data.ByteString.Internal (c2w) |
||||
import Data.Char (isLatin1) |
||||
import Data.Digest.Pure.MD5 |
||||
import qualified Data.Binary as DBi (Binary, encode) |
||||
import qualified Data.ByteString.Lazy as DBL (ByteString, append, pack, |
||||
fromChunks, toChunks, null) |
||||
import qualified Data.ByteString.Lazy.Char8 as DBLC (append, pack, unpack) |
||||
import qualified Data.List as DL |
||||
|
||||
|
||||
data Challenge1Error = C1MultipleCriticalAttributes | |
||||
C1NotAllParametersPresent | |
||||
C1SomeParamtersPresentMoreThanOnce | |
||||
C1WrongRealm | |
||||
C1UnsupportedAlgorithm | |
||||
C1UnsupportedCharset | |
||||
C1UnsupportedQOP |
||||
deriving Show |
||||
|
||||
|
||||
-- Will produce a list of key-value pairs given a string in the format of |
||||
-- realm="somerealm",nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8... |
||||
stringToList :: String -> [(String, String)] |
||||
stringToList "" = [] |
||||
stringToList s' = let (next, rest) = break' s' ',' |
||||
in break' next '=' : stringToList rest |
||||
where |
||||
-- Like break, but will remove the first char of the continuation, if |
||||
-- present. |
||||
break' :: String -> Char -> (String, String) |
||||
break' s' c = let (first, second) = break ((==) c) s' |
||||
in (first, removeCharIfPresent second c) |
||||
|
||||
-- Removes the first character, if present; "=hello" with '=' becomes |
||||
-- "hello". |
||||
removeCharIfPresent :: String -> Char -> String |
||||
removeCharIfPresent [] _ = [] |
||||
removeCharIfPresent (c:t) c' | c == c' = t |
||||
removeCharIfPresent s' c = s' |
||||
|
||||
-- Counts the number of directives in the pair list. |
||||
countDirectives :: String -> [(String, String)] -> Int |
||||
countDirectives v l = DL.length $ filter (isEntry v) l |
||||
where |
||||
isEntry :: String -> (String, String) -> Bool |
||||
isEntry name (name', _) | name == name' = True |
||||
| otherwise = False |
||||
|
||||
|
||||
-- Returns the given directive in the list of pairs, or Nothing. |
||||
lookupDirective :: String -> [(String, String)] -> Maybe String |
||||
lookupDirective d [] = Nothing |
||||
lookupDirective d ((d', v):t) | d == d' = Just v |
||||
| otherwise = lookupDirective d t |
||||
|
||||
|
||||
-- Returns the given directive in the list of pairs, or the default value |
||||
-- otherwise. |
||||
lookupDirectiveWithDefault :: String -> [(String, String)] -> String -> String |
||||
lookupDirectiveWithDefault di l de |
||||
| lookup == Nothing = de |
||||
| otherwise = let Just r = lookup in r |
||||
where |
||||
lookup = lookupDirective di l |
||||
|
||||
|
||||
-- Takes a challenge string (which is not Base64-encoded), the host name of the |
||||
-- Jabber server, the Jabber user name (JID), the password and a random and |
||||
-- unique "cnonce" value and generates either an error or a response to that |
||||
-- challenge. |
||||
|
||||
-- We have broken replyToChallenge1 for non-TLS authentication. In order to |
||||
-- change it back, just uncomment the lines relevant to the realm and match it |
||||
-- in the C1NotAllParametersSet case. |
||||
|
||||
replyToChallenge1 :: String -> String -> String -> String -> String -> |
||||
Either String Challenge1Error |
||||
replyToChallenge1 s h u p c = |
||||
-- Remove all new line characters. |
||||
let list = stringToList $ filter (/= '\n') s |
||||
in -- Count that there are no more than one nonce or algorithm directives. |
||||
case countDirectives "nonce" list <= 1 && |
||||
countDirectives "algorithm" list <= 1 of |
||||
True -> |
||||
let -- realm = lookupDirective "realm" list |
||||
nonce = lookupDirective "nonce" list |
||||
qop = lookupDirectiveWithDefault "qop" list "auth" |
||||
charset = lookupDirectiveWithDefault "charset" list "utf-8" |
||||
algorithm = lookupDirective "algorithm" list |
||||
|
||||
-- Verify that all necessary directives has been set. |
||||
in case (nonce, qop, charset, algorithm) of |
||||
(Just nonce', qop', charset', Just algorithm') -> |
||||
|
||||
-- Strip quotations of the directives that need it. |
||||
let -- realm'' = stripQuotations realm' |
||||
nonce'' = stripQuotations nonce' |
||||
qop'' = stripQuotations qop' -- It seems ejabberd gives us an errorous "auth" instead of auth |
||||
in |
||||
-- -- Verify that the realm is the same as the Jabber host. |
||||
-- case realm'' == h of |
||||
-- True -> |
||||
|
||||
-- Verify that QOP is "auth", charset is "utf-8" and that |
||||
-- the algorithm is "md5-sess". |
||||
case qop'' == "auth" of |
||||
True -> |
||||
case charset' == "utf-8" of |
||||
True -> |
||||
case algorithm' == "md5-sess" of |
||||
True -> |
||||
|
||||
-- All data is valid; generate the reply. |
||||
Left (reply nonce'' qop'') |
||||
|
||||
-- Errors are caught and reported below. |
||||
False -> Right C1UnsupportedAlgorithm |
||||
False -> Right C1UnsupportedCharset |
||||
False -> Right C1UnsupportedQOP |
||||
-- False -> Right C1WrongRealm |
||||
_ -> Right C1NotAllParametersPresent |
||||
where |
||||
reply n q = |
||||
let -- We start with what's in RFC 2831 is referred to as "A1", a 16 octet |
||||
-- MD5 hash. |
||||
|
||||
-- If the username or password values are in ISO-8859-1, we convert |
||||
-- them to ISO-8859-1 strings. |
||||
username = case all isLatin1 u of |
||||
True -> DBL.pack $ map c2w u |
||||
False -> DBLC.pack $ u |
||||
password = case all isLatin1 p of |
||||
True -> DBL.pack $ map c2w p |
||||
False -> DBLC.pack p |
||||
|
||||
nc = "00000001" |
||||
digestUri = "xmpp/" ++ h |
||||
|
||||
-- Build the "{ username-value, ":", realm-value, ":", passwd }" |
||||
-- bytestring, the rest of the bytestring and then join them. |
||||
a1a = DBi.encode $ md5 $ DBLC.append |
||||
(DBLC.append username (DBLC.pack (":" ++ h ++ ":"))) |
||||
password |
||||
a1aDebug = "DBi.encode $ md5 $ " ++ (DBLC.unpack $ DBLC.append |
||||
(DBLC.append username (DBLC.pack (":" ++ h ++ ":"))) |
||||
password) |
||||
a1b = DBLC.pack (":" ++ n ++ ":" ++ c) |
||||
a1 = DBLC.append a1a a1b |
||||
|
||||
-- Generate the "A2" value. |
||||
a2 = DBLC.pack ("AUTHENTICATE:" ++ digestUri) |
||||
|
||||
-- Produce the responseValue. |
||||
k = DBLC.pack (show $ md5 a1) |
||||
colon = DBLC.pack ":" |
||||
s0 = DBLC.pack (n ++ ":" ++ nc ++ ":" ++ c ++ ":" ++ |
||||
q ++ ":") |
||||
s1 = DBLC.pack $ show $ md5 a2 |
||||
|
||||
s_ = DBLC.append s0 s1 |
||||
-- append k:d and 16 octet hash it |
||||
kd = md5 (DBLC.append k (DBLC.append colon s_)) |
||||
|
||||
lol0 = DBLC.unpack s_ |
||||
lol1 = show kd |
||||
|
||||
response = show kd |
||||
in "username=\"" ++ u ++ "\",realm=\"" ++ h ++ "\",nonce=\"" ++ n ++ |
||||
"\",cnonce=\"" ++ c ++ "\",nc=" ++ nc ++ ",digest-uri=\"" ++ |
||||
digestUri ++ "\",qop=auth,response=" ++ response ++ ",charset=utf-8" |
||||
-- "\n\n" ++ |
||||
-- "a1aDebug: " ++ a1aDebug ++ "\n" ++ |
||||
-- "a1b: " ++ (DBLC.unpack a1b) ++ "\n" ++ |
||||
-- "a1: " ++ (DBLC.unpack a1) ++ "\n" ++ |
||||
-- "a2: " ++ (DBLC.unpack a2) ++ "\n" ++ |
||||
-- "k: " ++ (DBLC.unpack k) ++ "\n" ++ |
||||
-- "colon: " ++ (DBLC.unpack colon) ++ "\n" ++ |
||||
-- "s0: " ++ (DBLC.unpack s0) ++ "\n" ++ |
||||
-- "s1: " ++ (DBLC.unpack s1) ++ "\n" ++ |
||||
-- "s_: " ++ (DBLC.unpack s_) ++ "\n" |
||||
|
||||
|
||||
-- Stripts the quotations around a string, if any; "\"hello\"" becomes "hello". |
||||
|
||||
stripQuotations :: String -> String |
||||
stripQuotations "" = "" |
||||
stripQuotations s | (head s == '"') && (last s == '"') = tail $ init s |
||||
| otherwise = s |
||||
@ -0,0 +1,758 @@
@@ -0,0 +1,758 @@
|
||||
{- |
||||
|
||||
Copyright © 2010-2011 Jon Kristensen. |
||||
|
||||
This file is part of Pontarius XMPP. |
||||
|
||||
Pontarius XMPP is free software: you can redistribute it and/or modify it under |
||||
the terms of the GNU Lesser General Public License as published by the Free |
||||
Software Foundation, either version 3 of the License, or (at your option) any |
||||
later version. |
||||
|
||||
Pontarius XMPP 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 Lesser General Public License for more |
||||
details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along |
||||
with Pontarius XMPP. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
-} |
||||
|
||||
-- TODO: Better functions and events for stanzas, IncomingIQ, OutgoingIQ, etc. (ClientSession, ClientStanza) |
||||
|
||||
-- TODO: IO function to do everything related to the handle, instead of just connecting. |
||||
|
||||
-- TODO: Enumerate in the same thread? Enumerate one element at the time, non-blocking? |
||||
|
||||
-- I believe we need to use the MultiParamTypeClasses extension to be able to |
||||
-- work with arbitrary client states (solving the problem that the ClientState |
||||
-- type class is solving). However, I would be happy if someone proved me wrong. |
||||
|
||||
{-# LANGUAGE MultiParamTypeClasses #-} |
||||
|
||||
-- | |
||||
-- Module: $Header$ |
||||
-- Description: XMPP client session management module |
||||
-- Copyright: Copyright © 2010-2011 Jon Kristensen |
||||
-- License: LGPL-3 |
||||
-- |
||||
-- Maintainer: info@pontarius.org |
||||
-- Stability: unstable |
||||
-- Portability: portable |
||||
-- |
||||
-- This module provides the functions used by XMPP clients to manage their XMPP |
||||
-- sessions. |
||||
-- |
||||
-- Working with Pontarius XMPP is mostly done asynchronously with callbacks; |
||||
-- Pontarius XMPP "owns" the XMPP thread and carries the client state with it. A |
||||
-- client consists of a list of client handlers to handle XMPP events. This is |
||||
-- all set up through a @Session@ object, which a client can create by calling |
||||
-- the (blocking) function @createSession@. |
||||
-- |
||||
-- The Pontarius XMPP functions operate in an arbitrary MonadIO monad. |
||||
-- Typically, clients will use the IO monad. |
||||
-- |
||||
-- For more information, see the Pontarius XMPP Manual. |
||||
|
||||
module Network.XMPP.Session ( ClientHandler (..) |
||||
, ClientState (..) |
||||
, ConnectResult (..) |
||||
, Session |
||||
, TerminationReason |
||||
, OpenStreamResult (..) |
||||
, SecureWithTLSResult (..) |
||||
, AuthenticateResult (..) |
||||
, sendPresence |
||||
, sendIQ |
||||
, sendMessage |
||||
, connect |
||||
, openStream |
||||
, secureWithTLS |
||||
, authenticate |
||||
, session |
||||
, injectAction |
||||
, getID ) where |
||||
|
||||
import Network.XMPP.Address |
||||
import Network.XMPP.SASL |
||||
import Network.XMPP.Stanza |
||||
import Network.XMPP.Stream |
||||
import Network.XMPP.TLS |
||||
import Network.XMPP.Types |
||||
import Network.XMPP.Utilities |
||||
|
||||
import qualified Control.Exception as CE |
||||
import qualified Control.Exception.Base as CEB -- ? |
||||
import qualified Control.Monad.Error as CME |
||||
import qualified Control.Monad.State as CMS |
||||
import qualified Network as N |
||||
|
||||
------------- |
||||
|
||||
import Control.Concurrent.MVar |
||||
|
||||
import Codec.Binary.UTF8.String |
||||
import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan) |
||||
import Control.Concurrent (ThreadId, forkIO, killThread, threadDelay) |
||||
import Control.Monad.IO.Class (liftIO, MonadIO) |
||||
import Control.Monad.State hiding (State) |
||||
import Data.Enumerator (($$), Iteratee, continue, joinI, |
||||
run, run_, yield) |
||||
import Data.Enumerator.Binary (enumHandle, enumFile) |
||||
import Data.Maybe |
||||
import Data.String |
||||
import Data.XML.Types |
||||
import GHC.IO.Handle (Handle, hPutStr, hFlush, hSetBuffering, hWaitForInput) |
||||
import Network.TLS |
||||
import Network.TLS.Cipher |
||||
import System.IO (BufferMode, BufferMode(NoBuffering)) |
||||
import Text.XML.Enumerator.Parse (parseBytes, decodeEntities) |
||||
import Text.XML.Enumerator.Document (fromEvents) |
||||
import qualified Codec.Binary.Base64.String as CBBS |
||||
import qualified Data.ByteString as DB |
||||
import qualified Data.ByteString.Lazy as DBL (ByteString, append, pack, fromChunks, toChunks, null) |
||||
import qualified Data.ByteString.Lazy.Char8 as DBLC (append, pack, unpack) |
||||
import qualified Data.Enumerator as E |
||||
import qualified Data.Enumerator.List as EL |
||||
import qualified Data.List as DL |
||||
import qualified Data.Text as DT |
||||
import qualified Data.Text.Lazy as DTL |
||||
|
||||
|
||||
|
||||
-- ============================================================================= |
||||
-- EXPORTED TYPES AND FUNCTIONS |
||||
-- ============================================================================= |
||||
|
||||
|
||||
-- | The @Session@ object is used by clients when interacting with Pontarius |
||||
-- XMPP. It holds information needed by Pontarius XMPP; its content is not |
||||
-- accessible from the client. |
||||
|
||||
data Session s m = Session { sessionChannel :: Chan (InternalEvent s m) |
||||
, sessionIDGenerator :: IDGenerator } |
||||
|
||||
|
||||
-- | A client typically needs one or more @ClientHandler@ objects to interact |
||||
-- with Pontarius XMPP. Each client handler may provide four callback |
||||
-- functions; the first three callbacks deals with received stanzas, and the |
||||
-- last one is used when the session is terminated. |
||||
-- |
||||
-- These stanza functions takes the current client state and an object |
||||
-- containing the details of the stanza in question. The boolean returned |
||||
-- along with the possibly updated state signals whether or not the message |
||||
-- should be blocked to client handlerss further down the stack. For example, |
||||
-- an XEP-0030: Service Discovery handler may choose to hide disco\#info |
||||
-- requests to handlers above it in the stack. |
||||
-- |
||||
-- The 'sessionTerminated' callback function takes a 'TerminationReason' value |
||||
-- along with the state and will be sent to all client handlers. |
||||
|
||||
data MonadIO m => ClientHandler s m = |
||||
ClientHandler { messageReceived :: Maybe (Message -> StateT s m Bool) |
||||
, presenceReceived :: Maybe (Presence -> StateT s m Bool) |
||||
, iqReceived :: Maybe (IQ -> StateT s m Bool) |
||||
, sessionTerminated :: Maybe (TerminationReason -> |
||||
StateT s m ()) } |
||||
|
||||
|
||||
-- | @TerminationReason@ contains information on why the XMPP session was |
||||
-- terminated. |
||||
|
||||
data TerminationReason = WhateverReason -- TODO |
||||
|
||||
|
||||
-- | Creates an XMPP session. Blocks the current thread. The first parameter, |
||||
-- @s@, is an arbitrary state that is defined by the client. This is the |
||||
-- initial state, and it will be passed to the client (handlers) as XMPP |
||||
-- events are emitted. The second parameter is the list of @ClientHandler@s; |
||||
-- this is a way to provide a "layered" system of XMPP event handlers. For |
||||
-- example, a client may have a dedicated handler to manage messages, |
||||
-- implement a spam protection system, etc. Messages are piped through these |
||||
-- handlers one by one, and any handler may block the message from being sent |
||||
-- to the next handler(s) above in the stack. The third argument is a callback |
||||
-- function that will be called when the session has been initialized, and |
||||
-- this function should be used by the client to store the Session object in |
||||
-- its state. |
||||
|
||||
-- Creates the internal event channel, injects the Pontarius XMPP session object |
||||
-- into the ClientState object, runs the "session created" client callback (in |
||||
-- the new state context), and stores the updated client state in s''. Finally, |
||||
-- we launch the (main) state loop of Pontarius XMPP. |
||||
|
||||
session :: (MonadIO m, ClientState s m) => s -> [ClientHandler s m] -> |
||||
(CMS.StateT s m ()) -> m () |
||||
|
||||
session s h c = do |
||||
threadID <- liftIO $ newEmptyMVar |
||||
chan <- liftIO $ newChan |
||||
idGenerator <- liftIO $ idGenerator "" -- TODO: Prefix |
||||
((), clientState) <- runStateT c (putSession s $ session_ chan idGenerator) |
||||
(result, _) <- runStateT (stateLoop chan) |
||||
(defaultState chan threadID h clientState idGenerator) |
||||
case result of |
||||
Just (CE.SomeException e) -> do |
||||
liftIO $ putStrLn "Got an exception!" |
||||
threadID' <- liftIO $ tryTakeMVar threadID |
||||
case threadID' of |
||||
Nothing -> do |
||||
liftIO $ putStrLn "No thread ID to kill" |
||||
Just t -> do |
||||
liftIO $ putStrLn "Killing thread" |
||||
liftIO $ killThread t |
||||
CE.throw e |
||||
Nothing -> |
||||
return () |
||||
where |
||||
-- session :: Chan (InternalEvent m s) -> Session m s -- TODO |
||||
session_ c i = Session { sessionChannel = c, sessionIDGenerator = i } |
||||
|
||||
|
||||
defaultState :: (MonadIO m, ClientState s m) => Chan (InternalEvent s m) -> MVar ThreadId -> |
||||
[ClientHandler s m] -> s -> IDGenerator -> State s m |
||||
|
||||
defaultState c t h s i = State { stateClientHandlers = h |
||||
, stateClientState = s |
||||
, stateChannel = c |
||||
, stateConnectionState = Disconnected |
||||
, stateStreamState = PreStream |
||||
, stateTLSState = NoTLS |
||||
, stateOpenStreamCallback = Nothing |
||||
, stateSecureWithTLSCallback = Nothing |
||||
, stateAuthenticateCallback = Nothing |
||||
, stateAuthenticationState = NoAuthentication |
||||
, stateResource = Nothing |
||||
, stateShouldExit = False |
||||
, stateThreadID = t |
||||
, statePresenceCallbacks = [] |
||||
, stateMessageCallbacks = [] |
||||
, stateIQCallbacks = [] |
||||
, stateTimeoutStanzaIDs = [] |
||||
, stateIDGenerator = i } -- TODO: Prefix |
||||
|
||||
|
||||
connect :: MonadIO m => Session s m -> HostName -> PortNumber -> |
||||
Maybe (Certificate, (Certificate -> Bool)) -> |
||||
Maybe (UserName, Password, Maybe Resource) -> |
||||
(ConnectResult -> StateT s m ()) -> StateT s m () |
||||
|
||||
connect s h p t a c = openStream s h p connect' |
||||
where |
||||
connect' r = case r of |
||||
OpenStreamSuccess _ _ -> case t of -- TODO: Check for TLS support? |
||||
Just (certificate, certificateValidator) -> |
||||
secureWithTLS s certificate certificateValidator connect'' |
||||
Nothing -> connect'' (SecureWithTLSSuccess 1.0 "") -- TODO |
||||
OpenStreamFailure -> c ConnectOpenStreamFailure |
||||
connect'' r = case r of |
||||
SecureWithTLSSuccess _ _ -> case a of |
||||
Just (userName, password, resource) -> |
||||
authenticate s userName password resource connect''' |
||||
Nothing -> connect''' (AuthenticateSuccess 1.0 "" "todo") -- TODO |
||||
SecureWithTLSFailure -> c ConnectSecureWithTLSFailure |
||||
connect''' r = case r of |
||||
AuthenticateSuccess streamProperties streamFeatures resource -> |
||||
c (ConnectSuccess streamProperties streamFeatures (Just resource)) |
||||
AuthenticateFailure -> c ConnectAuthenticateFailure |
||||
|
||||
|
||||
openStream :: MonadIO m => Session s m -> HostName -> PortNumber -> |
||||
(OpenStreamResult -> StateT s m ()) -> StateT s m () |
||||
|
||||
openStream s h p c = CMS.get >>= |
||||
(\ state -> lift $ liftIO $ writeChan (sessionChannel s) |
||||
(IEC (CEOpenStream h p c))) |
||||
|
||||
|
||||
secureWithTLS :: MonadIO m => Session s m -> Certificate -> |
||||
(Certificate -> Bool) -> |
||||
(SecureWithTLSResult -> StateT s m ()) -> StateT s m () |
||||
|
||||
secureWithTLS s c a c_ = CMS.get >>= |
||||
(\ state -> lift $ liftIO $ |
||||
writeChan (sessionChannel s) |
||||
(IEC (CESecureWithTLS c a c_))) |
||||
|
||||
|
||||
-- | |
||||
|
||||
authenticate :: MonadIO m => Session s m -> UserName -> Password -> |
||||
Maybe Resource -> (AuthenticateResult -> StateT s m ()) -> |
||||
StateT s m () |
||||
|
||||
authenticate s u p r c = CMS.get >>= |
||||
(\ state -> lift $ liftIO $ |
||||
writeChan (sessionChannel s) |
||||
(IEC (CEAuthenticate u p r c))) |
||||
|
||||
|
||||
sendMessage :: MonadIO m => Session s m -> Message -> Maybe (Message -> StateT s m Bool) -> Maybe (Timeout, StateT s m ()) -> Maybe (StreamError -> StateT s m ()) -> StateT s m () |
||||
sendMessage se m c t st = CMS.get >>= |
||||
(\ state -> lift $ liftIO $ |
||||
writeChan (sessionChannel se) |
||||
(IEC (CEMessage m c t st))) |
||||
|
||||
sendPresence :: MonadIO m => Session s m -> Presence -> Maybe (Presence -> StateT s m Bool) -> Maybe (Timeout, StateT s m ()) -> Maybe (StreamError -> StateT s m ()) -> StateT s m () |
||||
sendPresence se p c t st = CMS.get >>= |
||||
(\ state -> lift $ liftIO $ |
||||
writeChan (sessionChannel se) |
||||
(IEC (CEPresence p c t st))) |
||||
|
||||
sendIQ :: MonadIO m => Session s m -> IQ -> Maybe (IQ -> StateT s m Bool) -> Maybe (Timeout, StateT s m ()) -> Maybe (StreamError -> StateT s m ()) -> StateT s m () |
||||
sendIQ se i c t st = CMS.get >>= |
||||
(\ state -> lift $ liftIO $ |
||||
writeChan (sessionChannel se) |
||||
(IEC (CEIQ i c t st))) |
||||
|
||||
injectAction :: MonadIO m => Session s m -> Maybe (StateT s m Bool) -> StateT s m () -> StateT s m () |
||||
injectAction s p a = CMS.get >>= |
||||
(\ state -> lift $ liftIO $ |
||||
writeChan (sessionChannel s) |
||||
(IEC (CEAction p a))) |
||||
|
||||
getID :: MonadIO m => Session s m -> StateT s m String |
||||
getID s = CMS.get >>= \ state -> lift $ liftIO $ nextID (sessionIDGenerator s) >>= \ id -> return id |
||||
|
||||
-- xmppDisconnect :: MonadIO m => Session s m -> Maybe (s -> (Bool, s)) -> m () |
||||
-- xmppDisconnect s c = xmppDisconnect s c |
||||
|
||||
class ClientState s m where |
||||
putSession :: s -> Session s m -> s |
||||
|
||||
|
||||
-- ============================================================================= |
||||
-- INTERNAL TYPES AND FUNCTIONS |
||||
-- ============================================================================= |
||||
|
||||
|
||||
type OpenStreamCallback s m = Maybe (OpenStreamResult -> CMS.StateT s m ()) |
||||
|
||||
type SecureWithTLSCallback s m = Maybe (SecureWithTLSResult -> CMS.StateT s m ()) |
||||
|
||||
type AuthenticateCallback s m = Maybe (AuthenticateResult -> CMS.StateT s m ()) |
||||
|
||||
|
||||
isConnected :: ConnectionState -> Bool |
||||
isConnected Disconnected = True |
||||
isConnected (Connected _ _) = True |
||||
|
||||
data MonadIO m => State s m = |
||||
State { stateClientHandlers :: [ClientHandler s m] |
||||
, stateClientState :: s |
||||
, stateChannel :: Chan (InternalEvent s m) |
||||
, stateConnectionState :: ConnectionState -- s m |
||||
, stateTLSState :: TLSState |
||||
, stateStreamState :: StreamState |
||||
, stateOpenStreamCallback :: OpenStreamCallback s m |
||||
, stateSecureWithTLSCallback :: SecureWithTLSCallback s m |
||||
, stateAuthenticateCallback :: AuthenticateCallback s m |
||||
, stateAuthenticationState :: AuthenticationState |
||||
, stateResource :: Maybe Resource |
||||
, stateShouldExit :: Bool |
||||
, stateThreadID :: MVar ThreadId |
||||
, statePresenceCallbacks :: [(StanzaID, (Presence -> StateT s m Bool))] |
||||
, stateMessageCallbacks :: [(StanzaID, (Message -> StateT s m Bool))] |
||||
, stateIQCallbacks :: [(StanzaID, (IQ -> StateT s m Bool))] |
||||
, stateTimeoutStanzaIDs :: [StanzaID] |
||||
, stateIDGenerator :: IDGenerator |
||||
} |
||||
|
||||
|
||||
-- Repeatedly reads internal events from the channel and processes them. This is |
||||
-- the main loop of the XMPP session process. |
||||
|
||||
-- The main loop of the XMPP library runs in the following monads: |
||||
-- |
||||
-- m, m => MonadIO (from the client) |
||||
-- StateT |
||||
-- ErrorT |
||||
|
||||
-- TODO: Will >> carry the updated state? |
||||
-- TODO: Should InternalState be in both places? |
||||
|
||||
stateLoop :: (MonadIO m, ClientState s m) => Chan (InternalEvent s m) -> |
||||
StateT (State s m) m (Maybe CE.SomeException) |
||||
|
||||
stateLoop c = do |
||||
event <- lift $ liftIO $ readChan c |
||||
lift $ liftIO $ putStrLn $ "Processing event " ++ (show event) ++ "." |
||||
result <- (processEvent event) |
||||
state <- get |
||||
case result of |
||||
Nothing -> do |
||||
case stateShouldExit state of |
||||
True -> |
||||
return $ Nothing |
||||
False -> |
||||
stateLoop c |
||||
Just e -> |
||||
return $ Just e |
||||
|
||||
|
||||
-- Process an InternalEvent and performs the necessary IO and updates the state |
||||
-- accordingly. |
||||
|
||||
processEvent :: (MonadIO m, ClientState s m) => (InternalEvent s m) -> |
||||
(StateT (State s m) m) (Maybe CE.SomeException) |
||||
|
||||
processEvent e = get >>= \ state -> |
||||
let handleOrTLSCtx = case stateTLSState state of |
||||
PostHandshake tlsCtx -> |
||||
Right tlsCtx |
||||
_ -> |
||||
let Connected _ handle = stateConnectionState state in Left handle |
||||
in case e of |
||||
|
||||
-- --------------------------------------------------------------------------- |
||||
-- CLIENT EVENTS |
||||
-- --------------------------------------------------------------------------- |
||||
-- |
||||
IEC (CEOpenStream hostName portNumber callback) -> do |
||||
|
||||
CEB.assert (stateConnectionState state == Disconnected) (return ()) |
||||
|
||||
let portNumber' = fromIntegral portNumber |
||||
|
||||
connectResult <- liftIO $ CE.try $ N.connectTo hostName |
||||
(N.PortNumber portNumber') |
||||
|
||||
case connectResult of |
||||
Right handle -> do |
||||
put $ state { stateConnectionState = Connected (ServerAddress hostName portNumber') handle |
||||
, stateStreamState = PreStream |
||||
, stateOpenStreamCallback = Just callback } |
||||
lift $ liftIO $ hSetBuffering handle NoBuffering |
||||
lift $ liftIO $ send ("<?xml version='1.0'?><stream:stream to='" ++ hostName ++ |
||||
"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.or" ++ |
||||
"g/streams' version='1.0'>") (Left handle) |
||||
threadID <- lift $ liftIO $ forkIO $ xmlEnumerator (stateChannel state) (Left handle) |
||||
lift $ liftIO $ putMVar (stateThreadID state) threadID |
||||
return Nothing |
||||
Left e -> do |
||||
let clientState = stateClientState state |
||||
((), clientState') <- lift $ runStateT (callback OpenStreamFailure) clientState |
||||
put $ state { stateShouldExit = True } |
||||
return $ Just e |
||||
|
||||
IEC (CESecureWithTLS certificate verifyCertificate callback) -> do |
||||
-- CEB.assert (not $ isTLSSecured (stateStreamState state)) (return ()) |
||||
let Connected _ handle = stateConnectionState state |
||||
lift $ liftIO $ send "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" (Left handle) |
||||
put $ state { stateStreamState = PreStream |
||||
, stateSecureWithTLSCallback = Just callback } |
||||
return Nothing |
||||
|
||||
-- TODO: Save callback in state. |
||||
IEC (CEAuthenticate userName password resource callback) -> do |
||||
-- CEB.assert (or [ stateConnectionState state == Connected |
||||
-- , stateConnectionState state == TLSSecured ]) (return ()) |
||||
-- CEB.assert (stateHandle state /= Nothing) (return ()) |
||||
put $ state { stateAuthenticationState = AuthenticatingPreChallenge1 userName password resource |
||||
, stateAuthenticateCallback = Just callback } |
||||
lift $ liftIO $ send "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>" handleOrTLSCtx |
||||
return Nothing |
||||
|
||||
IEE (EnumeratorXML (XEBeginStream stream)) -> do |
||||
put $ state { stateStreamState = PreFeatures (1.0) } |
||||
return Nothing |
||||
|
||||
IEE (EnumeratorXML (XEFeatures features)) -> do |
||||
let PreFeatures streamProperties = stateStreamState state |
||||
case stateTLSState state of |
||||
NoTLS -> let callback = fromJust $ stateOpenStreamCallback state in do |
||||
((), clientState) <- lift $ runStateT (callback $ OpenStreamSuccess streamProperties "TODO") (stateClientState state) |
||||
put $ state { stateClientState = clientState |
||||
, stateStreamState = PostFeatures streamProperties "TODO" } |
||||
return Nothing |
||||
_ -> case stateAuthenticationState state of |
||||
AuthenticatedUnbound _ resource -> do -- TODO: resource |
||||
case resource of |
||||
Nothing -> do |
||||
lift $ liftIO $ send ("<iq type=\"set\" id=\"bind_1\"><bind xmlns=\"urn:ietf:param" ++ "s:xml:ns:xmpp-bind\"></bind></iq>") handleOrTLSCtx |
||||
return () |
||||
_ -> do |
||||
lift $ liftIO $ send ("<iq type=\"set\" id=\"bind_1\"><bind xmlns=\"urn:ietf:param" ++ "s:xml:ns:xmpp-bind\"><resource>" ++ fromJust resource ++ "</resource></bind></iq>") handleOrTLSCtx |
||||
return () |
||||
id <- liftIO $ nextID $ stateIDGenerator state |
||||
lift $ liftIO $ send ("<iq type=\"set\" id=\"" ++ id ++ "\"><session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>" ++ "</iq>") handleOrTLSCtx |
||||
|
||||
-- TODO: Execute callback on iq result |
||||
|
||||
let callback = fromJust $ stateAuthenticateCallback state in do -- TODO: streamProperties "TODO" after success |
||||
((), clientState) <- lift $ runStateT (callback $ AuthenticateSuccess streamProperties "TODO" "todo") (stateClientState state) -- get proper resource value when moving to iq result |
||||
put $ state { stateClientState = clientState |
||||
, stateStreamState = PostFeatures streamProperties "TODO" } |
||||
state' <- get |
||||
return Nothing |
||||
_ -> do |
||||
let callback = fromJust $ stateSecureWithTLSCallback state in do |
||||
((), clientState) <- lift $ runStateT (callback $ SecureWithTLSSuccess streamProperties "TODO") (stateClientState state) |
||||
put $ state { stateClientState = clientState |
||||
, stateStreamState = PostFeatures streamProperties "TODO" } |
||||
return Nothing |
||||
|
||||
-- TODO: Can we assume that it's safe to start to enumerate on handle when it |
||||
-- might not have exited? |
||||
IEE (EnumeratorXML XEProceed) -> do |
||||
let Connected (ServerAddress hostName _) handle = stateConnectionState state |
||||
tlsCtx <- lift $ liftIO $ handshake' handle hostName |
||||
let tlsCtx_ = fromJust tlsCtx |
||||
put $ (defaultState (stateChannel state) (stateThreadID state) (stateClientHandlers state) (stateClientState state) (stateIDGenerator state)) { stateTLSState = PostHandshake tlsCtx_, stateConnectionState = (stateConnectionState state), stateSecureWithTLSCallback = (stateSecureWithTLSCallback state) } |
||||
threadID <- lift $ liftIO $ forkIO $ xmlEnumerator (stateChannel state) (Right tlsCtx_) -- double code |
||||
lift $ liftIO $ putStrLn "00000000000000000000000000000000" |
||||
lift $ liftIO $ swapMVar (stateThreadID state) threadID -- return value not used |
||||
lift $ liftIO $ putStrLn "00000000000000000000000000000000" |
||||
lift $ liftIO $ threadDelay 1000000 |
||||
lift $ liftIO $ putStrLn "00000000000000000000000000000000" |
||||
lift $ liftIO $ send ("<?xml version='1.0'?><stream:stream to='" ++ |
||||
hostName ++ "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/" ++ |
||||
"streams' version='1.0'>") (Right tlsCtx_) |
||||
lift $ liftIO $ putStrLn "00000000000000000000000000000000" |
||||
return Nothing |
||||
|
||||
IEE (EnumeratorXML (XEChallenge (Chal challenge))) -> do |
||||
let serverHost = "jonkristensen.com" |
||||
let challenge' = CBBS.decode challenge |
||||
case stateAuthenticationState state of |
||||
AuthenticatingPreChallenge1 userName password resource -> do |
||||
id <- liftIO $ nextID $ stateIDGenerator state |
||||
-- This is the first challenge - we need to calculate the reply |
||||
case replyToChallenge1 challenge' serverHost userName password id of |
||||
Left reply -> do |
||||
let reply' = (filter (/= '\n') (CBBS.encode reply)) |
||||
lift $ liftIO $ send ("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" ++ reply' ++ "</response>") handleOrTLSCtx |
||||
put $ state { stateAuthenticationState = AuthenticatingPreChallenge2 userName password resource } |
||||
return () |
||||
Right error -> do |
||||
state' <- get |
||||
lift $ liftIO $ putStrLn $ show error |
||||
return () |
||||
AuthenticatingPreChallenge2 userName password resource -> do |
||||
-- This is not the first challenge; [...] |
||||
-- TODO: Can we assume "rspauth"? |
||||
lift $ liftIO $ send "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>" handleOrTLSCtx |
||||
put $ state { stateAuthenticationState = AuthenticatingPreSuccess userName password resource } |
||||
return () |
||||
return Nothing |
||||
|
||||
-- We have received a SASL "success" message over a secured connection |
||||
-- TODO: Parse the success message? |
||||
-- TODO: <?xml version='1.0'?>? |
||||
IEE (EnumeratorXML (XESuccess (Succ _))) -> do |
||||
let serverHost = "jonkristensen.com" |
||||
let AuthenticatingPreSuccess userName _ resource = stateAuthenticationState state in do |
||||
lift $ liftIO $ send ("<?xml version='1.0'?><stream:stream to='" ++ serverHost ++ "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/" ++ "streams' version='1.0'>") handleOrTLSCtx |
||||
put $ state { stateAuthenticationState = AuthenticatedUnbound userName resource } |
||||
return Nothing |
||||
|
||||
IEE EnumeratorDone -> |
||||
-- TODO: Exit? |
||||
return Nothing |
||||
|
||||
-- --------------------------------------------------------------------------- |
||||
-- XML EVENTS |
||||
-- --------------------------------------------------------------------------- |
||||
|
||||
-- Ignore id="bind_1" and session IQ result, otherwise create client event |
||||
IEE (EnumeratorXML (XEIQ iqEvent)) -> |
||||
case shouldIgnoreIQ iqEvent of |
||||
True -> |
||||
return Nothing |
||||
False -> do |
||||
let stanzaID' = iqID iqEvent |
||||
let newTimeouts = case stanzaID' of |
||||
Just stanzaID'' -> |
||||
case stanzaID'' `elem` (stateTimeoutStanzaIDs state) of |
||||
True -> filter (\ e -> e /= stanzaID'') (stateTimeoutStanzaIDs state) |
||||
False -> (stateTimeoutStanzaIDs state) |
||||
Nothing -> (stateTimeoutStanzaIDs state) |
||||
let iqReceivedFunctions = map (\ x -> iqReceived x) (stateClientHandlers state) |
||||
let functions = map (\ x -> case x of |
||||
Just f -> Just (f iqEvent) |
||||
Nothing -> Nothing) iqReceivedFunctions |
||||
let functions' = case lookup (fromJust $ iqID $ iqEvent) (stateIQCallbacks state) of |
||||
Just f -> (Just (f $ iqEvent)):functions |
||||
Nothing -> functions |
||||
let clientState = stateClientState state |
||||
clientState' <- sendToClient functions' clientState |
||||
put $ state { stateClientState = clientState', stateTimeoutStanzaIDs = newTimeouts } |
||||
return Nothing |
||||
|
||||
IEE (EnumeratorXML (XEPresence presenceEvent)) -> do |
||||
let stanzaID' = presenceID $ presenceEvent |
||||
let newTimeouts = case stanzaID' of |
||||
Just stanzaID'' -> |
||||
case stanzaID'' `elem` (stateTimeoutStanzaIDs state) of |
||||
True -> filter (\ e -> e /= stanzaID'') (stateTimeoutStanzaIDs state) |
||||
False -> (stateTimeoutStanzaIDs state) |
||||
Nothing -> (stateTimeoutStanzaIDs state) |
||||
let presenceReceivedFunctions = map (\ x -> presenceReceived x) (stateClientHandlers state) |
||||
let functions = map (\ x -> case x of |
||||
Just f -> Just (f presenceEvent) |
||||
Nothing -> Nothing) presenceReceivedFunctions |
||||
let clientState = stateClientState state -- ClientState s m |
||||
clientState' <- sendToClient functions clientState |
||||
put $ state { stateClientState = clientState', stateTimeoutStanzaIDs = newTimeouts } |
||||
return Nothing |
||||
|
||||
IEE (EnumeratorXML (XEMessage messageEvent)) -> do |
||||
let stanzaID' = messageID $ messageEvent |
||||
let newTimeouts = case stanzaID' of |
||||
Just stanzaID'' -> |
||||
case stanzaID'' `elem` (stateTimeoutStanzaIDs state) of |
||||
True -> filter (\ e -> e /= stanzaID'') (stateTimeoutStanzaIDs state) |
||||
False -> (stateTimeoutStanzaIDs state) |
||||
Nothing -> (stateTimeoutStanzaIDs state) |
||||
let messageReceivedFunctions = map (\ x -> messageReceived x) (stateClientHandlers state) |
||||
let functions = map (\ x -> case x of |
||||
Just f -> Just (f messageEvent) |
||||
Nothing -> Nothing) messageReceivedFunctions |
||||
let clientState = stateClientState state -- ClientState s m |
||||
clientState' <- sendToClient functions clientState |
||||
put $ state { stateClientState = clientState', stateTimeoutStanzaIDs = newTimeouts } |
||||
return Nothing |
||||
|
||||
IEC (CEPresence presence stanzaCallback timeoutCallback streamErrorCallback) -> do |
||||
presence' <- case presenceID $ presence of |
||||
Nothing -> do |
||||
id <- liftIO $ nextID $ stateIDGenerator state |
||||
return $ presence { presenceID = Just (SID id) } |
||||
_ -> return presence |
||||
case timeoutCallback of |
||||
Just (t, timeoutCallback') -> |
||||
let stanzaID' = (fromJust $ presenceID $ presence') in do |
||||
registerTimeout (stateChannel state) stanzaID' t timeoutCallback' |
||||
put $ state { stateTimeoutStanzaIDs = stanzaID':(stateTimeoutStanzaIDs state) } |
||||
Nothing -> |
||||
return () |
||||
let xml = presenceToXML presence' |
||||
lift $ liftIO $ send xml handleOrTLSCtx |
||||
return Nothing |
||||
|
||||
IEC (CEMessage message stanzaCallback timeoutCallback streamErrorCallback) -> do |
||||
message' <- case messageID message of |
||||
Nothing -> do |
||||
id <- liftIO $ nextID $ stateIDGenerator state |
||||
return $ message { messageID = Just (SID id) } |
||||
_ -> return message |
||||
case timeoutCallback of |
||||
Just (t, timeoutCallback') -> |
||||
let stanzaID' = (fromJust $ messageID message') in do |
||||
registerTimeout (stateChannel state) stanzaID' t timeoutCallback' |
||||
put $ state { stateTimeoutStanzaIDs = stanzaID':(stateTimeoutStanzaIDs state) } |
||||
Nothing -> |
||||
return () |
||||
let xml = messageToXML message' |
||||
lift $ liftIO $ send xml handleOrTLSCtx |
||||
return Nothing |
||||
|
||||
IEC (CEIQ iq stanzaCallback timeoutCallback stanzaErrorCallback) -> do |
||||
iq' <- case iqID iq of |
||||
Nothing -> do |
||||
id <- liftIO $ nextID $ stateIDGenerator state |
||||
return $ case iq of |
||||
IQReq r -> do |
||||
IQReq (r { iqRequestID = Just (SID id) }) |
||||
IQRes r -> do |
||||
IQRes (r { iqResponseID = Just (SID id) }) |
||||
_ -> return iq |
||||
case stanzaCallback of |
||||
Just callback' -> case iq of |
||||
IQReq {} -> put $ state { stateIQCallbacks = (fromJust $ iqID iq, callback'):(stateIQCallbacks state) } |
||||
_ -> return () |
||||
Nothing -> return () |
||||
case timeoutCallback of |
||||
Just (t, timeoutCallback') -> |
||||
let stanzaID' = (fromJust $ iqID iq') in do |
||||
registerTimeout (stateChannel state) stanzaID' t timeoutCallback' |
||||
put $ state { stateTimeoutStanzaIDs = stanzaID':(stateTimeoutStanzaIDs state) } |
||||
Nothing -> |
||||
return () |
||||
-- TODO: Bind ID to callback |
||||
let xml = iqToXML iq' |
||||
lift $ liftIO $ send xml handleOrTLSCtx |
||||
return Nothing |
||||
|
||||
IEC (CEAction predicate callback) -> do |
||||
case predicate of |
||||
Just predicate' -> do |
||||
result <- runBoolClientCallback predicate' |
||||
case result of |
||||
True -> do |
||||
runUnitClientCallback callback |
||||
return Nothing |
||||
False -> return Nothing |
||||
Nothing -> do |
||||
runUnitClientCallback callback |
||||
return Nothing |
||||
|
||||
-- XOEDisconnect -> do |
||||
-- -- TODO: Close stream |
||||
-- return () |
||||
|
||||
IET (TimeoutEvent i t c) -> |
||||
case i `elem` (stateTimeoutStanzaIDs state) of |
||||
True -> do |
||||
runUnitClientCallback c |
||||
return Nothing |
||||
False -> return Nothing |
||||
|
||||
|
||||
e -> do |
||||
return Nothing |
||||
-- lift $ liftIO $ putStrLn $ "UNCAUGHT EVENT: " ++ (show e) |
||||
-- return $ Just (CE.SomeException $ CE.PatternMatchFail "processEvent") |
||||
where |
||||
-- Assumes handle is set |
||||
send :: String -> Either Handle TLSCtx -> IO () |
||||
send s o = case o of |
||||
Left handle -> do |
||||
liftIO $ hPutStr handle $ encodeString $ s |
||||
liftIO $ hFlush handle |
||||
return () |
||||
Right tlsCtx -> do |
||||
liftIO $ sendData tlsCtx $ DBLC.pack $ encodeString s |
||||
return () |
||||
shouldIgnoreIQ :: IQ -> Bool |
||||
shouldIgnoreIQ i = case iqPayload i of |
||||
Nothing -> False |
||||
Just e -> case nameNamespace $ elementName e of |
||||
Just x | x == DT.pack "urn:ietf:params:xml:ns:xmpp-bind" -> True |
||||
Just x | x == DT.pack "urn:ietf:params:xml:ns:xmpp-session" -> True |
||||
Just _ -> False |
||||
Nothing -> False |
||||
|
||||
|
||||
registerTimeout :: (ClientState s m, MonadIO m) => Chan (InternalEvent s m) -> StanzaID -> Timeout -> StateT s m () -> StateT (State s m) m () |
||||
registerTimeout ch i t ca = do |
||||
liftIO $ threadDelay $ t * 1000 |
||||
liftIO $ forkIO $ writeChan ch $ IET (TimeoutEvent i t ca) |
||||
return () |
||||
|
||||
|
||||
runBoolClientCallback :: (ClientState s m, MonadIO m) => StateT s m Bool -> StateT (State s m) m Bool |
||||
runBoolClientCallback c = do |
||||
state <- get |
||||
let clientState = stateClientState state |
||||
(bool, clientState') <- lift $ runStateT c clientState |
||||
put $ state { stateClientState = clientState' } |
||||
return bool |
||||
|
||||
|
||||
runUnitClientCallback :: (ClientState s m, MonadIO m) => StateT s m () -> StateT (State s m) m () |
||||
runUnitClientCallback c = do |
||||
state <- get |
||||
let clientState = stateClientState state |
||||
((), clientState') <- lift $ runStateT c clientState |
||||
put $ state { stateClientState = clientState' } |
||||
|
||||
|
||||
sendToClient :: (MonadIO m, ClientState s m) => [Maybe (StateT s m Bool)] -> s -> (StateT (State s m) m) s |
||||
sendToClient [] s = return s |
||||
sendToClient (Nothing:fs) s = sendToClient fs s |
||||
sendToClient ((Just f):fs) s = do |
||||
(b, s') <- lift $ runStateT f s |
||||
case b of |
||||
True -> return s' |
||||
False -> sendToClient fs s' |
||||
@ -0,0 +1,182 @@
@@ -0,0 +1,182 @@
|
||||
{- |
||||
|
||||
Copyright © 2010-2011 Jon Kristensen. |
||||
|
||||
This file is part of Pontarius XMPP. |
||||
|
||||
Pontarius XMPP is free software: you can redistribute it and/or modify it under |
||||
the terms of the GNU Lesser General Public License as published by the Free |
||||
Software Foundation, either version 3 of the License, or (at your option) any |
||||
later version. |
||||
|
||||
Pontarius XMPP 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 Lesser General Public License for more |
||||
details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along |
||||
with Pontarius XMPP. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
-} |
||||
|
||||
-- | |
||||
-- Module: $Header$ |
||||
-- Description: XMPP stanza types and utility functions |
||||
-- Copyright: Copyright © 2010-2011 Jon Kristensen |
||||
-- License: LGPL-3 |
||||
-- |
||||
-- Maintainer: info@pontarius.org |
||||
-- Stability: unstable |
||||
-- Portability: portable |
||||
-- |
||||
-- The stanza record types are generally pretty convenient to work with. |
||||
-- However, due to the fact that an "IQ" can be both an "IQRequest" and an |
||||
-- "IQResponse" we provide some helper functions in this module that work on |
||||
-- both types. |
||||
-- |
||||
-- We also provide functions to create a new stanza ID generator, and to |
||||
-- generate new IDs. |
||||
|
||||
|
||||
module Network.XMPP.Stanza ( |
||||
iqID, |
||||
iqFrom, |
||||
iqTo, |
||||
iqXMLLang, |
||||
iqPayload, |
||||
iqPayloadNamespace, |
||||
iqRequestPayloadNamespace, |
||||
iqResponsePayloadNamespace, |
||||
idGenerator, |
||||
nextID |
||||
) where |
||||
|
||||
import Network.XMPP.Address |
||||
import Network.XMPP.Types |
||||
|
||||
import Data.IORef (atomicModifyIORef, newIORef) |
||||
import Data.XML.Types (Element, elementName, nameNamespace) |
||||
import Data.Text (unpack) |
||||
|
||||
|
||||
-- | |
||||
-- Returns the @StanzaID@ value of the @IQ@, if any. |
||||
|
||||
iqID :: IQ -> Maybe StanzaID |
||||
|
||||
iqID (IQReq i) = iqRequestID i |
||||
iqID (IQRes i) = iqResponseID i |
||||
|
||||
|
||||
-- | |
||||
-- Returns the @From@ @JID@ value of the @IQ@, if any. |
||||
|
||||
iqFrom :: IQ -> Maybe From |
||||
|
||||
iqFrom (IQReq i) = iqRequestFrom i |
||||
iqFrom (IQRes i) = iqResponseFrom i |
||||
|
||||
|
||||
-- | |
||||
-- Returns the @To@ @JID@ value of the @IQ@, if any. |
||||
|
||||
iqTo :: IQ -> Maybe To |
||||
|
||||
iqTo (IQReq i) = iqRequestTo i |
||||
iqTo (IQRes i) = iqResponseTo i |
||||
|
||||
|
||||
-- | |
||||
-- Returns the @XMLLang@ value of the @IQ@, if any. |
||||
|
||||
iqXMLLang :: IQ -> Maybe XMLLang |
||||
|
||||
iqXMLLang (IQReq i) = iqRequestXMLLang i |
||||
iqXMLLang (IQRes i) = iqResponseXMLLang i |
||||
|
||||
|
||||
-- | |
||||
-- Returns the @Element@ payload value of the @IQ@, if any. If the IQ in |
||||
-- question is of the "request" type, use @iqRequestPayload@ instead. |
||||
|
||||
iqPayload :: IQ -> Maybe Element |
||||
|
||||
iqPayload (IQReq i) = Just (iqRequestPayload i) |
||||
iqPayload (IQRes i) = iqResponsePayload i |
||||
|
||||
|
||||
-- | |
||||
-- Returns the namespace of the element of the @IQ@, if any. |
||||
|
||||
iqPayloadNamespace :: IQ -> Maybe String |
||||
|
||||
iqPayloadNamespace i = case iqPayload i of |
||||
Nothing -> Nothing |
||||
Just p -> case nameNamespace $ elementName p of |
||||
Nothing -> Nothing |
||||
Just n -> Just (unpack n) |
||||
|
||||
|
||||
-- | |
||||
-- Returns the namespace of the element of the @IQRequest@, if any. |
||||
|
||||
iqRequestPayloadNamespace :: IQRequest -> Maybe String |
||||
|
||||
iqRequestPayloadNamespace i = let p = iqRequestPayload i in |
||||
case nameNamespace $ elementName p of |
||||
Nothing -> Nothing |
||||
Just n -> Just (unpack n) |
||||
|
||||
|
||||
-- | |
||||
-- Returns the namespace of the element of the @IQRequest@, if any. |
||||
|
||||
iqResponsePayloadNamespace :: IQResponse -> Maybe String |
||||
|
||||
iqResponsePayloadNamespace i = case iqResponsePayload i of |
||||
Nothing -> Nothing |
||||
Just p -> case nameNamespace $ elementName p of |
||||
Nothing -> Nothing |
||||
Just n -> Just (unpack n) |
||||
|
||||
|
||||
-- | |
||||
-- Creates a new stanza "IDGenerator". Internally, it will maintain an infinite |
||||
-- list of stanza IDs ('[\'a\', \'b\', \'c\'...]'). |
||||
|
||||
idGenerator :: String -> IO IDGenerator |
||||
|
||||
idGenerator p = newIORef (ids p) >>= \ ioRef -> return $ IDGenerator ioRef |
||||
|
||||
|
||||
-- | |
||||
-- Extracts an ID from the "IDGenerator", and updates the generators internal |
||||
-- state so that the same ID will not be generated again. |
||||
|
||||
nextID :: IDGenerator -> IO String |
||||
|
||||
nextID g = let IDGenerator ioRef = g |
||||
in atomicModifyIORef ioRef (\ (i:is) -> (is, i)) |
||||
|
||||
|
||||
-- Generates an infinite and predictable list of IDs, all beginning with the |
||||
-- provided prefix. |
||||
|
||||
ids :: String -> [String] |
||||
|
||||
-- Adds the prefix to all combinations of IDs (ids'). |
||||
ids p = map (\ id -> p ++ id) ids' |
||||
where |
||||
|
||||
-- Generate all combinations of IDs, with increasing length. |
||||
ids' :: [String] |
||||
ids' = concatMap ids'' [1..] |
||||
|
||||
-- Generates all combinations of IDs with the given length. |
||||
ids'' :: Integer -> [String] |
||||
ids'' 0 = [""] |
||||
ids'' l = [x:xs | x <- repertoire, xs <- ids'' (l - 1)] |
||||
|
||||
-- Characters allowed in IDs. |
||||
repertoire :: String |
||||
repertoire = ['a'..'z'] |
||||
@ -0,0 +1,457 @@
@@ -0,0 +1,457 @@
|
||||
----------------------------------------------------------------------------- |
||||
-- |
||||
-- Module : Network.XMPP.Stream |
||||
-- Copyright : Copyright © 2011, Jon Kristensen |
||||
-- License : UnknownLicense "LGPL3" |
||||
-- |
||||
-- Maintainer : jon.kristensen@pontarius.org |
||||
-- Stability : alpha |
||||
-- Portability : |
||||
-- |
||||
-- | |
||||
-- |
||||
----------------------------------------------------------------------------- |
||||
|
||||
module Network.XMPP.Stream ( |
||||
isTLSSecured, |
||||
xmlEnumerator, |
||||
xmlReader, |
||||
presenceToXML, |
||||
iqToXML, |
||||
messageToXML, |
||||
parsePresence, |
||||
parseIQ, |
||||
parseMessage |
||||
) where |
||||
|
||||
import Network.XMPP.Address hiding (fromString) |
||||
import qualified Network.XMPP.Address as X |
||||
import Network.XMPP.Types |
||||
import Network.XMPP.Utilities |
||||
import Network.XMPP.TLS |
||||
import Network.XMPP.Stanza |
||||
import qualified Control.Exception as CE |
||||
import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan) |
||||
import GHC.IO.Handle (Handle, hPutStr, hFlush, hSetBuffering, hWaitForInput) |
||||
import Network.TLS |
||||
import Network.TLS.Cipher |
||||
import Data.Enumerator (($$), Iteratee, continue, joinI, |
||||
run, run_, yield) |
||||
import Data.Enumerator.Binary (enumHandle, enumFile) |
||||
import Text.XML.Enumerator.Parse (parseBytes, decodeEntities) |
||||
import Text.XML.Enumerator.Document (fromEvents) |
||||
import qualified Data.ByteString as DB |
||||
import qualified Data.ByteString.Lazy as DBL (ByteString, append, pack, fromChunks, toChunks, null) |
||||
import qualified Data.ByteString.Lazy.Char8 as DBLC (append, pack, unpack) |
||||
import qualified Data.Enumerator as E |
||||
import qualified Data.Enumerator.List as EL |
||||
import qualified Data.List as DL |
||||
import qualified Data.Text as DT |
||||
import qualified Data.Text.Lazy as DTL |
||||
import Data.Maybe |
||||
|
||||
import Data.XML.Types |
||||
|
||||
import Control.Monad.IO.Class (liftIO, MonadIO) |
||||
import Data.String (IsString(..)) |
||||
|
||||
isTLSSecured :: TLSState -> Bool |
||||
isTLSSecured (PostHandshake _) = True |
||||
isTLSSecured _ = False |
||||
|
||||
|
||||
-- Reads from the provided handle or TLS context and sends the events to the |
||||
-- internal event channel. |
||||
|
||||
xmlEnumerator :: Chan (InternalEvent s m) -> Either Handle TLSCtx -> IO () |
||||
xmlEnumerator c s = do |
||||
enumeratorResult <- case s of |
||||
Left handle -> run $ enumHandle 1 handle $$ joinI $ |
||||
parseBytes decodeEntities $$ xmlReader c |
||||
Right tlsCtx -> run $ enumTLS tlsCtx $$ joinI $ |
||||
parseBytes decodeEntities $$ xmlReader c |
||||
case enumeratorResult of |
||||
Right _ -> |
||||
writeChan c $ IEE EnumeratorDone |
||||
Left e -> |
||||
writeChan c $ IEE (EnumeratorException e) |
||||
where |
||||
-- Behaves like enumHandle, but reads from the TLS context instead |
||||
enumTLS :: TLSCtx -> E.Enumerator DB.ByteString IO b |
||||
enumTLS c s = loop c s |
||||
|
||||
loop :: TLSCtx -> E.Step DB.ByteString IO b -> E.Iteratee DB.ByteString IO b |
||||
loop c (E.Continue k) = do |
||||
d <- recvData c |
||||
case DBL.null d of |
||||
True -> loop c (E.Continue k) |
||||
False -> k (E.Chunks $ DBL.toChunks d) E.>>== loop c |
||||
loop _ step = E.returnI step |
||||
|
||||
|
||||
xmlReader :: Chan (InternalEvent s m) -> Iteratee Event IO (Maybe Event) |
||||
|
||||
xmlReader c = xmlReader_ c [] 0 |
||||
|
||||
|
||||
xmlReader_ :: Chan (InternalEvent s m) -> [Event] -> Int -> |
||||
Iteratee Event IO (Maybe Event) |
||||
|
||||
xmlReader_ ch [EventBeginDocument] 0 = xmlReader_ ch [] 0 |
||||
|
||||
-- TODO: Safe to start change level here? We are doing this since the stream can |
||||
-- restart. |
||||
-- TODO: l < 2? |
||||
xmlReader_ ch [EventBeginElement name attribs] l |
||||
| l < 3 && nameLocalName name == DT.pack "stream" && |
||||
namePrefix name == Just (DT.pack "stream") = do |
||||
liftIO $ writeChan ch $ IEE $ EnumeratorXML $ XEBeginStream $ "StreamTODO" |
||||
xmlReader_ ch [] 1 |
||||
|
||||
xmlReader_ ch [EventEndElement name] 1 |
||||
| namePrefix name == Just (DT.pack "stream") && |
||||
nameLocalName name == DT.pack "stream" = do |
||||
liftIO $ writeChan ch $ IEE $ EnumeratorXML $ XEEndStream |
||||
return Nothing |
||||
|
||||
-- Check if counter is one to forward it to related function. |
||||
-- Should replace "reverse ((EventEndElement n):es)" with es |
||||
-- ... |
||||
xmlReader_ ch ((EventEndElement n):es) 1 |
||||
| nameLocalName n == DT.pack "proceed" = do |
||||
liftIO $ writeChan ch $ IEE $ EnumeratorXML $ XEProceed |
||||
E.yield Nothing (E.Chunks []) |
||||
| otherwise = do |
||||
-- liftIO $ putStrLn "Got an IEX Event..." |
||||
liftIO $ writeChan ch $ IEE $ EnumeratorXML $ (processEventList (DL.reverse ((EventEndElement n):es))) |
||||
xmlReader_ ch [] 1 |
||||
|
||||
-- Normal condition, buffer the event to events list. |
||||
xmlReader_ ch es co = do |
||||
head <- EL.head |
||||
let co' = counter co head |
||||
-- liftIO $ putStrLn $ show co' ++ "\t" ++ show head -- for test |
||||
case head of |
||||
Just e -> xmlReader_ ch (e:es) co' |
||||
Nothing -> xmlReader_ ch es co' |
||||
|
||||
|
||||
-- TODO: Generate real event. |
||||
processEventList :: [Event] -> XMLEvent |
||||
processEventList e |
||||
| namePrefix name == Just (DT.pack "stream") && |
||||
nameLocalName name == DT.pack "features" = XEFeatures "FeaturesTODO" |
||||
| nameLocalName name == DT.pack "challenge" = |
||||
let EventContent (ContentText c) = head es in XEChallenge $ Chal $ DT.unpack c |
||||
| nameLocalName name == DT.pack "success" = |
||||
let EventContent (ContentText c) = head es in XESuccess $ Succ $ "" -- DT.unpack c |
||||
| nameLocalName name == DT.pack "iq" = XEIQ $ parseIQ $ eventsToElement e |
||||
| nameLocalName name == DT.pack "presence" = XEPresence $ parsePresence $ eventsToElement e |
||||
| nameLocalName name == DT.pack "message" = XEMessage $ parseMessage $ eventsToElement e |
||||
| otherwise = XEOther $ elementToString $ Just (eventsToElement e) |
||||
where |
||||
(EventBeginElement name attribs) = head e |
||||
es = tail e |
||||
|
||||
eventsToElement :: [Event] -> Element |
||||
eventsToElement e = do |
||||
documentRoot $ fromJust (run_ $ enum e $$ fromEvents) |
||||
where |
||||
enum :: [Event] -> E.Enumerator Event Maybe Document |
||||
enum e_ (E.Continue k) = k $ E.Chunks e_ |
||||
enum e_ step = E.returnI step |
||||
|
||||
counter :: Int -> Maybe Event -> Int |
||||
counter c (Just (EventBeginElement _ _)) = (c + 1) |
||||
counter c (Just (EventEndElement _) ) = (c - 1) |
||||
counter c _ = c |
||||
|
||||
presenceToXML :: Presence -> String |
||||
presenceToXML p = "<presence" ++ from ++ id' ++ to ++ type' ++ ">" ++ |
||||
(elementsToString $ presencePayload p) ++ "</presence>" |
||||
where |
||||
from :: String |
||||
from = case presenceFrom p of |
||||
-- TODO: Lower-case |
||||
Just s -> " from='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
id' :: String |
||||
id' = case presenceID p of |
||||
Just (SID s) -> " id='" ++ s ++ "'" |
||||
Nothing -> "" |
||||
|
||||
to :: String |
||||
to = case presenceTo p of |
||||
-- TODO: Lower-case |
||||
Just s -> " to='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
type' :: String |
||||
type' = case presenceType p of |
||||
Available -> "" |
||||
t -> " type='" ++ (presenceTypeToString t) ++ "'" |
||||
|
||||
iqToXML :: IQ -> String |
||||
iqToXML (IQReq (IQGet { iqRequestID = i, iqRequestPayload = p, iqRequestFrom = f, iqRequestTo = t })) = |
||||
let type' = " type='get'" in "<iq" ++ from ++ id' ++ to ++ type' ++ ">" ++ (elementToString (Just p)) ++ "</iq>" |
||||
where |
||||
from :: String |
||||
from = case f of |
||||
-- TODO: Lower-case |
||||
Just s -> " from='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
id' :: String |
||||
id' = case i of |
||||
Just (SID s) -> " id='" ++ s ++ "'" |
||||
Nothing -> "" |
||||
|
||||
to :: String |
||||
to = case t of |
||||
-- TODO: Lower-case |
||||
Just s -> " to='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
iqToXML (IQReq (IQSet { iqRequestID = i, iqRequestPayload = p, iqRequestFrom = f, iqRequestTo = t })) = |
||||
let type' = " type='set'" in "<iq" ++ from ++ id' ++ to ++ type' ++ ">" ++ (elementToString (Just p)) ++ "</iq>" |
||||
where |
||||
from :: String |
||||
from = case f of |
||||
-- TODO: Lower-case |
||||
Just s -> " from='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
id' :: String |
||||
id' = case i of |
||||
Just (SID s) -> " id='" ++ s ++ "'" |
||||
Nothing -> "" |
||||
|
||||
to :: String |
||||
to = case t of |
||||
-- TODO: Lower-case |
||||
Just s -> " to='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
iqToXML (IQRes (IQResult { iqResponseID = i, iqResponsePayload = p, iqResponseFrom = f, iqResponseTo = t })) = |
||||
let type' = " type='result'" in "<iq" ++ from ++ id' ++ to ++ type' ++ ">" ++ (elementToString p) ++ "</iq>" |
||||
where |
||||
from :: String |
||||
from = case f of |
||||
-- TODO: Lower-case |
||||
Just s -> " from='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
id' :: String |
||||
id' = case i of |
||||
Just (SID s) -> " id='" ++ s ++ "'" |
||||
Nothing -> "" |
||||
|
||||
to :: String |
||||
to = case t of |
||||
-- TODO: Lower-case |
||||
Just s -> " to='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
-- TODO: Turn message errors into XML. |
||||
|
||||
messageToXML :: Message -> String |
||||
messageToXML Message { messageID = i, messageFrom = f, messageTo = t, messagePayload = p, messageType = ty } = "<message" ++ from ++ id' ++ to ++ type' ++ ">" ++ |
||||
(elementsToString $ p) ++ "</message>" |
||||
where |
||||
from :: String |
||||
from = case f of |
||||
-- TODO: Lower-case |
||||
Just s -> " from='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
id' :: String |
||||
id' = case i of |
||||
Just (SID s) -> " id='" ++ s ++ "'" |
||||
Nothing -> "" |
||||
|
||||
to :: String |
||||
to = case t of |
||||
-- TODO: Lower-case |
||||
Just s -> " to='" ++ (show s) ++ "'" |
||||
Nothing -> "" |
||||
|
||||
type' :: String |
||||
type' = case ty of |
||||
Normal -> "" |
||||
t -> " type='" ++ (messageTypeToString t) ++ "'" |
||||
|
||||
|
||||
parseIQ :: Element -> IQ |
||||
parseIQ e | typeAttr == "get" = let (Just payloadMust) = payload |
||||
in IQReq (IQGet idAttr fromAttr toAttr Nothing |
||||
payloadMust) |
||||
| typeAttr == "set" = let (Just payloadMust) = payload |
||||
in IQReq (IQSet idAttr fromAttr toAttr Nothing |
||||
payloadMust) |
||||
| typeAttr == "result" = IQRes (IQResult idAttr fromAttr toAttr |
||||
Nothing payload) |
||||
|
||||
where |
||||
-- TODO: Many duplicate functions from parsePresence. |
||||
|
||||
payload :: Maybe Element |
||||
payload = case null (elementChildren e) of |
||||
True -> Nothing |
||||
False -> Just $ head $ elementChildren e |
||||
|
||||
typeAttr :: String |
||||
typeAttr = case attributeText typeName e of |
||||
-- Nothing -> Nothing |
||||
Just a -> DT.unpack a |
||||
|
||||
fromAttr :: Maybe Address |
||||
fromAttr = case attributeText fromName e of |
||||
Nothing -> Nothing |
||||
Just a -> X.fromString $ DT.unpack a |
||||
|
||||
toAttr :: Maybe Address |
||||
toAttr = case attributeText toName e of |
||||
Nothing -> Nothing |
||||
Just a -> X.fromString $ DT.unpack a |
||||
|
||||
idAttr :: Maybe StanzaID |
||||
idAttr = case attributeText idName e of |
||||
Nothing -> Nothing |
||||
Just a -> Just (SID (DT.unpack a)) |
||||
|
||||
typeName :: Name |
||||
typeName = fromString "type" |
||||
|
||||
fromName :: Name |
||||
fromName = fromString "from" |
||||
|
||||
toName :: Name |
||||
toName = fromString "to" |
||||
|
||||
idName :: Name |
||||
idName = fromString "id" |
||||
|
||||
-- TODO: Parse xml:lang |
||||
|
||||
parsePresence :: Element -> Presence |
||||
parsePresence e = Presence idAttr fromAttr toAttr Nothing typeAttr (elementChildren e) |
||||
where |
||||
-- TODO: Many duplicate functions from parseIQ. |
||||
|
||||
typeAttr :: PresenceType |
||||
typeAttr = case attributeText typeName e of |
||||
Just t -> stringToPresenceType $ DT.unpack t |
||||
Nothing -> Available |
||||
|
||||
fromAttr :: Maybe Address |
||||
fromAttr = case attributeText fromName e of |
||||
Nothing -> Nothing |
||||
Just a -> X.fromString $ DT.unpack a |
||||
|
||||
toAttr :: Maybe Address |
||||
toAttr = case attributeText toName e of |
||||
Nothing -> Nothing |
||||
Just a -> X.fromString $ DT.unpack a |
||||
|
||||
idAttr :: Maybe StanzaID |
||||
idAttr = case attributeText idName e of |
||||
Nothing -> Nothing |
||||
Just a -> Just (SID (DT.unpack a)) |
||||
|
||||
fromName :: Name |
||||
fromName = fromString "from" |
||||
|
||||
typeName :: Name |
||||
typeName = fromString "type" |
||||
|
||||
toName :: Name |
||||
toName = fromString "to" |
||||
|
||||
idName :: Name |
||||
idName = fromString "id" |
||||
|
||||
parseMessage :: Element -> Message |
||||
parseMessage e = Message idAttr fromAttr toAttr Nothing typeAttr (elementChildren e) |
||||
where |
||||
-- TODO: Many duplicate functions from parseIQ. |
||||
|
||||
typeAttr :: MessageType |
||||
typeAttr = case attributeText typeName e of |
||||
Just t -> stringToMessageType $ DT.unpack t |
||||
Nothing -> Normal |
||||
|
||||
fromAttr :: Maybe Address |
||||
fromAttr = case attributeText fromName e of |
||||
Nothing -> Nothing |
||||
Just a -> X.fromString $ DT.unpack a |
||||
|
||||
toAttr :: Maybe Address |
||||
toAttr = case attributeText toName e of |
||||
Nothing -> Nothing |
||||
Just a -> X.fromString $ DT.unpack a |
||||
|
||||
idAttr :: Maybe StanzaID |
||||
idAttr = case attributeText idName e of |
||||
Nothing -> Nothing |
||||
Just a -> Just (SID (DT.unpack a)) |
||||
|
||||
fromName :: Name |
||||
fromName = fromString "from" |
||||
|
||||
typeName :: Name |
||||
typeName = fromString "type" |
||||
|
||||
toName :: Name |
||||
toName = fromString "to" |
||||
|
||||
idName :: Name |
||||
idName = fromString "id" |
||||
|
||||
-- stringToPresenceType "available" = Available |
||||
-- stringToPresenceType "away" = Away |
||||
-- stringToPresenceType "chat" = Chat |
||||
-- stringToPresenceType "dnd" = DoNotDisturb |
||||
-- stringToPresenceType "xa" = ExtendedAway |
||||
|
||||
stringToPresenceType "available" = Available -- TODO: Some client sent this |
||||
|
||||
stringToPresenceType "probe" = Probe |
||||
-- stringToPresenceType "error" = PresenceError -- TODO: Special case |
||||
|
||||
stringToPresenceType "unavailable" = Unavailable |
||||
stringToPresenceType "subscribe" = Subscribe |
||||
stringToPresenceType "subscribed" = Subscribed |
||||
stringToPresenceType "unsubscribe" = Unsubscribe |
||||
stringToPresenceType "unsubscribed" = Unsubscribed |
||||
|
||||
-- presenceTypeToString Available = "available" |
||||
|
||||
-- presenceTypeToString Away = "away" |
||||
-- presenceTypeToString Chat = "chat" |
||||
-- presenceTypeToString DoNotDisturb = "dnd" |
||||
-- presenceTypeToString ExtendedAway = "xa" |
||||
|
||||
presenceTypeToString Unavailable = "unavailable" |
||||
|
||||
presenceTypeToString Probe = "probe" |
||||
-- presenceTypeToString PresenceError = "error" -- TODO: Special case |
||||
|
||||
presenceTypeToString Subscribe = "subscribe" |
||||
presenceTypeToString Subscribed = "subscribed" |
||||
presenceTypeToString Unsubscribe = "unsubscribe" |
||||
presenceTypeToString Unsubscribed = "unsubscribed" |
||||
|
||||
stringToMessageType "chat" = Chat |
||||
stringToMessageType "error" = Error |
||||
stringToMessageType "groupchat" = Groupchat |
||||
stringToMessageType "headline" = Headline |
||||
stringToMessageType "normal" = Normal |
||||
stringToMessageType s = OtherMessageType s |
||||
|
||||
messageTypeToString Chat = "chat" |
||||
messageTypeToString Error = "error" |
||||
messageTypeToString Groupchat = "groupchat" |
||||
messageTypeToString Headline = "headline" |
||||
messageTypeToString Normal = "normal" |
||||
messageTypeToString (OtherMessageType s) = s |
||||
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
----------------------------------------------------------------------------- |
||||
-- |
||||
-- Module : Network.XMPP.TLS |
||||
-- Copyright : Copyright © 2011, Jon Kristensen |
||||
-- License : LGPL (Just (Version {versionBranch = [3], versionTags = []})) |
||||
-- |
||||
-- Maintainer : jon.kristensen@pontarius.org |
||||
-- Stability : alpha |
||||
-- Portability : |
||||
-- |
||||
-- | |
||||
-- |
||||
----------------------------------------------------------------------------- |
||||
|
||||
module Network.XMPP.TLS ( |
||||
getTLSParams, |
||||
handshake' |
||||
) where |
||||
|
||||
import Network.TLS |
||||
import Network.TLS.Cipher |
||||
import GHC.IO.Handle (Handle, hPutStr, hFlush, hSetBuffering, hWaitForInput) |
||||
|
||||
|
||||
getTLSParams :: TLSParams |
||||
getTLSParams = TLSParams { pConnectVersion = TLS10 |
||||
, pAllowedVersions = [TLS10,TLS11] |
||||
, pCiphers = [cipher_AES256_SHA1] -- Check the rest |
||||
, pCompressions = [nullCompression] |
||||
, pWantClientCert = False |
||||
, pCertificates = [] |
||||
, onCertificatesRecv = \_ -> return True } -- Verify cert chain |
||||
|
||||
handshake' :: Handle -> String -> IO (Maybe TLSCtx) |
||||
handshake' h s = do |
||||
let t = getTLSParams |
||||
r <- makeSRandomGen |
||||
case r of |
||||
Right sr -> do |
||||
putStrLn $ show sr |
||||
c <- client t sr h |
||||
handshake c |
||||
putStrLn ">>>>TLS data sended<<<<" |
||||
return (Just c) |
||||
Left ge -> do |
||||
putStrLn $ show ge |
||||
return Nothing |
||||
@ -0,0 +1,457 @@
@@ -0,0 +1,457 @@
|
||||
----------------------------------------------------------------------------- |
||||
-- |
||||
-- Module : Types |
||||
-- Copyright : Copyright © 2011, Jon Kristensen |
||||
-- License : LGPL (Just (Version {versionBranch = [3], versionTags = []})) |
||||
-- |
||||
-- Maintainer : jon.kristensen@pontarius.org |
||||
-- Stability : alpha |
||||
-- Portability : |
||||
-- |
||||
----------------------------------------------------------------------------- |
||||
|
||||
{-# LANGUAGE MultiParamTypeClasses #-} |
||||
|
||||
module Network.XMPP.Types ( |
||||
StanzaID (..), |
||||
From, |
||||
To, |
||||
IQ (..), |
||||
IQRequest (..), |
||||
IQResponse (..), |
||||
Message (..), |
||||
MessageType (..), |
||||
Presence (..), |
||||
PresenceType (..), |
||||
StanzaError (..), |
||||
StanzaErrorType (..), |
||||
StanzaErrorCondition (..), |
||||
HostName |
||||
, Password |
||||
, PortNumber |
||||
, Resource |
||||
, UserName, |
||||
EnumeratorEvent (..), |
||||
Challenge (..), |
||||
Success (..), |
||||
TLSState (..), |
||||
Address (..), |
||||
Localpart, |
||||
Serverpart, |
||||
Resourcepart, |
||||
XMLLang, |
||||
InternalEvent (..), |
||||
XMLEvent (..), |
||||
ConnectionState (..), |
||||
ClientEvent (..), |
||||
StreamState (..), |
||||
AuthenticationState (..), |
||||
Certificate, |
||||
ConnectResult (..), |
||||
OpenStreamResult (..), |
||||
SecureWithTLSResult (..), |
||||
AuthenticateResult (..), |
||||
ServerAddress (..), |
||||
XMPPError (..), |
||||
Timeout, |
||||
TimeoutEvent (..), |
||||
StreamError (..), |
||||
IDGenerator (..) |
||||
) where |
||||
|
||||
import GHC.IO.Handle (Handle, hPutStr, hFlush, hSetBuffering, hWaitForInput) |
||||
|
||||
import qualified Network as N |
||||
|
||||
import qualified Control.Exception as CE |
||||
|
||||
import Control.Monad.State hiding (State) |
||||
|
||||
import Data.XML.Types |
||||
|
||||
import Network.TLS |
||||
import Network.TLS.Cipher |
||||
|
||||
import qualified Control.Monad.Error as CME |
||||
|
||||
import Data.IORef |
||||
|
||||
|
||||
|
||||
-- ============================================================================= |
||||
-- STANZA TYPES |
||||
-- ============================================================================= |
||||
|
||||
|
||||
-- TODO: Would a Stanza class such as the one below be useful sometimes? |
||||
-- |
||||
-- class Stanza a where |
||||
-- stanzaID :: a -> Maybe StanzaID |
||||
-- stanzaFrom :: a -> Maybe From |
||||
-- stanzaTo :: a -> Maybe To |
||||
-- stanzaXMLLang :: a -> Maybe XMLLang |
||||
|
||||
|
||||
-- | |
||||
-- The StanzaID type wraps a string of random characters that in Pontarius XMPP |
||||
-- is guaranteed to be unique for the XMPP session. Clients can add a string |
||||
-- prefix for the IDs to guarantee that they are unique in a larger context by |
||||
-- specifying the stanzaIDPrefix setting. TODO |
||||
|
||||
data StanzaID = SID String deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- @From@ is a readability type synonym for @Address@. |
||||
|
||||
type From = Address |
||||
|
||||
|
||||
-- | |
||||
-- @To@ is a readability type synonym for @Address@. |
||||
|
||||
type To = Address |
||||
|
||||
|
||||
-- | |
||||
-- An Info/Query (IQ) stanza is either of the type "request" ("get" or "set") or |
||||
-- "response" ("result" or "error"). The @IQ@ type wraps these two sub-types. |
||||
|
||||
data IQ = IQReq IQRequest | IQRes IQResponse deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- A "request" Info/Query (IQ) stanza is one with either "get" or "set" as type. |
||||
-- They are guaranteed to always contain a payload. |
||||
|
||||
data IQRequest = IQGet { iqRequestID :: Maybe StanzaID |
||||
, iqRequestFrom :: Maybe From |
||||
, iqRequestTo :: Maybe To |
||||
, iqRequestXMLLang :: Maybe XMLLang |
||||
, iqRequestPayload :: Element } | |
||||
IQSet { iqRequestID :: Maybe StanzaID |
||||
, iqRequestFrom :: Maybe From |
||||
, iqRequestTo :: Maybe To |
||||
, iqRequestXMLLang :: Maybe XMLLang |
||||
, iqRequestPayload :: Element } |
||||
deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- A "response" Info/Query (IQ) stanza is one with either "result" or "error" as |
||||
-- type. |
||||
|
||||
data IQResponse = IQResult { iqResponseID :: Maybe StanzaID |
||||
, iqResponseFrom :: Maybe From |
||||
, iqResponseTo :: Maybe To |
||||
, iqResponseXMLLang :: Maybe XMLLang |
||||
, iqResponsePayload :: Maybe Element } | |
||||
IQError { iqResponseID :: Maybe StanzaID |
||||
, iqResponseFrom :: Maybe From |
||||
, iqResponseTo :: Maybe To |
||||
, iqResponseXMLLang :: Maybe XMLLang |
||||
, iqResponsePayload :: Maybe Element |
||||
, iqResponseStanzaError :: StanzaError } |
||||
deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- The message stanza - either a message or a message error. |
||||
|
||||
data Message = Message { messageID :: Maybe StanzaID |
||||
, messageFrom :: Maybe From |
||||
, messageTo :: Maybe To |
||||
, messageXMLLang :: Maybe XMLLang |
||||
, messageType :: MessageType |
||||
, messagePayload :: [Element] } | |
||||
MessageError { messageID :: Maybe StanzaID |
||||
, messageFrom :: Maybe From |
||||
, messageTo :: Maybe To |
||||
, messageXMLLang :: Maybe XMLLang |
||||
, messageErrorPayload :: Maybe [Element] |
||||
, messageErrorStanzaError :: StanzaError } |
||||
deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- @MessageType@ holds XMPP message types as defined in XMPP-IM. @Normal@ is the |
||||
-- default message type. |
||||
|
||||
data MessageType = Chat | |
||||
Error | |
||||
Groupchat | |
||||
Headline | |
||||
Normal | |
||||
OtherMessageType String deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- The presence stanza - either a presence or a presence error. |
||||
|
||||
data Presence = Presence { presenceID :: Maybe StanzaID |
||||
, presenceFrom :: Maybe From |
||||
, presenceTo :: Maybe To |
||||
, presenceXMLLang :: Maybe XMLLang |
||||
, presenceType :: PresenceType |
||||
, presencePayload :: [Element] } | |
||||
PresenceError { presenceID :: Maybe StanzaID |
||||
, presenceFrom :: Maybe From |
||||
, presenceTo :: Maybe To |
||||
, presenceXMLLang :: Maybe XMLLang |
||||
, presenceErrorPayload :: Maybe [Element] |
||||
, presenceErrorStanzaError :: StanzaError } |
||||
deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- @PresenceType@ holds XMPP presence types. When a presence type is not |
||||
-- provided, we assign the @PresenceType@ value @Available@. |
||||
|
||||
data PresenceType = Subscribe | -- ^ Sender wants to subscribe to presence |
||||
Subscribed | -- ^ Sender has approved the subscription |
||||
Unsubscribe | -- ^ Sender is unsubscribing from presence |
||||
Unsubscribed | -- ^ Sender has denied or cancelled a |
||||
-- subscription |
||||
Probe | -- ^ Sender requests current presence; |
||||
-- should only be used by servers |
||||
Available | -- ^ Sender did not specify a type attribute |
||||
Unavailable deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- All stanzas (IQ, message, presence) can cause errors, which in the XMPP |
||||
-- stream looks like <stanza-kind to='sender' type='error'>. These errors are |
||||
-- wrapped in the @StanzaError@ type. |
||||
|
||||
data StanzaError = StanzaError { stanzaErrorType :: StanzaErrorType |
||||
, stanzaErrorCondition :: StanzaErrorCondition |
||||
, stanzaErrorText :: Maybe String |
||||
, stanzaErrorApplicationSpecificCondition :: |
||||
Maybe Element } deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- @StanzaError@s always have one of these types. |
||||
|
||||
data StanzaErrorType = Cancel | -- ^ Error is unrecoverable - do not retry |
||||
Continue | -- ^ Conditition was a warning - proceed |
||||
Modify | -- ^ Change the data and retry |
||||
Auth | -- ^ Provide credentials and retry |
||||
Wait -- ^ Error is temporary - wait and retry |
||||
deriving (Eq, Show) |
||||
|
||||
|
||||
-- | |
||||
-- Stanza errors are accommodated with one of the error conditions listed below. |
||||
|
||||
data StanzaErrorCondition = BadRequest | -- ^ Malformed XML |
||||
Conflict | -- ^ Resource or session |
||||
-- with name already |
||||
-- exists |
||||
FeatureNotImplemented | |
||||
Forbidden | -- ^ Insufficient |
||||
-- permissions |
||||
Gone | -- ^ Entity can no longer |
||||
-- be contacted at this |
||||
-- address |
||||
InternalServerError | |
||||
ItemNotFound | |
||||
JIDMalformed | |
||||
NotAcceptable | -- ^ Does not meet policy |
||||
-- criteria |
||||
NotAllowed | -- ^ No entity may perform |
||||
-- this action |
||||
NotAuthorized | -- ^ Must provide proper |
||||
-- credentials |
||||
PaymentRequired | |
||||
RecipientUnavailable | -- ^ Temporarily |
||||
-- unavailable |
||||
Redirect | -- ^ Redirecting to other |
||||
-- entity, usually |
||||
-- temporarily |
||||
RegistrationRequired | |
||||
RemoteServerNotFound | |
||||
RemoteServerTimeout | |
||||
ResourceConstraint | -- ^ Entity lacks the |
||||
-- necessary system |
||||
-- resources |
||||
ServiceUnavailable | |
||||
SubscriptionRequired | |
||||
UndefinedCondition | -- ^ Application-specific |
||||
-- condition |
||||
UnexpectedRequest -- ^ Badly timed request |
||||
deriving (Eq, Show) |
||||
|
||||
|
||||
|
||||
-- ============================================================================= |
||||
-- OTHER STUFF |
||||
-- ============================================================================= |
||||
|
||||
|
||||
instance Eq ConnectionState where |
||||
Disconnected == Disconnected = True |
||||
(Connected p h) == (Connected p_ h_) = p == p_ && h == h_ |
||||
-- (ConnectedPostFeatures s p h t) == (ConnectedPostFeatures s p h t) = True |
||||
-- (ConnectedAuthenticated s p h t) == (ConnectedAuthenticated s p h t) = True |
||||
_ == _ = False |
||||
|
||||
data XMPPError = UncaughtEvent deriving (Eq, Show) |
||||
|
||||
instance CME.Error XMPPError where |
||||
strMsg "UncaughtEvent" = UncaughtEvent |
||||
|
||||
|
||||
-- | Readability type for host name Strings. |
||||
|
||||
type HostName = String -- This is defined in Network as well |
||||
|
||||
|
||||
-- | Readability type for port number Integers. |
||||
|
||||
type PortNumber = Integer -- We use N(etwork).PortID (PortNumber) internally |
||||
|
||||
|
||||
-- | Readability type for user name Strings. |
||||
|
||||
type UserName = String |
||||
|
||||
|
||||
-- | Readability type for password Strings. |
||||
|
||||
type Password = String |
||||
|
||||
|
||||
-- | Readability type for (Address) resource identifier Strings. |
||||
|
||||
type Resource = String |
||||
|
||||
|
||||
-- An XMLEvent is triggered by an XML stanza or some other XML event, and is |
||||
-- sent through the internal event channel, just like client action events. |
||||
|
||||
data XMLEvent = XEBeginStream String | XEFeatures String | |
||||
XEChallenge Challenge | XESuccess Success | |
||||
XEEndStream | XEIQ IQ | XEPresence Presence | |
||||
XEMessage Message | XEProceed | |
||||
XEOther String deriving (Show) |
||||
|
||||
data EnumeratorEvent = EnumeratorDone | |
||||
EnumeratorXML XMLEvent | |
||||
EnumeratorException CE.SomeException |
||||
deriving (Show) |
||||
|
||||
|
||||
-- Type to contain the internal events. |
||||
|
||||
data InternalEvent s m = IEC (ClientEvent s m) | IEE EnumeratorEvent | IET (TimeoutEvent s m) deriving (Show) |
||||
|
||||
data TimeoutEvent s m = TimeoutEvent StanzaID Timeout (StateT s m ()) |
||||
|
||||
instance Show (TimeoutEvent s m) where |
||||
show (TimeoutEvent (SID i) t _) = "TimeoutEvent (ID: " ++ (show i) ++ ", timeout: " ++ (show t) ++ ")" |
||||
|
||||
|
||||
data StreamState = PreStream | |
||||
PreFeatures StreamProperties | |
||||
PostFeatures StreamProperties StreamFeatures |
||||
|
||||
|
||||
data AuthenticationState = NoAuthentication | AuthenticatingPreChallenge1 String String (Maybe Resource) | AuthenticatingPreChallenge2 String String (Maybe Resource) | AuthenticatingPreSuccess String String (Maybe Resource) | AuthenticatedUnbound String (Maybe Resource) | AuthenticatedBound String Resource |
||||
|
||||
|
||||
-- Client actions that needs to be performed in the (main) state loop are |
||||
-- converted to ClientEvents and sent through the internal event channel. |
||||
|
||||
data ClientEvent s m = CEOpenStream N.HostName PortNumber |
||||
(OpenStreamResult -> StateT s m ()) | |
||||
CESecureWithTLS Certificate (Certificate -> Bool) |
||||
(SecureWithTLSResult -> StateT s m ()) | |
||||
CEAuthenticate UserName Password (Maybe Resource) |
||||
(AuthenticateResult -> StateT s m ()) | |
||||
CEMessage Message (Maybe (Message -> StateT s m Bool)) (Maybe (Timeout, StateT s m ())) (Maybe (StreamError -> StateT s m ())) | |
||||
CEPresence Presence (Maybe (Presence -> StateT s m Bool)) (Maybe (Timeout, StateT s m ())) (Maybe (StreamError -> StateT s m ())) | |
||||
CEIQ IQ (Maybe (IQ -> StateT s m Bool)) (Maybe (Timeout, StateT s m ())) (Maybe (StreamError -> StateT s m ())) | |
||||
CEAction (Maybe (StateT s m Bool)) (StateT s m ()) |
||||
|
||||
instance Show (ClientEvent s m) where |
||||
show (CEOpenStream h p _) = "CEOpenStream " ++ h ++ " " ++ (show p) |
||||
show (CESecureWithTLS c _ _) = "CESecureWithTLS " ++ c |
||||
show (CEAuthenticate u p r _) = "CEAuthenticate " ++ u ++ " " ++ p ++ " " ++ |
||||
(show r) |
||||
show (CEIQ s _ _ _) = "CEIQ" |
||||
show (CEMessage s _ _ _) = "CEMessage" |
||||
show (CEPresence s _ _ _) = "CEPresence" |
||||
|
||||
show (CEAction _ _) = "CEAction" |
||||
|
||||
|
||||
type StreamID = String |
||||
|
||||
data ConnectionState = Disconnected | Connected ServerAddress Handle |
||||
|
||||
data TLSState = NoTLS | PreProceed | PreHandshake | PostHandshake TLSCtx |
||||
|
||||
data Challenge = Chal String deriving (Show) |
||||
|
||||
data Success = Succ String deriving (Show) |
||||
|
||||
|
||||
type StreamProperties = Float |
||||
type StreamFeatures = String |
||||
|
||||
|
||||
data ConnectResult = ConnectSuccess StreamProperties StreamFeatures (Maybe Resource) | |
||||
ConnectOpenStreamFailure | |
||||
ConnectSecureWithTLSFailure | |
||||
ConnectAuthenticateFailure |
||||
|
||||
data OpenStreamResult = OpenStreamSuccess StreamProperties StreamFeatures | |
||||
OpenStreamFailure |
||||
|
||||
data SecureWithTLSResult = SecureWithTLSSuccess StreamProperties StreamFeatures | SecureWithTLSFailure |
||||
|
||||
data AuthenticateResult = AuthenticateSuccess StreamProperties StreamFeatures Resource | AuthenticateFailure |
||||
|
||||
type Certificate = String -- TODO |
||||
|
||||
-- Address is a data type that has to be constructed in this module using either |
||||
-- address or stringToAddress. |
||||
|
||||
data Address = Address { localpart :: Maybe Localpart |
||||
, serverpart :: Serverpart |
||||
, resourcepart :: Maybe Resourcepart } |
||||
deriving (Eq) |
||||
|
||||
instance Show Address where |
||||
show (Address { localpart = n, serverpart = s, resourcepart = r }) |
||||
| n == Nothing && r == Nothing = s |
||||
| r == Nothing = let Just n' = n in n' ++ "@" ++ s |
||||
| n == Nothing = let Just r' = r in s ++ "/" ++ r' |
||||
| otherwise = let Just n' = n; Just r' = r |
||||
in n' ++ "@" ++ s ++ "/" ++ r' |
||||
|
||||
type Localpart = String |
||||
type Serverpart = String |
||||
type Resourcepart = String |
||||
|
||||
data ServerAddress = ServerAddress N.HostName N.PortNumber deriving (Eq) |
||||
|
||||
type Timeout = Int |
||||
|
||||
data StreamError = StreamError |
||||
|
||||
|
||||
-- ============================================================================= |
||||
-- XML TYPES |
||||
-- ============================================================================= |
||||
|
||||
type XMLLang = String |
||||
-- Validate, protect. See: |
||||
-- http://tools.ietf.org/html/rfc6120#section-8.1.5 |
||||
-- http://www.w3.org/TR/2008/REC-xml-20081126/ |
||||
-- http://www.rfc-editor.org/rfc/bcp/bcp47.txt |
||||
-- http://www.ietf.org/rfc/rfc1766.txt |
||||
|
||||
|
||||
newtype IDGenerator = IDGenerator (IORef [String]) |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
{- |
||||
|
||||
Copyright © 2010-2011 Jon Kristensen. |
||||
|
||||
This file is part of Pontarius XMPP. |
||||
|
||||
Pontarius XMPP is free software: you can redistribute it and/or modify it under |
||||
the terms of the GNU Lesser General Public License as published by the Free |
||||
Software Foundation, either version 3 of the License, or (at your option) any |
||||
later version. |
||||
|
||||
Pontarius XMPP 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 Lesser General Public License for more |
||||
details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along |
||||
with Pontarius XMPP. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
-} |
||||
|
||||
-- | |
||||
-- Module: $Header$ |
||||
-- Description: Utility functions for Pontarius XMPP; currently only random ID |
||||
-- generation functions |
||||
-- Copyright: Copyright © 2010-2011 Jon Kristensen |
||||
-- License: LGPL-3 |
||||
-- |
||||
-- Maintainer: info@pontarius.org |
||||
-- Stability: unstable |
||||
-- Portability: portable |
||||
-- |
||||
-- This module will be documented soon. |
||||
|
||||
-- TODO: Document this module |
||||
-- TODO: Make is possible to customize characters |
||||
-- TODO: Make it possible to customize length |
||||
|
||||
module Network.XMPP.Utilities ( elementToString |
||||
, elementsToString ) where |
||||
|
||||
import Data.Word |
||||
import Data.XML.Types |
||||
import System.Crypto.Random |
||||
import System.Random |
||||
import qualified Data.ByteString as DB |
||||
import qualified Data.Map as DM |
||||
import qualified Data.Text as DT |
||||
|
||||
|
||||
|
||||
-- ============================================================================= |
||||
-- XML Utilities |
||||
-- ============================================================================= |
||||
|
||||
|
||||
elementsToString :: [Element] -> String |
||||
elementsToString [] = "" |
||||
elementsToString (e:es) = (elementToString $ Just e) ++ elementsToString es |
||||
|
||||
elementToString :: Maybe Element -> String |
||||
elementToString Nothing = "" |
||||
elementToString (Just e) = "<" ++ nameToString (elementName e) ++ xmlns ++ |
||||
attributes (elementAttributes e) ++ |
||||
">" ++ (nodesToString $ elementNodes e) ++ "</" ++ |
||||
nameToString (elementName e) ++ ">" |
||||
where |
||||
xmlns :: String |
||||
xmlns = case nameNamespace $ elementName e of |
||||
Nothing -> "" |
||||
Just t -> " xmlns='" ++ (DT.unpack t) ++ "'" |
||||
|
||||
nameToString :: Name -> String |
||||
nameToString Name { nameLocalName = n, namePrefix = Nothing } = DT.unpack n |
||||
nameToString Name { nameLocalName = n, namePrefix = Just p } = |
||||
(DT.unpack p) ++ ":" ++ (DT.unpack n) |
||||
|
||||
contentToString :: Content -> String |
||||
contentToString (ContentText t) = DT.unpack t |
||||
contentToString (ContentEntity t) = DT.unpack t |
||||
|
||||
attributes :: [(Name, [Content])] -> String |
||||
attributes [] = "" |
||||
attributes ((n, c):t) = (" " ++ (nameToString n) ++ "='" ++ |
||||
concat (map contentToString c) ++ "'") ++ |
||||
attributes t |
||||
|
||||
nodesToString :: [Node] -> String |
||||
nodesToString [] = "" |
||||
nodesToString ((NodeElement e):ns) = (elementToString $ Just e) ++ |
||||
(nodesToString ns) |
||||
nodesToString ((NodeContent c):ns) = (contentToString c) ++ |
||||
(nodesToString ns) |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
Pontarius XMPP aims to be a secure and easy-to-use XMPP library for Haskell. We |
||||
have just released a fourth alpha version with the following features: |
||||
|
||||
* Client-to-server Transport Layer Security and DIGEST-MD5 SASL Authentication |
||||
* Concurrent, Flexible and Event-based API for XMPP Client Developers |
||||
* Support for Info/Query, Presence and Message Stanzas |
||||
* Interoperable XML Parsing (Using enumerator, xml-enumerator and xml-types) |
||||
|
||||
Please note that we are not recommending anyone to use Pontarius XMPP at this |
||||
time as it’s still in an experimental stage and will have its API and data types |
||||
modified. However, if you are interested to use Pontarius XMPP anyway, feel free |
||||
to contact the Pontarius project and we will try to help you get started. You |
||||
can also see the Example directory for a usage example. |
||||
|
||||
We are currently working on general improvements and having the library support |
||||
all of RFC 3920: XMPP Core. |
||||
|
||||
The next version, 0.1 Alpha 6, is scheduled to be released on the 6th of July. |
||||
|
||||
Look at the Pontarius web site <http://www.pontarius.org/> and the Pontarius |
||||
XMPP Hackage page <http://hackage.haskell.org/package/pontarius-xmpp/> for more |
||||
information. |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
import Distribution.Simple |
||||
|
||||
main = defaultMain |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
Name: pontarius-xmpp |
||||
Version: 0.0.6.0 |
||||
Cabal-Version: >= 1.6 |
||||
Build-Type: Simple |
||||
License: LGPL-3 |
||||
License-File: LICENSE |
||||
Copyright: Copyright © 2011, Jon Kristensen |
||||
Author: Jon Kristensen, Mahdi Abdinejadi |
||||
Maintainer: jon.kristensen@pontarius.org |
||||
Stability: alpha |
||||
Homepage: http://www.pontarius.org/ |
||||
Bug-Reports: mailto:info@pontarius.org |
||||
-- Package-URL: |
||||
Synopsis: A prototyped and incomplete implementation of RFC 6120: |
||||
XMPP: Core |
||||
Description: A work in progress of an implementation of RFC 6120: XMPP: |
||||
Core, as well as RFC 6122: XMPP: Address Format and other |
||||
depending standards. A new version of Pontarius XMPP is |
||||
released every three weeks. |
||||
Category: Network |
||||
Tested-With: GHC ==7.0.2 |
||||
-- Data-Files: |
||||
-- Data-Dir: |
||||
-- Extra-Source-Files: |
||||
-- Extra-Tmp-Files: |
||||
|
||||
Library |
||||
Exposed-Modules: Network.XMPP, Network.XMPP.Address, Network.XMPP.SASL, |
||||
Network.XMPP.Session, Network.XMPP.Stanza, |
||||
Network.XMPP.Stream, Network.XMPP.TLS, Network.XMPP.Types, |
||||
Network.XMPP.Utilities |
||||
Exposed: True |
||||
Build-Depends: base >= 2 && < 5, parsec, enumerator, crypto-api, |
||||
base64-string, pureMD5, utf8-string, network, xml-types, |
||||
text, transformers, bytestring, binary, random, |
||||
xml-enumerator, tls ==0.4.1, containers, mtl, text-icu, |
||||
stringprep, idna2008 ==0.0.1.0 |
||||
-- Other-Modules: |
||||
-- HS-Source-Dirs: |
||||
-- Extensions: |
||||
-- Build-Tools: |
||||
-- Buildable: |
||||
-- GHC-Options: |
||||
-- GHC-Prof-Options: |
||||
-- Hugs-Options: |
||||
-- NHC98-Options: |
||||
-- Includes: |
||||
-- Install-Includes: |
||||
-- Include-Dirs: |
||||
-- C-Sources: |
||||
-- Extra-Libraries: |
||||
-- Extra-Lib-Dirs: |
||||
-- CC-Options: |
||||
-- LD-Options: |
||||
-- Pkgconfig-Depends: |
||||
-- Frameworks: |
||||
|
||||
Source-Repository head |
||||
Type: darcs |
||||
-- Module: |
||||
Location: https://patch-tag.com/r/jonkri/pontarius-xmpp |
||||
-- Subdir: |
||||
|
||||
Source-Repository this |
||||
Type: darcs |
||||
-- Module: |
||||
Location: https://patch-tag.com/r/jonkri/pontarius-xmpp |
||||
Tag: 0.0.6.0 |
||||
-- Subdir: |
||||
Loading…
Reference in new issue