diff --git a/.gitignore b/.gitignore index a9d37c560c6ab8d4afbf47eda643e8c42e857716..693699042b1a8ccf697636d3cd34b200f3a8278b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -target +/target +**/*.rs.bk Cargo.lock diff --git a/COPYING b/COPYING deleted file mode 100644 index a737dcfed5db21fb99a8fcb812e995937d38401b..0000000000000000000000000000000000000000 --- a/COPYING +++ /dev/null @@ -1,675 +0,0 @@ - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - 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. - - - Copyright (C) - - 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 . - -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: - - Copyright (C) - 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 -. - - 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 -. diff --git a/COPYING.LESSER b/COPYING.LESSER deleted file mode 100644 index 5f5ff16a4a0f6104fadb6a5beef527573e46b425..0000000000000000000000000000000000000000 --- a/COPYING.LESSER +++ /dev/null @@ -1,166 +0,0 @@ - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - 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. diff --git a/Cargo.toml b/Cargo.toml index 04aa98f991dee71086269fe39d055903dde1cce3..fc327514edf4079c8de4b7a04b18aa3063891137 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,20 @@ [package] name = "xmpp" -version = "0.2.0" -authors = ["lumi ", "Emmanuel Gil Peyrot "] -description = "A type-safe rust XMPP library. Still under development." -homepage = "https://gitlab.com/lumi/xmpp-rs" -repository = "https://gitlab.com/lumi/xmpp-rs" -documentation = "https://docs.rs/xmpp" -readme = "README.md" -keywords = ["xmpp", "xml"] +version = "0.3.0" +authors = [ + "Emmanuel Gil Peyrot ", + "Maxime “pep” Buquet ", +] +description = "High-level XMPP library" +homepage = "https://gitlab.com/linkmauve/xmpp-rs" +repository = "https://gitlab.com/linkmauve/xmpp-rs" +keywords = ["xmpp", "jabber", "chat", "messaging", "bot"] categories = ["network-programming"] -license = "LGPL-3.0+" - -[badges] -gitlab = { repository = "lumi/xmpp-rs" } +license = "MPL-2.0" +edition = "2018" [dependencies] -xml-rs = "0.4.1" -xmpp-parsers = "0.7.0" -openssl = "0.9.12" -base64 = "0.6.0" -minidom = "0.4.1" -jid = "0.2.1" -sasl = "0.4.0" -sha-1 = "0.4" -chrono = "0.4.0" -try_from = "0.2.2" - -[features] -insecure = [] +tokio-xmpp = "1.0.1" +xmpp-parsers = "0.15" +futures = "0.1" +tokio = "0.1" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..14e2f777f6c395e7e04ab4aa306bbcc4b0c1120e --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index a9a95960def469b89fc4866b88cd8575a9c548fd..c3dabb0a93605a04a3d820d884375f1c79b9ef77 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ xmpp-rs What's this? ------------ -A very much WIP rust XMPP library with the goals of being type-safe, well-tested and simple. +A very much WIP rust XMPP library with the goals of being type-safe and well-tested. Contact ------- @@ -13,32 +13,7 @@ There is an XMPP MUC for the discussion of this library, feel free to join! :) [chat@xmpp.rs](xmpp:chat@xmpp.rs?join) -Can I use it yet? ------------------ - -No, there's still a lot of work that needs to be done before this could be used for anything. - -I mean, if you really wanted to, sure, but don't expect it to work yet. - -What license is it under? -------------------------- - -LGPLv3 or later. See `COPYING` and `COPYING.LESSER`. - -License yadda yadda. --------------------- - - Copyright 2017, xmpp-rs contributors. - - This program 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. - - 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 Lesser General Public License for more details. +License +------- - You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see . +Mozilla Public License 2 (MPL2). See the LICENSE file. diff --git a/examples/client.rs b/examples/client.rs deleted file mode 100644 index e4d7728a258fd93700ab807c451d0e3043641816..0000000000000000000000000000000000000000 --- a/examples/client.rs +++ /dev/null @@ -1,44 +0,0 @@ -extern crate xmpp; - -use xmpp::jid::Jid; -use xmpp::client::ClientBuilder; -use xmpp::plugins::stanza_debug::StanzaDebugPlugin; -use xmpp::plugins::stanza::StanzaPlugin; -use xmpp::plugins::unhandled_iq::UnhandledIqPlugin; -use xmpp::plugins::messaging::{MessagingPlugin, MessageEvent}; -use xmpp::plugins::presence::{PresencePlugin, Type, Show}; -use xmpp::plugins::disco::DiscoPlugin; -use xmpp::plugins::caps::CapsPlugin; -use xmpp::plugins::ibb::IbbPlugin; -use xmpp::plugins::ping::PingPlugin; -use xmpp::event::{Priority, Propagation}; - -use std::env; - -fn main() { - let jid: Jid = env::var("JID").unwrap().parse().unwrap(); - let pass = env::var("PASS").unwrap(); - let mut client = ClientBuilder::new(jid.clone()) - .password(pass) - .connect() - .unwrap(); - if env::var("STANZA_DEBUG").is_ok() { - client.register_plugin(StanzaDebugPlugin::new()); - } - client.register_plugin(StanzaPlugin::new()); - client.register_plugin(UnhandledIqPlugin::new()); - client.register_plugin(MessagingPlugin::new()); - client.register_plugin(PresencePlugin::new()); - client.register_plugin(DiscoPlugin::new("client", "bot", "en", "xmpp-rs")); - client.register_plugin(CapsPlugin::new()); - client.register_plugin(IbbPlugin::new()); - client.register_plugin(PingPlugin::new()); - client.plugin::().init(); - client.plugin::().init(); - client.register_handler(Priority::Max, |e: &MessageEvent| { - println!("{:?}", e); - Propagation::Continue - }); - client.plugin::().set_presence(Type::None, Show::None, None).unwrap(); - client.main().unwrap(); -} diff --git a/examples/component.rs b/examples/component.rs deleted file mode 100644 index 97fc60a65929afde490aee709e9cbd321d889b08..0000000000000000000000000000000000000000 --- a/examples/component.rs +++ /dev/null @@ -1,20 +0,0 @@ -extern crate xmpp; - -use xmpp::jid::Jid; -use xmpp::component::ComponentBuilder; - -use std::env; - -fn main() { - let jid: Jid = env::var("JID").unwrap().parse().unwrap(); - let pass = env::var("PASS").unwrap(); - let host = env::var("HOST").unwrap(); - let port: u16 = env::var("PORT").unwrap().parse().unwrap(); - let mut component = ComponentBuilder::new(jid.clone()) - .password(pass) - .host(host) - .port(port) - .connect() - .unwrap(); - component.main().unwrap(); -} diff --git a/examples/hello_bot.rs b/examples/hello_bot.rs new file mode 100644 index 0000000000000000000000000000000000000000..dcfd060d573eabd7ca3595acc67ffe0de32da088 --- /dev/null +++ b/examples/hello_bot.rs @@ -0,0 +1,79 @@ +// Copyright (c) 2019 Emmanuel Gil Peyrot +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use futures::prelude::*; +use std::env::args; +use std::process::exit; +use tokio::runtime::current_thread::Runtime; +use xmpp_parsers::{message::MessageType, Jid}; +use xmpp::{ClientBuilder, ClientType, ClientFeature, Event}; + +fn main() { + let args: Vec = args().collect(); + if args.len() != 3 { + println!("Usage: {} ", args[0]); + exit(1); + } + let jid = &args[1]; + let password = &args[2]; + + // tokio_core context + let mut rt = Runtime::new().unwrap(); + + // Client instance + let (mut agent, stream) = ClientBuilder::new(jid, password) + .set_client(ClientType::Bot, "xmpp-rs") + .set_website("https://gitlab.com/xmpp-rs/xmpp-rs") + .set_default_nick("bot") + .enable_feature(ClientFeature::Avatars) + .enable_feature(ClientFeature::ContactList) + .enable_feature(ClientFeature::JoinRooms) + .build() + .unwrap(); + + // We return either Some(Error) if an error was encountered + // or None, if we were simply disconnected + let handler = stream.map_err(Some).for_each(|evt: Event| { + match evt { + Event::Online => { + println!("Online."); + }, + Event::Disconnected => { + println!("Disconnected."); + return Err(None); + }, + Event::ContactAdded(contact) => { + println!("Contact {} added.", contact.jid); + }, + Event::ContactRemoved(contact) => { + println!("Contact {} removed.", contact.jid); + }, + Event::ContactChanged(contact) => { + println!("Contact {} changed.", contact.jid); + }, + Event::OpenRoomBookmark(bookmark) => { + println!("Joining room “{}” ({})…", bookmark.name, bookmark.jid); + agent.join_room(bookmark.jid, bookmark.nick, bookmark.password, "en", "Yet another bot!"); + }, + Event::RoomJoined(jid) => { + println!("Joined room {}.", jid); + agent.send_message(Jid::Bare(jid), MessageType::Groupchat, "en", "Hello world!"); + }, + Event::RoomLeft(jid) => { + println!("Left room {}.", jid); + }, + Event::AvatarRetrieved(jid, path) => { + println!("Received avatar for {} in {}.", jid, path); + }, + } + Ok(()) + }); + + rt.block_on(handler).unwrap_or_else(|e| match e { + Some(e) => println!("Error: {:?}", e), + None => println!("Disconnected."), + }); +} diff --git a/src/avatar.rs b/src/avatar.rs new file mode 100644 index 0000000000000000000000000000000000000000..1b95b762c40e31a66f1f0d90129e6f182c42567b --- /dev/null +++ b/src/avatar.rs @@ -0,0 +1,97 @@ +// Copyright (c) 2019 Emmanuel Gil Peyrot +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::Event; +use futures::{sync::mpsc, Sink}; +use std::convert::TryFrom; +use std::fs::{self, File}; +use std::io::{self, Write}; +use tokio_xmpp::Packet; +use xmpp_parsers::{ + avatar::{Data, Metadata}, + iq::Iq, + ns, + pubsub::{ + event::Item, + pubsub::{Items, PubSub}, + NodeName, + }, + hashes::Hash, + Jid, +}; + +// TODO: Update xmpp-parsers to get this function for free on Hash. +fn hash_to_hex(hash: &Hash) -> String { + let mut bytes = vec![]; + for byte in hash.hash.iter() { + bytes.push(format!("{:02x}", byte)); + } + bytes.join("") +} + +pub(crate) fn handle_metadata_pubsub_event(from: &Jid, tx: &mut mpsc::UnboundedSender, items: Vec) -> impl IntoIterator { + let mut events = Vec::new(); + for item in items { + let payload = item.payload.clone().unwrap(); + if payload.is("metadata", ns::AVATAR_METADATA) { + let metadata = Metadata::try_from(payload).unwrap(); + for info in metadata.infos { + let filename = format!("data/{}/{}", from, hash_to_hex(&*info.id)); + let file_length = match fs::metadata(filename.clone()) { + Ok(metadata) => metadata.len(), + Err(_) => 0, + }; + // TODO: Also check the hash. + if info.bytes as u64 == file_length { + events.push(Event::AvatarRetrieved(from.clone(), filename)); + } else { + let iq = download_avatar(from); + tx.start_send(Packet::Stanza(iq.into())).unwrap(); + } + } + } + } + events +} + +fn download_avatar(from: &Jid) -> Iq { + Iq::from_get("coucou", PubSub::Items(Items { + max_items: None, + node: NodeName(String::from(ns::AVATAR_DATA)), + subid: None, + items: Vec::new(), + })) + .with_to(from.clone()) +} + +// The return value of this function will be simply pushed to a Vec in the caller function, +// so it makes no sense to allocate a Vec here - we're lazy instead +pub(crate) fn handle_data_pubsub_iq<'a>( + from: &'a Jid, + items: &'a Items, +) -> impl IntoIterator + 'a { + let from = from.clone(); + items + .items + .iter() + .filter_map(move |item| match (&item.id, &item.payload) { + (Some(id), Some(payload)) => { + let data = Data::try_from(payload.clone()).unwrap(); + let filename = save_avatar(&from, id.0.clone(), &data.data).unwrap(); + Some(Event::AvatarRetrieved(from.clone(), filename)) + } + _ => None, + }) +} + +fn save_avatar(from: &Jid, id: String, data: &[u8]) -> io::Result { + let directory = format!("data/{}", from); + let filename = format!("data/{}/{}", from, id); + fs::create_dir_all(directory)?; + let mut file = File::create(&filename)?; + file.write_all(data)?; + Ok(filename) +} diff --git a/src/client.rs b/src/client.rs deleted file mode 100644 index bfab1746ac7b00cd39e9fd1799c416ba9f7cea44..0000000000000000000000000000000000000000 --- a/src/client.rs +++ /dev/null @@ -1,297 +0,0 @@ -use xml; -use jid::Jid; -use transport::{Transport, SslTransport}; -use error::Error; -use ns; -use plugin::{Plugin, PluginInit, PluginProxyBinding, PluginContainer, PluginRef}; -use connection::{Connection, C2S}; -use sasl::client::Mechanism as SaslMechanism; -use sasl::client::mechanisms::{Plain, Scram}; -use sasl::common::{Credentials as SaslCredentials, Identity, Secret, ChannelBinding}; -use sasl::common::scram::{Sha1, Sha256}; -use components::sasl_error::SaslError; -use util::FromElement; -use event::{Event, Dispatcher, Propagation, SendElement, ReceiveElement, Priority}; - -use base64; - -use minidom::Element; - -use xml::reader::XmlEvent as ReaderEvent; - -use std::sync::{Mutex, Arc}; - -use std::collections::HashSet; - -/// Struct that should be moved somewhere else and cleaned up. -#[derive(Debug)] -pub struct StreamFeatures { - pub sasl_mechanisms: Option>, -} - -/// A builder for `Client`s. -pub struct ClientBuilder { - jid: Jid, - credentials: SaslCredentials, - host: Option, - port: u16, -} - -impl ClientBuilder { - /// Creates a new builder for an XMPP client that will connect to `jid` with default parameters. - pub fn new(jid: Jid) -> ClientBuilder { - ClientBuilder { - jid: jid, - credentials: SaslCredentials::default(), - host: None, - port: 5222, - } - } - - /// Sets the host to connect to. - pub fn host(mut self, host: String) -> ClientBuilder { - self.host = Some(host); - self - } - - /// Sets the port to connect to. - pub fn port(mut self, port: u16) -> ClientBuilder { - self.port = port; - self - } - - /// Sets the password to use. - pub fn password>(mut self, password: P) -> ClientBuilder { - self.credentials = SaslCredentials { - identity: Identity::Username(self.jid.node.clone().expect("JID has no node")), - secret: Secret::password_plain(password), - channel_binding: ChannelBinding::None, - }; - self - } - - /// Connects to the server and returns a `Client` when succesful. - pub fn connect(self) -> Result { - let host = &self.host.unwrap_or(self.jid.domain.clone()); - let mut transport = SslTransport::connect(host, self.port)?; - C2S::init(&mut transport, &self.jid.domain, "before_sasl")?; - let dispatcher = Arc::new(Dispatcher::new()); - let mut credentials = self.credentials; - credentials.channel_binding = transport.channel_bind(); - let transport = Arc::new(Mutex::new(transport)); - let plugin_container = Arc::new(PluginContainer::new()); - let mut client = Client { - jid: self.jid.clone(), - transport: transport.clone(), - binding: PluginProxyBinding::new(dispatcher.clone(), plugin_container.clone(), self.jid), - plugin_container: plugin_container, - dispatcher: dispatcher, - }; - client.dispatcher.register(Priority::Default, move |evt: &SendElement| { - let mut t = transport.lock().unwrap(); - t.write_element(&evt.0).unwrap(); - Propagation::Continue - }); - client.connect(credentials)?; - client.bind()?; - Ok(client) - } -} - -/// An XMPP client. -pub struct Client { - jid: Jid, - transport: Arc>, - plugin_container: Arc, - binding: PluginProxyBinding, - dispatcher: Arc, -} - -impl Client { - /// Returns a reference to the `Jid` associated with this `Client`. - pub fn jid(&self) -> &Jid { - &self.jid - } - - /// Registers a plugin. - pub fn register_plugin(&mut self, mut plugin: P) { - let binding = self.binding.clone(); - plugin.bind(binding); - let p = Arc::new(plugin); - P::init(&self.dispatcher, p.clone()); - self.plugin_container.register(p); - } - - pub fn register_handler(&mut self, pri: Priority, func: F) - where - E: Event, - F: Fn(&E) -> Propagation + 'static { - self.dispatcher.register(pri, func); - } - - /// Returns the plugin given by the type parameter, if it exists, else panics. - pub fn plugin(&self) -> PluginRef

{ - self.plugin_container.get::

().unwrap() - } - - /// Returns the next event and flush the send queue. - pub fn main(&mut self) -> Result<(), Error> { - self.dispatcher.flush_all(); - loop { - let elem = self.read_element()?; - self.dispatcher.dispatch(ReceiveElement(elem)); - self.dispatcher.flush_all(); - } - } - - fn reset_stream(&self) { - self.transport.lock().unwrap().reset_stream() - } - - fn read_element(&self) -> Result { - self.transport.lock().unwrap().read_element() - } - - fn write_element(&self, elem: &Element) -> Result<(), Error> { - self.transport.lock().unwrap().write_element(elem) - } - - fn read_event(&self) -> Result { - self.transport.lock().unwrap().read_event() - } - - fn connect(&mut self, mut credentials: SaslCredentials) -> Result<(), Error> { - let features = self.wait_for_features()?; - let ms = &features.sasl_mechanisms.ok_or(Error::SaslError(Some("no SASL mechanisms".to_owned())))?; - fn wrap_err(err: String) -> Error { Error::SaslError(Some(err)) } - // TODO: better way for selecting these, enabling anonymous auth - let mut mechanism: Box = if ms.contains("SCRAM-SHA-256-PLUS") && credentials.channel_binding != ChannelBinding::None { - Box::new(Scram::::from_credentials(credentials).map_err(wrap_err)?) - } - else if ms.contains("SCRAM-SHA-1-PLUS") && credentials.channel_binding != ChannelBinding::None { - Box::new(Scram::::from_credentials(credentials).map_err(wrap_err)?) - } - else if ms.contains("SCRAM-SHA-256") { - if credentials.channel_binding != ChannelBinding::None { - credentials.channel_binding = ChannelBinding::Unsupported; - } - Box::new(Scram::::from_credentials(credentials).map_err(wrap_err)?) - } - else if ms.contains("SCRAM-SHA-1") { - if credentials.channel_binding != ChannelBinding::None { - credentials.channel_binding = ChannelBinding::Unsupported; - } - Box::new(Scram::::from_credentials(credentials).map_err(wrap_err)?) - } - else if ms.contains("PLAIN") { - Box::new(Plain::from_credentials(credentials).map_err(wrap_err)?) - } - else { - return Err(Error::SaslError(Some("can't find a SASL mechanism to use".to_owned()))); - }; - let auth = mechanism.initial().map_err(|x| Error::SaslError(Some(x)))?; - let mut elem = Element::builder("auth") - .ns(ns::SASL) - .attr("mechanism", mechanism.name()) - .build(); - if !auth.is_empty() { - elem.append_text_node(base64::encode(&auth)); - } - self.write_element(&elem)?; - loop { - let n = self.read_element()?; - if n.is("challenge", ns::SASL) { - let text = n.text(); - let challenge = if text == "" { - Vec::new() - } - else { - base64::decode(&text)? - }; - let response = mechanism.response(&challenge).map_err(|x| Error::SaslError(Some(x)))?; - let mut elem = Element::builder("response") - .ns(ns::SASL) - .build(); - if !response.is_empty() { - elem.append_text_node(base64::encode(&response)); - } - self.write_element(&elem)?; - } - else if n.is("success", ns::SASL) { - let text = n.text(); - let data = if text == "" { - Vec::new() - } - else { - base64::decode(&text)? - }; - mechanism.success(&data).map_err(|x| Error::SaslError(Some(x)))?; - self.reset_stream(); - { - let mut g = self.transport.lock().unwrap(); - C2S::init(&mut *g, &self.jid.domain, "after_sasl")?; - } - self.wait_for_features()?; - return Ok(()); - } - else if n.is("failure", ns::SASL) { - let inner = SaslError::from_element(&n).map_err(|_| Error::SaslError(None))?; - return Err(Error::XmppSaslError(inner)); - } - } - } - - fn bind(&mut self) -> Result<(), Error> { - let mut elem = Element::builder("iq") - .attr("id", "bind") - .attr("type", "set") - .build(); - let mut bind = Element::builder("bind") - .ns(ns::BIND) - .build(); - if let Some(ref resource) = self.jid.resource { - let res = Element::builder("resource") - .ns(ns::BIND) - .append(resource.to_owned()) - .build(); - bind.append_child(res); - } - elem.append_child(bind); - self.write_element(&elem)?; - loop { - let n = self.read_element()?; - if n.is("iq", ns::CLIENT) && n.has_child("bind", ns::BIND) { - return Ok(()); - } - } - } - - fn wait_for_features(&mut self) -> Result { - // TODO: this is very ugly - loop { - let e = self.read_event()?; - match e { - ReaderEvent::StartElement { .. } => { - break; - }, - _ => (), - } - } - loop { - let n = self.read_element()?; - if n.is("features", ns::STREAM) { - let mut features = StreamFeatures { - sasl_mechanisms: None, - }; - if let Some(ms) = n.get_child("mechanisms", ns::SASL) { - let mut res = HashSet::new(); - for cld in ms.children() { - res.insert(cld.text()); - } - features.sasl_mechanisms = Some(res); - } - return Ok(features); - } - } - } -} diff --git a/src/component.rs b/src/component.rs deleted file mode 100644 index 00592ed2b06e67ea735f0fb1b39204e3c5281d7e..0000000000000000000000000000000000000000 --- a/src/component.rs +++ /dev/null @@ -1,173 +0,0 @@ -use xml; -use jid::Jid; -use transport::{Transport, PlainTransport}; -use error::Error; -use ns; -use plugin::{Plugin, PluginInit, PluginProxyBinding, PluginContainer, PluginRef}; -use event::{Dispatcher, ReceiveElement, SendElement, Propagation, Priority, Event}; -use connection::{Connection, Component2S}; -use sha_1::{Sha1, Digest}; - -use minidom::Element; - -use xml::reader::XmlEvent as ReaderEvent; - -use std::fmt::Write; -use std::sync::{Mutex, Arc}; - -/// A builder for `Component`s. -pub struct ComponentBuilder { - jid: Jid, - secret: String, - host: Option, - port: u16, -} - -impl ComponentBuilder { - /// Creates a new builder for an XMPP component that will connect to `jid` with default parameters. - pub fn new(jid: Jid) -> ComponentBuilder { - ComponentBuilder { - jid: jid, - secret: "".to_owned(), - host: None, - port: 5347, - } - } - - /// Sets the host to connect to. - pub fn host(mut self, host: String) -> ComponentBuilder { - self.host = Some(host); - self - } - - /// Sets the port to connect to. - pub fn port(mut self, port: u16) -> ComponentBuilder { - self.port = port; - self - } - - /// Sets the password to use. - pub fn password>(mut self, password: P) -> ComponentBuilder { - self.secret = password.into(); - self - } - - /// Connects to the server and returns a `Component` when succesful. - pub fn connect(self) -> Result { - let host = &self.host.unwrap_or(self.jid.domain.clone()); - let mut transport = PlainTransport::connect(host, self.port)?; - Component2S::init(&mut transport, &self.jid.domain, "stream_opening")?; - let dispatcher = Arc::new(Dispatcher::new()); - let transport = Arc::new(Mutex::new(transport)); - let plugin_container = Arc::new(PluginContainer::new()); - let mut component = Component { - jid: self.jid.clone(), - transport: transport.clone(), - binding: PluginProxyBinding::new(dispatcher.clone(), plugin_container.clone(), self.jid), - plugin_container: plugin_container, - dispatcher: dispatcher, - }; - component.dispatcher.register(Priority::Default, move |evt: &SendElement| { - let mut t = transport.lock().unwrap(); - t.write_element(&evt.0).unwrap(); - Propagation::Continue - }); - component.connect(self.secret)?; - Ok(component) - } -} - -/// An XMPP component. -pub struct Component { - jid: Jid, - transport: Arc>, - plugin_container: Arc, - binding: PluginProxyBinding, - dispatcher: Arc, -} - -impl Component { - /// Returns a reference to the `Jid` associated with this `Component`. - pub fn jid(&self) -> &Jid { - &self.jid - } - - /// Registers a plugin. - pub fn register_plugin(&mut self, mut plugin: P) { - let binding = self.binding.clone(); - plugin.bind(binding); - let p = Arc::new(plugin); - P::init(&self.dispatcher, p.clone()); - self.plugin_container.register(p); - } - - pub fn register_handler(&mut self, pri: Priority, func: F) - where - E: Event, - F: Fn(&E) -> Propagation + 'static { - self.dispatcher.register(pri, func); - } - - /// Returns the plugin given by the type parameter, if it exists, else panics. - pub fn plugin(&self) -> PluginRef

{ - self.plugin_container.get::

().unwrap() - } - - /// Returns the next event and flush the send queue. - pub fn main(&mut self) -> Result<(), Error> { - self.dispatcher.flush_all(); - loop { - let elem = self.read_element()?; - self.dispatcher.dispatch(ReceiveElement(elem)); - self.dispatcher.flush_all(); - } - } - - fn read_element(&self) -> Result { - self.transport.lock().unwrap().read_element() - } - - fn write_element(&self, elem: &Element) -> Result<(), Error> { - self.transport.lock().unwrap().write_element(elem) - } - - fn read_event(&self) -> Result { - self.transport.lock().unwrap().read_event() - } - - fn connect(&mut self, secret: String) -> Result<(), Error> { - let mut sid = String::new(); - loop { - let e = self.read_event()?; - match e { - ReaderEvent::StartElement { attributes, .. } => { - for attribute in attributes { - if attribute.name.namespace == None && attribute.name.local_name == "id" { - sid = attribute.value; - } - } - break; - }, - _ => (), - } - } - let concatenated = format!("{}{}", sid, secret); - let mut hasher = Sha1::default(); - hasher.input(concatenated.as_bytes()); - let mut handshake = String::new(); - for byte in hasher.result() { - write!(handshake, "{:02x}", byte)?; - } - let mut elem = Element::builder("handshake") - .ns(ns::COMPONENT_ACCEPT) - .build(); - elem.append_text_node(handshake); - self.write_element(&elem)?; - loop { - let n = self.read_element()?; - if n.is("handshake", ns::COMPONENT_ACCEPT) { - return Ok(()); - } - } - } -} diff --git a/src/components/mod.rs b/src/components/mod.rs deleted file mode 100644 index 1d7dcd7d7307dd54a38ce9d549aa9e3c12d46020..0000000000000000000000000000000000000000 --- a/src/components/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod sasl_error; diff --git a/src/components/sasl_error.rs b/src/components/sasl_error.rs deleted file mode 100644 index 0fc011e6de576ca42147844ff3dc06ec1393be79..0000000000000000000000000000000000000000 --- a/src/components/sasl_error.rs +++ /dev/null @@ -1,84 +0,0 @@ -use ns; -use minidom::Element; -use util::FromElement; - -#[derive(Clone, Debug)] -pub enum Condition { - Aborted, - AccountDisabled, - CredentialsExpired, - EncryptionRequired, - IncorrectEncoding, - InvalidAuthzid, - InvalidMechanism, - MalformedRequest, - MechanismTooWeak, - NotAuthorized, - TemporaryAuthFailure, - Unknown, -} - -#[derive(Clone, Debug)] -pub struct SaslError { - condition: Condition, - text: Option, -} - -impl FromElement for SaslError { - type Err = (); - - fn from_element(element: &Element) -> Result { - if !element.is("failure", ns::SASL) { - return Err(()); - } - let mut err = SaslError { - condition: Condition::Unknown, - text: None, - }; - if let Some(text) = element.get_child("text", ns::SASL) { - let desc = text.text(); - err.text = Some(desc); - } - if element.has_child("aborted", ns::SASL) { - err.condition = Condition::Aborted; - } - else if element.has_child("account-disabled", ns::SASL) { - err.condition = Condition::AccountDisabled; - } - else if element.has_child("credentials-expired", ns::SASL) { - err.condition = Condition::CredentialsExpired; - } - else if element.has_child("encryption-required", ns::SASL) { - err.condition = Condition::EncryptionRequired; - } - else if element.has_child("incorrect-encoding", ns::SASL) { - err.condition = Condition::IncorrectEncoding; - } - else if element.has_child("invalid-authzid", ns::SASL) { - err.condition = Condition::InvalidAuthzid; - } - else if element.has_child("malformed-request", ns::SASL) { - err.condition = Condition::MalformedRequest; - } - else if element.has_child("mechanism-too-weak", ns::SASL) { - err.condition = Condition::MechanismTooWeak; - } - else if element.has_child("not-authorized", ns::SASL) { - err.condition = Condition::NotAuthorized; - } - else if element.has_child("temporary-auth-failure", ns::SASL) { - err.condition = Condition::TemporaryAuthFailure; - } - else { - /* RFC 6120 section 6.5: - * - * However, because additional error conditions might be defined in - * the future, if an entity receives a SASL error condition that it - * does not understand then it MUST treat the unknown condition as - * a generic authentication failure, i.e., as equivalent to - * (Section 6.5.10). */ - err.condition = Condition::NotAuthorized; - } - Ok(err) - } -} diff --git a/src/connection.rs b/src/connection.rs deleted file mode 100644 index 4d639b4e839ec59330a4c44ad519f3768afb4919..0000000000000000000000000000000000000000 --- a/src/connection.rs +++ /dev/null @@ -1,61 +0,0 @@ -use transport::Transport; -use error::Error; -use ns; - -use xml::writer::XmlEvent as WriterEvent; - -pub trait Connection { - type InitError; - type CloseError; - - fn namespace() -> &'static str; - - fn init(transport: &mut T, domain: &str, id: &str) -> Result<(), Self::InitError>; - fn close(transport: &mut T) -> Result<(), Self::CloseError>; -} - -pub struct C2S; - -impl Connection for C2S { - type InitError = Error; - type CloseError = Error; - - fn namespace() -> &'static str { ns::CLIENT } - - fn init(transport: &mut T, domain: &str, id: &str) -> Result<(), Error> { - transport.write_event(WriterEvent::start_element("stream:stream") - .attr("to", domain) - .attr("id", id) - .default_ns(ns::CLIENT) - .ns("stream", ns::STREAM))?; - Ok(()) - } - - fn close(transport: &mut T) -> Result<(), Error> { - transport.write_event(WriterEvent::end_element())?; - Ok(()) - } -} - -pub struct Component2S; - -impl Connection for Component2S { - type InitError = Error; - type CloseError = Error; - - fn namespace() -> &'static str { ns::COMPONENT_ACCEPT } - - fn init(transport: &mut T, domain: &str, id: &str) -> Result<(), Error> { - transport.write_event(WriterEvent::start_element("stream:stream") - .attr("to", domain) - .attr("id", id) - .default_ns(ns::COMPONENT_ACCEPT) - .ns("stream", ns::STREAM))?; - Ok(()) - } - - fn close(transport: &mut T) -> Result<(), Error> { - transport.write_event(WriterEvent::end_element())?; - Ok(()) - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index f27e94ed70af2556c07bc3e085208a1adb103155..0000000000000000000000000000000000000000 --- a/src/error.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Provides an `Error` for use in this crate. - -use std::fmt::Error as FormatError; - -use std::io; - -use std::net::TcpStream; - -use openssl::ssl::HandshakeError; -use openssl::error::ErrorStack; - -use xml::reader::Error as XmlError; -use xml::writer::Error as EmitterError; - -use minidom::Error as MinidomError; - -use base64::DecodeError; - -use components::sasl_error::SaslError; - -/// An error which wraps a bunch of errors from different crates and the stdlib. -#[derive(Debug)] -pub enum Error { - XmlError(XmlError), - EmitterError(EmitterError), - IoError(io::Error), - HandshakeError(HandshakeError), - OpenSslErrorStack(ErrorStack), - MinidomError(MinidomError), - Base64Error(DecodeError), - SaslError(Option), - XmppSaslError(SaslError), - FormatError(FormatError), - StreamError, - EndOfDocument, -} - -impl From for Error { - fn from(err: XmlError) -> Error { - Error::XmlError(err) - } -} - -impl From for Error { - fn from(err: EmitterError) -> Error { - Error::EmitterError(err) - } -} - -impl From for Error { - fn from(err: io::Error) -> Error { - Error::IoError(err) - } -} - -impl From> for Error { - fn from(err: HandshakeError) -> Error { - Error::HandshakeError(err) - } -} - -impl From for Error { - fn from(err: ErrorStack) -> Error { - Error::OpenSslErrorStack(err) - } -} - -impl From for Error { - fn from(err: MinidomError) -> Error { - Error::MinidomError(err) - } -} - -impl From for Error { - fn from(err: DecodeError) -> Error { - Error::Base64Error(err) - } -} - -impl From for Error { - fn from(err: FormatError) -> Error { - Error::FormatError(err) - } -} diff --git a/src/event.rs b/src/event.rs deleted file mode 100644 index 35627213261d54fa523708d9d6b3994cd176eb28..0000000000000000000000000000000000000000 --- a/src/event.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::marker::PhantomData; -use std::any::{TypeId, Any}; -use std::fmt::Debug; -use std::collections::BTreeMap; -use std::cmp::Ordering; -use std::mem; -use std::sync::Mutex; - -use minidom::Element; - -/// A marker trait which marks all events. -pub trait Event: Any + Debug {} - -/// A trait which is implemented for all event handlers. -trait EventHandler: Any { - /// Handle an event, returns whether to propagate the event to the remaining handlers. - fn handle(&self, event: &AbstractEvent) -> Propagation; -} - -/// An abstract event. -pub struct AbstractEvent { - inner: Box, -} - -impl AbstractEvent { - /// Creates an abstract event from a concrete event. - pub fn new(event: E) -> AbstractEvent { - AbstractEvent { - inner: Box::new(event), - } - } - - /// Downcasts this abstract event into a concrete event. - pub fn downcast(&self) -> Option<&E> { - self.inner.downcast_ref::() - } - - /// Checks whether this abstract event is a specific concrete event. - pub fn is(&self) -> bool { - self.inner.is::() - } -} - -struct Record(P, T); - -impl PartialEq for Record { - fn eq(&self, other: &Record) -> bool { - self.0 == other.0 - } -} - -impl Eq for Record {} - -impl PartialOrd for Record { - fn partial_cmp(&self, other: &Record) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for Record { - fn cmp(&self, other: &Record) -> Ordering { - self.0.cmp(&other.0) - } -} - -/// An enum representing whether to keep propagating an event or to stop the propagation. -pub enum Propagation { - /// Stop the propagation of the event, the remaining handlers will not get invoked. - Stop, - /// Continue propagating the event. - Continue, -} - -/// An event dispatcher, this takes care of dispatching events to their respective handlers. -pub struct Dispatcher { - handlers: Mutex>>>>, - queue: Mutex>, -} - -impl Dispatcher { - /// Create a new `Dispatcher`. - pub fn new() -> Dispatcher { - Dispatcher { - handlers: Mutex::new(BTreeMap::new()), - queue: Mutex::new(Vec::new()), - } - } - - /// Register an event handler. - pub fn register(&self, priority: Priority, func: F) - where - E: Event, - F: Fn(&E) -> Propagation + 'static { - struct Handler where E: Event, F: Fn(&E) -> Propagation { - func: F, - _marker: PhantomData, - } - - impl Propagation + 'static> EventHandler for Handler { - fn handle(&self, evt: &AbstractEvent) -> Propagation { - if let Some(e) = evt.downcast::() { - (self.func)(e) - } - else { - Propagation::Continue - } - } - } - - let handler: Box = Box::new(Handler { - func: func, - _marker: PhantomData, - }) as Box; - let mut guard = self.handlers.lock().unwrap(); - let ent = guard.entry(TypeId::of::()) - .or_insert_with(|| Vec::new()); - ent.push(Record(priority, handler)); - ent.sort(); - } - - /// Append an event to the queue. - pub fn dispatch(&self, event: E) where E: Event { - self.queue.lock().unwrap().push((TypeId::of::(), AbstractEvent::new(event))); - } - - /// Flush all events in the queue so they can be handled by their respective handlers. - /// Returns whether there are still pending events. - pub fn flush(&self) -> bool { - let mut q = Vec::new(); - { - let mut my_q = self.queue.lock().unwrap(); - mem::swap(my_q.as_mut(), &mut q); - } - 'evts: for (t, evt) in q { - if let Some(handlers) = self.handlers.lock().unwrap().get_mut(&t) { - for &mut Record(_, ref mut handler) in handlers { - match handler.handle(&evt) { - Propagation::Stop => { continue 'evts; }, - Propagation::Continue => (), - } - } - } - } - !self.queue.lock().unwrap().is_empty() - } - - /// Flushes all events, like `flush`, but keeps doing this until there is nothing left in the - /// queue. - pub fn flush_all(&self) { - while self.flush() {} - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Priority { - Max, - Default, - Min, -} - -impl Default for Priority { - fn default() -> Priority { - Priority::Default - } -} - -#[derive(Debug)] -pub struct SendElement(pub Element); - -impl Event for SendElement {} - -#[derive(Debug)] -pub struct ReceiveElement(pub Element); - -impl Event for ReceiveElement {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic(expected = "success")] - fn test() { - let disp = Dispatcher::new(); - - #[derive(Debug)] - struct MyEvent { - should_be_42: u32, - } - - impl Event for MyEvent {} - - disp.register(Priority::Max, |evt: &MyEvent| { - if evt.should_be_42 == 42 { - Propagation::Continue - } - else { - Propagation::Stop - } - }); - - disp.register(Priority::Min, |_: &MyEvent| { - panic!("should not be called"); - }); - - disp.register(Priority::Default, |evt: &MyEvent| { - if evt.should_be_42 == 42 { - panic!("success"); - } - else { - panic!("not 42"); - } - }); - - disp.register(Priority::Min, |_: &MyEvent| { - panic!("should not be called"); - }); - - disp.dispatch(MyEvent { - should_be_42: 39, - }); - - disp.dispatch(MyEvent { - should_be_42: 42, - }); - - disp.flush(); - } -} diff --git a/src/lib.rs b/src/lib.rs index c022941d68eca093a87c6807c3d362c33d0bf9e3..91f320705284eb6840e4376a4052cd86a57538ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,382 @@ -extern crate xml; -extern crate xmpp_parsers; -extern crate openssl; -extern crate minidom; -extern crate base64; -extern crate sha_1; -extern crate chrono; -extern crate try_from; -pub extern crate jid; -pub extern crate sasl; - -pub mod ns; -pub mod transport; -pub mod error; -pub mod client; -pub mod component; -pub mod plugin; -#[macro_use] pub mod plugin_macro; -pub mod event; -pub mod plugins; -pub mod connection; -pub mod util; -pub mod components; - -mod locked_io; +// Copyright (c) 2019 Emmanuel Gil Peyrot +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![deny(bare_trait_objects)] + +use std::str::FromStr; +use std::rc::Rc; +use std::cell::RefCell; +use std::convert::TryFrom; +use futures::{Future,Stream, Sink, sync::mpsc}; +use tokio_xmpp::{ + Client as TokioXmppClient, + Event as TokioXmppEvent, + Packet, +}; +use xmpp_parsers::{ + bookmarks::{ + Autojoin, + Conference as ConferenceBookmark, + Storage as Bookmarks, + }, + caps::{compute_disco, hash_caps, Caps}, + disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity}, + hashes::Algo, + iq::{Iq, IqType}, + message::{Message, MessageType, Body}, + muc::{ + Muc, + user::{MucUser, Status}, + }, + ns, + presence::{Presence, Type as PresenceType}, + pubsub::{ + event::PubSubEvent, + pubsub::PubSub, + }, + roster::{Roster, Item as RosterItem}, + stanza_error::{StanzaError, ErrorType, DefinedCondition}, + Jid, BareJid, FullJid, JidParseError, +}; + +mod avatar; + +pub type Error = tokio_xmpp::Error; + +#[derive(Debug)] +pub enum ClientType { + Bot, + Pc, +} + +impl Default for ClientType { + fn default() -> Self { + ClientType::Bot + } +} + +impl ToString for ClientType { + fn to_string(&self) -> String { + String::from( + match self { + ClientType::Bot => "bot", + ClientType::Pc => "pc", + } + ) + } +} + +#[derive(PartialEq)] +pub enum ClientFeature { + Avatars, + ContactList, + JoinRooms, +} + +#[derive(Debug)] +pub enum Event { + Online, + Disconnected, + ContactAdded(RosterItem), + ContactRemoved(RosterItem), + ContactChanged(RosterItem), + AvatarRetrieved(Jid, String), + OpenRoomBookmark(ConferenceBookmark), + RoomJoined(BareJid), + RoomLeft(BareJid), +} + +#[derive(Default)] +pub struct ClientBuilder<'a> { + jid: &'a str, + password: &'a str, + website: String, + default_nick: String, + disco: (ClientType, String), + features: Vec, +} + +impl ClientBuilder<'_> { + pub fn new<'a>(jid: &'a str, password: &'a str) -> ClientBuilder<'a> { + ClientBuilder { + jid, + password, + website: String::from("https://gitlab.com/xmpp-rs/tokio-xmpp"), + default_nick: String::from("xmpp-rs"), + disco: (ClientType::default(), String::from("tokio-xmpp")), + features: vec![], + } + } + + pub fn set_client(mut self, type_: ClientType, name: &str) -> Self { + self.disco = (type_, String::from(name)); + self + } + + pub fn set_website(mut self, url: &str) -> Self { + self.website = String::from(url); + self + } + + pub fn set_default_nick(mut self, nick: &str) -> Self { + self.default_nick = String::from(nick); + self + } + + pub fn enable_feature(mut self, feature: ClientFeature) -> Self { + self.features.push(feature); + self + } + + fn make_disco(&self) -> DiscoInfoResult { + let identities = vec![Identity::new("client", self.disco.0.to_string(), + "en", self.disco.1.to_string())]; + let mut features = vec![ + Feature::new(ns::DISCO_INFO), + ]; + if self.features.contains(&ClientFeature::Avatars) { + features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA))); + } + if self.features.contains(&ClientFeature::JoinRooms) { + features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS))); + } + DiscoInfoResult { + node: None, + identities, + features, + extensions: vec![], + } + } + + fn make_initial_presence(disco: &DiscoInfoResult, node: &str) -> Presence { + let caps_data = compute_disco(disco); + let hash = hash_caps(&caps_data, Algo::Sha_1).unwrap(); + let caps = Caps::new(node, hash); + + let mut presence = Presence::new(PresenceType::None); + presence.add_payload(caps); + presence + } + + pub fn build( + self, + ) -> Result<(Agent, impl Stream), JidParseError> { + let client = TokioXmppClient::new(self.jid, self.password)?; + Ok(self.build_impl(client)) + } + + // This function is meant to be used for testing build + pub(crate) fn build_impl( + self, + stream: S, + ) -> (Agent, impl Stream) + where + S: Stream + + Sink, + { + let disco = self.make_disco(); + let node = self.website; + let (sender_tx, sender_rx) = mpsc::unbounded(); + + let client = stream; + let (sink, stream) = client.split(); + + let reader = { + let mut sender_tx = sender_tx.clone(); + let jid = self.jid.to_owned(); + stream.map(move |event| { + // Helper function to send an iq error. + let mut events = Vec::new(); + let send_error = |to, id, type_, condition, text: &str| { + let error = StanzaError::new(type_, condition, "en", text); + let iq = Iq::from_error(id, error) + .with_to(to) + .into(); + sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap(); + }; + + match event { + TokioXmppEvent::Online => { + let presence = ClientBuilder::make_initial_presence(&disco, &node).into(); + let packet = Packet::Stanza(presence); + sender_tx.unbounded_send(packet) + .unwrap(); + events.push(Event::Online); + // TODO: only send this when the ContactList feature is enabled. + let iq = Iq::from_get("roster", Roster { ver: None, items: vec![] }) + .into(); + sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap(); + } + TokioXmppEvent::Disconnected => { + events.push(Event::Disconnected); + } + TokioXmppEvent::Stanza(stanza) => { + if stanza.is("iq", "jabber:client") { + let iq = Iq::try_from(stanza).unwrap(); + if let IqType::Get(payload) = iq.payload { + if payload.is("query", ns::DISCO_INFO) { + let query = DiscoInfoQuery::try_from(payload); + match query { + Ok(query) => { + let mut disco_info = disco.clone(); + disco_info.node = query.node; + let iq = Iq::from_result(iq.id, Some(disco_info)) + .with_to(iq.from.unwrap()) + .into(); + sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap(); + }, + Err(err) => { + send_error(iq.from.unwrap(), iq.id, ErrorType::Modify, DefinedCondition::BadRequest, &format!("{}", err)); + }, + } + } else { + // We MUST answer unhandled get iqs with a service-unavailable error. + send_error(iq.from.unwrap(), iq.id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq."); + } + } else if let IqType::Result(Some(payload)) = iq.payload { + // TODO: move private iqs like this one somewhere else, for + // security reasons. + if payload.is("query", ns::ROSTER) && iq.from.is_none() { + let roster = Roster::try_from(payload).unwrap(); + for item in roster.items.into_iter() { + events.push(Event::ContactAdded(item)); + } + } else if payload.is("pubsub", ns::PUBSUB) { + let pubsub = PubSub::try_from(payload).unwrap(); + let from = + iq.from.clone().unwrap_or_else(|| Jid::from_str(&jid).unwrap()); + if let PubSub::Items(items) = pubsub { + if items.node.0 == ns::AVATAR_DATA { + let new_events = avatar::handle_data_pubsub_iq(&from, &items); + events.extend(new_events); + } + } + } + } else if let IqType::Set(_) = iq.payload { + // We MUST answer unhandled set iqs with a service-unavailable error. + send_error(iq.from.unwrap(), iq.id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq."); + } + } else if stanza.is("message", "jabber:client") { + let message = Message::try_from(stanza).unwrap(); + let from = message.from.clone().unwrap(); + for child in message.payloads { + if child.is("event", ns::PUBSUB_EVENT) { + let event = PubSubEvent::try_from(child).unwrap(); + if let PubSubEvent::PublishedItems { node, items } = event { + if node.0 == ns::AVATAR_METADATA { + let new_events = avatar::handle_metadata_pubsub_event(&from, &mut sender_tx, items); + events.extend(new_events); + } else if node.0 == ns::BOOKMARKS { + // TODO: Check that our bare JID is the sender. + assert_eq!(items.len(), 1); + let item = items.clone().pop().unwrap(); + let payload = item.payload.clone().unwrap(); + let bookmarks = match Bookmarks::try_from(payload) { + Ok(bookmarks) => bookmarks, + // XXX: Don’t panic… + Err(err) => panic!("… {}", err), + }; + for bookmark in bookmarks.conferences { + if bookmark.autojoin == Autojoin::True { + events.push(Event::OpenRoomBookmark(bookmark)); + } + } + } + } + } + } + } else if stanza.is("presence", "jabber:client") { + let presence = Presence::try_from(stanza).unwrap(); + let from: BareJid = match presence.from.clone().unwrap() { + Jid::Full(FullJid { node, domain, .. }) => BareJid { node, domain }, + Jid::Bare(bare) => bare, + }; + for payload in presence.payloads.into_iter() { + let muc_user = match MucUser::try_from(payload) { + Ok(muc_user) => muc_user, + _ => continue + }; + for status in muc_user.status.into_iter() { + if status == Status::SelfPresence { + events.push(Event::RoomJoined(from.clone())); + break; + } + } + } + } else if stanza.is("error", "http://etherx.jabber.org/streams") { + println!("Received a fatal stream error: {}", String::from(&stanza)); + } else { + panic!("Unknown stanza: {}", String::from(&stanza)); + } + } + } + + futures::stream::iter_ok(events) + }) + .flatten() + }; + + let sender = sender_rx + .map_err(|e| panic!("Sink error: {:?}", e)) + .forward(sink) + .map(|(rx, mut sink)| { + drop(rx); + let _ = sink.close(); + None + }); + + // TODO is this correct? + // Some(Error) means a real error + // None means the end of the sender stream and can be ignored + let future = reader + .map(Some) + .select(sender.into_stream()) + .filter_map(|x| x); + + let agent = Agent { + sender_tx, + default_nick: Rc::new(RefCell::new(self.default_nick)), + }; + + (agent, future) + } +} + +#[derive(Clone)] +pub struct Agent { + sender_tx: mpsc::UnboundedSender, + default_nick: Rc>, +} + +impl Agent { + pub fn join_room(&mut self, room: BareJid, nick: Option, password: Option, + lang: &str, status: &str) { + let mut muc = Muc::new(); + if let Some(password) = password { + muc = muc.with_password(password); + } + + let nick = nick.unwrap_or_else(|| self.default_nick.borrow().clone()); + let room_jid = room.with_resource(nick); + let mut presence = Presence::new(PresenceType::None) + .with_to(Some(Jid::Full(room_jid))); + presence.add_payload(muc); + presence.set_status(String::from(lang), String::from(status)); + let presence = presence.into(); + self.sender_tx.unbounded_send(Packet::Stanza(presence)) + .unwrap(); + } + + pub fn send_message(&mut self, recipient: Jid, type_: MessageType, lang: &str, text: &str) { + let mut message = Message::new(Some(recipient)); + message.type_ = type_; + message.bodies.insert(String::from(lang), Body(String::from(text))); + let message = message.into(); + self.sender_tx.unbounded_send(Packet::Stanza(message)) + .unwrap(); + } +} +>>>>>>> lm-master diff --git a/src/locked_io.rs b/src/locked_io.rs deleted file mode 100644 index fc78d630d7afc958badf3bad471bd124fd95c8ce..0000000000000000000000000000000000000000 --- a/src/locked_io.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::io; -use std::io::prelude::*; - -use std::sync::{Arc, Mutex}; - -pub struct LockedIO(Arc>); - -impl LockedIO { - pub fn from(inner: Arc>) -> LockedIO { - LockedIO(inner) - } -} - -impl Clone for LockedIO { - fn clone(&self) -> LockedIO { - LockedIO(self.0.clone()) - } -} - -impl io::Write for LockedIO { - fn write(&mut self, buf: &[u8]) -> io::Result { - let mut inner = self.0.lock().unwrap(); // TODO: make safer - inner.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - let mut inner = self.0.lock().unwrap(); // TODO: make safer - inner.flush() - } -} - -impl io::Read for LockedIO { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let mut inner = self.0.lock().unwrap(); // TODO: make safer - inner.read(buf) - } -} diff --git a/src/ns.rs b/src/ns.rs deleted file mode 100644 index 4b1a3edfefbddeb69d4cda65389404f9bcd09a95..0000000000000000000000000000000000000000 --- a/src/ns.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Provides constants for namespaces. - -pub const CLIENT: &'static str = "jabber:client"; -pub const COMPONENT_ACCEPT: &'static str = "jabber:component:accept"; -pub const STREAM: &'static str = "http://etherx.jabber.org/streams"; -pub const TLS: &'static str = "urn:ietf:params:xml:ns:xmpp-tls"; -pub const SASL: &'static str = "urn:ietf:params:xml:ns:xmpp-sasl"; -pub const BIND: &'static str = "urn:ietf:params:xml:ns:xmpp-bind"; -pub const STANZAS: &'static str = "urn:ietf:params:xml:ns:xmpp-stanzas"; -pub const PING: &'static str = "urn:xmpp:ping"; diff --git a/src/plugin.rs b/src/plugin.rs deleted file mode 100644 index 6053b6cb13cc78bfb108f0b4c2f069e55118da35..0000000000000000000000000000000000000000 --- a/src/plugin.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Provides the plugin infrastructure. - -use event::{Event, Dispatcher, SendElement, Priority, Propagation}; - -use std::any::{Any, TypeId}; - -use std::collections::HashMap; - -use std::sync::{RwLock, Arc}; - -use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; - -use std::marker::PhantomData; - -use std::ops::Deref; - -use std::convert::AsRef; - -use std::mem; - -use minidom::Element; - -use jid::Jid; - -pub struct PluginContainer { - plugins: RwLock>>, -} - -impl PluginContainer { - pub fn new() -> PluginContainer { - PluginContainer { - plugins: RwLock::new(HashMap::new()), - } - } - - pub fn register(&self, plugin: Arc

) { - let mut guard = self.plugins.write().unwrap(); - if guard.insert(TypeId::of::

(), plugin as Arc).is_some() { - panic!("registering a plugin that's already registered"); - } - } - - pub fn get(&self) -> Option> { - let guard = self.plugins.read().unwrap(); - let arc = guard.get(&TypeId::of::

()); - arc.map(|arc| PluginRef { - inner: arc.clone(), - _marker: PhantomData - }) - } -} - -#[derive(Clone)] -pub struct PluginRef { - inner: Arc, - _marker: PhantomData

, -} - -impl Deref for PluginRef

{ - type Target = P; - - fn deref(&self) -> &P { - self.inner.as_any().downcast_ref::

().expect("plugin downcast failure") - } -} - -impl AsRef

for PluginRef

{ - fn as_ref(&self) -> &P { - self.inner.as_any().downcast_ref::

().expect("plugin downcast failure") - } -} - -#[derive(Clone)] -pub struct PluginProxyBinding { - dispatcher: Arc, - plugin_container: Arc, - jid: Jid, - next_id: Arc, -} - -impl PluginProxyBinding { - pub fn new(dispatcher: Arc, plugin_container: Arc, jid: Jid) -> PluginProxyBinding { - PluginProxyBinding { - dispatcher: dispatcher, - plugin_container: plugin_container, - jid: jid, - next_id: Arc::new(ATOMIC_USIZE_INIT), - } - } -} - -pub enum PluginProxy { - Unbound, - BoundTo(PluginProxyBinding), -} - -impl PluginProxy { - /// Returns a new `PluginProxy`. - pub fn new() -> PluginProxy { - PluginProxy::Unbound - } - - /// Binds the `PluginProxy` to a `PluginProxyBinding`. - pub fn bind(&mut self, inner: PluginProxyBinding) { - if let PluginProxy::BoundTo(_) = *self { - panic!("trying to bind an already bound plugin proxy!"); - } - mem::replace(self, PluginProxy::BoundTo(inner)); - } - - fn with_binding R>(&self, f: F) -> R { - match *self { - PluginProxy::Unbound => { - panic!("trying to use an unbound plugin proxy!"); - }, - PluginProxy::BoundTo(ref binding) => { - f(binding) - }, - } - } - - /// Dispatches an event. - pub fn dispatch(&self, event: E) { - self.with_binding(move |binding| { - // TODO: proper error handling - binding.dispatcher.dispatch(event); - }); - } - - /// Registers an event handler. - pub fn register_handler(&self, priority: Priority, func: F) - where - E: Event, - F: Fn(&E) -> Propagation + 'static { - self.with_binding(move |binding| { - // TODO: proper error handling - binding.dispatcher.register(priority, func); - }); - } - - /// Tries to get another plugin. - pub fn plugin(&self) -> Option> { - self.with_binding(|binding| { - binding.plugin_container.get::

() - }) - } - - /// Sends a stanza. - pub fn send(&self, elem: Element) { - self.dispatch(SendElement(elem)); - } - - /// Get our own JID. - pub fn get_own_jid(&self) -> Jid { - self.with_binding(|binding| { - binding.jid.clone() - }) - } - - /// Get a new id. - pub fn gen_id(&self) -> String { - self.with_binding(|binding| { - format!("{}", binding.next_id.fetch_add(1, Ordering::SeqCst)) - }) - } -} - -/// A trait whch all plugins should implement. -pub trait Plugin: Any + PluginAny { - /// Gets a mutable reference to the inner `PluginProxy`. - fn get_proxy(&mut self) -> &mut PluginProxy; - - #[doc(hidden)] - fn bind(&mut self, inner: PluginProxyBinding) { - self.get_proxy().bind(inner); - } -} - -pub trait PluginInit { - fn init(dispatcher: &Dispatcher, me: Arc); -} - -pub trait PluginAny { - fn as_any(&self) -> &Any; -} - -impl PluginAny for T { - fn as_any(&self) -> &Any { self } -} diff --git a/src/plugin_macro.rs b/src/plugin_macro.rs deleted file mode 100644 index 9a289c490a1a519f1e2b885b27a5ff739f8d86ee..0000000000000000000000000000000000000000 --- a/src/plugin_macro.rs +++ /dev/null @@ -1,28 +0,0 @@ -#[macro_export] -macro_rules! impl_plugin { - ($plugin:ty, $proxy:ident, [$(($evt:ty, $pri:expr) => $func:ident),*]) => { - impl $crate::plugin::Plugin for $plugin { - fn get_proxy(&mut self) -> &mut $crate::plugin::PluginProxy { - &mut self.$proxy - } - } - - #[allow(unused_variables)] - impl $crate::plugin::PluginInit for $plugin { - fn init( dispatcher: &$crate::event::Dispatcher - , me: ::std::sync::Arc<$crate::plugin::Plugin>) { - $( - let new_arc = me.clone(); - dispatcher.register($pri, move |e: &$evt| { - let p = new_arc.as_any().downcast_ref::<$plugin>().unwrap(); - p . $func(e) - }); - )* - } - } - }; - - ($plugin:ty, $proxy:ident, [$(($evt:ty, $pri:expr) => $func:ident),*,]) => { - impl_plugin!($plugin, $proxy, [$(($evt, $pri) => $func),*]); - }; -} diff --git a/src/plugins/caps.rs b/src/plugins/caps.rs deleted file mode 100644 index c61e35ae1902a257b8d34e8bb1f0e37050f49981..0000000000000000000000000000000000000000 --- a/src/plugins/caps.rs +++ /dev/null @@ -1,137 +0,0 @@ -use std::collections::HashMap; -use try_from::TryFrom; -use std::sync::{Mutex, Arc}; - -use plugin::PluginProxy; -use event::{Event, Priority, Propagation}; -use jid::Jid; -use base64; - -use plugins::stanza::{Presence, Iq}; -use plugins::disco::DiscoInfoResult; -use xmpp_parsers::presence::Type as PresenceType; -use xmpp_parsers::iq::IqType; -use xmpp_parsers::disco::{DiscoInfoQuery, DiscoInfoResult as DiscoInfoResult_}; -use xmpp_parsers::caps::Caps; - -#[derive(Debug)] -pub struct DiscoInfoRequest { - pub from: Jid, - pub id: String, - pub node: Option, -} - -impl Event for DiscoInfoRequest {} - -pub struct CapsPlugin { - proxy: PluginProxy, - pending: Arc>>, - cache: Arc>>, -} - -impl CapsPlugin { - pub fn new() -> CapsPlugin { - CapsPlugin { - proxy: PluginProxy::new(), - pending: Arc::new(Mutex::new(HashMap::new())), - cache: Arc::new(Mutex::new(HashMap::new())), - } - } - - fn handle_presence(&self, presence: &Presence) -> Propagation { - let presence = presence.clone(); - match presence.type_ { - PresenceType::None => for payload in presence.payloads { - let caps = match Caps::try_from(payload) { - Ok(caps) => caps, - Err(_) => continue, - }; - let recipient = presence.from.unwrap(); - let node = format!("{}#{}", caps.node, base64::encode(&caps.hash.hash)); - { - let cache = self.cache.lock().unwrap(); - if cache.contains_key(&(recipient.clone(), node.clone())) { - break; - } - } - let id = self.proxy.gen_id(); - { - let mut pending = self.pending.lock().unwrap(); - pending.insert(recipient.clone(), (id.clone(), node.clone())); - } - let disco = DiscoInfoQuery { - node: Some(node), - }; - self.proxy.send(Iq { - to: Some(recipient), - from: None, - id: Some(id), - payload: IqType::Get(disco.into()), - }.into()); - break; - }, - PresenceType::Unavailable - | PresenceType::Error => { - let recipient = presence.from.unwrap(); - let mut pending = self.pending.lock().unwrap(); - let previous = pending.remove(&recipient); - if previous.is_none() { - // This wasn’t one of our requests. - return Propagation::Continue; - } - // TODO: maybe add a negative cache? - }, - _ => (), - } - Propagation::Continue - } - - fn handle_result(&self, result: &DiscoInfoResult) -> Propagation { - let from = result.from.clone(); - let mut pending = self.pending.lock().unwrap(); - let previous = pending.remove(&from.clone()); - if let Some((id, node)) = previous { - if id != result.id { - return Propagation::Continue; - } - if Some(node.clone()) != result.disco.node { - // TODO: make that a debug log. - println!("Wrong node in result!"); - return Propagation::Continue; - } - { - let mut cache = self.cache.lock().unwrap(); - cache.insert((from, node), result.disco.clone()); - } - } else { - // TODO: make that a debug log. - println!("No such request from us."); - return Propagation::Continue; - } - Propagation::Stop - } - - // This is only for errors. - // TODO: also do the same thing for timeouts. - fn handle_iq(&self, iq: &Iq) -> Propagation { - let iq = iq.clone(); - if let IqType::Error(_) = iq.payload { - let from = iq.from.unwrap(); - let mut pending = self.pending.lock().unwrap(); - let previous = pending.remove(&from.clone()); - if previous.is_none() { - // This wasn’t one of our requests. - return Propagation::Continue; - } - // TODO: maybe add a negative cache? - return Propagation::Stop; - } - Propagation::Continue - } -} - -impl_plugin!(CapsPlugin, proxy, [ - (Presence, Priority::Default) => handle_presence, - (Iq, Priority::Default) => handle_iq, - (DiscoInfoResult, Priority::Default) => handle_result, -]); diff --git a/src/plugins/disco.rs b/src/plugins/disco.rs deleted file mode 100644 index 209125925ba6458bb7195a545774bc35e7d1303a..0000000000000000000000000000000000000000 --- a/src/plugins/disco.rs +++ /dev/null @@ -1,141 +0,0 @@ -use try_from::TryFrom; -use std::sync::{Mutex, Arc}; - -use plugin::PluginProxy; -use event::{Event, Priority, Propagation}; -use jid::Jid; - -use plugins::stanza::Iq; -use xmpp_parsers::iq::IqType; -use xmpp_parsers::disco::{DiscoInfoQuery, DiscoInfoResult as DiscoInfoResult_, Identity, Feature}; -use xmpp_parsers::data_forms::DataForm; -use xmpp_parsers::ns; - -#[derive(Debug)] -pub struct DiscoInfoRequest { - pub from: Jid, - pub id: String, - pub node: Option, -} - -#[derive(Debug)] -pub struct DiscoInfoResult { - pub from: Jid, - pub id: String, - pub disco: DiscoInfoResult_, -} - -impl Event for DiscoInfoRequest {} -impl Event for DiscoInfoResult {} - -pub struct DiscoPlugin { - proxy: PluginProxy, - cached_disco: Arc>, -} - -impl DiscoPlugin { - pub fn new(category: &str, type_: &str, lang: &str, name: &str) -> DiscoPlugin { - DiscoPlugin { - proxy: PluginProxy::new(), - cached_disco: Arc::new(Mutex::new(DiscoInfoResult_ { - node: None, - identities: vec!(Identity { - category: category.to_owned(), - type_: type_.to_owned(), - lang: Some(lang.to_owned()), - name: Some(name.to_owned()) - }), - features: vec!(Feature { var: String::from(ns::DISCO_INFO) }), - extensions: vec!(), - })), - } - } - - pub fn add_identity(&self, category: &str, type_: &str, lang: Option<&str>, name: Option<&str>) { - let mut cached_disco = self.cached_disco.lock().unwrap(); - cached_disco.identities.push(Identity { - category: category.to_owned(), - type_: type_.to_owned(), - lang: lang.and_then(|lang| Some(lang.to_owned())), - name: name.and_then(|name| Some(name.to_owned())), - }); - } - - pub fn remove_identity(&self, category: &str, type_: &str, lang: Option<&str>, name: Option<&str>) { - let mut cached_disco = self.cached_disco.lock().unwrap(); - cached_disco.identities.retain(|identity| { - identity.category != category || - identity.type_ != type_ || - identity.lang != lang.and_then(|lang| Some(lang.to_owned())) || - identity.name != name.and_then(|name| Some(name.to_owned())) - }); - } - - pub fn add_feature(&self, var: &str) { - let mut cached_disco = self.cached_disco.lock().unwrap(); - cached_disco.features.push(Feature { var: String::from(var) }); - } - - pub fn remove_feature(&self, var: &str) { - let mut cached_disco = self.cached_disco.lock().unwrap(); - cached_disco.features.retain(|feature| feature.var != var); - } - - pub fn add_extension(&self, extension: DataForm) { - let mut cached_disco = self.cached_disco.lock().unwrap(); - cached_disco.extensions.push(extension); - } - - pub fn remove_extension(&self, form_type: &str) { - let mut cached_disco = self.cached_disco.lock().unwrap(); - cached_disco.extensions.retain(|extension| { - extension.form_type != Some(form_type.to_owned()) - }); - } - - fn handle_iq(&self, iq: &Iq) -> Propagation { - let iq = iq.clone(); - if let IqType::Get(payload) = iq.payload { - if let Ok(disco) = DiscoInfoQuery::try_from(payload) { - self.proxy.dispatch(DiscoInfoRequest { - from: iq.from.unwrap(), - id: iq.id.unwrap(), - node: disco.node, - }); - return Propagation::Stop; - } - } else if let IqType::Result(Some(payload)) = iq.payload { - if let Ok(disco) = DiscoInfoResult_::try_from(payload) { - self.proxy.dispatch(DiscoInfoResult { - from: iq.from.unwrap(), - id: iq.id.unwrap(), - disco: disco, - }); - return Propagation::Stop; - } - } - Propagation::Continue - } - - fn reply_disco_info(&self, request: &DiscoInfoRequest) -> Propagation { - let payload = if request.node.is_none() { - let cached_disco = self.cached_disco.lock().unwrap().clone(); - IqType::Result(Some(cached_disco.into())) - } else { - // TODO: handle the requests on nodes too. - return Propagation::Continue; - }; - self.proxy.send(Iq { - from: None, - to: Some(request.from.to_owned()), - id: Some(request.id.to_owned()), - payload, - }.into()); - Propagation::Stop - } -} - -impl_plugin!(DiscoPlugin, proxy, [ - (Iq, Priority::Default) => handle_iq, - (DiscoInfoRequest, Priority::Default) => reply_disco_info, -]); diff --git a/src/plugins/ibb.rs b/src/plugins/ibb.rs deleted file mode 100644 index a8738a278fe9e6e2d951ffa9d6ced9797a66fcec..0000000000000000000000000000000000000000 --- a/src/plugins/ibb.rs +++ /dev/null @@ -1,195 +0,0 @@ -use std::collections::{HashMap, BTreeMap}; -use std::collections::hash_map::Entry; -use try_from::TryFrom; -use std::sync::{Mutex, Arc}; - -use plugin::PluginProxy; -use event::{Event, Priority, Propagation}; -use jid::Jid; - -use plugins::stanza::Iq; -use plugins::disco::DiscoPlugin; -use xmpp_parsers::iq::{IqType, IqSetPayload}; -use xmpp_parsers::ibb::{IBB, Stanza}; -use xmpp_parsers::stanza_error::{StanzaError, ErrorType, DefinedCondition}; -use xmpp_parsers::ns; - -#[derive(Debug, Clone)] -pub struct Session { - stanza: Stanza, - block_size: u16, - cur_seq: u16, -} - -#[derive(Debug)] -pub struct IbbOpen { - pub session: Session, -} - -#[derive(Debug)] -pub struct IbbData { - pub session: Session, - pub data: Vec, -} - -#[derive(Debug)] -pub struct IbbClose { - pub session: Session, -} - -impl Event for IbbOpen {} -impl Event for IbbData {} -impl Event for IbbClose {} - -fn generate_error(type_: ErrorType, defined_condition: DefinedCondition, text: &str) -> StanzaError { - StanzaError { - type_: type_, - defined_condition: defined_condition, - texts: { - let mut texts = BTreeMap::new(); - texts.insert(String::new(), String::from(text)); - texts - }, - by: None, - other: None, - } -} - -pub struct IbbPlugin { - proxy: PluginProxy, - sessions: Arc>>, -} - -impl IbbPlugin { - pub fn new() -> IbbPlugin { - IbbPlugin { - proxy: PluginProxy::new(), - sessions: Arc::new(Mutex::new(HashMap::new())), - } - } - - // TODO: make that called automatically after plugins are created. - pub fn init(&self) { - if let Some(disco) = self.proxy.plugin::() { - disco.add_feature(ns::IBB); - } else { - panic!("Please handle dependencies in the correct order."); - } - } - - // TODO: make that called automatically before removal. - pub fn deinit(&self) { - if let Some(disco) = self.proxy.plugin::() { - disco.remove_feature(ns::IBB); - } else { - panic!("Please handle dependencies in the correct order."); - } - } - - fn handle_ibb(&self, from: Jid, ibb: IBB) -> Result<(), StanzaError> { - let mut sessions = self.sessions.lock().unwrap(); - match ibb { - IBB::Open { block_size, sid, stanza } => { - match sessions.entry((from.clone(), sid.clone())) { - Entry::Vacant(_) => Ok(()), - Entry::Occupied(_) => Err(generate_error( - ErrorType::Cancel, - DefinedCondition::NotAcceptable, - "This session is already open." - )), - }?; - let session = Session { - stanza, - block_size, - cur_seq: 65535u16, - }; - sessions.insert((from, sid), session.clone()); - self.proxy.dispatch(IbbOpen { - session: session, - }); - }, - IBB::Data { seq, sid, data } => { - let entry = match sessions.entry((from, sid)) { - Entry::Occupied(entry) => Ok(entry), - Entry::Vacant(_) => Err(generate_error( - ErrorType::Cancel, - DefinedCondition::ItemNotFound, - "This session doesn’t exist." - )), - }?; - let mut session = entry.into_mut(); - if session.stanza != Stanza::Iq { - return Err(generate_error( - ErrorType::Cancel, - DefinedCondition::NotAcceptable, - "Wrong stanza type." - )) - } - let cur_seq = session.cur_seq.wrapping_add(1); - if seq != cur_seq { - return Err(generate_error( - ErrorType::Cancel, - DefinedCondition::NotAcceptable, - "Wrong seq number." - )) - } - session.cur_seq = cur_seq; - self.proxy.dispatch(IbbData { - session: session.clone(), - data, - }); - }, - IBB::Close { sid } => { - let entry = match sessions.entry((from, sid)) { - Entry::Occupied(entry) => Ok(entry), - Entry::Vacant(_) => Err(generate_error( - ErrorType::Cancel, - DefinedCondition::ItemNotFound, - "This session doesn’t exist." - )), - }?; - let session = entry.remove(); - self.proxy.dispatch(IbbClose { - session, - }); - }, - } - Ok(()) - } - - fn handle_iq(&self, iq: &Iq) -> Propagation { - let iq = iq.clone(); - if let IqType::Set(payload) = iq.payload { - let from = iq.from.unwrap(); - let id = iq.id.unwrap(); - // TODO: use an intermediate plugin to parse this payload. - let payload = match IqSetPayload::try_from(payload) { - Ok(IqSetPayload::IBB(ibb)) => { - match self.handle_ibb(from.clone(), ibb) { - Ok(_) => IqType::Result(None), - Err(error) => IqType::Error(error), - } - }, - Err(err) => IqType::Error(generate_error( - ErrorType::Cancel, - DefinedCondition::NotAcceptable, - format!("{:?}", err).as_ref() - )), - Ok(_) => return Propagation::Continue, - }; - self.proxy.send(Iq { - from: None, - to: Some(from), - id: Some(id), - payload: payload, - }.into()); - Propagation::Stop - } else { - Propagation::Continue - } - } -} - -impl_plugin!(IbbPlugin, proxy, [ - (Iq, Priority::Default) => handle_iq, -]); diff --git a/src/plugins/messaging.rs b/src/plugins/messaging.rs deleted file mode 100644 index 9b0d1d439de6d63126babf8213fc44100e0205f8..0000000000000000000000000000000000000000 --- a/src/plugins/messaging.rs +++ /dev/null @@ -1,132 +0,0 @@ -use try_from::TryFrom; -use std::collections::BTreeMap; - -use plugin::PluginProxy; -use event::{Event, Priority, Propagation}; -use error::Error; -use jid::Jid; - -use plugins::stanza::Message; -use xmpp_parsers::message::{MessagePayload, MessageType}; -use xmpp_parsers::chatstates::ChatState; -use xmpp_parsers::receipts::Receipt; -use xmpp_parsers::stanza_id::StanzaId; - -// TODO: use the id (maybe even stanza-id) to identify every message. -#[derive(Debug)] -pub struct MessageEvent { - pub from: Jid, - pub body: String, - pub subject: Option, - pub thread: Option, -} - -#[derive(Debug)] -pub struct ChatStateEvent { - pub from: Jid, - pub chat_state: ChatState, -} - -#[derive(Debug)] -pub struct ReceiptRequestEvent { - pub from: Jid, -} - -#[derive(Debug)] -pub struct ReceiptReceivedEvent { - pub from: Jid, - pub id: String, -} - -#[derive(Debug)] -pub struct StanzaIdEvent { - pub from: Jid, - pub stanza_id: StanzaId, - pub message: Message, -} - -impl Event for MessageEvent {} -impl Event for ChatStateEvent {} -impl Event for ReceiptRequestEvent {} -impl Event for ReceiptReceivedEvent {} -impl Event for StanzaIdEvent {} - -pub struct MessagingPlugin { - proxy: PluginProxy, -} - -impl MessagingPlugin { - pub fn new() -> MessagingPlugin { - MessagingPlugin { - proxy: PluginProxy::new(), - } - } - - pub fn send_message(&self, to: &Jid, body: &str) -> Result<(), Error> { - let message = Message { - from: None, - to: Some(to.clone()), - type_: MessageType::Chat, - id: Some(self.proxy.gen_id()), - bodies: { - let mut bodies = BTreeMap::new(); - bodies.insert(String::new(), String::from(body)); - bodies - }, - subjects: BTreeMap::new(), - thread: None, - payloads: vec!(), - }; - self.proxy.send(message.into()); - Ok(()) - } - - fn handle_message(&self, message: &Message) -> Propagation { - let from = message.from.clone().unwrap_or(self.proxy.get_own_jid()); - for payload in message.payloads.clone() { - let payload = match MessagePayload::try_from(payload) { - Ok(payload) => payload, - Err(err) => { - println!("MessagePayload: {:?}", err); - continue; - } - }; - match payload { - // XEP-0085 - MessagePayload::ChatState(chat_state) => self.proxy.dispatch(ChatStateEvent { - from: from.clone(), - chat_state: chat_state, - }), - // XEP-0184 - MessagePayload::Receipt(Receipt::Request) => self.proxy.dispatch(ReceiptRequestEvent { - from: from.clone(), - }), - // XEP-0184 - MessagePayload::Receipt(Receipt::Received(id)) => self.proxy.dispatch(ReceiptReceivedEvent { - from: from.clone(), - id: id.unwrap(), - }), - // XEP-0359 - MessagePayload::StanzaId(stanza_id) => self.proxy.dispatch(StanzaIdEvent { - from: from.clone(), - stanza_id: stanza_id, - message: message.clone(), - }), - payload => println!("Unhandled payload: {:?}", payload), - } - } - if message.bodies.contains_key("") { - self.proxy.dispatch(MessageEvent { - from: from, - body: message.bodies[""].clone(), - subject: if message.subjects.contains_key("") { Some(message.subjects[""].clone()) } else { None }, - thread: message.thread.clone(), - }); - } - Propagation::Stop - } -} - -impl_plugin!(MessagingPlugin, proxy, [ - (Message, Priority::Default) => handle_message, -]); diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs deleted file mode 100644 index 2a5478110e370ca296faa7cce897f71d45ba5175..0000000000000000000000000000000000000000 --- a/src/plugins/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod messaging; -pub mod presence; -pub mod roster; -pub mod disco; -pub mod caps; -pub mod ping; -pub mod ibb; -pub mod stanza; -pub mod stanza_debug; -pub mod unhandled_iq; -pub mod muc; diff --git a/src/plugins/muc.rs b/src/plugins/muc.rs deleted file mode 100644 index c316ba347b37f4d751004c3d3815b57fc316b206..0000000000000000000000000000000000000000 --- a/src/plugins/muc.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::collections::BTreeMap; -use try_from::TryFrom; - -use jid::Jid; -use error::Error; -use plugin::PluginProxy; - -use event::{Event, Propagation, Priority}; - -pub use xmpp_parsers::muc::{Muc, MucUser}; -pub use xmpp_parsers::muc::user::{Status, Affiliation, Role}; -pub use xmpp_parsers::presence::{Presence, Type, Show}; - -#[derive(Debug)] -pub struct MucPresence { - pub room: Jid, - pub nick: Option, - pub to: Jid, - pub type_: Type, - pub x: MucUser, -} - -impl Event for MucPresence {} - -pub struct MucPlugin { - proxy: PluginProxy, -} - -impl MucPlugin { - pub fn new() -> MucPlugin { - MucPlugin { - proxy: PluginProxy::new(), - } - } - - pub fn join_room(&self, room: Jid) -> Result<(), Error> { - let x = Muc { password: None }; - let presence = Presence { - from: None, - to: Some(room), - id: None, - type_: Type::None, - show: Show::None, - priority: 0i8, - statuses: BTreeMap::new(), - payloads: vec![x.into()], - }; - self.proxy.send(presence.into()); - - Ok(()) - } - - pub fn leave_room(&self, room: Jid) -> Result<(), Error> { - let x = Muc { password: None }; - let presence = Presence { - from: None, - to: Some(room), - id: None, - type_: Type::None, - show: Show::None, - priority: 0i8, - statuses: BTreeMap::new(), - payloads: vec![x.into()], - }; - self.proxy.send(presence.into()); - Ok(()) - } - - fn handle_presence(&self, presence: &Presence) -> Propagation { - let from = presence.from.clone().unwrap(); - let room = from.clone().into_bare_jid(); - let nick = from.resource; - let to = presence.to.clone().unwrap(); - let type_ = presence.type_.clone(); - - for payload in presence.clone().payloads { - if let Ok(x) = MucUser::try_from(payload) { - self.proxy.dispatch(MucPresence { - room: room.clone(), - nick: nick.clone(), - to: to.clone(), - type_: type_.clone(), - x - }); - } - } - - Propagation::Stop - } -} - -impl_plugin!(MucPlugin, proxy, [ - (Presence, Priority::Default) => handle_presence, -]); diff --git a/src/plugins/ping.rs b/src/plugins/ping.rs deleted file mode 100644 index def22ba3eca29299ea260786f9d4e0b6d29245d3..0000000000000000000000000000000000000000 --- a/src/plugins/ping.rs +++ /dev/null @@ -1,91 +0,0 @@ -use try_from::TryFrom; - -use plugin::PluginProxy; -use event::{Event, Priority, Propagation}; -use error::Error; -use jid::Jid; - -use plugins::stanza::Iq; -use plugins::disco::DiscoPlugin; -use xmpp_parsers::iq::{IqType, IqGetPayload}; -use xmpp_parsers::ping::Ping; -use xmpp_parsers::ns; - -#[derive(Debug)] -pub struct PingEvent { - pub from: Jid, - pub id: String, -} - -impl Event for PingEvent {} - -pub struct PingPlugin { - proxy: PluginProxy, -} - -impl PingPlugin { - pub fn new() -> PingPlugin { - PingPlugin { - proxy: PluginProxy::new(), - } - } - - // TODO: make that called automatically after plugins are created. - pub fn init(&self) { - if let Some(disco) = self.proxy.plugin::() { - disco.add_feature(ns::PING); - } else { - panic!("Please handle dependencies in the correct order."); - } - } - - // TODO: make that called automatically before removal. - pub fn deinit(&self) { - if let Some(disco) = self.proxy.plugin::() { - disco.remove_feature(ns::PING); - } else { - panic!("Please handle dependencies in the correct order."); - } - } - - pub fn send_ping(&self, to: &Jid) -> Result<(), Error> { - let to = to.clone(); - self.proxy.send(Iq { - from: None, - to: Some(to), - id: Some(self.proxy.gen_id()), - payload: IqType::Get(IqGetPayload::Ping(Ping).into()), - }.into()); - Ok(()) - } - - fn handle_iq(&self, iq: &Iq) -> Propagation { - let iq = iq.clone(); - if let IqType::Get(payload) = iq.payload { - // TODO: use an intermediate plugin to parse this payload. - if let Ok(IqGetPayload::Ping(_)) = IqGetPayload::try_from(payload) { - self.proxy.dispatch(PingEvent { // TODO: safety!!! - from: iq.from.unwrap(), - id: iq.id.unwrap(), - }); - return Propagation::Stop; - } - } - Propagation::Continue - } - - fn reply_ping(&self, ping: &PingEvent) -> Propagation { - self.proxy.send(Iq { - from: None, - to: Some(ping.from.to_owned()), - id: Some(ping.id.to_owned()), - payload: IqType::Result(None), - }.into()); - Propagation::Continue - } -} - -impl_plugin!(PingPlugin, proxy, [ - (Iq, Priority::Default) => handle_iq, - (PingEvent, Priority::Default) => reply_ping, -]); diff --git a/src/plugins/presence.rs b/src/plugins/presence.rs deleted file mode 100644 index b0e57bdcfbe24a3fe49ceed00dac043044b409ca..0000000000000000000000000000000000000000 --- a/src/plugins/presence.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::collections::BTreeMap; - -use error::Error; -use plugin::PluginProxy; - -pub use xmpp_parsers::presence::{Presence, Type, Show}; - -pub struct PresencePlugin { - proxy: PluginProxy, -} - -impl PresencePlugin { - pub fn new() -> PresencePlugin { - PresencePlugin { - proxy: PluginProxy::new(), - } - } - - pub fn set_presence(&self, type_: Type, show: Show, status: Option) -> Result<(), Error> { - let presence = Presence { - from: None, - to: None, - id: Some(self.proxy.gen_id()), - type_: type_, - show: show, - priority: 0i8, - statuses: { - let mut statuses = BTreeMap::new(); - if let Some(status) = status { - statuses.insert(String::new(), status); - } - statuses - }, - payloads: vec!(), - }; - self.proxy.send(presence.into()); - Ok(()) - } -} - -impl_plugin!(PresencePlugin, proxy, []); diff --git a/src/plugins/roster.rs b/src/plugins/roster.rs deleted file mode 100644 index 487a90ff202cf42908bd86a3a55a4daea60ce7da..0000000000000000000000000000000000000000 --- a/src/plugins/roster.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::collections::HashMap; -use try_from::TryFrom; -use std::sync::Mutex; - -use plugin::PluginProxy; -use event::{Event, Priority, Propagation}; -use jid::Jid; - -use plugins::stanza::Iq; -use plugins::disco::DiscoPlugin; -use xmpp_parsers::iq::{IqType, IqSetPayload, IqResultPayload}; -use xmpp_parsers::roster::{Roster, Item, Subscription}; -use xmpp_parsers::ns; - -#[derive(Debug)] -pub struct RosterReceived { - pub ver: Option, - pub jids: HashMap, -} - -#[derive(Debug)] -pub enum RosterPush { - Added(Item), - Modified(Item), - Removed(Item), -} - -impl Event for RosterReceived {} -impl Event for RosterPush {} - -pub struct RosterPlugin { - proxy: PluginProxy, - current_version: Mutex>, - // TODO: allow for a different backing store. - jids: Mutex>, -} - -impl RosterPlugin { - pub fn new(ver: Option) -> RosterPlugin { - RosterPlugin { - proxy: PluginProxy::new(), - current_version: Mutex::new(ver), - jids: Mutex::new(HashMap::new()), - } - } - - // TODO: make that called automatically after plugins are created. - pub fn init(&self) { - if let Some(disco) = self.proxy.plugin::() { - disco.add_feature(ns::IBB); - } else { - panic!("Please handle dependencies in the correct order."); - } - } - - // TODO: make that called automatically before removal. - pub fn deinit(&self) { - if let Some(disco) = self.proxy.plugin::() { - disco.remove_feature(ns::IBB); - } else { - panic!("Please handle dependencies in the correct order."); - } - } - - pub fn send_roster_get(&self, ver: Option) { - let iq = Iq { - from: None, - to: None, - id: Some(self.proxy.gen_id()), - payload: IqType::Get(Roster { - ver, - items: vec!(), - }.into()), - }; - self.proxy.send(iq.into()); - } - - // TODO: use a better error type. - pub fn send_roster_set(&self, to: Option, item: Item) -> Result<(), String> { - if item.subscription.is_some() && item.subscription != Some(Subscription::Remove) { - return Err(String::from("Subscription must be either nothing or Remove.")); - } - let iq = Iq { - from: None, - to, - id: Some(self.proxy.gen_id()), - payload: IqType::Set(Roster { - ver: None, - items: vec!(item), - }.into()), - }; - self.proxy.send(iq.into()); - Ok(()) - } - - fn handle_roster_reply(&self, roster: Roster) { - // TODO: handle the same-ver case! - let mut current_version = self.current_version.lock().unwrap(); - *current_version = roster.ver; - let mut jids = self.jids.lock().unwrap(); - jids.clear(); - for item in roster.items { - jids.insert(item.jid.clone(), item); - } - self.proxy.dispatch(RosterReceived { - ver: current_version.clone(), - jids: jids.clone(), - }); - } - - fn handle_roster_push(&self, roster: Roster) -> Result<(), String> { - let item = roster.items.get(0); - if item.is_none() || roster.items.len() != 1 { - return Err(String::from("Server sent an invalid roster push!")); - } - let item = item.unwrap().clone(); - let mut jids = self.jids.lock().unwrap(); - let previous = jids.insert(item.jid.clone(), item.clone()); - if previous.is_none() { - assert!(item.subscription != Some(Subscription::Remove)); - self.proxy.dispatch(RosterPush::Added(item)); - } else { - if item.subscription == Some(Subscription::Remove) { - self.proxy.dispatch(RosterPush::Removed(item)); - } else { - self.proxy.dispatch(RosterPush::Modified(item)); - } - } - Ok(()) - } - - fn handle_iq(&self, iq: &Iq) -> Propagation { - let jid = self.proxy.get_own_jid(); - let jid = Jid::bare(jid.node.unwrap(), jid.domain); - if iq.from.is_some() && iq.from != Some(jid) { - // Not from our roster. - return Propagation::Continue; - } - let iq = iq.clone(); - let id = iq.id.unwrap(); - match iq.payload { - IqType::Result(Some(payload)) => { - match IqResultPayload::try_from(payload) { - Ok(IqResultPayload::Roster(roster)) => { - self.handle_roster_reply(roster); - Propagation::Stop - }, - Ok(_) - | Err(_) => Propagation::Continue, - } - }, - IqType::Set(payload) => { - match IqSetPayload::try_from(payload) { - Ok(IqSetPayload::Roster(roster)) => { - let payload = match self.handle_roster_push(roster) { - Ok(_) => IqType::Result(None), - Err(string) => { - // The specification says that the server should ignore an error. - println!("{}", string); - IqType::Result(None) - }, - }; - self.proxy.send(Iq { - from: None, - to: None, - id: Some(id), - payload: payload, - }.into()); - Propagation::Stop - }, - Ok(_) - | Err(_) => return Propagation::Continue, - } - }, - IqType::Result(None) - | IqType::Get(_) - | IqType::Error(_) => { - Propagation::Continue - }, - } - } -} - -impl_plugin!(RosterPlugin, proxy, [ - (Iq, Priority::Default) => handle_iq, -]); diff --git a/src/plugins/stanza.rs b/src/plugins/stanza.rs deleted file mode 100644 index 894bdc3db484f0bac48c59880e8e30b5284661fa..0000000000000000000000000000000000000000 --- a/src/plugins/stanza.rs +++ /dev/null @@ -1,52 +0,0 @@ -use try_from::TryFrom; - -use plugin::PluginProxy; -use event::{Event, ReceiveElement, Propagation, Priority}; -use ns; - -pub use xmpp_parsers::message::Message; -pub use xmpp_parsers::presence::Presence; -pub use xmpp_parsers::iq::Iq; - -impl Event for Message {} -impl Event for Presence {} -impl Event for Iq {} - -pub struct StanzaPlugin { - proxy: PluginProxy, -} - -impl StanzaPlugin { - pub fn new() -> StanzaPlugin { - StanzaPlugin { - proxy: PluginProxy::new(), - } - } - - fn handle_receive_element(&self, evt: &ReceiveElement) -> Propagation { - let elem = &evt.0; - - // TODO: make the handle take an Element instead of a reference. - let elem = elem.clone(); - - if elem.is("message", ns::CLIENT) { - let message = Message::try_from(elem).unwrap(); - self.proxy.dispatch(message); - } else if elem.is("presence", ns::CLIENT) { - let presence = Presence::try_from(elem).unwrap(); - self.proxy.dispatch(presence); - } else if elem.is("iq", ns::CLIENT) { - let iq = Iq::try_from(elem).unwrap(); - self.proxy.dispatch(iq); - } else { - // TODO: handle nonzas too. - return Propagation::Continue; - } - - Propagation::Stop - } -} - -impl_plugin!(StanzaPlugin, proxy, [ - (ReceiveElement, Priority::Default) => handle_receive_element, -]); diff --git a/src/plugins/stanza_debug.rs b/src/plugins/stanza_debug.rs deleted file mode 100644 index 12c8c681f4aa39a49d56bc0b628424e4a514c126..0000000000000000000000000000000000000000 --- a/src/plugins/stanza_debug.rs +++ /dev/null @@ -1,30 +0,0 @@ -use plugin::PluginProxy; -use event::{SendElement, ReceiveElement, Propagation, Priority}; -use chrono::Local; - -pub struct StanzaDebugPlugin { - proxy: PluginProxy, -} - -impl StanzaDebugPlugin { - pub fn new() -> StanzaDebugPlugin { - StanzaDebugPlugin { - proxy: PluginProxy::new(), - } - } - - fn handle_send_element(&self, evt: &SendElement) -> Propagation { - println!("{} SEND: {:?}", Local::now(), evt.0); - Propagation::Continue - } - - fn handle_receive_element(&self, evt: &ReceiveElement) -> Propagation { - println!("{} RECV: {:?}", Local::now(), evt.0); - Propagation::Continue - } -} - -impl_plugin!(StanzaDebugPlugin, proxy, [ - (SendElement, Priority::Min) => handle_send_element, - (ReceiveElement, Priority::Max) => handle_receive_element, -]); diff --git a/src/plugins/unhandled_iq.rs b/src/plugins/unhandled_iq.rs deleted file mode 100644 index 6255bf50965286c5853eee1502f47cf1f52cf507..0000000000000000000000000000000000000000 --- a/src/plugins/unhandled_iq.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::collections::BTreeMap; - -use plugin::PluginProxy; -use event::{Priority, Propagation}; - -use plugins::stanza::Iq; -use xmpp_parsers::iq::IqType; -use xmpp_parsers::stanza_error::{StanzaError, ErrorType, DefinedCondition}; - -pub struct UnhandledIqPlugin { - proxy: PluginProxy, -} - -impl UnhandledIqPlugin { - pub fn new() -> UnhandledIqPlugin { - UnhandledIqPlugin { - proxy: PluginProxy::new(), - } - } - - fn reply_unhandled_iq(&self, iq: &Iq) -> Propagation { - let iq = iq.clone(); - match iq.payload { - IqType::Get(_) - | IqType::Set(_) => { - self.proxy.send(Iq { - from: None, - to: Some(iq.from.unwrap()), - id: Some(iq.id.unwrap()), - payload: IqType::Error(StanzaError { - type_: ErrorType::Cancel, - defined_condition: DefinedCondition::ServiceUnavailable, - texts: BTreeMap::new(), - by: None, - other: None, - }), - }.into()); - Propagation::Stop - }, - IqType::Result(_) - | IqType::Error(_) => Propagation::Continue - } - } -} - -impl_plugin!(UnhandledIqPlugin, proxy, [ - (Iq, Priority::Min) => reply_unhandled_iq, -]); diff --git a/src/transport.rs b/src/transport.rs deleted file mode 100644 index 527134a0ce0dd86106ba4387ce3c4258ef823002..0000000000000000000000000000000000000000 --- a/src/transport.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! Provides transports for the xml streams. - -use std::io::prelude::*; - -use std::net::{TcpStream, Shutdown}; - -use xml::reader::{EventReader, XmlEvent as XmlReaderEvent}; -use xml::writer::{EventWriter, XmlEvent as XmlWriterEvent, EmitterConfig}; - -use std::sync::{Arc, Mutex}; - -use ns; - -use minidom; - -use locked_io::LockedIO; - -use error::Error; - -#[allow(unused_imports)] -use openssl::ssl::{SslMethod, Ssl, SslContextBuilder, SslStream, SSL_VERIFY_NONE, SslConnectorBuilder}; - -use sasl::common::ChannelBinding; - -/// A trait which transports are required to implement. -pub trait Transport { - /// Writes an `xml::writer::XmlEvent` to the stream. - fn write_event<'a, E: Into>>(&mut self, event: E) -> Result<(), Error>; - - /// Reads an `xml::reader::XmlEvent` from the stream. - fn read_event(&mut self) -> Result; - - /// Writes a `minidom::Element` to the stream. - fn write_element(&mut self, element: &minidom::Element) -> Result<(), Error>; - - /// Reads a `minidom::Element` from the stream. - fn read_element(&mut self) -> Result; - - /// Resets the stream. - fn reset_stream(&mut self); - - /// Gets channel binding data. - fn channel_bind(&self) -> ChannelBinding { - ChannelBinding::None - } -} - -/// A plain text transport, completely unencrypted. -pub struct PlainTransport { - inner: Arc>, // TODO: this feels rather ugly - reader: EventReader>, // TODO: especially feels ugly because - // this read would keep the lock - // held very long (potentially) - writer: EventWriter>, -} - -impl Transport for PlainTransport { - fn write_event<'a, E: Into>>(&mut self, event: E) -> Result<(), Error> { - Ok(self.writer.write(event)?) - } - - fn read_event(&mut self) -> Result { - Ok(self.reader.next()?) - } - - fn write_element(&mut self, element: &minidom::Element) -> Result<(), Error> { - Ok(element.write_to(&mut self.writer)?) - } - - fn read_element(&mut self) -> Result { - let element = minidom::Element::from_reader(&mut self.reader)?; - Ok(element) - } - - fn reset_stream(&mut self) { - let locked_io = LockedIO::from(self.inner.clone()); - self.reader = EventReader::new(locked_io.clone()); - self.writer = EventWriter::new_with_config(locked_io, EmitterConfig { - line_separator: "".into(), - perform_indent: false, - normalize_empty_elements: false, - .. Default::default() - }); - } - - fn channel_bind(&self) -> ChannelBinding { - // TODO: channel binding - ChannelBinding::None - } -} - -impl PlainTransport { - /// Connects to a server without any encryption. - pub fn connect(host: &str, port: u16) -> Result { - let tcp_stream = TcpStream::connect((host, port))?; - let parser = EventReader::new(tcp_stream); - let parser_stream = parser.into_inner(); - let stream = Arc::new(Mutex::new(parser_stream)); - let locked_io = LockedIO::from(stream.clone()); - let reader = EventReader::new(locked_io.clone()); - let writer = EventWriter::new_with_config(locked_io, EmitterConfig { - line_separator: "".into(), - perform_indent: false, - normalize_empty_elements: false, - .. Default::default() - }); - Ok(PlainTransport { - inner: stream, - reader: reader, - writer: writer, - }) - } - - /// Closes the stream. - pub fn close(&mut self) { - self.inner.lock() - .unwrap() - .shutdown(Shutdown::Both) - .unwrap(); // TODO: safety, return value and such - } -} - -/// A transport which uses STARTTLS. -pub struct SslTransport { - inner: Arc>>, // TODO: this feels rather ugly - reader: EventReader>>, // TODO: especially feels ugly because - // this read would keep the lock - // held very long (potentially) - writer: EventWriter>>, -} - -impl Transport for SslTransport { - fn write_event<'a, E: Into>>(&mut self, event: E) -> Result<(), Error> { - Ok(self.writer.write(event)?) - } - - fn read_event(&mut self) -> Result { - Ok(self.reader.next()?) - } - - fn write_element(&mut self, element: &minidom::Element) -> Result<(), Error> { - Ok(element.write_to(&mut self.writer)?) - } - - fn read_element(&mut self) -> Result { - Ok(minidom::Element::from_reader(&mut self.reader)?) - } - - fn reset_stream(&mut self) { - let locked_io = LockedIO::from(self.inner.clone()); - self.reader = EventReader::new(locked_io.clone()); - self.writer = EventWriter::new_with_config(locked_io, EmitterConfig { - line_separator: "".into(), - perform_indent: false, - normalize_empty_elements: false, - .. Default::default() - }); - } - - fn channel_bind(&self) -> ChannelBinding { - // TODO: channel binding - ChannelBinding::None - } -} - -impl SslTransport { - /// Connects to a server using STARTTLS. - pub fn connect(host: &str, port: u16) -> Result { - // TODO: very quick and dirty, blame starttls - let mut stream = TcpStream::connect((host, port))?; - write!(stream, "" - , ns::CLIENT, ns::STREAM, host)?; - write!(stream, "" - , ns::TLS)?; - let mut parser = EventReader::new(stream); - loop { // TODO: possibly a timeout? - match parser.next()? { - XmlReaderEvent::StartElement { name, .. } => { - if let Some(ns) = name.namespace { - if ns == ns::TLS && name.local_name == "proceed" { - break; - } - else if ns == ns::STREAM && name.local_name == "error" { - return Err(Error::StreamError); - } - } - }, - _ => {}, - } - } - let stream = parser.into_inner(); - #[cfg(feature = "insecure")] - let ssl_stream = { - let mut ctx = SslContextBuilder::new(SslMethod::tls())?; - ctx.set_verify(SSL_VERIFY_NONE); - let ssl = Ssl::new(&ctx.build())?; - ssl.connect(stream)? - }; - #[cfg(not(feature = "insecure"))] - let ssl_stream = { - let ssl_connector = SslConnectorBuilder::new(SslMethod::tls())?.build(); - ssl_connector.connect(host, stream)? - }; - let ssl_stream = Arc::new(Mutex::new(ssl_stream)); - let locked_io = LockedIO::from(ssl_stream.clone()); - let reader = EventReader::new(locked_io.clone()); - let writer = EventWriter::new_with_config(locked_io, EmitterConfig { - line_separator: "".into(), - perform_indent: false, - normalize_empty_elements: false, - .. Default::default() - }); - Ok(SslTransport { - inner: ssl_stream, - reader: reader, - writer: writer, - }) - } - - /// Closes the stream. - pub fn close(&mut self) { - self.inner.lock() - .unwrap() - .shutdown() - .unwrap(); // TODO: safety, return value and such - } -} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 9651c09c2f8c9afb91caa0a64c6978758c8152c8..0000000000000000000000000000000000000000 --- a/src/util.rs +++ /dev/null @@ -1,19 +0,0 @@ -use minidom::Element; - -pub trait FromElement where Self: Sized { - type Err; - - fn from_element(elem: &Element) -> Result; -} - -pub trait FromParentElement where Self: Sized { - type Err; - - fn from_parent_element(elem: &Element) -> Result; -} - -pub trait ToElement where Self: Sized { - type Err; - - fn to_element(&self) -> Result; -}