pax_global_header00006660000000000000000000000064127577717200014531gustar00rootroot0000000000000052 comment=02e99b9933fe6444c887501f5d5e48931d813482 goopg-0.3.1/000077500000000000000000000000001275777172000126455ustar00rootroot00000000000000goopg-0.3.1/.gitignore000066400000000000000000000001231275777172000146310ustar00rootroot00000000000000*.pyc app/goopg-web-extension-id.js app/lib/css/goopg-bootstrap.css app/lib/fonts/ goopg-0.3.1/AUTHORS000066400000000000000000000002451275777172000137160ustar00rootroot00000000000000Maintainers: Leo Iannacone Contributors: Paolo Rotolo for doc/icons.svg Kartik Mistry goopg-0.3.1/COPYING000066400000000000000000001045141275777172000137050ustar00rootroot00000000000000 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 . goopg-0.3.1/README.md000066400000000000000000000024461275777172000141320ustar00rootroot00000000000000![logo.png](http://leoiannacone.github.io/goopg/images/logo.png) Goopg is an extension for the **Chrome** and the **Chromium** browser which enables GPG sign and verification in the Gmail web page. # Installation: The installation consists in two phases: * Install the browser extension from [this link](https://chrome.google.com/webstore/detail/goopg/ifpoaednafmgolabhpjmbimllaoidelg) * Install the goopg plugin: ``` sudo add-apt-repository ppa:team-goopg/goopg sudo apt-get update sudo apt-get install goopg-chromium ``` Replace `goopg-chromium` with `goopg-chrome` if you use Chrome. Open http://gmail.com # Missing gpg-agent In case goopg is able to check signatures but unable to sign and send emails, chances are your system lacks a properly configured gpg-agent to unlock your private key In this case, please check [this link](https://wiki.archlinux.org/index.php/GnuPG#gpg-agent) and follow the instructions there to enable gpg-agent. #### If you want help in develop If you would like to help in developing, take a look at the [dev-install](doc/dev-install.md) and at the [project documentation](doc/project.md). # Donate If you like this project, please consider a donation. See the [homepage](http://leoiannacone.github.io/goopg/) for more info. This project is NOT affiliated with Google. goopg-0.3.1/app/000077500000000000000000000000001275777172000134255ustar00rootroot00000000000000goopg-0.3.1/app/goopg-web-chrome-port.js000066400000000000000000000015501275777172000201070ustar00rootroot00000000000000/* global GOOPG_EXTENSION_ID */ "use strict"; // port to communicate with background var web_port = null; var Port = { // return a new port get: function () { window.console.log("Connecting to web port..."); var port = window.chrome.runtime.connect(GOOPG_EXTENSION_ID); port.onDisconnect.addListener(function () { window.console.log("Failed to connect: " + window.chrome.runtime.lastError.message); }); port.onMessage.addListener(Port.handler); return port; }, // send a bundle send: function (bundle) { try { web_port.postMessage(bundle); } catch (err) { web_port = Port.get(); web_port.postMessage(bundle); } }, handler: function (bundle) { // This must is implemented elsewhere return; } }; goopg-0.3.1/app/goopg-web.js000066400000000000000000000464561275777172000156700ustar00rootroot00000000000000/* global Port */ /* global GLOBALS */ "use strict"; // get the username var USERNAME = GLOBALS[10]; var PLUGIN_MIN_VERSION = '0.2.0'; // my css classes var GOOPG_CLASS_BANNER = "goopg"; var GOOPG_CLASS_PREFIX = "goopg-"; var GOOPG_CLASS_CHECKED = GOOPG_CLASS_PREFIX + "checked"; var GOOPG_CLASS_STDERR = GOOPG_CLASS_PREFIX + "stderr"; var GOOPG_CLASS_SENDBUTTON = GOOPG_CLASS_PREFIX + "sendbutton"; var GOOPG_CLASS_ALERT = GOOPG_CLASS_PREFIX + "alert"; var GOOPG_CLASS_KEYID = GOOPG_CLASS_PREFIX + "keyid-"; var GOOPG_CLASS_IMPORT = GOOPG_CLASS_PREFIX + "import-"; var GOOPG_CLASS_MSG_SIGNINLINE = GOOPG_CLASS_PREFIX + 'signinline'; // google css classes var GOOGLE_CLASS_MESSAGE = "ii"; var GOOGLE_CLASS_SENDBUTTON = "aoO"; var GOOGLE_CLASS_DISCARD_BUTTON = 'og'; var GOOGLE_CLASS_ALERT = 'vh'; var GOOGLE_CLASS_MESSAGE_SAVED = 'aOy'; // if cannot comunicate with the py port var native_available = true; var Utils = { escape_html: function (string) { return String(string).replace(/[&<>"'\/]/g, function (s) { return { "&": "&", "<": "<", ">": ">", "\"": """, "'": "'", "/": "/" }[s]; }); }, toggle_display: function (div) { if (div.style.display == "none") div.style.display = "block"; else div.style.display = "none"; }, capitalize: function (str) { return str.replace(/(?:^|\s)\S/g, function (a) { return a.toUpperCase(); }); }, get_init_bundle: function () { var bundle = {}; bundle.command = 'init'; bundle.username = USERNAME; return bundle; }, check_version: function (version) { if (version === undefined || version === null) return false; var got_version = version.split('.'); var required_version = PLUGIN_MIN_VERSION.split('.'); var result = false; for (var i = 0; i < required_version.length; i++) { if (i >= got_version.length) break; var i_got = got_version[i]; var i_required = required_version[i]; result = true; if (i_got < i_required) { result = false; break; } } return result; } }; var Alert = { // set an alert msg set: function (msg) { var alert = document.getElementsByClassName(GOOGLE_CLASS_ALERT)[0]; var random_id = GOOPG_CLASS_ALERT + Math.random(); alert.innerHTML = '' + msg + ''; // show the alert ; gmail hides this by setting top = -10000px over this container var alert_container = alert.parentElement.parentElement; alert_container.style.top = '0px'; // remove the alert after 15 s if still present setTimeout(function () { var this_alert = document.getElementById(random_id); if (this_alert) { var parent = this_alert.parentElement; parent.removeChild(this_alert); // revert if (parent.children.length === 0) { alert_container.style.top = "-10000px"; } } }, 15000); }, // replace the next message with a new one replace_incoming_msg: function (new_msg) { var alert = document.getElementsByClassName(GOOGLE_CLASS_ALERT)[0]; function changer(e) { alert.removeEventListener(e.type, changer); alert.innerHTML = new_msg; } alert.addEventListener("DOMSubtreeModified", changer); } }; function SignedMessage(msg_id) { this.msg_id = msg_id; this.key_id = null; this.div = document.getElementsByClassName("m" + msg_id)[0]; this.exists = function () { return this.div !== null && this.div !== undefined; }; // hide signature this.hide_signature = function (filename) { function get_sign_inline_hook(text) { return '
' + text + '
'; } if (!this.exists()) return; if (filename === undefined) { // Signature inline, try to hide it var body = this.div.firstChild.innerHTML; // stricty_reg to check PGP signed inline with HTML trailing
elements // var strinct_reg = /-----BEGIN PGP SIGNED MESSAGE-----.*\nHash.*\n.*\n((.*\n)+)-----BEGIN PGP SIGNATURE-----(.*\n)+-----END PGP SIGNATURE-----.*\n/m; // permissive_reg is a strinct_reg which allows any HTML code before the PGP signature (need tests) var permissive_reg = /-----BEGIN PGP SIGNED MESSAGE-----.*\n.*Hash.*\n.*\n((.*\n)+).*-----BEGIN PGP SIGNATURE-----(.*\n)+.*-----END PGP SIGNATURE-----.*\n/m; var reg = permissive_reg; if (reg.test(body)) { var header = get_sign_inline_hook('-----BEGIN PGP SIGNED MESSAGE INLINE-----'); var footer = get_sign_inline_hook('-----END PGP SIGNED MESSAGE INLINE-----'); this.div.firstChild.innerHTML = body.replace(reg, header + ' $1 ' + footer); } } else { // Here only if signature is attached // If the signature is attached with no name, // gmail will show it as a 'noname' attachment, so set ... if (filename === null) filename = 'noname'; this.hide_attachment(filename); } }; // get the attachments as HTML elements this.get_attachments = function () { return this.div.parentElement.parentElement.getElementsByTagName("span"); }; // get the attchment as HTML element this.get_attachment = function (filename) { var attachments = this.get_attachments(); var url_regex = new RegExp(':' + filename + ':https://mail.google.com/'); for (var i = 0; i < attachments.length; i++) { var attach = attachments[i]; var download_url = attach.getAttribute("download_url"); if (download_url && download_url.match(url_regex)) { return attach; } } return null; }; // hide an attachment given a filename this.hide_attachment = function (filename) { var attach = this.get_attachment(filename); if (attach === null) return false; attach.style.display = "none"; if (attach.parentElement.children.length == 2) { // it was only one attachment, hide the whole attachments box attach.parentElement.parentElement.style.display = "none"; } return true; }; // build the banner this.add_banner = function (gpg, force) { if (!this.exists()) return; if (gpg.status === undefined || gpg.status === null) return; var existing_banner = this.div.getElementsByClassName(GOOPG_CLASS_BANNER); if (existing_banner.length == 1) this.div.removeChild(existing_banner[0]); this.key_id = gpg.key_id; var className; var icon; var import_key = false; var key_id = gpg.key_id; // // show the whole key and add a space every 4 chars // key_id = key_id.replace(/(.{4})/g, "$1 "); // show only last 8 chars for key id if (key_id.length > 8) key_id = key_id.substring(8); var text = []; if (gpg.username !== null) text.push(gpg.username); if (key_id) text.push(key_id); if (gpg.key_status) text.push("• " + gpg.key_status); text = text.join(' '); if (gpg.status == "no public key") { icon = "question-sign"; className = "warning"; text = "public key " + key_id + " not found"; import_key = true; } else if (gpg.valid) { icon = "ok-sign"; className = "success"; } else { // (gpg.status == "signature bad") icon = "exclamation-sign"; className = "danger"; } // build the banner var banner = document.createElement("div"); banner.className = "alert alert-" + className; var header = document.createElement("div"); header.className = "alert-header"; var icon_status = ""; header.innerHTML = icon_status; // if import_key, add the import button if (import_key) { var icon_import = document.createElement("span"); icon_import.className = "pull-right glyphicon glyphicon-download "; icon_import.className += GOOPG_CLASS_IMPORT + this.key_id; icon_import.title = "Import the key"; icon_import.addEventListener("click", function (event) { event.stopPropagation(); var button = event.target; // change the icon status button.style.opacity = '0.3'; button.className = event.target.className.replace('glyphicon-download', 'glyphicon-cloud-download'); button.title = "Importing ..."; // get the key and send the "import" bundle for (var i = 0; i < button.classList.length; i++) { if (button.classList[i].indexOf(GOOPG_CLASS_IMPORT) >= 0) { var key_id = button.classList[i].replace(GOOPG_CLASS_IMPORT, ''); var bundle = {}; bundle.command = 'import'; bundle.id = key_id; Port.send(bundle); break; } } }); header.appendChild(icon_import); } var span_text = document.createElement('span'); span_text.innerHTML = "" + Utils.capitalize(gpg.status) + ": " + Utils.escape_html(text); header.appendChild(span_text); banner.appendChild(header); if (gpg.stderr) { var stderr = document.createElement("div"); var gpg_stderr_clean = gpg.stderr.replace(/^.GNUPG:.*\n?/mg, ""); stderr.className = "raw " + GOOPG_CLASS_STDERR; stderr.style.display = "none"; header.addEventListener("click", function () { Utils.toggle_display(this.parentElement.getElementsByClassName(GOOPG_CLASS_STDERR)[0]); }); header.style.cursor = "pointer"; stderr.innerHTML = Utils.escape_html(gpg_stderr_clean); banner.appendChild(stderr); } // wrap the banner var wrapper = document.createElement("div"); wrapper.className = GOOPG_CLASS_BANNER; wrapper.appendChild(banner); this.div.insertBefore(wrapper, this.div.firstChild); var class_keyid = GOOPG_CLASS_KEYID + gpg.key_id; if (!this.div.classList.contains(class_keyid)) this.div.classList.add(class_keyid); }; } var SignSendButton = { // build the button element get: function () { var new_button = document.createElement('div'); new_button.className = GOOPG_CLASS_SENDBUTTON; new_button.id += GOOPG_CLASS_PREFIX + Math.random(); //new_button.className += ' T-I-JW'; new_button.innerHTML = 'Sign and Send'; return new_button; }, // check if message is saved, starting from the sending button message_is_saved: function (button) { var tr = button.parentElement.parentElement.parentElement; return tr.getElementsByClassName(GOOGLE_CLASS_MESSAGE_SAVED).length == 1; }, // get the message id wrapped around button get_message_id: function (button) { var e = button; // get the message id, in html: while (e.parentElement) { e = e.parentElement; var inputs = e.getElementsByTagName('input'); for (var j = 0; j < inputs.length; j++) { var input = inputs[j]; if (input.getAttribute('name') == "draft") { return input.getAttribute('value'); } } } }, on_click: function (event, interactions) { // the sending button var button = event.target; var draft_id = SignSendButton.get_message_id(button); if (draft_id == "undefined") { Alert.set("Please save the Draft before sending."); return; } // prevent multi click button.removeEventListener("click", SignSendButton.on_click); // stylish the button pressed button.style.width = window.getComputedStyle(button).width; button.style.color = "#999"; button.innerHTML = "Sending"; // If message is not saved, sleep a while (SLEEP_TIME in ms) and auto-recall // Do this for MAX_ITERATIONS times (?) var SLEEP_TIME = 500; var MAX_ITERATIONS = 20; if (interactions === undefined) { interactions = 0; } if (!SignSendButton.message_is_saved(button) && interactions < MAX_ITERATIONS) { setTimeout(function () { SignSendButton.on_click(event, interactions + 1); }, SLEEP_TIME); return; } var msg = {}; msg.command = "sign"; msg.id = draft_id; msg.button_id = button.id; Port.send(msg); }, hide_compositor: function (button_id) { var button = document.getElementById(button_id); if (button === null) return; var e = button; while (e.parentElement) { e = e.parentElement; var discard = e.getElementsByClassName(GOOGLE_CLASS_DISCARD_BUTTON); if (discard.length === 0) continue; else if (discard.length > 1) return; else { discard = discard[0]; Alert.replace_incoming_msg("Your message has been signed and sent. "); discard.click(); break; } } }, }; // the generic bundle handler function BundleHandler(bundle) { // handle the message received if (bundle.command == 'request_init') { var init_bundle = Utils.get_init_bundle(); Port.send(init_bundle); } else if (bundle.command == "init") { native_available = Utils.check_version(bundle.result.version); if (!native_available) { setTimeout(function () { var installation = 'Please update your system or check the installation instructions.'; Alert.set('This version of Goopg requires ' + PLUGIN_MIN_VERSION + ' minimum plugin version.
' + installation); }, 5000); } } else if (bundle.command == "verify") { if (bundle.result.status === null) return; var signedmessage = new SignedMessage(bundle.id); if (signedmessage.exists()) { signedmessage.hide_signature(bundle.result.filename); signedmessage.add_banner(bundle.result); } } else if (bundle.command == "sign") { if (bundle.result === true) { SignSendButton.hide_compositor(bundle.button_id); } else if (bundle.result === false) { Alert.set("Your message was not sent. Please retry."); var button = document.getElementById(bundle.button_id); if (button) { button.addEventListener('click', SignSendButton.on_click); button.style.color = ""; button.innerHTML = "Sign and Send"; } } } else if (bundle.command == "import") { if (bundle.result === true) { var messages = document.getElementsByClassName(GOOPG_CLASS_KEYID + bundle.id); for (var i = 0; i < messages.length; i++) { var message = messages[i]; for (var j = 0; j < message.classList.length; j++) { if (message.classList[j].length > 5 && message.classList[j][0] == "m") { var id = message.classList[j].substring(1); var bundle_verify = {}; bundle_verify.command = "verify"; bundle_verify.force = true; bundle_verify.id = id; Port.send(bundle_verify); break; } } } } } else if (bundle.port_error) { var error_message = bundle.port_error; if (error_message == 'Access to the specified native messaging host is forbidden.' || error_message == 'Specified native messaging host not found.') { native_available = false; setTimeout(function () { var installation = 'Please see the installation instructions.'; Alert.set('Goopg cannot communicate with the plugin. ' + error_message + ' ' + installation); }, 5000); } } } function look_for_signedmessages() { if (native_available === false) return; var messages = document.getElementsByClassName(GOOGLE_CLASS_MESSAGE); for (var i = 0; i < messages.length; i++) { var id = null; var realMessage = messages[i].children[0] var classList = realMessage.classList; if (classList.contains(GOOPG_CLASS_CHECKED)) continue; for (var j = 0; j < classList.length; j++) { if (classList[j].length > 5 && classList[j][0] == "m") { id = classList[j].substring(1); realMessage.classList.add(GOOPG_CLASS_CHECKED); break; } } if (id) { var bundle = {}; // guess if message is GPG signed inline var body = realMessage.innerText; if (body.indexOf('-----BEGIN PGP SIGNATURE-----') >= 0) bundle.force = true; // This will not work, since Gmail has not yet showed the attachments // this message. // else if (new SignedMessage(id).get_attachment('signature.asc')) // bundle.force = true; bundle.command = "verify"; bundle.id = id; Port.send(bundle); } } } function look_for_compositors() { if (native_available === false) return; var sendButtons = document.getElementsByClassName(GOOGLE_CLASS_SENDBUTTON); // append the sign&send button for (var i = 0; i < sendButtons.length; i++) { var button = sendButtons[i]; var parent = button.parentNode; if (parent.className.indexOf(GOOPG_CLASS_CHECKED) > -1) continue; parent.className += ' ' + GOOPG_CLASS_CHECKED; var new_button = SignSendButton.get(button); new_button.addEventListener("click", SignSendButton.on_click); parent.appendChild(new_button); } } // assign the handler Port.handler = BundleHandler; // initalize the native application on start Port.send(Utils.get_init_bundle()); document.body.addEventListener("DOMSubtreeModified", look_for_signedmessages, false); document.body.addEventListener("DOMSubtreeModified", look_for_compositors, false); goopg-0.3.1/app/goopg.js000066400000000000000000000026441275777172000151040ustar00rootroot00000000000000"use strict"; var web_ports = []; var py_port = null; function web_broadcast(bundle) { for (var i = 0; i < web_ports.length; i++) { try { web_ports[i].postMessage(bundle); } catch (err) {} } } function get_py_port() { window.console.log("Connecting to py script ..."); var py_port = window.chrome.runtime.connectNative("com.leoiannacone.goopg"); py_port.onMessage.addListener(function (bundle) { window.console.log("Received ", bundle); web_broadcast(bundle); }); py_port.onDisconnect.addListener(function () { var error = window.chrome.runtime.lastError.message; window.console.log("Failed to connect: " + error); web_broadcast({ 'port_error': error }); }); return py_port; } window.chrome.runtime.onConnectExternal.addListener(function (my_web_port) { web_ports.push(my_web_port); my_web_port.onMessage.addListener(function (bundle) { window.console.log("Sending", bundle); try { py_port.postMessage(bundle); } catch (err) { py_port = get_py_port(); py_port.postMessage(bundle); } }); my_web_port.onDisconnect.addListener(function () { var index = web_ports.indexOf(my_web_port); window.console.log("Web port", index, "disconnecting"); if (index > -1) web_ports.splice(index, 1); }); }); goopg-0.3.1/app/icon128.png000066400000000000000000000165541275777172000153310ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs77DtEXtSoftwarewww.inkscape.org<IDATxyx[Օi$6 ٩@iA%R2Pҡ2a:-@Y:,CFt(KV4 (P HBvxzo{eĿIWws=WP ]B 0AaPC}O8S*;9@b|N8XLɡ<4 Pk/'Cr(?ɻ`y`BS0kpPA1?ʭk,(:؛o}Og6'z|Nap6F?@ǁoK"&coui~5b -Sv՘gw§"*r(p~ \#9h)Ŷ x !gvc4Ʌ@9$ hzBlW18>%{z[[m.wqU( ؛_b%Ez15taFW̽Cq'z!4ohwEIi}6{WބWX`pVzDԕ0w{ rd +&-fM>YBKcJ]|֥EҹlJPJu~`nVT)xMWJoiDS ~3QS#^i|s&,ami¢wbb_­V:_OiuΌ*4جlthb͵6܆ˬC(C7Vz=YG-+:6N%YŽ”'ϏǪ&~C=LݝYR0؛u=_}Gn -' PH7mD{ɲns@iRHAɏ 5 GjAYRx:Fʂ GeO7o * -IF{tH9Qvbs1WSn!0N \bKK-ɪ.Q1Q<"ʃ0D+*/Q} )Qcܥ:1oD:,rÃ%m\+R>D<*pCk.B \1"*Z0I _> s+l.s-P9T'-/z%C &@l|^y/7{ުN[ |'rtj_\X"uC]5dC>bbvwTd˕Al7/)-z \B&pBAfG/4gI1 Qz15S'/ܯZ\  2,aOpn_w_Xnjv Y\fPx7Q!9W\ dYۍlD`JNo6U {);u`ҩ{\KwOQ5\JmY[#e=xB̓ϵ4Y+)jo4K|3"oҢV*􀨜7K9r$*>ez/P ##7,۩<{+^-2|LQogFޚ~~\^.*r7&PK*Ɖ?)}g# _3fz%VBf,J|NǨv7{%'R߽hg&}F+}N!n=@Ӊ?-*bn}5wF|)Lɦl AOX^Ga"RU"PiG{<Ffwȶ_ pBg<&/Jp0_s,8Su2 N.WMZƾ'0GjeR \R s )9d4q1LwC5\Uo7rsAmW5JVټOfGQsjE9:ghkG;`)Y(7ԙ\ 9[i+޴нq.g?Yޫ*7WBW?]g q_eh=KO7 Օ!X?&&(׷q2.2 i {S [Ӵ-='"[( fafq6>>`GzޫO5OwF&m+;6FO>u z~ëJIh^憯n5&諡r w9DD;-(\th%owiɩdµj)+޴X*d SFY)ǽG`Vbz8XFe/ V߬Tx^|R~}~1yX;π~\BR?f 去\K x߮tu:WZri6+J0nw!˭7@!E˞NYC{4b 1>PF,*鍌۽vw?^譀MBW G)@2j=ɠ1ʹǭ3D'o -LfD>}e֒uq>c/pU:7'%#?R)e`%C٠f+vr)sq+%BW'܁&˽3.1Inɡ<\O v+{;.D'ӑ!%>(c&Ӧ̄>*[L]5HtMi&9'%mƒBBn`_3ofPKp-is|[\lޡ[&-.&5t}=u?o"(fWʊMLP7)z^4#lZ-%)Q $1*ZtނA.O5-VnrS(PmlQ^h*si6>_˴촏JS8XNX*s^N~*~vނ13R嶗-ZN.{,R!#P쀦!Ri#A6WGPזIUGhdl@r(nf.6p}7!]#ߟiu,fٖu͈췒Cf.Ǝn6WݏϠfuYOf`{Grd~g|ɡlAu7ͻ D^T~l=d4ddGr(JOtzd1}lzPӱq,9RowmZ>H{y+mX;d[#2 BgSGNp#;^X L5n_Blxns.VJ`HVI%+yNrw/.sNFw"D^aNY2.x@ 邝/'p%8(4W͋'YR{I+ a]^:a3КiQ9QO`DғB+&xTf\xDAЌ׼i-~}H{ <*$pH|G_Q n_١ZK+@Gnz?%"tм#0yVOid5|'%dFw_7&13ůߍ״i-(*[]zTH *y<@"͢_D1"/Ǹi-=ulD²*G:ҭ. '%V' 7ʙ;&׌fGih>n(Xt )67ϒhMyXAWH3I+8؏,@IwI`@Blz!&ְ97Sϸu:x|1w"l@Pk˜(޴МPyfCAm@6a'Dsl:HX彦 CqdCQ;꼄 .#\_>-uei2 pR<ƩT\wpIW$xJlfZ״ar x3A=&+)CO\sZizD UnB4'_gsI{.-Y$%oSg]I/NmJ+-q&͕+t ]Hݟue0f 7]NrʄT_њ$Pkhy[0üL%7&pݲqHD-Ô-j!ak{14m2p~70yb@l2Puh;p0|QX6ǗWbܕt&vTe{*h [id r//Ș*t1{k^QQ3]i6z8$~jM9t%+1 JX$`sD[5UXgs ze@b_20MʂM 4Wl %y: `sE]* yA-%#Μnܚ4%B|N$Q*>U x@sU r8rEq>`gX;;fҵI}ڞV~(˝1b\))?dQL/"H%s(<&mds窮\0 mSRr(#H s "Kj,!bnS%mdO[9x^!$Qi!Ƶ s _'vG>L(3~<-'  p컓/dev/null # make the tmp dir TMP_DIR=/tmp/goopg if [ -d $TMP_DIR ]; then rm -fr $TMP_DIR ; fi mkdir -p $TMP_DIR/lib/css mkdir -p $TMP_DIR/lib/fonts # copy the the files cp ./*.js ./*.png ./*.json $TMP_DIR for subdir in "lib/css" "lib/fonts" ; do cp -r "$subdir" "$TMP_DIR/lib" done # build the extension_id file EXT_ID="ifpoaednafmgolabhpjmbimllaoidelg" EXT_ID_FILE="goopg-web-extension-id.js" TEMPLATE="../templates/$EXT_ID_FILE.in" sed "s|@EXT_ID@|$EXT_ID|" "$TEMPLATE" > "$TMP_DIR/$EXT_ID_FILE" # make the zip cd $TMP_DIR ZIP_NAME=~/goopg-chrome-store.zip zip $ZIP_NAME $(find -type f) > /dev/null echo File $ZIP_NAME created goopg-0.3.1/debian/000077500000000000000000000000001275777172000140675ustar00rootroot00000000000000goopg-0.3.1/debian/changelog000066400000000000000000000023441275777172000157440ustar00rootroot00000000000000goopg (0.3.1) UNRELEASED; urgency=medium * Fix catching exception. -- Leo Iannacone Fri, 26 Aug 2016 08:45:08 +0100 goopg (0.3.0) UNRELEASED; urgency=medium * Update to python-oauth2client (>= 2.0.1-1) * Update to new Gmail HTML format * debian/control: - bump debhelper version 9 - bump StandardsVersion 3.9.8 - fix VCS git secure to https - add dep python-oauth2client (>= 2.0.1-1) * debian/compat: bump 9 -- Leo Iannacone Thu, 25 Aug 2016 23:17:18 +0100 goopg (0.2.2) UNRELEASED; urgency=medium * Fix Receivers addresses with comma. -- Leo Iannacone Fri, 27 Mar 2015 11:00:08 +0100 goopg (0.2.1) UNRELEASED; urgency=medium * New host release. -- Leo Iannacone Fri, 27 Mar 2015 11:00:08 +0100 goopg (0.2.0) UNRELEASED; urgency=medium [Leo Iannacone] * Returns the version of the plugin on init bundle. * Add import key handler. [Kartik Mistry] * Fix Debian install using dh_python2. * Fix documentation style. -- Leo Iannacone Sun, 16 Nov 2014 16:50:34 +0100 goopg (0.1.0) UNRELEASED; urgency=medium * Initial release. -- Leo Iannacone Sat, 08 Nov 2014 14:34:27 +0100 goopg-0.3.1/debian/compat000066400000000000000000000000021275777172000152650ustar00rootroot000000000000009 goopg-0.3.1/debian/control000066400000000000000000000027211275777172000154740ustar00rootroot00000000000000Source: goopg Section: python Priority: extra Maintainer: Leo Iannacone Build-Depends: debhelper (>= 9), dh-buildinfo, dh-python, python | python-all | python-dev | python-all-dev Standards-Version: 3.9.8 Homepage: http://leoiannacone.github.io/goopg/ Vcs-Git: https://github.com/LeoIannacone/goopg.git Vcs-Browser: https://github.com/LeoIannacone/goopg/ Package: goopg-common Architecture: all Depends: python, python-gflags, python-gnupg, python-googleapi, python-oauth2client (>= 2.0.1-1), python-xdg, gnupg-agent, gnupg2, ${misc:Depends} Description: GPG for Gmail - common files Goopg is a an extension for Gmail which allows one to verify and sign emails in Gmail web page. . This package contains the common files Package: goopg-chrome Architecture: all Depends: goopg-common (= ${binary:Version}), ${misc:Depends} Description: GPG for Gmail - Chrome browser plugin Goopg is a an extension for Gmail which allows one to verify and sign emails in Gmail web page. . This package installs the plugin for the Chrome browser Package: goopg-chromium Architecture: all Depends: goopg-common (= ${binary:Version}), ${misc:Depends} Description: GPG for Gmail - Chromium browser plugin Goopg is a an extension for Gmail which allows one to verify and sign emails in Gmail web page. . This package installs the plugin for the Chromium browser goopg-0.3.1/debian/copyright000066400000000000000000000020201275777172000160140ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: goopg Upstream-Contact: Leo Iannacone Source: https://github.com/LeoIannacone/goopg Files: * Copyright: 2014 Leo Iannacone License: GPL-3+ 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 package 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 . . On Debian systems, the complete text of the GNU General Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". goopg-0.3.1/debian/goopg-chrome.install000066400000000000000000000001121275777172000200370ustar00rootroot00000000000000debian/com.leoiannacone.goopg.json etc/opt/chrome/native-messaging-hosts/ goopg-0.3.1/debian/goopg-chromium.install000066400000000000000000000001101275777172000204030ustar00rootroot00000000000000debian/com.leoiannacone.goopg.json etc/chromium/native-messaging-hosts/ goopg-0.3.1/debian/goopg-common.install000066400000000000000000000000671275777172000200630ustar00rootroot00000000000000host/*.py usr/share/goopg/ host/gmail usr/share/goopg/ goopg-0.3.1/debian/rules000077500000000000000000000010271275777172000151470ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ --with python2 HOST_PATH := /usr/share/goopg/chrome-main.py EXT_ID := ifpoaednafmgolabhpjmbimllaoidelg TEMPLATE := templates/com.leoiannacone.goopg.json.in OUT := debian/com.leoiannacone.goopg.json override_dh_auto_build: sed -e "s|@HOST_PATH@|$(HOST_PATH)|" -e "s|@EXT_ID@|$(EXT_ID)|" $(TEMPLATE) > $(OUT) override_dh_auto_clean: make -C app/lib/ clean rm -f $(OUT) app/goopg-web-extension-id.js `find -name *.pyc` goopg-0.3.1/debian/source/000077500000000000000000000000001275777172000153675ustar00rootroot00000000000000goopg-0.3.1/debian/source/format000066400000000000000000000000151275777172000165760ustar00rootroot000000000000003.0 (native) goopg-0.3.1/doc/000077500000000000000000000000001275777172000134125ustar00rootroot00000000000000goopg-0.3.1/doc/dev-install.md000066400000000000000000000020341275777172000161550ustar00rootroot00000000000000# Installation for developers ### Remove other installations If you installed the Goopg plugins and extensions, please remove them before continue. ### Deps Install these dependencies: ```bash sudo apt-get install node-less python-googleapi python-gflags python-xdg python-gnupg ``` ### Build the css If your distribution ships the `libjs-bootstrap` package, install it! (otherwise bootstrap will be downloaded): ```bash cd app/lib make cd - ``` ### Install the extension in Chrome/Chromium * Open [chrome://extensions](chrome://extensions) in the browser * Click on "Developer mode" * Then click on "Load unpackaged extension" and select the dir `app` ### Update the app ID and install the host Once extension is installed, get the `NEW_ID` of extension in chrome://extension and run the following commands: ```bash cd templates NEW_ID=the_id_you_found BROWSER=your_browser # (google-chrome or chromium) bash build.sh $NEW_ID $BROWSER cd - ``` Reload http://gmail.com # About logging For log information, see the ```~/.cache/goopg/log file``` goopg-0.3.1/doc/icon.svg000066400000000000000000000163521275777172000150720ustar00rootroot00000000000000 image/svg+xml goopg-0.3.1/doc/mac-install.md000066400000000000000000000012701275777172000161400ustar00rootroot00000000000000# Install in Mac # Goopg should work in you Mac too. What you have to do to get it properly installed is: 1. Install the extension from the [Chrome Store](https://chrome.google.com/webstore/detail/goopg/ifpoaednafmgolabhpjmbimllaoidelg) 2. Install these python libraries: ``` python-googleapi python-gflags python-xdg python-gnupg ``` 3. Clone this repository: ``` git clone https://github.com/LeoIannacone/goopg/ ``` Save it somewhere because you will need this files and you cannot remove them. 4. Then install the plugin using this command in the goopg directory: ``` export TARGET_DIR=~/NativeMessagingHosts bash templates/build.sh ifpoaednafmgolabhpjmbimllaoidelg chrome ``` goopg-0.3.1/doc/project.md000066400000000000000000000177471275777172000154220ustar00rootroot00000000000000![logo](http://leoiannacone.github.io/goopg/images/logo.png) # Requirements Without using external tools, such as Email Clients, Gmail users must be able to: * to encrypt and decrypt files and emails * sign and verify emails (both in-line and attached as described in RFC 3156) The UI must be the Gmail web interface, providing users with the Google User Experience. ## Requirements analysis Gmail web interface is not able to handle with GPG nowadays. Some other projects had tried to fix it during last years, bundling PGP algorithms in JavaScript code and heavy modifying the Gmail interface. This kind of approach seems to have failed, since Gmail can suddenly change its own HTML structure, therefore making those applications useless. A new way to look for a solution is empowering external tools which are designed to easy and quickly do PGP transformations and verifications. According with [its developer documentation](https://developer.chrome.com/extensions/messaging), Chrome is able to make web pages communicate with native applications, hence the JavaScript running as extension may get information, pass it to the native application, which is in charge to use PGP and to apply the transformation (if needed), and get back the result. The main contexts (draft structure and interaction): ``` web page <--> chrome background extension <--> external tool (native application) ``` The interaction between contexts (draft behavior): ``` web page chrome background extension external tool (native application) get info ---> gateway (adapt to I/O if needed) ---> make transformation / verification | | show result <--- gateway (adapt to web if needed) <--- send result ``` ## Risks analysis * Gmail web interface might change HTML someday. As less as possible HTML modifications and interactions could prevent future work. * Even if surfing the web it is enough easy to find documentation about PGP, it seems there is a lack of tool/APIs and examples showing how emails are verified/signed/encrypted/decrypted through PGP. Understanding deeply how it works, as well as how to get and send emails, can heavy delay the development. ## Getting the original message The web interface provides a simple way to retrieve the original email content, just opening URI like: ``` https://mail.google.com/mail/u/0/?ui=2&ik=%(auth_token)s&view=om&th=%(gmail_message_id)s ``` With: * `auto_token` - bundled in the Gmail HTML (variable GLOBALS at index 9) * `gmail_message_id` - the Gmail internal message ID (known as `X-GM-MSGID`), bundled as ClassName of messages in the HTML (starting with 'm') Hence, get the `X-GM-MSGID` is simple as get the the div.ClassName. ## PGP operations Each PGP operation will be implemented outside the web context, in other words, implemented in the native application. ## Sending messages Analyzing the HTML comes out that there is no (simple) way to (given a string) send a message via Gmail web interface. Understanding how to do that could fit into the first risk analyzed. Sending the message will be done in the native application, which instead could fit in the second risk, but remains the preferred choice. # Project analysis and development Gmail HTML structure is compiled (or something like that), making hard to understand the HTML elements IDs and ClassNames. This makes stronger the first point described in Risk analysis. The logical operations must be moved outside the web context as mush as possible. ## The representation of data Data must be exchanged between contexts in JSON format. The main package (called bundle) has a event-message structure as described below: ``` bundle { command: 'str' // the command name (init, sign, verify, encrypt, // decrypt, import, config [set, get]) force: true|false // force the command to be excecuted id: 'str' // the X-GM-MSGID result: {} // depends on the command result, it comes from the // Host and it may be as simple as a boolean } ``` ## The web extension, the app directory ### Structure * `manifest.json`: the manifest of the extension (see [the official doc](https://developer.chrome.com/extensions/manifest)) * `lib/*`: contains the css and fonts used in web (it uses [bootstrap](http://getbootstrap.com/)) * `inject.js`: injects javascripts (goopg-web*.js) files in Gmail web page * `goopg.js`: is the background script which takes the bundles from the web pages and forwards them to then native Host, and viceversa. It is the Gateway we talked about before and it will be described below. * `goopg-web-extension-id.js`: exposes the EXTENSION_ID (this is needed to easing the development) * `goopg-web.js`: the main javascript ``` // main functions look_for_compositors(): on HTML change, looks for Gmail compositors and adds a 'Sign and Send' button look_for_signedmessages: on HTML change, looks for messages and sends a `verify` command // main objects Utils: contain some utils Alert: is used to show messages to the users (like "Message has been signed and sent") Port: is the port communication to send and receive bundles SingedMessage: represents a Gmail signed message in the current view of the web page SingSendButton: represent a 'Sing and Send' button of the compositors ``` ### Interaction ``` web_page send bundle to gateway web_page receive bundle from gateway ``` ### Behavior ``` html.onChange(): look_for_compositors() look_for_signedmessages() ``` ## The gateway/background ### Structure * `app/goopg.js`: creates the web-port and py-port and forwards bundles between them. ### Interaction ``` gateway send bundle to web_page gateway receive bundle from web_page web_page send bundle to native web_page receive bundle from native ``` ### Behavior ``` py_port.onMessage(bundle): web_port.postMessage(bundle) web_port.onMessage(bundle): py_port.postMessage(bundle) ``` ## The native application, the host directory The native application is placed in host directory and it is written in python. ### Structure * `chrome-main.py`: it is the main script executed by the browser. It reads from stdin the `bundle` sent by the gateway and writes to stdout a new `bundle` as the result of the operation. It calls CommandHandler to consume the `bundle`. * `commandhandler.py`: contains a class which takes care to parse the `bundle` and generates the result to send back. It calls GPGMail and Gmail classes to operate with messages. In the future in may call a ConfigurationParses to properly initalize the rest of the script * `gpgmail.py`: contains the main functions to verify and sign a email message, it uses [gnupg.GPG](https://pythonhosted.org/python-gnupg/) * `gmail/__init__.py`: this is the file which contains the [Gmail API](https://developers.google.com/gmail/api) calls. The code is super documented, for more info take a look inside. ``` - get(id): get a the message by id - get_headers(id): get the headers of a message - message_matches(id, query): check if message matches the given query - send(id, message): sends a message. For now it uses a SMTP connection. GMail APIs no work properly with signed messages as defined in RFC 3156, therefore the `id` is not used. ``` * `gmail/client_secret.json`: contains the identification of Goopg, required during the login process ### Interaction ``` native receive bundle from gateway native send bundle to gateway ``` ### Behavior ``` chrome-main.py: while true: bundle = read_from_stdin() result = commandhanlder.parse(bundle) write_to_stdout(result) ``` ### Test integration Some tests can be found in `host/tests/` directory. # RoadMap * verify signatures (done) * sign messages and send them (done) * decrypt messages * encrypt messages and send them * configuration goopg-0.3.1/host/000077500000000000000000000000001275777172000136225ustar00rootroot00000000000000goopg-0.3.1/host/chrome-main.py000077500000000000000000000040621275777172000164000ustar00rootroot00000000000000#!/usr/bin/env python import struct import sys import json import logging from commandhandler import CommandHandler from logger import GoopgLogger def send_bundle(bundle): """Helper function that sends a bundle to the webapp.""" # Get a json string from the bundle bundle = json.dumps(bundle) # Write bundle size. sys.stdout.write(struct.pack('I', len(bundle))) # Write the bundle itself. sys.stdout.write(bundle) sys.stdout.flush() def read_bundle(): """Helper that reads bundles from the webapp.""" # Read the bundle length (first 4 bytes). text_length_bytes = sys.stdin.read(4) # Unpack bundle length as 4 byte integer. text_length = struct.unpack('i', text_length_bytes)[0] # Read the text (JSON object) of the bundle. raw_text = sys.stdin.read(text_length).decode('utf-8') return json.loads(raw_text) def main(): """Thread that reads messages from the webapp.""" GoopgLogger() handler = CommandHandler() # a queue to store bundles received before the 'init' command queue = [] while 1: try: bundle = read_bundle() except struct.error as e: logger.error("Error while reading stdin: \"{}\"" " - Exit.".format(e.message)) sys.exit(1) if not handler.initialized and bundle['command'] != 'init': # send init request send_bundle({"command": "request_init"}) queue.append(bundle) else: def parse_and_send_result(b): result = handler.parse(b) if result is not None: b['result'] = result send_bundle(b) parse_and_send_result(bundle) if len(queue) > 0: for bundle in queue: parse_and_send_result(bundle) queue = [] logger = logging.getLogger('chrome-main') if __name__ == '__main__': try: main() except Exception as e: logger.error("General error: {}".format(e.message)) sys.exit(0) goopg-0.3.1/host/commandhandler.py000066400000000000000000000066251275777172000171610ustar00rootroot00000000000000from gnupg import GPG from gpgmail import GPGMail from gmail import Gmail import logging from logger import GoopgLogger VERSION = "0.3.1" class CommandHandler(object): def __init__(self): self.gpg = None self.gmail = None self.gpgmail = None self.initialized = False self.logger = logging.getLogger('CommandHandler') def parse(self, bundle): if not 'command' in bundle: self.logger.error("no command in bundle {}".format(bundle)) return None result = None command = bundle["command"] if command == 'init': result = self.init(bundle) else: # do nothing if not intialized if not self.initialized: return False if command == 'verify': result = self.verify(bundle) elif command == 'sign': result = self.sign(bundle) elif command == 'import': result = self.import_key(bundle) return result def init(self, bundle): if not 'username' in bundle: return False self.gpg = GPG(use_agent=True) self.gpgmail = GPGMail(self.gpg) self.gmail = Gmail(bundle['username']) self.initialized = True return {'version': VERSION} def verify(self, bundle): if not 'id' in bundle: return False id = bundle['id'] def _verify(): mail = self.gmail.get(id) return self.gpgmail.verify(mail) # verify the message only if bundle['force'] = true if 'force' in bundle and bundle['force']: return _verify() # or content_type of message is 'multipart/signed' headers = self.gmail.get_headers(id, ['Content-Type', 'Message-ID']) if 'Content-Type' in headers: content_type = headers['Content-Type'] if content_type.find('multipart/signed') >= 0: return _verify() # or if message is multipart and it may contain a pgp-signature is_multipart = content_type.find('multipart/') >= 0 if (is_multipart and 'Message-ID' in headers): rfc822msgid = headers['Message-ID'] query = 'filename:asc || filename:gpg || filename:signature || ("BEGIN PGP SIGNATURE" "END PGP SIGNATURE")' match = self.gmail.message_matches(id, query, rfc822msgid) if match: return _verify() # else return None def sign(self, bundle): if not 'id' in bundle: return False result = False id = bundle['id'] try: draft = self.gmail.get(id) new_message = self.gpgmail.sign(draft) if new_message: self.gmail.send(id, new_message) result = True except Exception as e: self.logger.exception(e) finally: return result def import_key(self, bundle): if not 'id' in bundle: return False imp = self.gpg.recv_keys('keyserver.ubuntu.com', bundle['id']) result = imp.imported > 0 complete_result = imp.results[0] self.logger.info("import key {} - {}: {}".format( complete_result['fingerprint'], result, complete_result['text'])) return result goopg-0.3.1/host/gmail/000077500000000000000000000000001275777172000147135ustar00rootroot00000000000000goopg-0.3.1/host/gmail/__init__.py000066400000000000000000000233711275777172000170320ustar00rootroot00000000000000import httplib2 import base64 import email import os import sys import logging from apiclient.discovery import build from oauth2client.client import flow_from_clientsecrets from oauth2client.file import Storage from oauth2client.tools import run_flow, argparser from smtplib import SMTP, SMTPServerDisconnected from multiprocessing import Process, Queue from subprocess import PIPE from xdg import BaseDirectory from logger import StreamToLogger # Check https://developers.google.com/gmail/api/auth/scopes # for all available scopes OAUTH_SCOPE = 'https://mail.google.com/' # Path to the client_secret.json, file comes from the Google Developer Console CLIENT_SECRET_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'client_secret.json') # Directory where the credentials storage files are placed STORAGE_DIR = BaseDirectory.save_cache_path(os.path.join('goopg', 'storage')) class Gmail(): def __init__(self, username): # the main username self.username = username self.http = httplib2.Http() self.logger = logging.getLogger('Gmail') self.logger.setLevel(logging.DEBUG) # Start the OAuth flow to retrieve credentials flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE, redirect_uri='urn:ietf:wg:oauth:2.0:oob:auto') # The storage for current user storage = Storage(os.path.join(STORAGE_DIR, self.username)) # Try to retrieve credentials from storage # or run the flow to generate them self.credentials = storage.get() if self.credentials is None or self.credentials.invalid: # call a subprocess as workaround for stdin/stdout # blocked by the main process, this is the main function: def _subprocess_login(queue, fnstdin): sys.stdin = os.fdopen(fnstdin) sys.stdout = StreamToLogger(self.logger, logging.DEBUG) sys.stderr = StreamToLogger(self.logger, logging.ERROR) queue.put(run_flow(flow, storage, argparser.parse_args(['--auth_host_port', '1234', '--logging_level', 'DEBUG']), self.http)) # A Queue to get the result from subprocess queue = Queue() fnstdin = sys.stdin.fileno() p = Process(target=_subprocess_login, args=(queue, fnstdin)) self.logger.debug("start login process") p.start() p.join() self.logger.debug("end login process") # Retrieve the credentials self.credentials = queue.get() # self.http = self.credentials.authorize(self.http) # Access to the Gmail APIs self._gmail_API_login() # Use smtp as workaround to send message, GMail API # bugged with Content-Type: multipart/signed self._smtp_login() def _gmail_API_login(self): """ Login in Gmail APIs """ self._refresh_credentials() # Authorize the httplib2.Http object with our credentials authorized_http = self.credentials.authorize(self.http) # Build the Gmail service from discovery self.gmail_service = build('gmail', 'v1', http=authorized_http) self.messages = self.gmail_service.users().messages() self.drafts = self.gmail_service.users().drafts() def _refresh_credentials(self): """Refresh credentials if needed""" if self.credentials.access_token_expired: self.logger.info("credentials expired - refreshing") self.credentials.refresh(self.http) def _smtp_login(self): """ Login in Gmail smtp """ self._refresh_credentials() # initialize SMTP procedure self.smtp = SMTP('smtp.gmail.com', 587) if self.logger.getEffectiveLevel() == logging.DEBUG: self.smtp.set_debuglevel(True) self.smtp.starttls() self.smtp.ehlo() # XOATH2 authentication smtp_auth_string = 'user={}\1auth=Bearer {}\1\1' access_token = self.credentials.access_token smtp_auth_string = smtp_auth_string.format(self.username, access_token) smpt_auth_b64 = base64.b64encode(smtp_auth_string) # smtp login self.smtp.docmd("AUTH", "XOAUTH2 {}".format(smpt_auth_b64)) self.smtp.send("\r\n") def get(self, id): """ Get a Message from the Gmail message id (as known as X-GM-MSGID). """ self.logger.info('getting message {}'.format(id)) # this return a message.raw in url safe base64 message = self.messages.get(userId='me', id=id, format='raw').execute() # decode it message = base64.urlsafe_b64decode(str(message['raw'])) self.logger.debug('message id {}\n{}'.format(id, message)) message = email.message_from_string(message) return message def get_headers(self, id, headers=None): """ Get the headers of a Gmail message id (as known as X-GM-MSGID). If headers (list) is given, only include the headers specified. """ self.logger.info('getting the headers {} of message {}' .format(headers, id)) message = self.messages.get(userId='me', id=id, format='metadata', metadataHeaders=headers).execute() # build a dict for the headers, pythonic way result = {} for header_msg in message['payload']['headers']: result[header_msg['name']] = header_msg['value'] self.logger.debug('headers of message {}: {}' .format(id, result)) return result def message_matches(self, id, query, rfc822msgid=None): """ Check if the Gmail message id (as known as X-GM-MSGID) matches the query. Value rfc822msgid is optional (if None will be automatically requested), and represents the the value of Message-ID in the header of the message. Query is defined as the same str used in the Gmail search box: https://support.google.com/mail/answer/7190 """ self.logger.info('check if message {} matches query: {}' .format(id, query)) if rfc822msgid is None: headers = self.get_headers(id, ['Message-ID']) if ['Message-ID'] in headers: rfc822msgid = headers['Message-ID'] result = False if rfc822msgid: # build the query adding the Message-ID q = "rfc822msgid:{} {}".format(rfc822msgid, query) found = self.messages.list(userId='me', includeSpamTrash=True, q=q, fields='messages').execute() # look for the same message id in found if 'messages' in found: for m in found['messages']: if m['id'] == id: result = True break self.logger.info('message {} matches the query: {} - [{}]' .format(id, query, result)) return result @staticmethod def _get_receivers(message): """ Get the sender and receivers from message (email.Message) Receivers are defined into To, Cc and Bcc message header """ receivers = [] for header in ['To', 'Cc', 'Bcc']: if header in message: receivers += message.get_all(header, []) addresses = [] for name, addr in email.utils.getaddresses(receivers): if not addr in addresses: addresses.append(addr) return addresses @staticmethod def _remove_bcc_from_header(message): """ Return a new message (str) without the Bcc field """ if not isinstance(message, (str, unicode)): message = message.as_string() # split the message in header and body header, body = message.split('\n\n', 1) # check for Bcc in headers and remove it headers = header.split('\n') for i in range(0, len(headers)): line = headers[i] if line.find('Bcc:') == 0: headers.pop(i) # check if next lines start with ' ', which means # Bcc field is continuing, and remove them i += 1 max_length = len(headers) while(i < max_length and headers[i].find(' ') == 0): headers.pop(i) break header = '\n'.join(headers) # return the new message return '\n\n'.join([header, body]) def send(self, id, message, delete_draft=True): if isinstance(message, (str, unicode)): msg_str = message message = email.message_from_string(message) else: msg_str = message.as_string() receivers = self._get_receivers(message) if receivers is None or len(receivers) is 0: raise ValueError("receiver is None") if 'Bcc' in message: msg_str = self._remove_bcc_from_header(msg_str) # APIs do not work, they reset the multipart/signed Content-type # to multipart/mixed # raw = base64.urlsafe_b64encode(msg_str) # self.messages.send(userId='me', body={'raw': raw}).execute() try: self.smtp.sendmail(self.username, receivers, msg_str) except SMTPServerDisconnected: self.logger.info('smtp disconnected - reconnecting') self._smtp_login() self.smtp.sendmail(self.username, receivers, msg_str) goopg-0.3.1/host/gmail/client_secret.json000066400000000000000000000006541275777172000204360ustar00rootroot00000000000000{"installed":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","client_secret":"pfMnP-DQKmGhG3tTavLUFsoK","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","oob"],"client_x509_cert_url":"","client_id":"333313917298-rr7th7jvf4alej59jkv5bbs2fl8sc0j9.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}goopg-0.3.1/host/gpgmail.py000066400000000000000000000202031275777172000156110ustar00rootroot00000000000000import re import email from cStringIO import StringIO from email.generator import Generator from email.message import Message from email.mime.multipart import MIMEMultipart from email.mime.nonmultipart import MIMENonMultipart from gnupg import GPG from gnupg import logger as GPGLogger import logging class GPGMail(object): def __init__(self, gpg=None): if gpg: self.gpg = gpg else: self.gpg = GPG(gpgbinary="gpg2", use_agent=True) GPGLogger.setLevel(logging.DEBUG) self.logger = logging.getLogger('GPGMail') def _armor(self, container, message, signature): """ Make the armor signed message """ if container.get_param('protocol') == 'application/pgp-signature': m = re.match(r'^pgp-(.*)$', container.get_param('micalg')) if m: TEMPLATE = '-----BEGIN PGP SIGNED MESSAGE-----\n' \ 'Hash: %s\n\n' \ '%s\n%s\n' s = StringIO() text = re.sub(r'(?m)^(-.*)$', r'- \1', self._flatten(message)) s.write(TEMPLATE % (m.group(1).upper(), text, signature.get_payload())) return s.getvalue() return None def _filter_parts(sefl, m, f): """Iterate over messages that satisfy predicate.""" for x in m.walk(): if f(x): yield x def _flatten(self, message): """Return raw string representation of message.""" try: s = StringIO() g = Generator(s, mangle_from_=False, maxheaderlen=0) g.flatten(message) return s.getvalue() finally: s.close() def _signed_parts(self, message): """Iterate over signed parts of message yielding GPG verification status and signed contents.""" f = lambda m: \ m.is_multipart() and m.get_content_type() == 'multipart/signed' \ or not m.is_multipart() and m.get_content_maintype() == 'text' for part in self._filter_parts(message, f): if part.is_multipart(): try: signed_part, signature = part.get_payload() s = None sign_type = signature.get_content_type() if sign_type == 'application/pgp-signature': s = self._armor(part, signed_part, signature) yield self.gpg.verify(s), True, signature.get_filename() except ValueError: pass else: payload = part.get_payload(decode=True) yield self.gpg.verify(payload), False, None def verify(self, message): """Verify signature of a email message and returns the GPG info""" result = {} for verified, sign_attached, filename in self._signed_parts(message): if verified is not None: result = verified.__dict__ break if 'status' in result and result['status'] is None: return None if 'gpg' in result: del(result['gpg']) if sign_attached: result['filename'] = filename return result def _get_digest_algo(self, signature): """ Returns a string representation of the digest algo used in signature. Raises a TypeError if signature.hash_algo does not exists. Acceptable values for signature.hash_algo are: MD5 1 SHA1 2 RMD160 3 SHA256 8 SHA384 9 SHA512 10 SHA224 11 See gnupg/include/cipher.h for more info """ values = { 1: "MD5", 2: "SHA1", 3: "RMD160", 8: "SHA256", 9: "SHA384", 10: "SHA512", 11: "SHA224" } hash_algo = signature.hash_algo try: if isinstance(hash_algo, (str, unicode)): hash_algo = int(hash_algo) return values[hash_algo] except: raise TypeError("Invalid signature hash_algo {}".format(hash_algo)) def sign(self, message): """Sign a email message and return the new message signed as string""" if isinstance(message, (str, unicode)): message = email.message_from_string(message) # Create the basetxt, which contains the original body message # If original message is multipart, take the # Content-Type and the Body - ignore other Headers.. if message.is_multipart(): content_type = 'Content-Type: {}'.format(message['Content-Type']) try: body = self._flatten(message).split('\n\n', 1)[1] except: raise ValueError("Message cannot be split in Headers and Body") basetxt = '{}\n\n{}'.format(content_type, body) # else get only the body message else: basetxt = MIMEUTF8QPText(message).as_string() # See RFC 3156 (Section 5.) to understand these transformations # 1. all the lines must end with basetxt = basetxt.replace('\r\n', '\n')\ .replace('\n', '\r\n') # 2. remove trailing spaces basetxt = re.sub(r' +\r\n', '\r\n', basetxt, flags=re.M) # signing signature = self.gpg.sign(basetxt, detach=True) self.logger.error("signature %s %s" % (basetxt, signature)) # create the new message as multipart/signed (see RFC 3156) micalg = "pgp-{}".format(self._get_digest_algo(signature).lower()) new_message = MIMEMultipart(_subtype="signed", micalg=micalg, protocol="application/pgp-signature") # copy the headers header_to_copy = [ "Date", "Subject", "From", "To", "Bcc", "Cc", "Reply-To", "References", "In-Reply-To" ] for h in header_to_copy: if h in message: new_message[h] = message[h] # attach signed_part and signature and return message as string return self._attach_signed_parts(new_message, basetxt, signature) def _attach_signed_parts(self, message, signed_part, signature): """ Attach the signed_part and signature to the message (MIMEMultipart) and returns the new message as str """ # According with RFC 3156 the signed_part in the message # must be equal to the signed one. # The best way to do this is "hard" attach the parts # using strings. if not isinstance(signature, (str, unicode)): signature = str(signature) # get the body of the MIMEMultipart message, # remove last lines which close the boundary msg_lines = message.as_string().split('\n')[:-3] # get the last opening boundary boundary = msg_lines.pop() # create the signature as attachment sigmsg = Message() sigmsg['Content-Type'] = 'application/pgp-signature; ' + \ 'name="signature.asc"' sigmsg['Content-Description'] = 'GooPG digital signature' sigmsg.set_payload(signature) # attach the signed_part msg_lines += [boundary, signed_part] # attach the signature msg_lines += [boundary, sigmsg.as_string(), '{}--'.format(boundary)] # return message a string return '\n'.join(msg_lines) class MIMEUTF8QPText(MIMENonMultipart): """ This class is used to have a message with: Content-Transfer-Encoding: quoted-printable As required in RFC 3156 """ def __init__(self, message): MIMENonMultipart.__init__(self, 'text', 'plain', charset='utf-8') utf8qp = email.charset.Charset('utf-8') utf8qp.body_encoding = email.charset.QP payload = message.get_payload(decode=True) self.set_payload(payload, charset=utf8qp) goopg-0.3.1/host/http-main.py000077500000000000000000000037661275777172000161140ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from urlparse import urlparse, parse_qs from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from json import dumps from logger import GoopgLogger from commandhandler import CommandHandler PORT = 40094 class GoopgHandler(BaseHTTPRequestHandler): #Handler for the GET requests def do_GET(self): if self.path == '/favicon.ico': self.send_response(404) return bundle = self.from_path_to_bundle() if not 'command' in bundle: self.send_response(400) self.end_headers() return self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() initialized = self.server.handler.initialized if initialized is False and bundle['command'] != 'init': bundle = {"command": "request_init"} self.wfile.write(dumps(bundle, indent=4)) return bundle['result'] = self.server.handler.parse(bundle) self.wfile.write(dumps(bundle, indent=4)) return def from_path_to_bundle(self): """ Parse the path and returns the bundle """ bundle = {} url = urlparse(self.path) query = parse_qs(url.query) # query is a dict of keys and values (list), so keep only the # first value for k in query: bundle[k] = query[k].pop() return bundle class GoopgHTTPServer(HTTPServer): def __init__(self, host, request_handler): self.handler = CommandHandler() self.logger = logging.getLogger('http-main') HTTPServer.__init__(self, host, request_handler) def main(): GoopgLogger() #Create a web server and define the handler to manage the #incoming request server = GoopgHTTPServer(('127.0.0.1', PORT), GoopgHandler) #Wait forever for incoming http requests server.serve_forever() if __name__ == '__main__': main() sys.exit(0) goopg-0.3.1/host/logger.py000066400000000000000000000020101275777172000154440ustar00rootroot00000000000000import logging import os import sys from xdg import BaseDirectory class StreamToLogger(object): """ Fake file-like stream object that redirects writes to a logger instance. Adapted from: http://bit.ly/1xLpNuF """ def __init__(self, logger, log_level=logging.DEBUG): self.logger = logger self.log_level = log_level self.linebuf = '' def write(self, buf): for line in buf.rstrip().splitlines(): self.logger.log(self.log_level, line.rstrip()) def flush(*arg): pass class GoopgLogger(object): """ A simple class wich configure the basic logger """ filelog = os.path.join(BaseDirectory.save_cache_path('goopg'), 'log') logging.basicConfig(filename=filelog, filemode='a', level=logging.ERROR, format='%(asctime)s:%(levelname)s:%(name)s:%(message)s') # redirect stderr to logger sys.stderr = StreamToLogger(logging.getLogger('STDERR'), logging.ERROR) goopg-0.3.1/host/tests/000077500000000000000000000000001275777172000147645ustar00rootroot00000000000000goopg-0.3.1/host/tests/emails/000077500000000000000000000000001275777172000162365ustar00rootroot00000000000000goopg-0.3.1/host/tests/emails/signed.attached.text.html000066400000000000000000000054261275777172000231430ustar00rootroot00000000000000 Return-Path: Received: from leoiannacone.com ([2.234.35.59]) by mx.google.com with ESMTPSA id cz3sm23169410wjb.23.2014.11.10.05.14.10 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Mon, 10 Nov 2014 05:14:11 -0800 (PST) Sender: Leo Iannacone Received: by leoiannacone.com (sSMTP sendmail emulation); Mon, 10 Nov 2014 14:14:07 +0100 Message-ID: <1415625247.22522.5.camel@ubuntu.com> Subject: HTML signed attached From: Leo Iannacone To: "info@leoiannacone.com" Date: Mon, 10 Nov 2014 14:14:07 +0100 Content-Type: multipart/signed; micalg="pgp-sha512"; protocol="application/pgp-signature"; boundary="=-Y3vXmAZ27XxtztGwHZDJ" X-Mailer: Evolution 3.12.7-0ubuntu1 Mime-Version: 1.0 --=-Y3vXmAZ27XxtztGwHZDJ Content-Type: multipart/alternative; boundary="=-zJZMnIBEr5zQda7IRqbX" --=-zJZMnIBEr5zQda7IRqbX Content-Type: text/plain Content-Transfer-Encoding: quoted-printable This is a HTML email with signed attached (RFC 3156) --=-zJZMnIBEr5zQda7IRqbX Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: quoted-printable This is a HTML email with signed attached (RFC 3156) --=-zJZMnIBEr5zQda7IRqbX-- --=-Y3vXmAZ27XxtztGwHZDJ Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCgAGBQJUYLofAAoJEDMrlP7Sgvwl0pgQAMVjKPWoENWn8ODPz+JaYvox o43XZztPD/piXT5lD2pz9aISqrlnkHBn1UvrY9gmvvlKT8rDKM7QQhRgBj6UwLD1 dTp5FyrwlPDG/8emHGDTqtZnTWTszceYM0+tWHU529kHN0HjiuVD+pbx4VRKlv3x qZQx8rMhUgsMAHw0wdB8h8rcBLdlhmdFmjZlrqQQ26i412TmGzhY2mZZt/7hqbg7 B4CDl4NqMedI2qrnFx15jQ5LS5tO846NrpiAZvHlYAFM83oFhg7Lrri4tzWGxlLB /7wPpXe8zkeZ5EyLKEaOY7FoH3kfTbM5jR3tQQNZtmRcY//3K0qdVwqVPW6HyU5e 6saL4t38qHcM6G2v+iAArYJDsERli0GRxbM1dF20wCr0ehgOxcfGkI6FW4q3N5sv o65gI05viDpd920hPjPy9digodGzzeFD7bby/0S4ls6b5isK3mb5Yi8/7EjTsMV2 1YWRlLoHfIizPKYiB5woLP8AMwx+kUg6FOuPnUbqNzCubYRbjSzLfbRsEO3mV97a zC1W5Jve4GImbPu2HbaLiBbO8T1AtXuqhwpR0uKNkj7qLefS5dmt0mPceWKKqPHP Hg42sxk8nhv2PS18FFMZR7DF2GfWXtG1Qo+GCWAOl7OGxDCVFtaB2koLdiPlts8M 84aer1I5M6jQ582yvB5Y =9e3Y -----END PGP SIGNATURE----- --=-Y3vXmAZ27XxtztGwHZDJ-- goopg-0.3.1/host/tests/emails/signed.attached.text.plain000066400000000000000000000043661275777172000233040ustar00rootroot00000000000000 Return-Path: Received: from leoiannacone.com (2-234-35-59.ip221.fastwebnet.it. [2.234.35.59]) by mx.google.com with ESMTPSA id bc1sm13371966wib.16.2014.11.10.05.12.05 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Mon, 10 Nov 2014 05:12:06 -0800 (PST) Sender: Leo Iannacone Received: by leoiannacone.com (sSMTP sendmail emulation); Mon, 10 Nov 2014 14:12:01 +0100 Message-ID: <1415625121.22522.3.camel@ubuntu.com> Subject: Signed attached From: Leo Iannacone To: "info@leoiannacone.com" Date: Mon, 10 Nov 2014 14:12:01 +0100 Content-Type: multipart/signed; micalg="pgp-sha512"; protocol="application/pgp-signature"; boundary="=-NwM/4p8Yj4XkhKxD8n7L" X-Mailer: Evolution 3.12.7-0ubuntu1 Mime-Version: 1.0 --=-NwM/4p8Yj4XkhKxD8n7L Content-Type: text/plain Content-Transfer-Encoding: quoted-printable This is a RFC 3156 email (text/plain) --=-NwM/4p8Yj4XkhKxD8n7L Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCgAGBQJUYLmhAAoJEDMrlP7Sgvwlz7gP/j8tDHIrnr7xnbkT9HMtheoe i9/mYXYSeCat5XTcJQDIplmCzYWTVwxsfJucxkc1zu3NAsk4cD870oIUyDIqZ9Ov oZChf6iPgIOwQ+vgz8XDqufla1pnI32RYhZPMxnY5TjlH/QvGwjVIaKAR2iVCLm9 1kMGJzV9JQPCZrxkp0fnCU18eWrEhefNZD2GF/tFJJTEexXOI2o1SZg9EkT4FLQY W+10Mr0K9Vu2gTXo3AbQXRHd3pSjJGei8InSRDGK55spy+ks1pSHCUP9/DFRc9Y4 FGwixSRH+7laAp/lyrDPzr7WXqJE2D7X0tWtDoWT0uiw2hnCFGiwAoniDdctqUwA GPs4qV6dn5biF+vdihAUIKt7bwR9UyKAWz9eEKXWCzNy8/7UVP5kj6jbA3Wz/Tyk 6hreo+hlhhXyTD2MtY7i5IG/qS9ypPIVbqz9Ol1ARjqMmg4pWIMu8LNF7sWAE3tH y9grNoe7vce+phjfgflpbJvJIjDGq1hhxd8fetOEUsGuwhBEhC4E+6n0bkWbmYFo Y84oWHMhnDcxyoy89j+mBW/NGiFRoUH41j+qXMgyYpHmG4/6Du/yhSVi5msbDCMc krW5oL5ZveXMq5Y4kSVVn7r0Xz9sj1DrL552gtqzftL3WKOePz1x0X+uqAEFKS0j lMGZ6DXOypG8oeFNfCfL =5cpm -----END PGP SIGNATURE----- --=-NwM/4p8Yj4XkhKxD8n7L-- goopg-0.3.1/host/tests/emails/signed.attached.text.plain.with-attachment000066400000000000000000000073151275777172000264010ustar00rootroot00000000000000 Return-Path: Received: from leoiannacone.com (2-234-35-59.ip221.fastwebnet.it. [2.234.35.59]) by mx.google.com with ESMTPSA id a8sm13378433wiz.21.2014.11.10.05.16.58 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Mon, 10 Nov 2014 05:16:59 -0800 (PST) Sender: Leo Iannacone Received: by leoiannacone.com (sSMTP sendmail emulation); Mon, 10 Nov 2014 14:16:55 +0100 Message-ID: <1415625415.22522.7.camel@ubuntu.com> Subject: Singed (attached) email with attachments From: Leo Iannacone To: "info@leoiannacone.com" Date: Mon, 10 Nov 2014 14:16:55 +0100 Content-Type: multipart/signed; micalg="pgp-sha512"; protocol="application/pgp-signature"; boundary="=-O9rkV9LUfKLU2JGGPrfq" X-Mailer: Evolution 3.12.7-0ubuntu1 Mime-Version: 1.0 --=-O9rkV9LUfKLU2JGGPrfq Content-Type: multipart/mixed; boundary="=-gKifldXUJwlC4Jq6JVUS" --=-gKifldXUJwlC4Jq6JVUS Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable This a signed email (RFC 3156) with attachments. The body of attachment is: """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ without ". --=-gKifldXUJwlC4Jq6JVUS Content-Disposition: attachment; filename="lorem-ipsum.txt" Content-Transfer-Encoding: base64 Content-Type: text/plain; name="lorem-ipsum.txt"; charset="UTF-8" TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg c2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu YSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0 aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1 YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2 ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRl dXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1 aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgo= --=-gKifldXUJwlC4Jq6JVUS-- --=-O9rkV9LUfKLU2JGGPrfq Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCgAGBQJUYLrHAAoJEDMrlP7SgvwlIQsP/3OpCG/jZoCdpxfpQUrKhwhq rqytvtUQsVVe94tffYx17HRUa8gWvmnMq9l2iZUWXbGaqQ19ZtAz/uxCip/Vwxl8 bT9PKV+dJQa+xTNigzRtrJXNuzHAEdz8P79PAW4eBQq5HFM6jyLYCmfGf92PzeIg tfTdwfda4o81GH8HGVeI8P1XWL/1E9tkseQFMICJY72IBCVMZTWo+Acyv9PbtYfM Yh2bgRuaHbf8t4NgDdAu00WDFj/DrY+gPqb1aFShBgpH7KsmvrFSfghu59rzDOBf pjyo5ob354TK9K0BUl/G92TEmlbn6QcDz3nmL553gaADylE4yy0GMBHinqGiGakG sMpEYT+hcHxvYDX/ayIJzeA3XF3PSPteYv7TgwWCs7FmUJS9TFYNG/lnJQG22Ilu W4bRDZysxNtXn3e3JQ3Buuel/j69zWPOysc9Q+EAYmCGAyRRhlGB/wqPQh6bViul T/TVCqn98BONiOQAS4qi/EW3fRLVLfUyiilLeLiyCxN/BGYnWSMQ6fEoR8ZmjEGO J71A7jO49RyFj4aDlx+lxSrCqa9AjSOsHBm5f+LWYETQIK3/MOZpKJqETK/OtnZK DqzOV7Afo86HPHQVKGGk0Z3Clrz0P+8klYBD9yAZ22u0mhJ0nc95Ow0SFbdkrYlQ 3aT271HvDLCrGtabQbRu =n9oW -----END PGP SIGNATURE----- --=-O9rkV9LUfKLU2JGGPrfq-- goopg-0.3.1/host/tests/emails/signed.inline000066400000000000000000000035251275777172000207140ustar00rootroot00000000000000 Return-Path: Received: from home (2-234-35-59.ip221.fastwebnet.it. [2.234.35.59]) by mx.google.com with ESMTPSA id m6sm13371067wiy.16.2014.11.10.05.09.18 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 10 Nov 2014 05:09:18 -0800 (PST) Message-ID: <1415624955.22522.1.camel@gmail.com> Subject: Singed in line From: Leo Iannacone To: "info@leoiannacone.com" Date: Mon, 10 Nov 2014 14:09:15 +0100 Content-Type: text/plain; charset="UTF-8" X-Mailer: Evolution 3.12.7-0ubuntu1 Mime-Version: 1.0 Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 This is a signed inline email -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAEBCgAGBQJUYLioAAoJEDMrlP7SgvwlLi4QAJ5xp8qGVaHh/onnNHk/h/j6 licHGHVEPMUWuiZn0J0rgi/PnYniXjwb5YUghdNKOqO3rTUZ4JZEEAvRbBfN76HW ALWKdGGJ3kGkLyXs9F/cQCs0/1L0zv+ahbouhK/FmtKEVslVuhmTC1wKvDeF42t/ QWrPIihVfH4fRiuwIuNrOtud68AsNIIyYoVGNOTsnN5yH+cjoapRbz71sbGGYMQW JDW+FQR1RpvjqJw3sioVz+YazwoI9nqPUjg3gdVWbEsvDKdeqZHSnla2mQTI/9Q8 kK1op/4BTXSXqY3jSFQ3ikwAYHVKwdlFUEsZVRRlNMvtYEKynbROpJ2sI9j/zOYS 1xKoLNAERPz/t/a9VjkBrC8W6uZJy3PoQ/3CJPVwMT9sS94HG8fzECtMu5PW2gLK MmXjmOH0g+FYOqm84hKvE1RZ1MfLmPLZfggSVKGaM2NvgWAH44Nkvyp4Oj1Llodt avK2aegOF5XhFslc9HjeF0OO2gT6S2l7TLF7r8TOkJ6f+ipJszWyLIHQApqLMUIC g/m4ITNwTsLo/Tys1Yuwjop7nw+RIN/rRERfjm0pGWjSpF9ndcvCK/odYtwU/8oF dZBlaYAR9urn2V/alNsCcoXzY2kEr59FhP7SkYY6njaDxPyqhIYroopboKKIzzwA D+y5Rrhvg3eTXdbXgEk4 =hx2T -----END PGP SIGNATURE----- goopg-0.3.1/host/tests/emails/text.html000066400000000000000000000031071275777172000201110ustar00rootroot00000000000000 Return-Path: Received: from leoiannacone.com (2-234-35-59.ip221.fastwebnet.it. [2.234.35.59]) by mx.google.com with ESMTPSA id cu9sm23310081wjb.0.2014.11.10.05.48.00 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Mon, 10 Nov 2014 05:48:02 -0800 (PST) Sender: Leo Iannacone Received: by leoiannacone.com (sSMTP sendmail emulation); Mon, 10 Nov 2014 14:47:58 +0100 Message-ID: <1415627278.22522.9.camel@ubuntu.com> Subject: HTML email From: Leo Iannacone To: "info@leoiannacone.com" Date: Mon, 10 Nov 2014 14:47:58 +0100 Content-Type: multipart/alternative; boundary="=-fHxbgJVNj/mhSchavdl+" X-Mailer: Evolution 3.12.7-0ubuntu1 Mime-Version: 1.0 --=-fHxbgJVNj/mhSchavdl+ Content-Type: text/plain Content-Transfer-Encoding: 7bit This is a HTML email. --=-fHxbgJVNj/mhSchavdl+ Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: quoted-printable This is a HTML email. --=-fHxbgJVNj/mhSchavdl+-- goopg-0.3.1/host/tests/emails/text.plain000066400000000000000000000021011275777172000202410ustar00rootroot00000000000000 Return-Path: Received: from leoiannacone.com (2-234-35-59.ip221.fastwebnet.it. [2.234.35.59]) by mx.google.com with ESMTPSA id w10sm23290192wje.10.2014.11.10.05.47.06 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Mon, 10 Nov 2014 05:47:08 -0800 (PST) Sender: Leo Iannacone Received: by leoiannacone.com (sSMTP sendmail emulation); Mon, 10 Nov 2014 14:47:03 +0100 Message-ID: <1415627223.22522.8.camel@ubuntu.com> Subject: Simple text From: Leo Iannacone To: "info@leoiannacone.com" Date: Mon, 10 Nov 2014 14:47:03 +0100 Content-Type: text/plain Content-Transfer-Encoding: 7bit X-Mailer: Evolution 3.12.7-0ubuntu1 Mime-Version: 1.0 This is a simple text email goopg-0.3.1/host/tests/emails/text.plain.with-attachment000066400000000000000000000050171275777172000233520ustar00rootroot00000000000000 Return-Path: Received: from leoiannacone.com (2-234-35-59.ip221.fastwebnet.it. [2.234.35.59]) by mx.google.com with ESMTPSA id ny6sm13478920wic.22.2014.11.10.05.49.15 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Mon, 10 Nov 2014 05:49:17 -0800 (PST) Sender: Leo Iannacone Received: by leoiannacone.com (sSMTP sendmail emulation); Mon, 10 Nov 2014 14:49:12 +0100 Message-ID: <1415627352.22522.10.camel@ubuntu.com> Subject: Email with attachment From: Leo Iannacone To: "info@leoiannacone.com" Date: Mon, 10 Nov 2014 14:49:12 +0100 Content-Type: multipart/mixed; boundary="=-lp70mo+lwy1bY/QdHMJR" X-Mailer: Evolution 3.12.7-0ubuntu1 Mime-Version: 1.0 --=-lp70mo+lwy1bY/QdHMJR Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable This is a email with attachment The body of attachment is: """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ without ". --=-lp70mo+lwy1bY/QdHMJR Content-Disposition: attachment; filename="lorem-ipsum.txt" Content-Transfer-Encoding: base64 Content-Type: text/plain; name="lorem-ipsum.txt"; charset="UTF-8" TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg c2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu YSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0 aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1 YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2 ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRl dXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1 aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgo= --=-lp70mo+lwy1bY/QdHMJR-- goopg-0.3.1/host/tests/gmail.tests.py000066400000000000000000000062661275777172000176020ustar00rootroot00000000000000import unittest import sys import os import email from email.message import Message from email.mime.text import MIMEText # change the syspath to import Gmail # FIX_ME: make module name current = os.path.dirname(os.path.abspath(__file__)) sys.path.append(os.path.join(current, '..')) from gmail import Gmail class gmail_tests(unittest.TestCase): def test_sends_to_everyone(self): """ Tests if message is going to be sent to: To: CC: Bcc: """ my_sender = 'Me ' to = 'Leo Iannacone ' Cc = 'Leo2 Iannacone , Leo3 Iannacone ' Bcc = 'Leo4 Iannacone , Leo5 Iannacone ' m = Message() m['To'] = to m['Cc'] = Cc m['Bcc'] = Bcc m['From'] = my_sender my_receivers = ', '.join([to, Cc, Bcc]).split(',') my_addresses = email.utils.getaddresses(my_receivers) addresses = Gmail._get_receivers(m) for name, addr in my_addresses: self.assertIn(addr, addresses) def test_remove_bcc_from_header(self): """ Test if Bcc is removed from message header before send it """ my_sender = 'Me ' to = 'Leo Iannacone ' Cc = 'Leo2 Iannacone , Leo3 Iannacone ' Bcc = ['Leo{0} Nnc '.format(i) for i in range(4, 30)] Bcc = ', '.join(Bcc) payload = 'This is the payload of test_remove_bcc_from_header' m = MIMEText(payload) m['To'] = to m['Cc'] = Cc m['Bcc'] = Bcc m['From'] = my_sender new_message = Gmail._remove_bcc_from_header(m) # message must be a correct email (parsable) new_message = email.message_from_string(new_message) self.assertIsInstance(new_message, Message) # must not have 'Bcc' self.assertFalse('Bcc' in new_message) # and must have the same payload self.assertEqual(payload, new_message.get_payload()) def test_allow_comma_in_receiver_issue_14(self): """ Test if email is sent to the correct contact, containing comma in name """ my_sender = 'Me ' to = '"Leo2, Ianna" , "Leo, Iannacone" ' payload = 'This is the payload of test_remove_bcc_from_header' m = MIMEText(payload) m['To'] = to m['From'] = my_sender addr = Gmail._get_receivers(m) self.assertIn("leo3@tests.com", addr) self.assertIn("leo@tests.com", addr) def test_do_not_duplicate_senders(self): """ Test if email is sent twice to the same receiver """ my_sender = 'Me ' to = '"Leo2, Ianna" , "Leo, Iannacone" ' payload = 'This is the payload of test_remove_bcc_from_header' m = MIMEText(payload) m['To'] = to m['From'] = my_sender addr = Gmail._get_receivers(m) self.assertEqual(1, len(addr)) self.assertIn("leo@tests.com", addr) if __name__ == '__main__': unittest.main() goopg-0.3.1/host/tests/gpgmail.tests.py000066400000000000000000000071771275777172000201330ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- import unittest import sys import os import email from email.mime.text import MIMEText # change the syspath to import GPGMail # FIX_ME: make module name current = os.path.dirname(os.path.abspath(__file__)) sys.path.append(os.path.join(current, '..')) from gpgmail import GPGMail class BaseTest(unittest.TestCase): def __init__(self, *args, **kwargs): #super(unittest.TestCase, self).__init__(*args, **kwargs) unittest.TestCase.__init__(self, *args, **kwargs) self.gpgmail = GPGMail() def _verify_file(self, filename): filename = os.path.join(current, 'emails', filename) with open(filename, 'r') as fd: message = email.message_from_file(fd) return self.gpgmail.verify(message) def _sign_file(self, filename): filename = os.path.join(current, 'emails', filename) with open(filename, 'r') as fd: message = email.message_from_file(fd) return self.gpgmail.sign(message) def _assert_rfc3156(self, v): self.assertIsNotNone(v) self.assertIn('filename', v) self.assertEqual(v['filename'], 'signature.asc') class Verify(BaseTest): def __init__(self, *args, **kwargs): #super(BaseTest, self).__init__(*args, **kwargs) BaseTest.__init__(self, *args, **kwargs) def test_verify_inline_text(self): v = self._verify_file('signed.inline') self.assertIsNotNone(v) self.assertNotIn('filename', v) def test_verify_rfc3156_text_plain(self): v = self._verify_file('signed.attached.text.plain') self._assert_rfc3156(v) def test_verify_rfc3156_text_html(self): v = self._verify_file('signed.attached.text.html') self._assert_rfc3156(v) def test_verify_rfc3156_text_plain_with_attachment(self): v = self._verify_file('signed.attached.text.plain.with-attachment') self._assert_rfc3156(v) def test_verify_message_nosigned(self): v = self._verify_file('text.plain') self.assertIsNone(v) class Sign(BaseTest): def __init__(self, *args, **kwargs): #super(BaseTest, self).__init__(*args, **kwargs) BaseTest.__init__(self, *args, **kwargs) def _verify_message_str(self, message): message = email.message_from_string(message) v = self.gpgmail.verify(message) self._assert_rfc3156(v) def test_sign_text_plain(self): s = self._sign_file('text.plain') self._verify_message_str(s) def test_sign_text_html(self): s = self._sign_file('text.html') self._verify_message_str(s) def test_sign_text_plain_with_attachment(self): s = self._sign_file('text.plain.with-attachment') self._verify_message_str(s) def test_same_headers_and_payload(self): payload = 'A simple text message with UTF-8 chars\r\nàèéìòù' orig_message = MIMEText(payload) header_to_copy = [ "Date", "Subject", "From", "To", "Bcc", "Cc", "Reply-To", "References", "In-Reply-To" ] for h in header_to_copy: orig_message[h] = '{} of the message'.format(h) signed_message_str = self.gpgmail.sign(orig_message) signed_message = email.message_from_string(signed_message_str) for h in header_to_copy: self.assertEqual(orig_message[h], signed_message[h]) signed_part, signature = signed_message.get_payload() self.assertEqual(payload, signed_part.get_payload(decode=True)) if __name__ == '__main__': unittest.main() goopg-0.3.1/templates/000077500000000000000000000000001275777172000146435ustar00rootroot00000000000000goopg-0.3.1/templates/build.sh000066400000000000000000000022361275777172000163010ustar00rootroot00000000000000#!/bin/bash set -e usage() { echo "usage: ${0##*/} extension_id [chromium|google-chrome]" exit 1 } EXT_ID=$1 BROWSER=$2 DIR="$( cd "$( dirname "$0" )" && pwd )" if [ "$BROWSER" = "" ] ; then BROWSER="chromium" fi if [ "$BROWSER" != "chromium" -a "$BROWSER" != "google-chrome" ] ; then usage fi if [ "$EXT_ID" = "" ]; then usage fi if [ "$TARGET_DIR" = "" ] ; then TARGET_DIR="$HOME/.config/$BROWSER/NativeMessagingHosts" fi if [ "$HOST_PATH" = "" ] ; then HOST_PATH=$(realpath "$DIR/../host") fi # Create directory to store native messaging host. mkdir -p "$TARGET_DIR" # Copy native messaging host manifest. JSON="com.leoiannacone.goopg.json" cp "$DIR/$JSON.in" "$TARGET_DIR/$JSON" # Update host path in the manifest. HOST_PATH="$HOST_PATH/chrome-main.py" sed -i "s|@HOST_PATH@|$HOST_PATH|" "$TARGET_DIR/$JSON" sed -i "s|@EXT_ID@|$EXT_ID|" "$TARGET_DIR/$JSON" # Set permissions for the manifest so that all users can read it. chmod o+r "$TARGET_DIR/$JSON" echo Native messaging host $JSON has been installed. "$TARGET_DIR/$JSON" # make the goopg-web-extension-id JS=goopg-web-extension-id.js sed "s|@EXT_ID@|$EXT_ID|" "$DIR/$JS.in" > "$DIR/../app/$JS" goopg-0.3.1/templates/com.leoiannacone.goopg.json.in000066400000000000000000000003021275777172000224600ustar00rootroot00000000000000{ "name": "com.leoiannacone.goopg", "description": "GPG wrapper", "path": "@HOST_PATH@", "type": "stdio", "allowed_origins": [ "chrome-extension://@EXT_ID@/" ] } goopg-0.3.1/templates/goopg-web-extension-id.js.in000066400000000000000000000003301275777172000220740ustar00rootroot00000000000000"use strict"; // This file is used only for have a EXTENSION_ID variable // out of the main file, so people can git-pull without // having conflicts with their own EXTENSION_ID var GOOPG_EXTENSION_ID = "@EXT_ID@";