From 76af2710af23419fc7633ee31e08a0c916549a79 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 3 Mar 2016 21:32:12 -0500 Subject: [PATCH 01/24] Initial commit --- .gitignore | 27 +++ LICENSE | 674 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 3 files changed, 703 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7c280441 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.buildlog +.packages +.project +.pub/ +build/ +**/packages/ + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +pubspec.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..9cecc1d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + 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. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +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: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..af780523 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# body_parser +Parse request bodies and query strings in Dart. From f41f367808a21f58b094f2e6657deb681946f577 Mon Sep 17 00:00:00 2001 From: regiostech Date: Thu, 3 Mar 2016 22:47:20 -0500 Subject: [PATCH 02/24] Tests failing + incomplete --- .gitignore | 75 +++- Test Results - Run_All_Tests.html | 636 ++++++++++++++++++++++++++++++ lib/body_parser.dart | 67 ++++ pubspec.yaml | 9 + test/all_tests.dart | 60 +++ 5 files changed, 830 insertions(+), 17 deletions(-) create mode 100644 Test Results - Run_All_Tests.html create mode 100644 lib/body_parser.dart create mode 100644 pubspec.yaml create mode 100644 test/all_tests.dart diff --git a/.gitignore b/.gitignore index 7c280441..b306bf88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,68 @@ -# See https://www.dartlang.org/tools/private-files.html +### VisualStudioCode template +.settings -# Files and directories created by pub +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +/Test Results - Run_All_Tests.html +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +### Dart template +# Don’t commit the following directories created by pub. .buildlog -.packages -.project .pub/ build/ -**/packages/ +packages +.packages -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) +# Or the files created by dart2js. *.dart.js -*.part.js +*.js_ *.js.deps *.js.map -*.info.json -# Directory created by dartdoc -doc/api/ - -# Don't commit pubspec lock file -# (Library packages only! Remove pattern if developing an application package) +# Include when developing application packages. pubspec.lock + diff --git a/Test Results - Run_All_Tests.html b/Test Results - Run_All_Tests.html new file mode 100644 index 00000000..5eb5a1aa --- /dev/null +++ b/Test Results - Run_All_Tests.html @@ -0,0 +1,636 @@ + + + + +Test Results — Run All Tests + + + + + + + + + +
+ +
+
    +
  • + +
    922 ms
    +
    Test server support
    +
      +
    • + +
      922 ms
      +
      JSON
      +
        +
      • + +
        528 ms
        +
        passedPost Simple JSON
        +
          +
        • +Test server listening on http://localhost:60667
          +
        • +
        +
      • +
      • + +
        394 ms
        +
        failedPost Complex JSON
        +
          +
        • +Test server listening on http://localhost:60669
          +
        • +
        • +package:test expect
          test\all_tests.dart 56:9 main.<fn>.<fn>.<fn>.<async>
          ===== asynchronous gap ===========================
          dart:async _Completer.completeError
          test\all_tests.dart 57:8 main.<fn>.<fn>.<fn>.<async>
          ===== asynchronous gap ===========================
          dart:async _asyncThenWrapperHelper
          test\all_tests.dart main.<fn>.<fn>.<fn>
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + diff --git a/lib/body_parser.dart b/lib/body_parser.dart new file mode 100644 index 00000000..cdb02558 --- /dev/null +++ b/lib/body_parser.dart @@ -0,0 +1,67 @@ +// A library for parsing HTTP request bodies and queries. + +library body_parser; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:json_god/json_god.dart'; + +/// A representation of data from an incoming request. +class BodyParseResult { + /// The parsed body. + Map body = {}; + + /// The parsed query string. + Map query = {}; +} + +/// Grabs data from an incoming request. +/// +/// Supports urlencoded and JSON. +Future parseBody(HttpRequest request) async { + BodyParseResult result = new BodyParseResult(); + ContentType contentType = request.headers.contentType; + + // Parse body + if (contentType != null) { + if (contentType.mimeType == 'application/json') + result.body = JSON.decode(await request.transform(UTF8.decoder).join()); + else if (contentType.mimeType == 'application/x-www-form-urlencoded') { + String body = await request.transform(UTF8.decoder).join(); + buildMapFromUri(result.body, body); + } + } + + // Parse query + RegExp queryRgx = new RegExp(r'\?(.+)$'); + String uriString = request.requestedUri.toString(); + if (queryRgx.hasMatch(uriString)) { + Match queryMatch = queryRgx.firstMatch(uriString); + buildMapFromUri(result.query, queryMatch.group(1)); + } + + return result; +} + +/// Parses a URI-encoded string into real data! **Wow!** +buildMapFromUri(Map map, String body) { + God god = new God(); + for (String keyValuePair in body.split('&')) { + if (keyValuePair.contains('=')) { + List split = keyValuePair.split('='); + String key = Uri.decodeQueryComponent(split[0]); + String value = Uri.decodeQueryComponent(split[1]); + num numValue = num.parse(value, (_) => double.NAN); + if (!numValue.isNaN) + map[key] = numValue; + else if (value.startsWith('[') && value.endsWith(']')) + map[key] = god.deserialize(value); + else if (value.startsWith('{') && value.endsWith('}')) + map[key] = god.deserialize(value); + else if (value.trim().toLowerCase() == 'null') + map[key] = null; + else map[key] = value; + } else map[Uri.decodeQueryComponent(keyValuePair)] = true; + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..1bdc9254 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,9 @@ +name: body_parser +author: Tobe O +version: 1.0.0-dev +description: Parse request bodies and query strings in Dart. +dependencies: + json_god: any +dev_dependencies: + http: any + test: any \ No newline at end of file diff --git a/test/all_tests.dart b/test/all_tests.dart new file mode 100644 index 00000000..de173cd8 --- /dev/null +++ b/test/all_tests.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:body_parser/body_parser.dart'; +import 'package:http/http.dart' as http; +import 'package:json_god/json_god.dart'; +import 'package:test/test.dart'; + +main() { + group('Test server support', () { + HttpServer server; + String url; + http.Client client; + God god; + + setUp(() async { + server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0); + server.listen((HttpRequest request) async { + //Server will simply return a JSON representation of the parsed body + request.response.write(god.serialize(await parseBody(request))); + await request.response.close(); + }); + url = 'http://localhost:${server.port}'; + print('Test server listening on $url'); + client = new http.Client(); + god = new God(); + }); + tearDown(() async { + await server.close(force: true); + client.close(); + server = null; + url = null; + client = null; + god = null; + }); + + group('JSON', () { + test('Post Simple JSON', () async { + var response = await client.post(url, body: { + 'hello': 'world' + }); + expect(response.body, equals('{"body":{"hello":"world"},"query":{}}')); + }); + + test('Post Complex JSON', () async { + var postData = god.serialize({ + 'hello': 'world', + 'nums': [1, 2.0, 3 - 1], + 'map': { + 'foo': { + 'bar': 'baz' + } + } + }); + var response = await client.post(url, body: postData); + var body = god.deserialize(response.body)['body']; + expect(body['hello'], equals('world')); + }); + }); + }); +} \ No newline at end of file From c63ee84acbb7d7a9ecabb91ee4863c2e7397615e Mon Sep 17 00:00:00 2001 From: regiostech Date: Thu, 3 Mar 2016 23:30:13 -0500 Subject: [PATCH 03/24] Next comes array support. --- README.md | 72 ++++++++++++++++++++++++++++++- Test Results - Run_All_Tests.html | 69 ++++++++++++++++++++++------- lib/body_parser.dart | 3 +- test/all_tests.dart | 70 +++++++++++++++++++++++++++--- 4 files changed, 188 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index af780523..02b1cda1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,72 @@ -# body_parser +# Body Parser +![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev-red.svg) + +**NOT YET PRODUCTION READY** + Parse request bodies and query strings in Dart. + +### Contents + +* [Body Parser](#body-parser) +* [About](#about) +* [Installation](#installation) +* [Usage](#usage) +* [Thanks](#thank-you-for-using-body_parser) + +# About + +I needed something like Express.js's `body-parser` module, so I made it here. It fully supports JSON requests. +x-www-form-urlencoded is partially supported, as well as query strings. By the next update, they will be fully supported. +The only missing link in the implementation is that I have not yet provided support for arrays in the query. File upload support +will also be present by the production 1.0.0 release. + +A benefit of this is that primitive types are automatically deserialized correctly. As in, if you have a `hello=1.5` request, then +`body['hello']` will equal `1.5` and not `'1.5'`. A very semantic difference, yes, but it relieves stress in my head. + +# Installation + +To install Body Parser for your Dart project, simply add body_parser to your +pub dependencies. + + dependencies: + body_parser: any + +# Usage + +Body Parser exposes a simple class called [BodyParseResult]. +You can easily parse the query string and request body for a request by calling `Future parseBody`. + +```dart +import 'dart:convert'; +import 'package:body_parser/body_parser.dart'; + +main() async { + // ... + await for (HttpRequest request in server) { + request.response.write(JSON.encode(await parseBody(request).body)); + await request.response.close(); + } +} +``` + +This can easily be used with a library like [JSON God](https://github.com/thosakwe/json_god) +to build structured JSON/REST APIs. Add validation and you've got an instant backend. + +```dart + +MyClass create(HttpRequest request) async { + God god = new God(); + return god.deserialize(await parseBody(request).body, MyClass); +} +``` + + +# Thank you for using Body Parser + +Thank you for using this library. I hope you like it. + +Feel free to follow me on Twitter: + +[@thosakwe](http://twitter.com/thosakwe) +or +[@regios_tech](http://twitter.com/regios_tech) \ No newline at end of file diff --git a/Test Results - Run_All_Tests.html b/Test Results - Run_All_Tests.html index 5eb5a1aa..1cac1124 100644 --- a/Test Results - Run_All_Tests.html +++ b/Test Results - Run_All_Tests.html @@ -572,8 +572,8 @@ jQuery.cookie = function(name, value, options) {
diff --git a/lib/body_parser.dart b/lib/body_parser.dart index cdb02558..5a0902f1 100644 --- a/lib/body_parser.dart +++ b/lib/body_parser.dart @@ -1,5 +1,4 @@ -// A library for parsing HTTP request bodies and queries. - +/// A library for parsing HTTP request bodies and queries. library body_parser; import 'dart:async'; diff --git a/test/all_tests.dart b/test/all_tests.dart index de173cd8..e56d95f2 100644 --- a/test/all_tests.dart +++ b/test/all_tests.dart @@ -33,15 +33,66 @@ main() { god = null; }); - group('JSON', () { - test('Post Simple JSON', () async { - var response = await client.post(url, body: { - 'hello': 'world' - }); + group('query string', () { + test('GET Simple', () async { + print('GET $url/?hello=world'); + var response = await client.get('$url/?hello=world'); + print('Response: ${response.body}'); + expect(response.body, equals('{"body":{},"query":{"hello":"world"}}')); + }); + + test('GET Complex', () async { + var postData = 'hello=world&nums[]=1&nums[]=2.0&nums[]=${3 - + 1}&map.foo.bar=baz'; + var response = await client.get('$url/?$postData'); + var body = god.deserialize(response.body)['body']; + expect(body['hello'], equals('world')); + expect(body['nums'][2], equals(2)); + expect(body['map'] is Map, equals(true)); + expect(body['map']['foo'], equals({'bar': 'baz'})); + }, skip: 'Array support via query string is pending.'); + }); + + group('urlencoded', () { + Map headers = { + HttpHeaders.CONTENT_TYPE: 'application/x-www-form-urlencoded' + }; + test('POST Simple', () async { + print('Body: hello=world'); + var response = await client.post( + url, headers: headers, body: 'hello=world'); + print('Response: ${response.body}'); expect(response.body, equals('{"body":{"hello":"world"},"query":{}}')); }); - test('Post Complex JSON', () async { + test('Post Complex', () async { + var postData = 'hello=world&nums[]=1&nums[]=2.0&nums[]=${3 - + 1}&map.foo.bar=baz'; + var response = await client.post(url, headers: headers, body: postData); + var body = god.deserialize(response.body)['body']; + expect(body['hello'], equals('world')); + expect(body['nums'][2], equals(2)); + expect(body['map'] is Map, equals(true)); + expect(body['map']['foo'], equals({'bar': 'baz'})); + }, skip: 'Array support via urlencoded is pending.'); + }); + + group('JSON', () { + Map headers = { + HttpHeaders.CONTENT_TYPE: ContentType.JSON.toString() + }; + test('Post Simple', () async { + var postData = god.serialize({ + 'hello': 'world' + }); + print('Body: $postData'); + var response = await client.post( + url, headers: headers, body: postData); + print('Response: ${response.body}'); + expect(response.body, equals('{"body":{"hello":"world"},"query":{}}')); + }); + + test('Post Complex', () async { var postData = god.serialize({ 'hello': 'world', 'nums': [1, 2.0, 3 - 1], @@ -51,9 +102,14 @@ main() { } } }); - var response = await client.post(url, body: postData); + print('Body: $postData'); + var response = await client.post(url, headers: headers, body: postData); + print('Response: ${response.body}'); var body = god.deserialize(response.body)['body']; expect(body['hello'], equals('world')); + expect(body['nums'][2], equals(2)); + expect(body['map'] is Map, equals(true)); + expect(body['map']['foo'], equals({'bar': 'baz'})); }); }); }); From 5f8369e66fca36406e343ef628b48ff83e1042f9 Mon Sep 17 00:00:00 2001 From: regiostech Date: Thu, 3 Mar 2016 23:35:59 -0500 Subject: [PATCH 04/24] Modified README --- .gitignore | 5 ++++- README.md | 3 ++- lib/body_parser.dart | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b306bf88..172a6cef 100644 --- a/.gitignore +++ b/.gitignore @@ -49,8 +49,9 @@ atlassian-ide-plugin.xml com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties + ### Dart template -# Don’t commit the following directories created by pub. +# Don't commit the following directories created by pub. .buildlog .pub/ build/ @@ -66,3 +67,5 @@ packages # Include when developing application packages. pubspec.lock +doc/api + diff --git a/README.md b/README.md index 02b1cda1..3723597c 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,12 @@ main() async { } ``` +You can also use `buildMapFromUri(Map, String)` to populate a map from a URL encoded string. + This can easily be used with a library like [JSON God](https://github.com/thosakwe/json_god) to build structured JSON/REST APIs. Add validation and you've got an instant backend. ```dart - MyClass create(HttpRequest request) async { God god = new God(); return god.deserialize(await parseBody(request).body, MyClass); diff --git a/lib/body_parser.dart b/lib/body_parser.dart index 5a0902f1..80e2914c 100644 --- a/lib/body_parser.dart +++ b/lib/body_parser.dart @@ -44,6 +44,8 @@ Future parseBody(HttpRequest request) async { } /// Parses a URI-encoded string into real data! **Wow!** +/// +/// Whichever map you provide will be automatically populated from the urlencoded body string you provide. buildMapFromUri(Map map, String body) { God god = new God(); for (String keyValuePair in body.split('&')) { From 6146e410ec2586f2eabf239372161b7636ecd2ae Mon Sep 17 00:00:00 2001 From: regiostech Date: Thu, 3 Mar 2016 23:36:45 -0500 Subject: [PATCH 05/24] Modified README again --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3723597c..6301b6ab 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Parse request bodies and query strings in Dart. * [About](#about) * [Installation](#installation) * [Usage](#usage) -* [Thanks](#thank-you-for-using-body_parser) +* [Thanks](#thank-you-for-using-body-parser) # About From 5b7025815ad269d2ca6174bbf61141bb8fc77c3a Mon Sep 17 00:00:00 2001 From: regiostech Date: Sat, 16 Apr 2016 23:07:59 -0400 Subject: [PATCH 06/24] Entering beta. JSON God dep removed, ironically. --- .idea/libraries/Dart_Packages.xml | 323 ++++++++++++++++++++++++++++++ README.md | 4 +- Test Results - Run_All_Tests.html | 85 +++++--- lib/body_parser.dart | 52 +++-- pubspec.yaml | 5 +- test/all_tests.dart | 20 +- 6 files changed, 431 insertions(+), 58 deletions(-) create mode 100644 .idea/libraries/Dart_Packages.xml diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml new file mode 100644 index 00000000..ab2bf802 --- /dev/null +++ b/.idea/libraries/Dart_Packages.xml @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 6301b6ab..fa54699e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Body Parser -![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev-red.svg) +![version 1.0.0-beta](https://img.shields.io/badge/version-1.0.0--beta-blue.svg) **NOT YET PRODUCTION READY** -Parse request bodies and query strings in Dart. +Parse request bodies and query strings in Dart. No external dependencies required. ### Contents diff --git a/Test Results - Run_All_Tests.html b/Test Results - Run_All_Tests.html index 1cac1124..a7f4b43e 100644 --- a/Test Results - Run_All_Tests.html +++ b/Test Results - Run_All_Tests.html @@ -572,8 +572,8 @@ jQuery.cookie = function(name, value, options) {
diff --git a/lib/body_parser.dart b/lib/body_parser.dart index 80e2914c..f2e61274 100644 --- a/lib/body_parser.dart +++ b/lib/body_parser.dart @@ -4,7 +4,6 @@ library body_parser; import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:json_god/json_god.dart'; /// A representation of data from an incoming request. class BodyParseResult { @@ -47,22 +46,51 @@ Future parseBody(HttpRequest request) async { /// /// Whichever map you provide will be automatically populated from the urlencoded body string you provide. buildMapFromUri(Map map, String body) { - God god = new God(); + RegExp parseArray = new RegExp(r'^(.+)\[\]$'); + for (String keyValuePair in body.split('&')) { if (keyValuePair.contains('=')) { List split = keyValuePair.split('='); String key = Uri.decodeQueryComponent(split[0]); String value = Uri.decodeQueryComponent(split[1]); - num numValue = num.parse(value, (_) => double.NAN); - if (!numValue.isNaN) - map[key] = numValue; - else if (value.startsWith('[') && value.endsWith(']')) - map[key] = god.deserialize(value); - else if (value.startsWith('{') && value.endsWith('}')) - map[key] = god.deserialize(value); - else if (value.trim().toLowerCase() == 'null') - map[key] = null; - else map[key] = value; + + if (parseArray.hasMatch(key)) { + Match queryMatch = parseArray.firstMatch(key); + key = queryMatch.group(1); + if (!(map[key] is List)) { + map[key] = []; + } + + map[key].add(getValue(value)); + } else if(key.contains('.')) { + // i.e. map.foo.bar => [map, foo, bar] + List keys = key.split('.'); + + Map targetMap = map[keys[0]] ?? {}; + map[keys[0]] = targetMap; + for (int i = 1; i < keys.length; i++) { + if (i < keys.length - 1) { + targetMap[keys[i]] = targetMap[keys[i]] ?? {}; + targetMap = targetMap[keys[i]]; + } else { + targetMap[keys[i]] = getValue(value); + } + } + } + else map[key] = getValue(value); } else map[Uri.decodeQueryComponent(keyValuePair)] = true; } +} + +getValue(String value) { + num numValue = num.parse(value, (_) => double.NAN); + if (!numValue.isNaN) + return numValue; + else if (value.startsWith('[') && value.endsWith(']')) + return JSON.decode(value); + else if (value.startsWith('{') && value.endsWith('}')) + return JSON.decode(value); + else if (value.trim().toLowerCase() == 'null') + return null; + else return value; } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 1bdc9254..75074e53 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,8 @@ name: body_parser author: Tobe O -version: 1.0.0-dev +version: 1.0.0-beta description: Parse request bodies and query strings in Dart. -dependencies: - json_god: any dev_dependencies: http: any + json_god: any test: any \ No newline at end of file diff --git a/test/all_tests.dart b/test/all_tests.dart index e56d95f2..770b2101 100644 --- a/test/all_tests.dart +++ b/test/all_tests.dart @@ -42,15 +42,17 @@ main() { }); test('GET Complex', () async { - var postData = 'hello=world&nums[]=1&nums[]=2.0&nums[]=${3 - + var postData = 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - 1}&map.foo.bar=baz'; + print('Body: $postData'); var response = await client.get('$url/?$postData'); - var body = god.deserialize(response.body)['body']; - expect(body['hello'], equals('world')); - expect(body['nums'][2], equals(2)); - expect(body['map'] is Map, equals(true)); - expect(body['map']['foo'], equals({'bar': 'baz'})); - }, skip: 'Array support via query string is pending.'); + print('Response: ${response.body}'); + var query = god.deserialize(response.body)['query']; + expect(query['hello'], equals('world')); + expect(query['nums'][2], equals(2)); + expect(query['map'] is Map, equals(true)); + expect(query['map']['foo'], equals({'bar': 'baz'})); + }); }); group('urlencoded', () { @@ -66,7 +68,7 @@ main() { }); test('Post Complex', () async { - var postData = 'hello=world&nums[]=1&nums[]=2.0&nums[]=${3 - + var postData = 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - 1}&map.foo.bar=baz'; var response = await client.post(url, headers: headers, body: postData); var body = god.deserialize(response.body)['body']; @@ -74,7 +76,7 @@ main() { expect(body['nums'][2], equals(2)); expect(body['map'] is Map, equals(true)); expect(body['map']['foo'], equals({'bar': 'baz'})); - }, skip: 'Array support via urlencoded is pending.'); + }); }); group('JSON', () { From 0e74bc9d9b3d4c710fea5f40dd24cd7077b016e8 Mon Sep 17 00:00:00 2001 From: regiostech Date: Sat, 16 Apr 2016 23:11:33 -0400 Subject: [PATCH 07/24] Awaiting file upload support. --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fa54699e..a7b1a4bc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Body Parser -![version 1.0.0-beta](https://img.shields.io/badge/version-1.0.0--beta-blue.svg) +![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev-red.svg) **NOT YET PRODUCTION READY** @@ -16,9 +16,8 @@ Parse request bodies and query strings in Dart. No external dependencies require # About I needed something like Express.js's `body-parser` module, so I made it here. It fully supports JSON requests. -x-www-form-urlencoded is partially supported, as well as query strings. By the next update, they will be fully supported. -The only missing link in the implementation is that I have not yet provided support for arrays in the query. File upload support -will also be present by the production 1.0.0 release. +x-www-form-urlencoded fully supported, as well as query strings. You can also include arrays in your query, +in the same way you would for a PHP application. File upload support will also be present by the production 1.0.0 release. A benefit of this is that primitive types are automatically deserialized correctly. As in, if you have a `hello=1.5` request, then `body['hello']` will equal `1.5` and not `'1.5'`. A very semantic difference, yes, but it relieves stress in my head. From 1f37cd1b701e2f134327ee783b24297a1d31cc63 Mon Sep 17 00:00:00 2001 From: regiostech Date: Sat, 16 Apr 2016 23:51:55 -0400 Subject: [PATCH 08/24] File support is nearly there. --- lib/body_parser.dart | 45 ++++++++++++++++++++++++++++++++++++++++---- test/all_tests.dart | 29 +++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/lib/body_parser.dart b/lib/body_parser.dart index f2e61274..37946a90 100644 --- a/lib/body_parser.dart +++ b/lib/body_parser.dart @@ -12,6 +12,9 @@ class BodyParseResult { /// The parsed query string. Map query = {}; + + /// All files uploaded within this request. + List files = []; } /// Grabs data from an incoming request. @@ -39,14 +42,48 @@ Future parseBody(HttpRequest request) async { buildMapFromUri(result.query, queryMatch.group(1)); } + // Accept file + if (contentType != null && request.method == 'POST') { + RegExp parseBoundaryRgx = new RegExp( + r'^multipart\/form-data;\s*boundary=(.+)$'); + if (parseBoundaryRgx.hasMatch(contentType.toString())) { + Match boundaryMatch = parseBoundaryRgx.firstMatch(contentType.toString()); + String body = await request.transform(UTF8.decoder).join(); + for (String chunk in body.split(boundaryMatch.group(1))) { + var fileData = getFileDataFromChunk(chunk); + if (fileData != null) + result.files.add(fileData); + } + } + } + return result; } +/// Parses file data from a multipart/form-data chunk. +Map getFileDataFromChunk(String chunk) { + Map result = {}; + RegExp isFormDataRgx = new RegExp( + r'Content-Dispositition:\s*form-data;\s*name="([^"]+)"'); + + if (isFormDataRgx.hasMatch(chunk)) { + result['name'] = isFormDataRgx.firstMatch(chunk).group(1); + + RegExp contentTypeRgx = new RegExp(r'Content-Type:\s*([^\n]+)'); + if (contentTypeRgx.hasMatch(chunk)) { + result['type'] = contentTypeRgx.firstMatch(chunk).group(1); + return result; + } + } + + return null; +} + /// Parses a URI-encoded string into real data! **Wow!** /// /// Whichever map you provide will be automatically populated from the urlencoded body string you provide. buildMapFromUri(Map map, String body) { - RegExp parseArray = new RegExp(r'^(.+)\[\]$'); + RegExp parseArrayRgx = new RegExp(r'^(.+)\[\]$'); for (String keyValuePair in body.split('&')) { if (keyValuePair.contains('=')) { @@ -54,15 +91,15 @@ buildMapFromUri(Map map, String body) { String key = Uri.decodeQueryComponent(split[0]); String value = Uri.decodeQueryComponent(split[1]); - if (parseArray.hasMatch(key)) { - Match queryMatch = parseArray.firstMatch(key); + if (parseArrayRgx.hasMatch(key)) { + Match queryMatch = parseArrayRgx.firstMatch(key); key = queryMatch.group(1); if (!(map[key] is List)) { map[key] = []; } map[key].add(getValue(value)); - } else if(key.contains('.')) { + } else if (key.contains('.')) { // i.e. map.foo.bar => [map, foo, bar] List keys = key.split('.'); diff --git a/test/all_tests.dart b/test/all_tests.dart index 770b2101..61a71c2b 100644 --- a/test/all_tests.dart +++ b/test/all_tests.dart @@ -38,7 +38,7 @@ main() { print('GET $url/?hello=world'); var response = await client.get('$url/?hello=world'); print('Response: ${response.body}'); - expect(response.body, equals('{"body":{},"query":{"hello":"world"}}')); + expect(response.body, equals('{"body":{},"query":{"hello":"world"},"files":[]}')); }); test('GET Complex', () async { @@ -64,7 +64,7 @@ main() { var response = await client.post( url, headers: headers, body: 'hello=world'); print('Response: ${response.body}'); - expect(response.body, equals('{"body":{"hello":"world"},"query":{}}')); + expect(response.body, equals('{"body":{"hello":"world"},"query":{},"files":[]}')); }); test('Post Complex', () async { @@ -91,7 +91,7 @@ main() { var response = await client.post( url, headers: headers, body: postData); print('Response: ${response.body}'); - expect(response.body, equals('{"body":{"hello":"world"},"query":{}}')); + expect(response.body, equals('{"body":{"hello":"world"},"query":{},"files":[]}')); }); test('Post Complex', () async { @@ -114,5 +114,28 @@ main() { expect(body['map']['foo'], equals({'bar': 'baz'})); }); }); + + group('File', () { + test('Single upload', () async { + String boundary = '----myBoundary'; + Map headers = { + HttpHeaders.CONTENT_TYPE: 'multipart/form-data; boundary=$boundary' + }; + String postData = ''' + + $boundary + Content-Dispositition: form-data; name="file" + Content-Type: text/plain + + Hello world + $boundary-- + '''; + + print('Form Data: \n$postData'); + var response = await client.post(url, headers: headers, body: postData); + print('Response: ${response.body}'); + var body = god.deserialize(response.body)['body']; + }); + }); }); } \ No newline at end of file From 5c0ee725d10b1fe53c7751c8c015b47afed4c6f8 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 17 Apr 2016 15:51:40 -0400 Subject: [PATCH 09/24] Single file uploads = complete --- .gitignore | 2 +- .idea/libraries/Dart_Packages.xml | 156 +++++++++++++++--------------- README.md | 5 +- Test Results - Run_All_Tests.html | 107 +++++++++++--------- lib/body_parser.dart | 61 +++++++++--- lib/file_upload_info.dart | 15 +++ pubspec.yaml | 3 +- test/all_tests.dart | 53 +++++++--- 8 files changed, 245 insertions(+), 157 deletions(-) create mode 100644 lib/file_upload_info.dart diff --git a/.gitignore b/.gitignore index 172a6cef..70817aa5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ *.iml ## Directory-based project format: -.idea/ +.idea /Test Results - Run_All_Tests.html # if you remove the above rule, at least ignore the following: diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index ab2bf802..407e0ca6 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -5,318 +5,318 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index a7b1a4bc..1f705e3f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ **NOT YET PRODUCTION READY** -Parse request bodies and query strings in Dart. No external dependencies required. +Parse request bodies and query strings in Dart, as well multipart/form-data uploads. No external +dependencies required. ### Contents @@ -17,7 +18,7 @@ Parse request bodies and query strings in Dart. No external dependencies require I needed something like Express.js's `body-parser` module, so I made it here. It fully supports JSON requests. x-www-form-urlencoded fully supported, as well as query strings. You can also include arrays in your query, -in the same way you would for a PHP application. File upload support will also be present by the production 1.0.0 release. +in the same way you would for a PHP application. Full file upload support will also be present by the production 1.0.0 release. A benefit of this is that primitive types are automatically deserialized correctly. As in, if you have a `hello=1.5` request, then `body['hello']` will equal `1.5` and not `'1.5'`. A very semantic difference, yes, but it relieves stress in my head. diff --git a/Test Results - Run_All_Tests.html b/Test Results - Run_All_Tests.html index a7f4b43e..f974794f 100644 --- a/Test Results - Run_All_Tests.html +++ b/Test Results - Run_All_Tests.html @@ -2,7 +2,7 @@ -Test Results — Run All Tests +Test Results — All Tests - - - - - - - - -
- -
-
    -
  • - -
    1.86 s
    -
    Test server support
    -
      -
    • - -
      1.03 s
      -
      query string
      -
        -
      • - -
        910 ms
        -
        passedGET Simple
        -
          -
        • -Test server listening on http://localhost:45244
          GET http://localhost:45244/?hello=world
          Response: {"body":{},"query":{"hello":"world"},"files":[]}
          -
        • -
        -
      • -
      • - -
        121 ms
        -
        passedGET Complex
        -
          -
        • -Test server listening on http://localhost:54627
          Body: hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=2&map.foo.bar=baz
          Response: {"body":{},"query":{"hello":"world","nums":[1,2.0,2],"map":{"foo":{"bar":"baz"}}},"files":[]}
          -
        • -
        -
      • -
      -
    • -
    • - -
      454 ms
      -
      urlencoded
      -
        -
      • - -
        334 ms
        -
        passedPOST Simple
        -
          -
        • -Test server listening on http://localhost:38381
          Body: hello=world
          Response: {"body":{"hello":"world"},"query":{},"files":[]}
          -
        • -
        -
      • -
      • - -
        120 ms
        -
        passedPost Complex
        -
          -
        • -Test server listening on http://localhost:52437
          -
        • -
        -
      • -
      -
    • -
    • - -
      195 ms
      -
      JSON
      -
        -
      • - -
        83 ms
        -
        passedPost Simple
        -
          -
        • -Test server listening on http://localhost:59756
          Body: {"hello":"world"}
          Response: {"body":{"hello":"world"},"query":{},"files":[]}
          -
        • -
        -
      • -
      • - -
        112 ms
        -
        passedPost Complex
        -
          -
        • -Test server listening on http://localhost:39215
          Body: {"hello":"world","nums":[1,2.0,2],"map":{"foo":{"bar":"baz"}}}
          Response: {"body":{"hello":"world","nums":[1,2.0,2],"map":{"foo":{"bar":"baz"}}},"query":{},"files":[]}
          -
        • -
        -
      • -
      -
    • -
    • - -
      181 ms
      -
      File
      -
        -
      • - -
        181 ms
        -
        passedSingle upload
        -
          -
        • -Test server listening on http://localhost:55740
          Form Data:

          ----myBoundary
          Content-Disposition: form-data; name="hello"
          world
          ----myBoundary
          Content-Disposition: file; name="file"; filename="app.dart"
          Content-Type: text/plain
          Hello world
          ----myBoundary--
          Response: {"body":{"hello":"world"},"query":{},"files":[{"mimeType":"text/plain","name":"file","filename":"app.dart","data":[72,101,108,108,111,32,119,111,114,108,100]}]}
          -
        • -
        -
      • -
      • -ignoredMultiple upload -
      • -
      -
    • -
    -
  • -
-
-
- - - diff --git a/lib/body_parser.dart b/lib/body_parser.dart index 1f1b22ba..9bb69e35 100644 --- a/lib/body_parser.dart +++ b/lib/body_parser.dart @@ -4,161 +4,7 @@ library body_parser; import 'dart:async'; import 'dart:convert'; import 'dart:io'; - -part 'file_upload_info.dart'; - -/// A representation of data from an incoming request. -class BodyParseResult { - /// The parsed body. - Map body = {}; - - /// The parsed query string. - Map query = {}; - - /// All files uploaded within this request. - List files = []; -} - -/// Grabs data from an incoming request. -/// -/// Supports urlencoded and JSON, as well as multipart/form-data uploads. -/// On a file upload request, only fields with the name **'file'** are processed -/// as files. Anything else is put in the body. You can change the upload file name -/// via the *fileUploadName* parameter. :) -Future parseBody(HttpRequest request, - {String fileUploadName: 'file'}) async { - BodyParseResult result = new BodyParseResult(); - ContentType contentType = request.headers.contentType; - - // Parse body - if (contentType != null) { - if (contentType.mimeType == 'application/json') - result.body = JSON.decode(await request.transform(UTF8.decoder).join()); - else if (contentType.mimeType == 'application/x-www-form-urlencoded') { - String body = await request.transform(UTF8.decoder).join(); - buildMapFromUri(result.body, body); - } - } - - // Parse query - RegExp queryRgx = new RegExp(r'\?(.+)$'); - String uriString = request.requestedUri.toString(); - if (queryRgx.hasMatch(uriString)) { - Match queryMatch = queryRgx.firstMatch(uriString); - buildMapFromUri(result.query, queryMatch.group(1)); - } - - // Accept file - if (contentType != null && request.method == 'POST') { - RegExp parseBoundaryRgx = new RegExp( - r'multipart\/form-data;\s*boundary=([^\s;]+)'); - if (parseBoundaryRgx.hasMatch(contentType.toString())) { - Match boundaryMatch = parseBoundaryRgx.firstMatch(contentType.toString()); - String boundary = boundaryMatch.group(1); - String body = await request.transform(UTF8.decoder).join(); - for (String chunk in body.split(boundary)) { - var fileData = getFileDataFromChunk( - chunk, boundary, fileUploadName, result.body); - if (fileData != null) - fileData.forEach((x) => result.files.add(x)); - } - } - } - - return result; -} - -/// Parses file data from a multipart/form-data chunk. -List getFileDataFromChunk(String chunk, String boundary, String fileUploadName, - Map body) { - FileUploadInfo result = new FileUploadInfo(); - RegExp isFormDataRgx = new RegExp( - r'Content-Disposition:\s*([^;]+);\s*name="([^"]+)"'); - - if (isFormDataRgx.hasMatch(chunk)) { - Match formDataMatch = isFormDataRgx.firstMatch(chunk); - String disposition = formDataMatch.group(1); - String name = formDataMatch.group(2); - String restOfChunk = chunk.substring(formDataMatch.end); - - RegExp parseFilenameRgx = new RegExp(r'filename="([^"]+)"'); - if (parseFilenameRgx.hasMatch(chunk)) { - result.filename = parseFilenameRgx.firstMatch(chunk).group(1); - } - - RegExp contentTypeRgx = new RegExp(r'Content-Type:\s*([^\r\n]+)\r\n'); - if (contentTypeRgx.hasMatch(restOfChunk)) { - Match contentTypeMatch = contentTypeRgx.firstMatch(restOfChunk); - restOfChunk = restOfChunk.substring(contentTypeMatch.end); - result.mimeType = contentTypeMatch.group(1); - } else restOfChunk = restOfChunk.replaceAll(new RegExp(r'^(\r\n)+'), ""); - - restOfChunk = restOfChunk - .replaceAll(boundary, "") - .replaceFirst(new RegExp(r'\r\n$'), ""); - - if (disposition == 'file' && name == fileUploadName) { - result.name = name; - result.data = UTF8.encode(restOfChunk); - return [result]; - } else { - buildMapFromUri(body, "$name=$restOfChunk"); - return null; - } - } - - return null; -} - -/// Parses a URI-encoded string into real data! **Wow!** -/// -/// Whichever map you provide will be automatically populated from the urlencoded body string you provide. -buildMapFromUri(Map map, String body) { - RegExp parseArrayRgx = new RegExp(r'^(.+)\[\]$'); - - for (String keyValuePair in body.split('&')) { - if (keyValuePair.contains('=')) { - List split = keyValuePair.split('='); - String key = Uri.decodeQueryComponent(split[0]); - String value = Uri.decodeQueryComponent(split[1]); - - if (parseArrayRgx.hasMatch(key)) { - Match queryMatch = parseArrayRgx.firstMatch(key); - key = queryMatch.group(1); - if (!(map[key] is List)) { - map[key] = []; - } - - map[key].add(getValue(value)); - } else if (key.contains('.')) { - // i.e. map.foo.bar => [map, foo, bar] - List keys = key.split('.'); - - Map targetMap = map[keys[0]] ?? {}; - map[keys[0]] = targetMap; - for (int i = 1; i < keys.length; i++) { - if (i < keys.length - 1) { - targetMap[keys[i]] = targetMap[keys[i]] ?? {}; - targetMap = targetMap[keys[i]]; - } else { - targetMap[keys[i]] = getValue(value); - } - } - } - else map[key] = getValue(value); - } else map[Uri.decodeQueryComponent(keyValuePair)] = true; - } -} - -getValue(String value) { - num numValue = num.parse(value, (_) => double.NAN); - if (!numValue.isNaN) - return numValue; - else if (value.startsWith('[') && value.endsWith(']')) - return JSON.decode(value); - else if (value.startsWith('{') && value.endsWith('}')) - return JSON.decode(value); - else if (value.trim().toLowerCase() == 'null') - return null; - else return value; -} \ No newline at end of file +export 'src/body_parser.dart'; +export 'src/body_parse_result.dart'; +export 'src/file_upload_info.dart'; +export 'src/parse_body.dart'; diff --git a/lib/src/body_parse_result.dart b/lib/src/body_parse_result.dart new file mode 100644 index 00000000..98131fa9 --- /dev/null +++ b/lib/src/body_parse_result.dart @@ -0,0 +1,13 @@ +import 'file_upload_info.dart'; + +/// A representation of data from an incoming request. +class BodyParseResult { + /// The parsed body. + Map body = {}; + + /// The parsed query string. + Map query = {}; + + /// All files uploaded within this request. + List files = []; +} diff --git a/lib/src/body_parser.dart b/lib/src/body_parser.dart new file mode 100644 index 00000000..ad199e7b --- /dev/null +++ b/lib/src/body_parser.dart @@ -0,0 +1,21 @@ +import 'dart:async'; +import 'dart:io'; +import 'body_parse_result.dart'; + +class BodyParser implements StreamTransformer, BodyParseResult> { + + @override + Stream bind(HttpRequest stream) { + var _stream = new StreamController(); + + stream.toList().then((lists) { + var ints = []; + lists.forEach(ints.addAll); + _stream.close(); + + + }); + + return _stream.stream; + } +} \ No newline at end of file diff --git a/lib/src/chunk.dart b/lib/src/chunk.dart new file mode 100644 index 00000000..2dd36d0b --- /dev/null +++ b/lib/src/chunk.dart @@ -0,0 +1,8 @@ +import 'file_upload_info.dart'; + +List getFileDataFromChunk(String chunk, String boundary, + String fileUploadName, + Map body) { + List result = []; + return result; +} \ No newline at end of file diff --git a/lib/file_upload_info.dart b/lib/src/file_upload_info.dart similarity index 94% rename from lib/file_upload_info.dart rename to lib/src/file_upload_info.dart index d0556f39..85be74dd 100644 --- a/lib/file_upload_info.dart +++ b/lib/src/file_upload_info.dart @@ -1,5 +1,3 @@ -part of body_parser; - /// Represents a file uploaded to the server. class FileUploadInfo { /// The MIME type of the uploaded file. diff --git a/lib/src/get_value.dart b/lib/src/get_value.dart new file mode 100644 index 00000000..73e01507 --- /dev/null +++ b/lib/src/get_value.dart @@ -0,0 +1,14 @@ +import 'dart:convert'; + +getValue(String value) { + num numValue = num.parse(value, (_) => double.NAN); + if (!numValue.isNaN) + return numValue; + else if (value.startsWith('[') && value.endsWith(']')) + return JSON.decode(value); + else if (value.startsWith('{') && value.endsWith('}')) + return JSON.decode(value); + else if (value.trim().toLowerCase() == 'null') + return null; + else return value; +} \ No newline at end of file diff --git a/lib/src/map_from_uri.dart b/lib/src/map_from_uri.dart new file mode 100644 index 00000000..22b5603b --- /dev/null +++ b/lib/src/map_from_uri.dart @@ -0,0 +1,41 @@ +import 'get_value.dart'; + +/// Parses a URI-encoded string into real data! **Wow!** +/// +/// Whichever map you provide will be automatically populated from the urlencoded body string you provide. +buildMapFromUri(Map map, String body) { + RegExp parseArrayRgx = new RegExp(r'^(.+)\[\]$'); + + for (String keyValuePair in body.split('&')) { + if (keyValuePair.contains('=')) { + List split = keyValuePair.split('='); + String key = Uri.decodeQueryComponent(split[0]); + String value = Uri.decodeQueryComponent(split[1]); + + if (parseArrayRgx.hasMatch(key)) { + Match queryMatch = parseArrayRgx.firstMatch(key); + key = queryMatch.group(1); + if (!(map[key] is List)) { + map[key] = []; + } + + map[key].add(getValue(value)); + } else if (key.contains('.')) { + // i.e. map.foo.bar => [map, foo, bar] + List keys = key.split('.'); + + Map targetMap = map[keys[0]] ?? {}; + map[keys[0]] = targetMap; + for (int i = 1; i < keys.length; i++) { + if (i < keys.length - 1) { + targetMap[keys[i]] = targetMap[keys[i]] ?? {}; + targetMap = targetMap[keys[i]]; + } else { + targetMap[keys[i]] = getValue(value); + } + } + } + else map[key] = getValue(value); + } else map[Uri.decodeQueryComponent(keyValuePair)] = true; + } +} \ No newline at end of file diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart new file mode 100644 index 00000000..e9c953ca --- /dev/null +++ b/lib/src/parse_body.dart @@ -0,0 +1,59 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:http_server/http_server.dart'; +import 'package:mime/mime.dart'; +import 'body_parse_result.dart'; +import 'chunk.dart'; +import 'file_upload_info.dart'; +import 'map_from_uri.dart'; + +/// Grabs data from an incoming request. +/// +/// Supports URL-encoded and JSON, as well as multipart/* forms. +/// On a file upload request, only fields with the name **'file'** are processed +/// as files. Anything else is put in the body. You can change the upload file name +/// via the *fileUploadName* parameter. :) +Future parseBody(HttpRequest request) async { + var result = new BodyParseResult(); + + if (request.headers.contentType != null) { + if (request.headers.contentType.primaryType == 'multipart' && + request.headers.contentType.parameters.containsKey('boundary')) { + var parts = request + .transform(new MimeMultipartTransformer( + request.headers.contentType.parameters['boundary'])) + .map((part) => + HttpMultipartFormData.parse(part, defaultEncoding: UTF8)); + + await for (HttpMultipartFormData part in parts) { + if (part.isBinary || part.contentDisposition.parameters.containsKey("filename")) { + BytesBuilder builder = await part.fold(new BytesBuilder(), (BytesBuilder b, d) => b..add(d is! String ? d : d.codeUnits)); + var upload = new FileUploadInfo( + mimeType: part.contentType.mimeType, + name: part.contentDisposition.parameters['name'], + filename: part.contentDisposition.parameters['filename'] ?? 'file', + data: builder.takeBytes()); + result.files.add(upload); + } else if (part.isText) { + var text = await part.join(); + buildMapFromUri(result.body, '${part.contentDisposition.parameters["name"]}=$text'); + } else { + print("Found something else : ${part.contentDisposition}"); + } + } + } else if (request.headers.contentType.mimeType == + ContentType.JSON.mimeType) { + result.body + .addAll(JSON.decode(await request.transform(UTF8.decoder).join())); + } else if (request.headers.contentType.mimeType == + 'application/x-www-form-urlencoded') { + String body = await request.transform(UTF8.decoder).join(); + buildMapFromUri(result.body, body); + } + } else if (request.uri.hasQuery) { + buildMapFromUri(result.query, request.uri.query); + } + + return result; +} diff --git a/pubspec.yaml b/pubspec.yaml index 6a99e196..93b5f323 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,11 @@ name: body_parser author: Tobe O -version: 1.0.0-dev +version: 1.0.0-dev+1 description: Parse request bodies and query strings in Dart. homepage: https://github.com/thosakwe/body_parser +dependencies: + http_server: ">=0.9.6 <1.0.0" dev_dependencies: - http: any - json_god: any - test: any \ No newline at end of file + http: ">=0.11.3 <0.12.0" + json_god: ">=2.0.0-beta <3.0.0" + test: ">=0.12.15 <0.13.0" \ No newline at end of file diff --git a/test/all_tests.dart b/test/all_tests.dart index 7adf25a4..0686d5f0 100644 --- a/test/all_tests.dart +++ b/test/all_tests.dart @@ -1,168 +1,9 @@ -import 'dart:io'; - -import 'package:body_parser/body_parser.dart'; -import 'package:http/http.dart' as http; -import 'package:json_god/json_god.dart'; import 'package:test/test.dart'; +import 'server.dart' as server; +import 'uploads.dart' as uploads; + main() { - group('Test server support', () { - HttpServer server; - String url; - http.Client client; - God god; - - setUp(() async { - server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0); - server.listen((HttpRequest request) async { - //Server will simply return a JSON representation of the parsed body - request.response.write(god.serialize(await parseBody(request))); - await request.response.close(); - }); - url = 'http://localhost:${server.port}'; - print('Test server listening on $url'); - client = new http.Client(); - god = new God(); - }); - tearDown(() async { - await server.close(force: true); - client.close(); - server = null; - url = null; - client = null; - god = null; - }); - - group('query string', () { - test('GET Simple', () async { - print('GET $url/?hello=world'); - var response = await client.get('$url/?hello=world'); - print('Response: ${response.body}'); - expect(response.body, - equals('{"body":{},"query":{"hello":"world"},"files":[]}')); - }); - - test('GET Complex', () async { - var postData = 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - - 1}&map.foo.bar=baz'; - print('Body: $postData'); - var response = await client.get('$url/?$postData'); - print('Response: ${response.body}'); - var query = god.deserialize(response.body)['query']; - expect(query['hello'], equals('world')); - expect(query['nums'][2], equals(2)); - expect(query['map'] is Map, equals(true)); - expect(query['map']['foo'], equals({'bar': 'baz'})); - }); - }); - - group('urlencoded', () { - Map headers = { - HttpHeaders.CONTENT_TYPE: 'application/x-www-form-urlencoded' - }; - test('POST Simple', () async { - print('Body: hello=world'); - var response = await client.post( - url, headers: headers, body: 'hello=world'); - print('Response: ${response.body}'); - expect(response.body, - equals('{"body":{"hello":"world"},"query":{},"files":[]}')); - }); - - test('Post Complex', () async { - var postData = 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - - 1}&map.foo.bar=baz'; - var response = await client.post(url, headers: headers, body: postData); - var body = god.deserialize(response.body)['body']; - expect(body['hello'], equals('world')); - expect(body['nums'][2], equals(2)); - expect(body['map'] is Map, equals(true)); - expect(body['map']['foo'], equals({'bar': 'baz'})); - }); - }); - - group('JSON', () { - Map headers = { - HttpHeaders.CONTENT_TYPE: ContentType.JSON.toString() - }; - test('Post Simple', () async { - var postData = god.serialize({ - 'hello': 'world' - }); - print('Body: $postData'); - var response = await client.post( - url, headers: headers, body: postData); - print('Response: ${response.body}'); - expect(response.body, - equals('{"body":{"hello":"world"},"query":{},"files":[]}')); - }); - - test('Post Complex', () async { - var postData = god.serialize({ - 'hello': 'world', - 'nums': [1, 2.0, 3 - 1], - 'map': { - 'foo': { - 'bar': 'baz' - } - } - }); - print('Body: $postData'); - var response = await client.post(url, headers: headers, body: postData); - print('Response: ${response.body}'); - var body = god.deserialize(response.body)['body']; - expect(body['hello'], equals('world')); - expect(body['nums'][2], equals(2)); - expect(body['map'] is Map, equals(true)); - expect(body['map']['foo'], equals({'bar': 'baz'})); - }); - }); - - group('File', () { - test('Single upload', () async { - String boundary = '----myBoundary'; - Map headers = { - HttpHeaders.CONTENT_TYPE: 'multipart/form-data; boundary=$boundary' - }; - String postData = '\r\n$boundary\r\n' + - 'Content-Disposition: form-data; name="hello"\r\nworld\r\n$boundary\r\n' + - 'Content-Disposition: file; name="file"; filename="app.dart"\r\n' + - 'Content-Type: text/plain\r\nHello world\r\n$boundary--'; - - print('Form Data: \n$postData'); - var response = await client.post(url, headers: headers, body: postData); - print('Response: ${response.body}'); - Map json = god.deserialize(response.body); - List files = json['files']; - expect(files.length, equals(1)); - expect(files[0]['name'], equals('file')); - expect(files[0]['mimeType'], equals('text/plain')); - expect(files[0]['data'].length, equals(11)); - expect(files[0]['filename'], equals('app.dart')); - expect(json['body']['hello'], equals('world')); - }); - - test('Multiple upload', () async { - String boundary = '----myBoundary'; - Map headers = { - HttpHeaders.CONTENT_TYPE: 'multipart/form-data; boundary=$boundary' - }; - String postData = '\r\n$boundary\r\n' + - 'Content-Disposition: form-data; name="json"\r\ngod\r\n$boundary\r\n' + - 'Content-Disposition: file; name="file"; filename="app.dart"\r\n' + - 'Content-Type: text/plain\r\nHello world\r\n$boundary--'; - - print('Form Data: \n$postData'); - var response = await client.post(url, headers: headers, body: postData); - print('Response: ${response.body}'); - Map json = god.deserialize(response.body); - List files = json['files']; - expect(files.length, equals(1)); - expect(files[0]['name'], equals('file')); - expect(files[0]['mimeType'], equals('text/plain')); - expect(files[0]['data'].length, equals(11)); - expect(json['body']['json'], equals('god')); - }, skip: 'Multiple file uploads are yet to come.'); - }); - }); -} \ No newline at end of file + group('server', server.main); + group('uploads', uploads.main); +} diff --git a/test/server.dart b/test/server.dart new file mode 100644 index 00000000..6b205684 --- /dev/null +++ b/test/server.dart @@ -0,0 +1,110 @@ +import 'dart:io'; +import 'package:body_parser/body_parser.dart'; +import 'package:http/http.dart' as http; +import 'package:json_god/json_god.dart' as god; +import 'package:test/test.dart'; + +main() { + HttpServer server; + String url; + http.Client client; + + setUp(() async { + server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0); + server.listen((HttpRequest request) async { + //Server will simply return a JSON representation of the parsed body + request.response.write(god.serialize(await parseBody(request))); + await request.response.close(); + }); + url = 'http://localhost:${server.port}'; + print('Test server listening on $url'); + client = new http.Client(); + }); + tearDown(() async { + await server.close(force: true); + client.close(); + server = null; + url = null; + client = null; + }); + + group('query string', () { + test('GET Simple', () async { + print('GET $url/?hello=world'); + var response = await client.get('$url/?hello=world'); + print('Response: ${response.body}'); + expect(response.body, + equals('{"body":{},"query":{"hello":"world"},"files":[]}')); + }); + + test('GET Complex', () async { + var postData = 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - + 1}&map.foo.bar=baz'; + print('Body: $postData'); + var response = await client.get('$url/?$postData'); + print('Response: ${response.body}'); + var query = god.deserialize(response.body)['query']; + expect(query['hello'], equals('world')); + expect(query['nums'][2], equals(2)); + expect(query['map'] is Map, equals(true)); + expect(query['map']['foo'], equals({'bar': 'baz'})); + }); + }); + + group('urlencoded', () { + Map headers = { + HttpHeaders.CONTENT_TYPE: 'application/x-www-form-urlencoded' + }; + test('POST Simple', () async { + print('Body: hello=world'); + var response = + await client.post(url, headers: headers, body: 'hello=world'); + print('Response: ${response.body}'); + expect(response.body, + equals('{"body":{"hello":"world"},"query":{},"files":[]}')); + }); + + test('Post Complex', () async { + var postData = 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - + 1}&map.foo.bar=baz'; + var response = await client.post(url, headers: headers, body: postData); + var body = god.deserialize(response.body)['body']; + expect(body['hello'], equals('world')); + expect(body['nums'][2], equals(2)); + expect(body['map'] is Map, equals(true)); + expect(body['map']['foo'], equals({'bar': 'baz'})); + }); + }); + + group('JSON', () { + Map headers = { + HttpHeaders.CONTENT_TYPE: ContentType.JSON.toString() + }; + test('Post Simple', () async { + var postData = god.serialize({'hello': 'world'}); + print('Body: $postData'); + var response = await client.post(url, headers: headers, body: postData); + print('Response: ${response.body}'); + expect(response.body, + equals('{"body":{"hello":"world"},"query":{},"files":[]}')); + }); + + test('Post Complex', () async { + var postData = god.serialize({ + 'hello': 'world', + 'nums': [1, 2.0, 3 - 1], + 'map': { + 'foo': {'bar': 'baz'} + } + }); + print('Body: $postData'); + var response = await client.post(url, headers: headers, body: postData); + print('Response: ${response.body}'); + var body = god.deserialize(response.body)['body']; + expect(body['hello'], equals('world')); + expect(body['nums'][2], equals(2)); + expect(body['map'] is Map, equals(true)); + expect(body['map']['foo'], equals({'bar': 'baz'})); + }); + }); +} diff --git a/test/uploads.dart b/test/uploads.dart new file mode 100644 index 00000000..496279cc --- /dev/null +++ b/test/uploads.dart @@ -0,0 +1,135 @@ +import 'dart:io'; +import 'package:body_parser/body_parser.dart'; +import 'package:http/http.dart' as http; +import 'package:json_god/json_god.dart' as god; +import 'package:test/test.dart'; + +main() { + HttpServer server; + String url; + http.Client client; + + setUp(() async { + server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0); + server.listen((HttpRequest request) async { + //Server will simply return a JSON representation of the parsed body + request.response.write(god.serialize(await parseBody(request))); + await request.response.close(); + }); + url = 'http://localhost:${server.port}'; + print('Test server listening on $url'); + client = new http.Client(); + }); + + tearDown(() async { + await server.close(force: true); + client.close(); + server = null; + url = null; + client = null; + }); + + test('No upload', () async { + String boundary = 'myBoundary'; + Map headers = { + HttpHeaders.CONTENT_TYPE: 'multipart/form-data; boundary=$boundary' + }; + String postData = ''' +--$boundary +Content-Disposition: form-data; name="hello" + +world +--$boundary-- +''' + .replaceAll("\n", "\r\n"); + + print( + 'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}'); + var response = await client.post(url, headers: headers, body: postData); + print('Response: ${response.body}'); + Map json = god.deserialize(response.body); + List files = json['files']; + expect(files.length, equals(0)); + expect(json['body']['hello'], equals('world')); + }); + + test('Single upload', () async { + String boundary = 'myBoundary'; + Map headers = { + HttpHeaders.CONTENT_TYPE: new ContentType("multipart", "form-data", + parameters: {"boundary": boundary}).toString() + }; + String postData = ''' +--$boundary +Content-Disposition: form-data; name="hello" + +world +--$boundary +Content-Disposition: form-data; name="file"; filename="app.dart" +Content-Type: application/dart + +Hello world +--$boundary-- +''' + .replaceAll("\n", "\r\n"); + + print( + 'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}'); + var response = await client.post(url, headers: headers, body: postData); + print('Response: ${response.body}'); + Map json = god.deserialize(response.body); + List files = json['files']; + expect(files.length, equals(1)); + expect(files[0]['name'], equals('file')); + expect(files[0]['mimeType'], equals('application/dart')); + expect(files[0]['data'].length, equals(11)); + expect(files[0]['filename'], equals('app.dart')); + expect(json['body']['hello'], equals('world')); + }); + + test('Multiple upload', () async { + String boundary = 'myBoundary'; + Map headers = { + HttpHeaders.CONTENT_TYPE: 'multipart/form-data; boundary=$boundary' + }; + String postData = ''' +--$boundary +Content-Disposition: form-data; name="json" + +god +--$boundary +Content-Disposition: form-data; name="num" + +14.50000 +--$boundary +Content-Disposition: form-data; name="file"; filename="app.dart" +Content-Type: text/plain + +Hello world +--$boundary +Content-Disposition: form-data; name="entry-point"; filename="main.js" +Content-Type: text/javascript + +function main() { + console.log("Hello, world!"); +} +--$boundary-- +''' + .replaceAll("\n", "\r\n"); + + print( + 'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}'); + var response = await client.post(url, headers: headers, body: postData); + print('Response: ${response.body}'); + Map json = god.deserialize(response.body); + List files = json['files']; + expect(files.length, equals(2)); + expect(files[0]['name'], equals('file')); + expect(files[0]['mimeType'], equals('text/plain')); + expect(files[0]['data'].length, equals(11)); + expect(files[1]['name'], equals('entry-point')); + expect(files[1]['mimeType'], equals('text/javascript')); + expect(json['body']['json'], equals('god')); + expect(json['body']['num'], equals(14.5)); + }); +} From 10e2516301e0ce2595bfd7cb9815464b16db2f55 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 24 Sep 2016 14:33:16 -0400 Subject: [PATCH 11/24] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 7975e410..5391e72b 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ to build structured JSON/REST APIs. Add validation and you've got an instant bac ```dart MyClass create(HttpRequest request) async { - God god = new God(); return god.deserialize(await parseBody(request).body, MyClass); } ``` @@ -68,4 +67,4 @@ Thank you for using this library. I hope you like it. Feel free to follow me on Twitter: -[@_wapaa_](http://twitter.com/_wapaa_) \ No newline at end of file +[@_wapaa_](http://twitter.com/_wapaa_) From 374b80c44dd5a787a172c3c5fe09ddaa6b07098b Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 4 Dec 2016 21:31:25 -0500 Subject: [PATCH 12/24] Resolved all issues --- .travis.yml | 1 + README.md | 3 +- lib/src/parse_body.dart | 70 ++++++++++++++------------ pubspec.yaml | 2 +- test/{all_tests.dart => all_test.dart} | 0 5 files changed, 42 insertions(+), 34 deletions(-) create mode 100644 .travis.yml rename test/{all_tests.dart => all_test.dart} (100%) diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..de2210c9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: dart \ No newline at end of file diff --git a/README.md b/README.md index 5391e72b..68fd6f97 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Body Parser -![version 1.0.0-dev+1](https://img.shields.io/badge/version-1.0.0--dev-red.svg) +![version 1.0.0-dev+2](https://img.shields.io/badge/version-1.0.0--dev+2-red.svg) +![build status](https://travis-ci.org/thosakwe/body_parser.svg) **NOT YET PRODUCTION READY** diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart index e9c953ca..0b611efb 100644 --- a/lib/src/parse_body.dart +++ b/lib/src/parse_body.dart @@ -17,42 +17,48 @@ import 'map_from_uri.dart'; Future parseBody(HttpRequest request) async { var result = new BodyParseResult(); - if (request.headers.contentType != null) { - if (request.headers.contentType.primaryType == 'multipart' && - request.headers.contentType.parameters.containsKey('boundary')) { - var parts = request - .transform(new MimeMultipartTransformer( - request.headers.contentType.parameters['boundary'])) - .map((part) => - HttpMultipartFormData.parse(part, defaultEncoding: UTF8)); + try { + if (request.headers.contentType != null) { + if (request.headers.contentType.primaryType == 'multipart' && + request.headers.contentType.parameters.containsKey('boundary')) { + var parts = request + .transform(new MimeMultipartTransformer( + request.headers.contentType.parameters['boundary'])) + .map((part) => + HttpMultipartFormData.parse(part, defaultEncoding: UTF8)); - await for (HttpMultipartFormData part in parts) { - if (part.isBinary || part.contentDisposition.parameters.containsKey("filename")) { - BytesBuilder builder = await part.fold(new BytesBuilder(), (BytesBuilder b, d) => b..add(d is! String ? d : d.codeUnits)); - var upload = new FileUploadInfo( - mimeType: part.contentType.mimeType, - name: part.contentDisposition.parameters['name'], - filename: part.contentDisposition.parameters['filename'] ?? 'file', - data: builder.takeBytes()); - result.files.add(upload); - } else if (part.isText) { - var text = await part.join(); - buildMapFromUri(result.body, '${part.contentDisposition.parameters["name"]}=$text'); - } else { - print("Found something else : ${part.contentDisposition}"); + await for (HttpMultipartFormData part in parts) { + if (part.isBinary || + part.contentDisposition.parameters.containsKey("filename")) { + BytesBuilder builder = await part.fold(new BytesBuilder(), + (BytesBuilder b, d) => b..add(d is! String ? d : d.codeUnits)); + var upload = new FileUploadInfo( + mimeType: part.contentType.mimeType, + name: part.contentDisposition.parameters['name'], + filename: + part.contentDisposition.parameters['filename'] ?? 'file', + data: builder.takeBytes()); + result.files.add(upload); + } else if (part.isText) { + var text = await part.join(); + buildMapFromUri(result.body, + '${part.contentDisposition.parameters["name"]}=$text'); + } } + } else if (request.headers.contentType.mimeType == + ContentType.JSON.mimeType) { + result.body + .addAll(JSON.decode(await request.transform(UTF8.decoder).join())); + } else if (request.headers.contentType.mimeType == + 'application/x-www-form-urlencoded') { + String body = await request.transform(UTF8.decoder).join(); + buildMapFromUri(result.body, body); } - } else if (request.headers.contentType.mimeType == - ContentType.JSON.mimeType) { - result.body - .addAll(JSON.decode(await request.transform(UTF8.decoder).join())); - } else if (request.headers.contentType.mimeType == - 'application/x-www-form-urlencoded') { - String body = await request.transform(UTF8.decoder).join(); - buildMapFromUri(result.body, body); + } else if (request.uri.hasQuery) { + buildMapFromUri(result.query, request.uri.query); } - } else if (request.uri.hasQuery) { - buildMapFromUri(result.query, request.uri.query); + } catch (e) { + // } return result; diff --git a/pubspec.yaml b/pubspec.yaml index 93b5f323..ee5c25f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: body_parser author: Tobe O -version: 1.0.0-dev+1 +version: 1.0.0-dev+2 description: Parse request bodies and query strings in Dart. homepage: https://github.com/thosakwe/body_parser dependencies: diff --git a/test/all_tests.dart b/test/all_test.dart similarity index 100% rename from test/all_tests.dart rename to test/all_test.dart From 0d144606881878aa83c5a504ecb78f97e9f94432 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 22 Dec 2016 13:08:45 -0500 Subject: [PATCH 13/24] Equals in query now ;) --- LICENSE | 695 ++------------------------------------ README.md | 2 +- lib/src/map_from_uri.dart | 8 +- pubspec.yaml | 2 +- test/server.dart | 19 ++ 5 files changed, 47 insertions(+), 679 deletions(-) diff --git a/LICENSE b/LICENSE index 9cecc1d4..3832f450 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,21 @@ - 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. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -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: - - {project} Copyright (C) {year} {fullname} - 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 -. +The MIT License (MIT) + +Copyright (c) 2016 Tobe O + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 68fd6f97..3abe30b3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Body Parser -![version 1.0.0-dev+2](https://img.shields.io/badge/version-1.0.0--dev+2-red.svg) +![version 1.0.0-dev+3](https://img.shields.io/badge/version-1.0.0--dev+3-red.svg) ![build status](https://travis-ci.org/thosakwe/body_parser.svg) **NOT YET PRODUCTION READY** diff --git a/lib/src/map_from_uri.dart b/lib/src/map_from_uri.dart index 22b5603b..eab8bf69 100644 --- a/lib/src/map_from_uri.dart +++ b/lib/src/map_from_uri.dart @@ -8,9 +8,11 @@ buildMapFromUri(Map map, String body) { for (String keyValuePair in body.split('&')) { if (keyValuePair.contains('=')) { - List split = keyValuePair.split('='); - String key = Uri.decodeQueryComponent(split[0]); - String value = Uri.decodeQueryComponent(split[1]); + var equals = keyValuePair.indexOf('='); + String key = Uri.decodeQueryComponent(keyValuePair.substring(0, equals)); + String value = keyValuePair.substring(equals + 1); //Uri.decodeQueryComponent(split[1]); + print('Key: $key'); + print('Value: $value'); if (parseArrayRgx.hasMatch(key)) { Match queryMatch = parseArrayRgx.firstMatch(key); diff --git a/pubspec.yaml b/pubspec.yaml index ee5c25f2..cd0d05a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: body_parser author: Tobe O -version: 1.0.0-dev+2 +version: 1.0.0-dev+3 description: Parse request bodies and query strings in Dart. homepage: https://github.com/thosakwe/body_parser dependencies: diff --git a/test/server.dart b/test/server.dart index 6b205684..fadf6706 100644 --- a/test/server.dart +++ b/test/server.dart @@ -4,6 +4,9 @@ import 'package:http/http.dart' as http; import 'package:json_god/json_god.dart' as god; import 'package:test/test.dart'; +const TOKEN = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjcuMC4wLjEiLCJleHAiOi0xLCJpYXQiOiIyMDE2LTEyLTIyVDEyOjQ5OjUwLjM2MTQ0NiIsImlzcyI6ImFuZ2VsX2F1dGgiLCJzdWIiOiIxMDY2OTQ4Mzk2MDIwMjg5ODM2NTYifQ==.PYw7yUb-cFWD7N0sSLztP7eeRvO44nu1J2OgDNyT060='; + main() { HttpServer server; String url; @@ -49,6 +52,15 @@ main() { expect(query['map'] is Map, equals(true)); expect(query['map']['foo'], equals({'bar': 'baz'})); }); + + test('JWT', () async { + var postData = 'token=$TOKEN'; + print('Body: $postData'); + var response = await client.get('$url/?$postData'); + print('Response: ${response.body}'); + var query = god.deserialize(response.body)['query']; + expect(query['token'], equals(TOKEN)); + }); }); group('urlencoded', () { @@ -74,6 +86,13 @@ main() { expect(body['map'] is Map, equals(true)); expect(body['map']['foo'], equals({'bar': 'baz'})); }); + + test('JWT', () async { + var postData = 'token=$TOKEN'; + var response = await client.post(url, headers: headers, body: postData); + var body = god.deserialize(response.body)['body']; + expect(body['token'], equals(TOKEN)); + }); }); group('JSON', () { From 331e7aa14c44aca5740c887df543970b3eb8a56a Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 22 Dec 2016 13:20:49 -0500 Subject: [PATCH 14/24] Bye print --- lib/src/map_from_uri.dart | 2 -- pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/map_from_uri.dart b/lib/src/map_from_uri.dart index eab8bf69..81d110ca 100644 --- a/lib/src/map_from_uri.dart +++ b/lib/src/map_from_uri.dart @@ -11,8 +11,6 @@ buildMapFromUri(Map map, String body) { var equals = keyValuePair.indexOf('='); String key = Uri.decodeQueryComponent(keyValuePair.substring(0, equals)); String value = keyValuePair.substring(equals + 1); //Uri.decodeQueryComponent(split[1]); - print('Key: $key'); - print('Value: $value'); if (parseArrayRgx.hasMatch(key)) { Match queryMatch = parseArrayRgx.firstMatch(key); diff --git a/pubspec.yaml b/pubspec.yaml index cd0d05a6..3a57a38d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: body_parser author: Tobe O -version: 1.0.0-dev+3 +version: 1.0.0-dev+4 description: Parse request bodies and query strings in Dart. homepage: https://github.com/thosakwe/body_parser dependencies: From 1acfeeee24995d94e6dba9f3f66226de36ab8043 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 14 Jan 2017 08:50:02 -0500 Subject: [PATCH 15/24] +5 --- README.md | 4 +-- lib/body_parser.dart | 3 -- lib/src/body_parse_result.dart | 13 ++++++--- lib/src/parse_body.dart | 53 +++++++++++++++++++++++++++++----- pubspec.yaml | 2 +- test/server.dart | 26 ++++++++++++----- 6 files changed, 77 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 3abe30b3..38fdea9c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Body Parser -![version 1.0.0-dev+3](https://img.shields.io/badge/version-1.0.0--dev+3-red.svg) +![version 1.0.0-dev+5](https://img.shields.io/badge/version-1.0.0--dev+5-red.svg) ![build status](https://travis-ci.org/thosakwe/body_parser.svg) **NOT YET PRODUCTION READY** @@ -68,4 +68,4 @@ Thank you for using this library. I hope you like it. Feel free to follow me on Twitter: -[@_wapaa_](http://twitter.com/_wapaa_) +[@thosakwe](http://twitter.com/thosakwe) diff --git a/lib/body_parser.dart b/lib/body_parser.dart index 9bb69e35..778bc540 100644 --- a/lib/body_parser.dart +++ b/lib/body_parser.dart @@ -1,9 +1,6 @@ /// A library for parsing HTTP request bodies and queries. library body_parser; -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; export 'src/body_parser.dart'; export 'src/body_parse_result.dart'; export 'src/file_upload_info.dart'; diff --git a/lib/src/body_parse_result.dart b/lib/src/body_parse_result.dart index 98131fa9..ca75beaf 100644 --- a/lib/src/body_parse_result.dart +++ b/lib/src/body_parse_result.dart @@ -1,13 +1,18 @@ import 'file_upload_info.dart'; /// A representation of data from an incoming request. -class BodyParseResult { +abstract class BodyParseResult { /// The parsed body. - Map body = {}; + Map get body; /// The parsed query string. - Map query = {}; + Map get query; /// All files uploaded within this request. - List files = []; + List get files; + + /// The original body bytes sent with this request. + /// + /// You must set [storeOriginalBuffer] to `true` to see this. + List get originalBuffer; } diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart index 0b611efb..bec9fb84 100644 --- a/lib/src/parse_body.dart +++ b/lib/src/parse_body.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:http_server/http_server.dart'; import 'package:mime/mime.dart'; import 'body_parse_result.dart'; -import 'chunk.dart'; import 'file_upload_info.dart'; import 'map_from_uri.dart'; @@ -14,14 +13,41 @@ import 'map_from_uri.dart'; /// On a file upload request, only fields with the name **'file'** are processed /// as files. Anything else is put in the body. You can change the upload file name /// via the *fileUploadName* parameter. :) -Future parseBody(HttpRequest request) async { - var result = new BodyParseResult(); +/// +/// Use [storeOriginalBuffer] to add the original request bytes to the result. +Future parseBody(HttpRequest request, + {bool storeOriginalBuffer: false}) async { + var result = new _BodyParseResultImpl(); + + Future> getBytes() async { + return await request.fold([], (a, b) => a..addAll(b)); + } + + Future getBody() async { + if (storeOriginalBuffer) { + List bytes = await getBytes(); + return UTF8.decode(result.originalBuffer = bytes); + } else + return await request.transform(UTF8.decoder).join(); + } try { if (request.headers.contentType != null) { if (request.headers.contentType.primaryType == 'multipart' && request.headers.contentType.parameters.containsKey('boundary')) { - var parts = request + Stream> stream; + + if (storeOriginalBuffer) { + var bytes = result.originalBuffer = await getBytes(); + var ctrl = new StreamController>() + ..add(bytes) + ..close(); + stream = ctrl.stream; + } else { + stream = request; + } + + var parts = stream .transform(new MimeMultipartTransformer( request.headers.contentType.parameters['boundary'])) .map((part) => @@ -47,11 +73,10 @@ Future parseBody(HttpRequest request) async { } } else if (request.headers.contentType.mimeType == ContentType.JSON.mimeType) { - result.body - .addAll(JSON.decode(await request.transform(UTF8.decoder).join())); + result.body.addAll(JSON.decode(await getBody())); } else if (request.headers.contentType.mimeType == 'application/x-www-form-urlencoded') { - String body = await request.transform(UTF8.decoder).join(); + String body = await getBody(); buildMapFromUri(result.body, body); } } else if (request.uri.hasQuery) { @@ -63,3 +88,17 @@ Future parseBody(HttpRequest request) async { return result; } + +class _BodyParseResultImpl implements BodyParseResult { + @override + Map body = {}; + + @override + List files = []; + + @override + List originalBuffer; + + @override + Map query = {}; +} diff --git a/pubspec.yaml b/pubspec.yaml index 3a57a38d..eff46b08 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: body_parser author: Tobe O -version: 1.0.0-dev+4 +version: 1.0.0-dev+5 description: Parse request bodies and query strings in Dart. homepage: https://github.com/thosakwe/body_parser dependencies: diff --git a/test/server.dart b/test/server.dart index fadf6706..922b4a5c 100644 --- a/test/server.dart +++ b/test/server.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:body_parser/body_parser.dart'; import 'package:http/http.dart' as http; @@ -16,7 +17,8 @@ main() { server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0); server.listen((HttpRequest request) async { //Server will simply return a JSON representation of the parsed body - request.response.write(god.serialize(await parseBody(request))); + request.response.write( + god.serialize(await parseBody(request, storeOriginalBuffer: true))); await request.response.close(); }); url = 'http://localhost:${server.port}'; @@ -36,8 +38,11 @@ main() { print('GET $url/?hello=world'); var response = await client.get('$url/?hello=world'); print('Response: ${response.body}'); - expect(response.body, - equals('{"body":{},"query":{"hello":"world"},"files":[]}')); + var result = JSON.decode(response.body); + expect(result['body'], equals({})); + expect(result['query'], equals({'hello': 'world'})); + expect(result['files'], equals([])); + expect(result['originalBuffer'], isNull); }); test('GET Complex', () async { @@ -72,8 +77,12 @@ main() { var response = await client.post(url, headers: headers, body: 'hello=world'); print('Response: ${response.body}'); - expect(response.body, - equals('{"body":{"hello":"world"},"query":{},"files":[]}')); + var result = JSON.decode(response.body); + expect(result['query'], equals({})); + expect(result['body'], equals({'hello': 'world'})); + expect(result['files'], equals([])); + expect(result['originalBuffer'], isList); + expect(result['originalBuffer'], isNotEmpty); }); test('Post Complex', () async { @@ -104,8 +113,11 @@ main() { print('Body: $postData'); var response = await client.post(url, headers: headers, body: postData); print('Response: ${response.body}'); - expect(response.body, - equals('{"body":{"hello":"world"},"query":{},"files":[]}')); + var result = JSON.decode(response.body); + expect(result['body'], equals({'hello': 'world'})); + expect(result['query'], equals({})); + expect(result['files'], equals([])); + expect(result['originalBuffer'], allOf(isList, isNotEmpty)); }); test('Post Complex', () async { From b70b881e855bddf8f7604882ec38884f6ff70fbe Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 7 Mar 2017 15:42:01 -0500 Subject: [PATCH 16/24] 1.0.0 --- README.md | 4 +--- lib/src/parse_body.dart | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 38fdea9c..e27c829e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ # Body Parser -![version 1.0.0-dev+5](https://img.shields.io/badge/version-1.0.0--dev+5-red.svg) +![version 1.0.0](https://img.shields.io/badge/version-1.0.0-brightgreen.svg) ![build status](https://travis-ci.org/thosakwe/body_parser.svg) -**NOT YET PRODUCTION READY** - Parse request bodies and query strings in Dart, as well multipart/form-data uploads. No external dependencies required. diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart index bec9fb84..91a2f417 100644 --- a/lib/src/parse_body.dart +++ b/lib/src/parse_body.dart @@ -26,9 +26,9 @@ Future parseBody(HttpRequest request, Future getBody() async { if (storeOriginalBuffer) { List bytes = await getBytes(); - return UTF8.decode(result.originalBuffer = bytes); + return Uri.decodeFull(UTF8.decode(result.originalBuffer = bytes)); } else - return await request.transform(UTF8.decoder).join(); + return await request.transform(UTF8.decoder).join().then(Uri.decodeFull); } try { diff --git a/pubspec.yaml b/pubspec.yaml index eff46b08..2eeabbdd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: body_parser author: Tobe O -version: 1.0.0-dev+5 +version: 1.0.0 description: Parse request bodies and query strings in Dart. homepage: https://github.com/thosakwe/body_parser dependencies: From 2f7663abd2c84fa0ecfe446b7a7c1748f172f5ae Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 7 Mar 2017 16:29:18 -0500 Subject: [PATCH 17/24] 1.0.1 --- README.md | 2 +- lib/src/map_from_uri.dart | 12 +++++++----- pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e27c829e..a24ce468 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Body Parser -![version 1.0.0](https://img.shields.io/badge/version-1.0.0-brightgreen.svg) +![version 1.0.01(https://img.shields.io/badge/version-1.0.1-brightgreen.svg) ![build status](https://travis-ci.org/thosakwe/body_parser.svg) Parse request bodies and query strings in Dart, as well multipart/form-data uploads. No external diff --git a/lib/src/map_from_uri.dart b/lib/src/map_from_uri.dart index 81d110ca..15ca9f4b 100644 --- a/lib/src/map_from_uri.dart +++ b/lib/src/map_from_uri.dart @@ -10,7 +10,8 @@ buildMapFromUri(Map map, String body) { if (keyValuePair.contains('=')) { var equals = keyValuePair.indexOf('='); String key = Uri.decodeQueryComponent(keyValuePair.substring(0, equals)); - String value = keyValuePair.substring(equals + 1); //Uri.decodeQueryComponent(split[1]); + String value = + Uri.decodeQueryComponent(keyValuePair.substring(equals + 1)); if (parseArrayRgx.hasMatch(key)) { Match queryMatch = parseArrayRgx.firstMatch(key); @@ -34,8 +35,9 @@ buildMapFromUri(Map map, String body) { targetMap[keys[i]] = getValue(value); } } - } - else map[key] = getValue(value); - } else map[Uri.decodeQueryComponent(keyValuePair)] = true; + } else + map[key] = getValue(value); + } else + map[Uri.decodeQueryComponent(keyValuePair)] = true; } -} \ No newline at end of file +} diff --git a/pubspec.yaml b/pubspec.yaml index 2eeabbdd..fde9ce06 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: body_parser author: Tobe O -version: 1.0.0 +version: 1.0.1 description: Parse request bodies and query strings in Dart. homepage: https://github.com/thosakwe/body_parser dependencies: From 9d0b7756949d722c7f8dd0403e9e7b751843ce7f Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 19 Jul 2017 17:20:57 -0400 Subject: [PATCH 18/24] 1.0.2 --- .idea/libraries/Dart_Packages.xml | 212 +++++++++++++++++++----------- README.md | 31 +++-- lib/src/body_parse_result.dart | 10 ++ lib/src/parse_body.dart | 23 +++- pubspec.yaml | 4 +- test/server.dart | 2 +- 6 files changed, 188 insertions(+), 94 deletions(-) diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 6f7ec188..d2bdf597 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -5,105 +5,119 @@ - - - - - - + + + + + + - - - - + + + + + + - - - - - @@ -114,31 +128,52 @@ - + - - + - + + + + + + + + + + + + - - + + + + + + @@ -149,10 +184,17 @@ + + + + + + - @@ -166,35 +208,35 @@ - - - - - @@ -208,70 +250,77 @@ - - - - - - - - + + + + + + - - @@ -285,70 +334,77 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - + + + + + + + - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + + - - - + + + diff --git a/README.md b/README.md index a24ce468..7c1ffb18 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ -# Body Parser -![version 1.0.01(https://img.shields.io/badge/version-1.0.1-brightgreen.svg) +# body_parser +[![Pub](https://img.shields.io/pub/v/body_parser.svg)](https://pub.dartlang.org/packages/body_parser) ![build status](https://travis-ci.org/thosakwe/body_parser.svg) Parse request bodies and query strings in Dart, as well multipart/form-data uploads. No external dependencies required. +This is the request body parser powering the +[Angel](https://angel-dart.github.io) +framework. If you are looking for a server-side solution with dependency injection, +WebSockets, and more, then I highly recommend it as your first choice. Bam! + ### Contents * [Body Parser](#body-parser) @@ -32,7 +37,7 @@ pub dependencies. # Usage -Body Parser exposes a simple class called [BodyParseResult]. +Body Parser exposes a simple class called `BodyParseResult`. You can easily parse the query string and request body for a request by calling `Future parseBody`. ```dart @@ -59,11 +64,19 @@ MyClass create(HttpRequest request) async { } ``` +## Custom Body Parsing +In cases where you need to parse unrecognized content types, `body_parser` won't be of any help to you +on its own. However, you can use the `originalBuffer` property of a `BodyParseResult` to see the original +request buffer. To get this functionality, pass `storeOriginalBuffer` as `true` when calling `parseBody`. -# Thank you for using Body Parser +For example, if you wanted to +[parse GraphQL queries within your server](https://github.com/angel-dart/graphql)... -Thank you for using this library. I hope you like it. - -Feel free to follow me on Twitter: - -[@thosakwe](http://twitter.com/thosakwe) +```dart +app.get('/graphql', (req, res) async { + if (req.headers.contentType.mimeType == 'application/graphql') { + var graphQlString = new String.fromCharCodes(req.originalBuffer); + // ... + } +}); +``` \ No newline at end of file diff --git a/lib/src/body_parse_result.dart b/lib/src/body_parse_result.dart index ca75beaf..4fa826e1 100644 --- a/lib/src/body_parse_result.dart +++ b/lib/src/body_parse_result.dart @@ -15,4 +15,14 @@ abstract class BodyParseResult { /// /// You must set [storeOriginalBuffer] to `true` to see this. List get originalBuffer; + + /// If an error was encountered while parsing the body, it will appear here. + /// + /// Otherwise, this is `null`. + dynamic get error; + + /// If an error was encountered while parsing the body, the call stack will appear here. + /// + /// Otherwise, this is `null`. + StackTrace get stack; } diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart index 91a2f417..926f5e71 100644 --- a/lib/src/parse_body.dart +++ b/lib/src/parse_body.dart @@ -78,12 +78,21 @@ Future parseBody(HttpRequest request, 'application/x-www-form-urlencoded') { String body = await getBody(); buildMapFromUri(result.body, body); + } else if (storeOriginalBuffer == true) { + result.originalBuffer = await getBytes(); + } + } else { + if (request.uri.hasQuery) { + buildMapFromUri(result.query, request.uri.query); + } + + if (storeOriginalBuffer == true) { + result.originalBuffer = await getBytes(); } - } else if (request.uri.hasQuery) { - buildMapFromUri(result.query, request.uri.query); } - } catch (e) { - // + } catch (e, st) { + result.error = e; + result.stack = st; } return result; @@ -101,4 +110,10 @@ class _BodyParseResultImpl implements BodyParseResult { @override Map query = {}; + + @override + var error = null; + + @override + StackTrace stack = null; } diff --git a/pubspec.yaml b/pubspec.yaml index fde9ce06..f376b47f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,8 @@ name: body_parser author: Tobe O -version: 1.0.1 +version: 1.0.2 description: Parse request bodies and query strings in Dart. -homepage: https://github.com/thosakwe/body_parser +homepage: https://github.com/angel-dart/body_parser dependencies: http_server: ">=0.9.6 <1.0.0" dev_dependencies: diff --git a/test/server.dart b/test/server.dart index 922b4a5c..91288491 100644 --- a/test/server.dart +++ b/test/server.dart @@ -42,7 +42,7 @@ main() { expect(result['body'], equals({})); expect(result['query'], equals({'hello': 'world'})); expect(result['files'], equals([])); - expect(result['originalBuffer'], isNull); + //expect(result['originalBuffer'], isNull); }); test('GET Complex', () async { From 2f9cf3e45e77ade52687ae3287227d89ae89c8c5 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Wed, 11 Oct 2017 22:32:42 -0400 Subject: [PATCH 19/24] 1.0.3 --- .gitignore | 71 ++++---- .idea/body_parser.iml | 14 ++ .idea/libraries/Dart_Packages.xml | 201 +++++++++++---------- .idea/modules.xml | 8 + .idea/runConfigurations/server_dart.xml | 7 + .idea/vcs.xml | 6 + README.md | 2 +- analysis_options.yaml | 2 + example/post.lua | 6 + example/server.dart | 40 ++++ lib/body_parser.dart | 1 - lib/src/body_parser.dart | 21 --- lib/src/parse_body.dart | 19 +- pubspec.yaml | 2 +- test/all_test.dart | 9 - test/{uploads.dart => form_data_test.dart} | 0 test/{server.dart => server_test.dart} | 0 17 files changed, 230 insertions(+), 179 deletions(-) create mode 100644 .idea/body_parser.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/runConfigurations/server_dart.xml create mode 100644 .idea/vcs.xml create mode 100644 analysis_options.yaml create mode 100644 example/post.lua create mode 100644 example/server.dart delete mode 100644 lib/src/body_parser.dart delete mode 100644 test/all_test.dart rename test/{uploads.dart => form_data_test.dart} (100%) rename test/{server.dart => server_test.dart} (100%) diff --git a/.gitignore b/.gitignore index 70817aa5..b4d6e266 100644 --- a/.gitignore +++ b/.gitignore @@ -1,43 +1,39 @@ -### VisualStudioCode template -.settings - +# Created by .ignore support plugin (hsz.mobi) ### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio - -*.iml - -## Directory-based project format: -.idea -/Test Results - Run_All_Tests.html -# if you remove the above rule, at least ignore the following: +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries # Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml # Gradle: -# .idea/gradle.xml -# .idea/libraries +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ # Mongo Explorer plugin: -# .idea/mongoSettings.xml +.idea/**/mongoSettings.xml ## File-based project format: -*.ipr *.iws ## Plugin-specific files: # IntelliJ -/out/ +out/ # mpeltonen/sbt-idea plugin .idea_modules/ @@ -45,27 +41,24 @@ # JIRA plugin atlassian-ide-plugin.xml +# Cursive Clojure plugin +.idea/replstate.xml + # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties - +fabric.properties ### Dart template -# Don't commit the following directories created by pub. -.buildlog +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.packages .pub/ build/ -packages -.packages - -# Or the files created by dart2js. -*.dart.js -*.js_ -*.js.deps -*.js.map - -# Include when developing application packages. +# If you're building an application, you may want to check-in your pubspec.lock pubspec.lock -doc/api - +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ diff --git a/.idea/body_parser.iml b/.idea/body_parser.iml new file mode 100644 index 00000000..0fd729f3 --- /dev/null +++ b/.idea/body_parser.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index d2bdf597..83757bd2 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -5,407 +5,408 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..9c283837 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/server_dart.xml b/.idea/runConfigurations/server_dart.xml new file mode 100644 index 00000000..2171ad5d --- /dev/null +++ b/.idea/runConfigurations/server_dart.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 7c1ffb18..6c577eab 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # body_parser [![Pub](https://img.shields.io/pub/v/body_parser.svg)](https://pub.dartlang.org/packages/body_parser) -![build status](https://travis-ci.org/thosakwe/body_parser.svg) +[![build status](https://travis-ci.org/angel-dart/body_parser.svg)](https://travis-ci.org/angel-dart/body_parser) Parse request bodies and query strings in Dart, as well multipart/form-data uploads. No external dependencies required. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..518eb901 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,2 @@ +analyzer: + strong-mode: true \ No newline at end of file diff --git a/example/post.lua b/example/post.lua new file mode 100644 index 00000000..524febc6 --- /dev/null +++ b/example/post.lua @@ -0,0 +1,6 @@ +-- example HTTP POST script which demonstrates setting the +-- HTTP method, body, and adding a header + +wrk.method = "POST" +wrk.body = "foo=bar&baz=quux" +wrk.headers["Content-Type"] = "application/x-www-form-urlencoded" \ No newline at end of file diff --git a/example/server.dart b/example/server.dart new file mode 100644 index 00000000..791d0524 --- /dev/null +++ b/example/server.dart @@ -0,0 +1,40 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; +import 'package:body_parser/body_parser.dart'; + +main() async { + var address = InternetAddress.LOOPBACK_IP_V4; + var port = 3000; + var futures = []; + + for (int i = 1; i < Platform.numberOfProcessors; i++) { + futures.add(Isolate.spawn(start, [address.address, port, i])); + } + + Future.wait(futures).then((_) { + print('All instances started.'); + print( + 'Test with "wrk -t12 -c400 -d30s -s ./example/post.lua http://localhost:3000" or similar'); + start([address.address, port, 0]); + }); +} + +void start(List args) { + var address = new InternetAddress(args[0]); + int port = args[1], id = args[2]; + + HttpServer.bind(address, port, shared: true).then((server) { + server.listen((request) async { + var body = await parseBody(request); + request.response + ..headers.contentType = ContentType.JSON + ..write(JSON.encode(body.body)) + ..close(); + }); + + print( + 'Server #$id listening at http://${server.address.address}:${server.port}'); + }); +} diff --git a/lib/body_parser.dart b/lib/body_parser.dart index 778bc540..0541b3e6 100644 --- a/lib/body_parser.dart +++ b/lib/body_parser.dart @@ -1,7 +1,6 @@ /// A library for parsing HTTP request bodies and queries. library body_parser; -export 'src/body_parser.dart'; export 'src/body_parse_result.dart'; export 'src/file_upload_info.dart'; export 'src/parse_body.dart'; diff --git a/lib/src/body_parser.dart b/lib/src/body_parser.dart deleted file mode 100644 index ad199e7b..00000000 --- a/lib/src/body_parser.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'body_parse_result.dart'; - -class BodyParser implements StreamTransformer, BodyParseResult> { - - @override - Stream bind(HttpRequest stream) { - var _stream = new StreamController(); - - stream.toList().then((lists) { - var ints = []; - lists.forEach(ints.addAll); - _stream.close(); - - - }); - - return _stream.stream; - } -} \ No newline at end of file diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart index 926f5e71..5b830502 100644 --- a/lib/src/parse_body.dart +++ b/lib/src/parse_body.dart @@ -19,16 +19,20 @@ Future parseBody(HttpRequest request, {bool storeOriginalBuffer: false}) async { var result = new _BodyParseResultImpl(); - Future> getBytes() async { - return await request.fold([], (a, b) => a..addAll(b)); + Future> getBytes() { + return request + .fold(new BytesBuilder(copy: false), (a, b) => a..add(b)) + .then((b) => b.takeBytes()); } - Future getBody() async { + Future getBody() { if (storeOriginalBuffer) { - List bytes = await getBytes(); - return Uri.decodeFull(UTF8.decode(result.originalBuffer = bytes)); + return getBytes().then((bytes) { + result.originalBuffer = bytes; + return UTF8.decode(bytes); + }); } else - return await request.transform(UTF8.decoder).join().then(Uri.decodeFull); + return request.transform(UTF8.decoder).join(); } try { @@ -56,7 +60,8 @@ Future parseBody(HttpRequest request, await for (HttpMultipartFormData part in parts) { if (part.isBinary || part.contentDisposition.parameters.containsKey("filename")) { - BytesBuilder builder = await part.fold(new BytesBuilder(), + BytesBuilder builder = await part.fold( + new BytesBuilder(copy: false), (BytesBuilder b, d) => b..add(d is! String ? d : d.codeUnits)); var upload = new FileUploadInfo( mimeType: part.contentType.mimeType, diff --git a/pubspec.yaml b/pubspec.yaml index f376b47f..dd230230 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: body_parser author: Tobe O -version: 1.0.2 +version: 1.0.3 description: Parse request bodies and query strings in Dart. homepage: https://github.com/angel-dart/body_parser dependencies: diff --git a/test/all_test.dart b/test/all_test.dart deleted file mode 100644 index 0686d5f0..00000000 --- a/test/all_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:test/test.dart'; - -import 'server.dart' as server; -import 'uploads.dart' as uploads; - -main() { - group('server', server.main); - group('uploads', uploads.main); -} diff --git a/test/uploads.dart b/test/form_data_test.dart similarity index 100% rename from test/uploads.dart rename to test/form_data_test.dart diff --git a/test/server.dart b/test/server_test.dart similarity index 100% rename from test/server.dart rename to test/server_test.dart From d691c652f216f4605be84159a8b399d13b7e11f6 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 4 Mar 2018 17:32:14 -0500 Subject: [PATCH 20/24] 1.1.0 --- .idea/runConfigurations/main_dart.xml | 7 +++++ .idea/runConfigurations/server_dart.xml | 7 ----- CHANGELOG.md | 2 ++ example/{server.dart => main.dart} | 0 lib/src/parse_body.dart | 41 ++++++++++++++++--------- pubspec.yaml | 6 ++-- 6 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 .idea/runConfigurations/main_dart.xml delete mode 100644 .idea/runConfigurations/server_dart.xml create mode 100644 CHANGELOG.md rename example/{server.dart => main.dart} (100%) diff --git a/.idea/runConfigurations/main_dart.xml b/.idea/runConfigurations/main_dart.xml new file mode 100644 index 00000000..750f7262 --- /dev/null +++ b/.idea/runConfigurations/main_dart.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/server_dart.xml b/.idea/runConfigurations/server_dart.xml deleted file mode 100644 index 2171ad5d..00000000 --- a/.idea/runConfigurations/server_dart.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..8d27178a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# 1.1.0 +* Add `parseBodyFromStream` \ No newline at end of file diff --git a/example/server.dart b/example/main.dart similarity index 100% rename from example/server.dart rename to example/main.dart diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart index 5b830502..cca4456e 100644 --- a/lib/src/parse_body.dart +++ b/lib/src/parse_body.dart @@ -1,12 +1,26 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:http_parser/http_parser.dart'; import 'package:http_server/http_server.dart'; import 'package:mime/mime.dart'; import 'body_parse_result.dart'; import 'file_upload_info.dart'; import 'map_from_uri.dart'; +/// Forwards to [parseBodyFromStream]. +@deprecated +Future parseBody(HttpRequest request, + {bool storeOriginalBuffer: false}) { + return parseBodyFromStream( + request, + request.headers.contentType != null + ? new MediaType.parse(request.headers.contentType.toString()) + : null, + request.uri, + storeOriginalBuffer: storeOriginalBuffer); +} + /// Grabs data from an incoming request. /// /// Supports URL-encoded and JSON, as well as multipart/* forms. @@ -15,12 +29,13 @@ import 'map_from_uri.dart'; /// via the *fileUploadName* parameter. :) /// /// Use [storeOriginalBuffer] to add the original request bytes to the result. -Future parseBody(HttpRequest request, +Future parseBodyFromStream( + Stream> data, MediaType contentType, Uri requestUri, {bool storeOriginalBuffer: false}) async { var result = new _BodyParseResultImpl(); Future> getBytes() { - return request + return data .fold(new BytesBuilder(copy: false), (a, b) => a..add(b)) .then((b) => b.takeBytes()); } @@ -32,13 +47,13 @@ Future parseBody(HttpRequest request, return UTF8.decode(bytes); }); } else - return request.transform(UTF8.decoder).join(); + return data.transform(UTF8.decoder).join(); } try { - if (request.headers.contentType != null) { - if (request.headers.contentType.primaryType == 'multipart' && - request.headers.contentType.parameters.containsKey('boundary')) { + if (contentType != null) { + if (contentType.type == 'multipart' && + contentType.parameters.containsKey('boundary')) { Stream> stream; if (storeOriginalBuffer) { @@ -48,12 +63,12 @@ Future parseBody(HttpRequest request, ..close(); stream = ctrl.stream; } else { - stream = request; + stream = data; } var parts = stream .transform(new MimeMultipartTransformer( - request.headers.contentType.parameters['boundary'])) + contentType.parameters['boundary'])) .map((part) => HttpMultipartFormData.parse(part, defaultEncoding: UTF8)); @@ -76,19 +91,17 @@ Future parseBody(HttpRequest request, '${part.contentDisposition.parameters["name"]}=$text'); } } - } else if (request.headers.contentType.mimeType == - ContentType.JSON.mimeType) { + } else if (contentType.mimeType == ContentType.JSON.mimeType) { result.body.addAll(JSON.decode(await getBody())); - } else if (request.headers.contentType.mimeType == - 'application/x-www-form-urlencoded') { + } else if (contentType.mimeType == 'application/x-www-form-urlencoded') { String body = await getBody(); buildMapFromUri(result.body, body); } else if (storeOriginalBuffer == true) { result.originalBuffer = await getBytes(); } } else { - if (request.uri.hasQuery) { - buildMapFromUri(result.query, request.uri.query); + if (requestUri.hasQuery) { + buildMapFromUri(result.query, requestUri.query); } if (storeOriginalBuffer == true) { diff --git a/pubspec.yaml b/pubspec.yaml index dd230230..424d74ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,12 @@ name: body_parser author: Tobe O -version: 1.0.3 -description: Parse request bodies and query strings in Dart. +version: 1.1.0 +description: Parse request bodies and query strings in Dart. Supports JSON, URL-encoded, and multi-part bodies. homepage: https://github.com/angel-dart/body_parser dependencies: + http_parser: ">=3.1.1 <4.0.0" http_server: ">=0.9.6 <1.0.0" + mime: ">=0.9.3 <1.0.0" dev_dependencies: http: ">=0.11.3 <0.12.0" json_god: ">=2.0.0-beta <3.0.0" From 6ca0d2126a1aae03e196837c167c7f3451dba234 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 10 Aug 2018 22:08:44 -0400 Subject: [PATCH 21/24] Dart 2 fixes -> 1.1.1 --- .idea/body_parser.iml | 1 + .idea/libraries/Dart_Packages.xml | 237 ++++++++++++++++-------------- .travis.yml | 5 +- CHANGELOG.md | 3 + analysis_options.yaml | 3 +- example/main.dart | 13 +- lib/src/body_parse_result.dart | 2 +- lib/src/chunk.dart | 7 +- lib/src/file_upload_info.dart | 8 +- lib/src/get_value.dart | 30 ++-- lib/src/map_from_uri.dart | 2 +- lib/src/parse_body.dart | 27 +++- pubspec.yaml | 6 +- test/form_data_test.dart | 45 +++--- test/server_test.dart | 66 ++++++--- 15 files changed, 260 insertions(+), 195 deletions(-) diff --git a/.idea/body_parser.iml b/.idea/body_parser.iml index 0fd729f3..ae9af975 100644 --- a/.idea/body_parser.iml +++ b/.idea/body_parser.iml @@ -3,6 +3,7 @@ + diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 83757bd2..3a4aead0 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -5,408 +5,417 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - + - - - + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index de2210c9..a9e2c109 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,4 @@ -language: dart \ No newline at end of file +language: dart +dart: + - dev + - stable \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d27178a..2304947d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,5 @@ +# 1.1.1 +* Dart 2 updates; should fix Angel in Travis. + # 1.1.0 * Add `parseBodyFromStream` \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 518eb901..eae1e42a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,2 +1,3 @@ analyzer: - strong-mode: true \ No newline at end of file + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index 791d0524..ce0af703 100644 --- a/example/main.dart +++ b/example/main.dart @@ -5,32 +5,33 @@ import 'dart:isolate'; import 'package:body_parser/body_parser.dart'; main() async { - var address = InternetAddress.LOOPBACK_IP_V4; + var address = '127.0.0.1'; var port = 3000; var futures = []; for (int i = 1; i < Platform.numberOfProcessors; i++) { - futures.add(Isolate.spawn(start, [address.address, port, i])); + futures.add(Isolate.spawn(start, [address, port, i])); } Future.wait(futures).then((_) { print('All instances started.'); print( 'Test with "wrk -t12 -c400 -d30s -s ./example/post.lua http://localhost:3000" or similar'); - start([address.address, port, 0]); + start([address, port, 0]); }); } void start(List args) { - var address = new InternetAddress(args[0]); + var address = new InternetAddress(args[0] as String); int port = args[1], id = args[2]; HttpServer.bind(address, port, shared: true).then((server) { server.listen((request) async { + // ignore: deprecated_member_use var body = await parseBody(request); request.response - ..headers.contentType = ContentType.JSON - ..write(JSON.encode(body.body)) + ..headers.contentType = new ContentType('application', 'json') + ..write(json.encode(body.body)) ..close(); }); diff --git a/lib/src/body_parse_result.dart b/lib/src/body_parse_result.dart index 4fa826e1..806335c3 100644 --- a/lib/src/body_parse_result.dart +++ b/lib/src/body_parse_result.dart @@ -12,7 +12,7 @@ abstract class BodyParseResult { List get files; /// The original body bytes sent with this request. - /// + /// /// You must set [storeOriginalBuffer] to `true` to see this. List get originalBuffer; diff --git a/lib/src/chunk.dart b/lib/src/chunk.dart index 2dd36d0b..58776ca0 100644 --- a/lib/src/chunk.dart +++ b/lib/src/chunk.dart @@ -1,8 +1,7 @@ import 'file_upload_info.dart'; -List getFileDataFromChunk(String chunk, String boundary, - String fileUploadName, - Map body) { +List getFileDataFromChunk( + String chunk, String boundary, String fileUploadName, Map body) { List result = []; return result; -} \ No newline at end of file +} diff --git a/lib/src/file_upload_info.dart b/lib/src/file_upload_info.dart index 85be74dd..5db8b785 100644 --- a/lib/src/file_upload_info.dart +++ b/lib/src/file_upload_info.dart @@ -2,12 +2,16 @@ class FileUploadInfo { /// The MIME type of the uploaded file. String mimeType; + /// The name of the file field from the request. String name; + /// The filename of the file. String filename; + /// The bytes that make up this file. List data; - FileUploadInfo({this.mimeType, this.name, this.filename, this.data: const []}) {} -} \ No newline at end of file + FileUploadInfo( + {this.mimeType, this.name, this.filename, this.data: const []}) {} +} diff --git a/lib/src/get_value.dart b/lib/src/get_value.dart index 73e01507..d3c6c3f1 100644 --- a/lib/src/get_value.dart +++ b/lib/src/get_value.dart @@ -1,14 +1,20 @@ -import 'dart:convert'; +import 'package:dart2_constant/convert.dart'; getValue(String value) { - num numValue = num.parse(value, (_) => double.NAN); - if (!numValue.isNaN) - return numValue; - else if (value.startsWith('[') && value.endsWith(']')) - return JSON.decode(value); - else if (value.startsWith('{') && value.endsWith('}')) - return JSON.decode(value); - else if (value.trim().toLowerCase() == 'null') - return null; - else return value; -} \ No newline at end of file + try { + num numValue = num.parse(value); + if (!numValue.isNaN) + return numValue; + else + return value; + } on FormatException { + if (value.startsWith('[') && value.endsWith(']')) + return json.decode(value); + else if (value.startsWith('{') && value.endsWith('}')) + return json.decode(value); + else if (value.trim().toLowerCase() == 'null') + return null; + else + return value; + } +} diff --git a/lib/src/map_from_uri.dart b/lib/src/map_from_uri.dart index 15ca9f4b..97f1d56c 100644 --- a/lib/src/map_from_uri.dart +++ b/lib/src/map_from_uri.dart @@ -30,7 +30,7 @@ buildMapFromUri(Map map, String body) { for (int i = 1; i < keys.length; i++) { if (i < keys.length - 1) { targetMap[keys[i]] = targetMap[keys[i]] ?? {}; - targetMap = targetMap[keys[i]]; + targetMap = targetMap[keys[i]] as Map; } else { targetMap[keys[i]] = getValue(value); } diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart index cca4456e..00dcf663 100644 --- a/lib/src/parse_body.dart +++ b/lib/src/parse_body.dart @@ -1,9 +1,11 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; + +import 'package:dart2_constant/convert.dart'; import 'package:http_parser/http_parser.dart'; import 'package:http_server/http_server.dart'; import 'package:mime/mime.dart'; + import 'body_parse_result.dart'; import 'file_upload_info.dart'; import 'map_from_uri.dart'; @@ -44,10 +46,10 @@ Future parseBodyFromStream( if (storeOriginalBuffer) { return getBytes().then((bytes) { result.originalBuffer = bytes; - return UTF8.decode(bytes); + return utf8.decode(bytes); }); } else - return data.transform(UTF8.decoder).join(); + return data.transform(utf8.decoder).join(); } try { @@ -70,14 +72,17 @@ Future parseBodyFromStream( .transform(new MimeMultipartTransformer( contentType.parameters['boundary'])) .map((part) => - HttpMultipartFormData.parse(part, defaultEncoding: UTF8)); + HttpMultipartFormData.parse(part, defaultEncoding: utf8)); await for (HttpMultipartFormData part in parts) { if (part.isBinary || part.contentDisposition.parameters.containsKey("filename")) { BytesBuilder builder = await part.fold( new BytesBuilder(copy: false), - (BytesBuilder b, d) => b..add(d is! String ? d : d.codeUnits)); + (BytesBuilder b, d) => b + ..add(d is! String + ? (d as List) + : (d as String).codeUnits)); var upload = new FileUploadInfo( mimeType: part.contentType.mimeType, name: part.contentDisposition.parameters['name'], @@ -91,8 +96,9 @@ Future parseBodyFromStream( '${part.contentDisposition.parameters["name"]}=$text'); } } - } else if (contentType.mimeType == ContentType.JSON.mimeType) { - result.body.addAll(JSON.decode(await getBody())); + } else if (contentType.mimeType == 'application/json') { + result.body + .addAll(_foldToStringDynamic(json.decode(await getBody()) as Map)); } else if (contentType.mimeType == 'application/x-www-form-urlencoded') { String body = await getBody(); buildMapFromUri(result.body, body); @@ -135,3 +141,10 @@ class _BodyParseResultImpl implements BodyParseResult { @override StackTrace stack = null; } + +Map _foldToStringDynamic(Map map) { + return map == null + ? null + : map.keys.fold>( + {}, (out, k) => out..[k.toString()] = map[k]); +} diff --git a/pubspec.yaml b/pubspec.yaml index 424d74ab..e07c6368 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,13 +1,13 @@ name: body_parser author: Tobe O -version: 1.1.0 +version: 1.1.1 description: Parse request bodies and query strings in Dart. Supports JSON, URL-encoded, and multi-part bodies. homepage: https://github.com/angel-dart/body_parser dependencies: + dart2_constant: ^1.0.0 http_parser: ">=3.1.1 <4.0.0" http_server: ">=0.9.6 <1.0.0" mime: ">=0.9.3 <1.0.0" dev_dependencies: http: ">=0.11.3 <0.12.0" - json_god: ">=2.0.0-beta <3.0.0" - test: ">=0.12.15 <0.13.0" \ No newline at end of file + test: ">=0.12.15" \ No newline at end of file diff --git a/test/form_data_test.dart b/test/form_data_test.dart index 496279cc..354c42db 100644 --- a/test/form_data_test.dart +++ b/test/form_data_test.dart @@ -1,8 +1,9 @@ import 'dart:io'; import 'package:body_parser/body_parser.dart'; +import 'package:dart2_constant/convert.dart'; import 'package:http/http.dart' as http; -import 'package:json_god/json_god.dart' as god; import 'package:test/test.dart'; +import 'server_test.dart'; main() { HttpServer server; @@ -10,10 +11,11 @@ main() { http.Client client; setUp(() async { - server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0); + server = await HttpServer.bind('127.0.0.1', 0); server.listen((HttpRequest request) async { //Server will simply return a JSON representation of the parsed body - request.response.write(god.serialize(await parseBody(request))); + // ignore: deprecated_member_use + request.response.write(jsonEncodeBody(await parseBody(request))); await request.response.close(); }); url = 'http://localhost:${server.port}'; @@ -31,8 +33,8 @@ main() { test('No upload', () async { String boundary = 'myBoundary'; - Map headers = { - HttpHeaders.CONTENT_TYPE: 'multipart/form-data; boundary=$boundary' + Map headers = { + 'content-type': 'multipart/form-data; boundary=$boundary' }; String postData = ''' --$boundary @@ -47,16 +49,21 @@ world 'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}'); var response = await client.post(url, headers: headers, body: postData); print('Response: ${response.body}'); - Map json = god.deserialize(response.body); - List files = json['files']; + Map jsons = json.decode(response.body); + var files = jsons['files'].map((map) { + return map == null + ? null + : map.keys.fold>( + {}, (out, k) => out..[k.toString()] = map[k]); + }); expect(files.length, equals(0)); - expect(json['body']['hello'], equals('world')); + expect(jsons['body']['hello'], equals('world')); }); test('Single upload', () async { String boundary = 'myBoundary'; - Map headers = { - HttpHeaders.CONTENT_TYPE: new ContentType("multipart", "form-data", + Map headers = { + 'content-type': new ContentType("multipart", "form-data", parameters: {"boundary": boundary}).toString() }; String postData = ''' @@ -77,20 +84,20 @@ Hello world 'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}'); var response = await client.post(url, headers: headers, body: postData); print('Response: ${response.body}'); - Map json = god.deserialize(response.body); - List files = json['files']; + Map jsons = json.decode(response.body); + var files = jsons['files']; expect(files.length, equals(1)); expect(files[0]['name'], equals('file')); expect(files[0]['mimeType'], equals('application/dart')); expect(files[0]['data'].length, equals(11)); expect(files[0]['filename'], equals('app.dart')); - expect(json['body']['hello'], equals('world')); + expect(jsons['body']['hello'], equals('world')); }); test('Multiple upload', () async { String boundary = 'myBoundary'; - Map headers = { - HttpHeaders.CONTENT_TYPE: 'multipart/form-data; boundary=$boundary' + Map headers = { + 'content-type': 'multipart/form-data; boundary=$boundary' }; String postData = ''' --$boundary @@ -121,15 +128,15 @@ function main() { 'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}'); var response = await client.post(url, headers: headers, body: postData); print('Response: ${response.body}'); - Map json = god.deserialize(response.body); - List files = json['files']; + Map jsons = json.decode(response.body); + var files = jsons['files']; expect(files.length, equals(2)); expect(files[0]['name'], equals('file')); expect(files[0]['mimeType'], equals('text/plain')); expect(files[0]['data'].length, equals(11)); expect(files[1]['name'], equals('entry-point')); expect(files[1]['mimeType'], equals('text/javascript')); - expect(json['body']['json'], equals('god')); - expect(json['body']['num'], equals(14.5)); + expect(jsons['body']['json'], equals('god')); + expect(jsons['body']['num'], equals(14.5)); }); } diff --git a/test/server_test.dart b/test/server_test.dart index 91288491..277ff495 100644 --- a/test/server_test.dart +++ b/test/server_test.dart @@ -1,24 +1,43 @@ -import 'dart:convert'; -import 'dart:io'; +import 'dart:io' show HttpRequest, HttpServer; + import 'package:body_parser/body_parser.dart'; +import 'package:dart2_constant/convert.dart'; import 'package:http/http.dart' as http; -import 'package:json_god/json_god.dart' as god; import 'package:test/test.dart'; const TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjcuMC4wLjEiLCJleHAiOi0xLCJpYXQiOiIyMDE2LTEyLTIyVDEyOjQ5OjUwLjM2MTQ0NiIsImlzcyI6ImFuZ2VsX2F1dGgiLCJzdWIiOiIxMDY2OTQ4Mzk2MDIwMjg5ODM2NTYifQ==.PYw7yUb-cFWD7N0sSLztP7eeRvO44nu1J2OgDNyT060='; +String jsonEncodeBody(BodyParseResult result) { + return json.encode({ + 'query': result.query, + 'body': result.body, + 'error': result.error?.toString(), + 'files': result.files.map((f) { + return { + 'name': f.name, + 'mimeType': f.mimeType, + 'filename': f.filename, + 'data': f.data, + }; + }).toList(), + 'originalBuffer': result.originalBuffer, + 'stack': null, //result.stack.toString(), + }); +} + main() { HttpServer server; String url; http.Client client; setUp(() async { - server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0); + server = await HttpServer.bind('127.0.0.1', 0); server.listen((HttpRequest request) async { //Server will simply return a JSON representation of the parsed body request.response.write( - god.serialize(await parseBody(request, storeOriginalBuffer: true))); + // ignore: deprecated_member_use + jsonEncodeBody(await parseBody(request, storeOriginalBuffer: true))); await request.response.close(); }); url = 'http://localhost:${server.port}'; @@ -38,7 +57,7 @@ main() { print('GET $url/?hello=world'); var response = await client.get('$url/?hello=world'); print('Response: ${response.body}'); - var result = JSON.decode(response.body); + var result = json.decode(response.body); expect(result['body'], equals({})); expect(result['query'], equals({'hello': 'world'})); expect(result['files'], equals([])); @@ -46,12 +65,12 @@ main() { }); test('GET Complex', () async { - var postData = 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - - 1}&map.foo.bar=baz'; + var postData = + 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - 1}&map.foo.bar=baz'; print('Body: $postData'); var response = await client.get('$url/?$postData'); print('Response: ${response.body}'); - var query = god.deserialize(response.body)['query']; + var query = json.decode(response.body)['query']; expect(query['hello'], equals('world')); expect(query['nums'][2], equals(2)); expect(query['map'] is Map, equals(true)); @@ -63,21 +82,21 @@ main() { print('Body: $postData'); var response = await client.get('$url/?$postData'); print('Response: ${response.body}'); - var query = god.deserialize(response.body)['query']; + var query = json.decode(response.body)['query']; expect(query['token'], equals(TOKEN)); }); }); group('urlencoded', () { Map headers = { - HttpHeaders.CONTENT_TYPE: 'application/x-www-form-urlencoded' + 'content-type': 'application/x-www-form-urlencoded' }; test('POST Simple', () async { print('Body: hello=world'); var response = await client.post(url, headers: headers, body: 'hello=world'); print('Response: ${response.body}'); - var result = JSON.decode(response.body); + var result = json.decode(response.body); expect(result['query'], equals({})); expect(result['body'], equals({'hello': 'world'})); expect(result['files'], equals([])); @@ -86,10 +105,11 @@ main() { }); test('Post Complex', () async { - var postData = 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - - 1}&map.foo.bar=baz'; + var postData = + 'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - 1}&map.foo.bar=baz'; var response = await client.post(url, headers: headers, body: postData); - var body = god.deserialize(response.body)['body']; + print('Response: ${response.body}'); + var body = json.decode(response.body)['body']; expect(body['hello'], equals('world')); expect(body['nums'][2], equals(2)); expect(body['map'] is Map, equals(true)); @@ -99,21 +119,19 @@ main() { test('JWT', () async { var postData = 'token=$TOKEN'; var response = await client.post(url, headers: headers, body: postData); - var body = god.deserialize(response.body)['body']; + var body = json.decode(response.body)['body']; expect(body['token'], equals(TOKEN)); }); }); - group('JSON', () { - Map headers = { - HttpHeaders.CONTENT_TYPE: ContentType.JSON.toString() - }; + group('json', () { + Map headers = {'content-type': 'application/json'}; test('Post Simple', () async { - var postData = god.serialize({'hello': 'world'}); + var postData = json.encode({'hello': 'world'}); print('Body: $postData'); var response = await client.post(url, headers: headers, body: postData); print('Response: ${response.body}'); - var result = JSON.decode(response.body); + var result = json.decode(response.body); expect(result['body'], equals({'hello': 'world'})); expect(result['query'], equals({})); expect(result['files'], equals([])); @@ -121,7 +139,7 @@ main() { }); test('Post Complex', () async { - var postData = god.serialize({ + var postData = json.encode({ 'hello': 'world', 'nums': [1, 2.0, 3 - 1], 'map': { @@ -131,7 +149,7 @@ main() { print('Body: $postData'); var response = await client.post(url, headers: headers, body: postData); print('Response: ${response.body}'); - var body = god.deserialize(response.body)['body']; + var body = json.decode(response.body)['body']; expect(body['hello'], equals('world')); expect(body['nums'][2], equals(2)); expect(body['map'] is Map, equals(true)); From 20fa8e417777cd0e8682d4543992844ca01edcd7 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 10 Aug 2018 22:10:35 -0400 Subject: [PATCH 22/24] Bump SDK constraints --- pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index e07c6368..567852ca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,6 +3,8 @@ author: Tobe O version: 1.1.1 description: Parse request bodies and query strings in Dart. Supports JSON, URL-encoded, and multi-part bodies. homepage: https://github.com/angel-dart/body_parser +environment: + sdk: ">=1.8.0 <3.0.0" dependencies: dart2_constant: ^1.0.0 http_parser: ">=3.1.1 <4.0.0" From 3434b4cf59e01835d8bea5eb655bab5d530ca964 Mon Sep 17 00:00:00 2001 From: Jonathan Rezende Date: Wed, 9 Oct 2019 15:59:52 -0300 Subject: [PATCH 23/24] Uint8List update --- lib/src/parse_body.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/src/parse_body.dart b/lib/src/parse_body.dart index 00dcf663..8fdb94b9 100644 --- a/lib/src/parse_body.dart +++ b/lib/src/parse_body.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:typed_data'; import 'package:dart2_constant/convert.dart'; import 'package:http_parser/http_parser.dart'; @@ -32,11 +33,11 @@ Future parseBody(HttpRequest request, /// /// Use [storeOriginalBuffer] to add the original request bytes to the result. Future parseBodyFromStream( - Stream> data, MediaType contentType, Uri requestUri, + Stream data, MediaType contentType, Uri requestUri, {bool storeOriginalBuffer: false}) async { var result = new _BodyParseResultImpl(); - Future> getBytes() { + Future getBytes() { return data .fold(new BytesBuilder(copy: false), (a, b) => a..add(b)) .then((b) => b.takeBytes()); @@ -48,19 +49,20 @@ Future parseBodyFromStream( result.originalBuffer = bytes; return utf8.decode(bytes); }); - } else - return data.transform(utf8.decoder).join(); + } else { + return utf8.decoder.bind(data).join(); + } } try { if (contentType != null) { if (contentType.type == 'multipart' && contentType.parameters.containsKey('boundary')) { - Stream> stream; + Stream stream; if (storeOriginalBuffer) { var bytes = result.originalBuffer = await getBytes(); - var ctrl = new StreamController>() + var ctrl = new StreamController() ..add(bytes) ..close(); stream = ctrl.stream; @@ -68,9 +70,8 @@ Future parseBodyFromStream( stream = data; } - var parts = stream - .transform(new MimeMultipartTransformer( - contentType.parameters['boundary'])) + var parts = MimeMultipartTransformer( + contentType.parameters['boundary']).bind(stream) .map((part) => HttpMultipartFormData.parse(part, defaultEncoding: utf8)); From d31a6242c909dcf984980313c40434c35efde192 Mon Sep 17 00:00:00 2001 From: Jonathan Rezende Date: Wed, 9 Oct 2019 16:00:39 -0300 Subject: [PATCH 24/24] analyzer error of dynamic variable to Map --- lib/src/map_from_uri.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/map_from_uri.dart b/lib/src/map_from_uri.dart index 97f1d56c..0945ce6b 100644 --- a/lib/src/map_from_uri.dart +++ b/lib/src/map_from_uri.dart @@ -25,7 +25,7 @@ buildMapFromUri(Map map, String body) { // i.e. map.foo.bar => [map, foo, bar] List keys = key.split('.'); - Map targetMap = map[keys[0]] ?? {}; + Map targetMap = map[keys[0]] != null ? map[keys[0]] as Map : {}; map[keys[0]] = targetMap; for (int i = 1; i < keys.length; i++) { if (i < keys.length - 1) {