diff --git a/.all-contributorsrc b/.all-contributorsrc deleted file mode 100644 index 1def304..0000000 --- a/.all-contributorsrc +++ /dev/null @@ -1,39 +0,0 @@ -{ - "projectName": "pusher", - "projectOwner": "hackherz", - "files": [ - "README.md" - ], - "imageSize": 100, - "commit": false, - "contributors": [ - { - "login": "nlohmann", - "name": "Niels Lohmann", - "avatar_url": "https://avatars2.githubusercontent.com/u/159488?v=4", - "profile": "http:/nlohmann.me", - "contributions": [ - "code" - ] - }, - { - "login": "jamesterjim", - "name": "James Rose", - "avatar_url": "https://avatars1.githubusercontent.com/u/1824603?v=4", - "profile": "http://jameserose.co.uk", - "contributions": [ - "doc" - ] - }, - { - "login": "HackHerz", - "name": "Daniel Stein", - "avatar_url": "https://avatars1.githubusercontent.com/u/2885148?v=4", - "profile": "https://hackherz.com/", - "contributions": [ - "code", - "doc" - ] - } - ] -} diff --git a/.gitignore b/.gitignore index e34ab2c..e5afc2a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ pusher *~ *.o +*.txt *.deb +*cmake* +*cpack* +CMake* +_CPack* diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 34c1c3b..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "src/pushnotifier-sdk-cpp"] - path = src/pushnotifier-sdk-cpp - url = https://github.com/HackHerz/pushnotifier-sdk-cpp -[submodule "src/simpleini"] - path = src/simpleini - url = https://github.com/brofield/simpleini diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5edede6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.8) +project(pusher CXX) + +# C++11 +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +add_executable(pusher src/main.cpp + src/pushhandler.cpp + src/json/json.cc + src/simpleini/ConvertUTF.c) + +# Libraries +target_link_libraries(pusher curl) + +# Install +INSTALL_TARGETS("/bin" pusher) + +# Debian package +set(CPACK_GENERATOR "DEB") +set(CPACK_DEBIAN_PACKAGE_NAME "pusher") + +set(CPACK_PACKAGE_VERSION "0.3") +set(CPACK_DEBIAN_PACKAGE_VERSION "0.3") +execute_process(COMMAND dpkg --print-architecture OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE) + +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl3 (>= 7.37.1), libstdc++6 (>= 4.9.1), libgcc1 (1:4.9.1)") + +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Daniel Stein ") +set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "push it to the limits.") +set(CPACK_DEBIAN_PACKAGE_SECTION "util") + +include(CPack) diff --git a/LICENSE b/LICENSE index 94a9ed0..985eba7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,13 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +Copyright (c) 2014 HackHerz - 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. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Preamble + 1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + 2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 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. + 3) If you modify the source in any way, you have to remove the authors PushNotifier API-token and replace it with your own. It is the same for the package-name. - 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. + 4) You have to make sure that the modified program doesn't break the PushNotifer API rules. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS β€œAS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..091a89e --- /dev/null +++ b/Makefile @@ -0,0 +1,284 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 2.8 + +# Default target executed when no arguments are given to make. +default_target: all +.PHONY : default_target + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Remove some rules from gmake that .SUFFIXES does not remove. +SUFFIXES = + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /usr/bin/cmake + +# The command to remove a file. +RM = /usr/bin/cmake -E remove -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /home/hackherz/Dokumente/GitHub/pusher + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /home/hackherz/Dokumente/GitHub/pusher + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running interactive CMake command-line interface..." + /usr/bin/cmake -i . +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache +.PHONY : edit_cache/fast + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." + /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: install/local +.PHONY : install/local/fast + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." + /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: install/strip +.PHONY : install/strip/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components +.PHONY : list_install_components/fast + +# Special rule for the target package +package: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Run CPack packaging tool..." + /usr/bin/cpack --config ./CPackConfig.cmake +.PHONY : package + +# Special rule for the target package +package/fast: package +.PHONY : package/fast + +# Special rule for the target package_source +package_source: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Run CPack packaging tool for source..." + /usr/bin/cpack --config ./CPackSourceConfig.cmake /home/hackherz/Dokumente/GitHub/pusher/CPackSourceConfig.cmake +.PHONY : package_source + +# Special rule for the target package_source +package_source/fast: package_source +.PHONY : package_source/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." + /usr/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache +.PHONY : rebuild_cache/fast + +# The main all target +all: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /home/hackherz/Dokumente/GitHub/pusher/CMakeFiles /home/hackherz/Dokumente/GitHub/pusher/CMakeFiles/progress.marks + $(MAKE) -f CMakeFiles/Makefile2 all + $(CMAKE_COMMAND) -E cmake_progress_start /home/hackherz/Dokumente/GitHub/pusher/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + $(MAKE) -f CMakeFiles/Makefile2 clean +.PHONY : clean + +# The main clean target +clean/fast: clean +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + $(MAKE) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + $(MAKE) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +#============================================================================= +# Target rules for targets named pusher + +# Build rule for target. +pusher: cmake_check_build_system + $(MAKE) -f CMakeFiles/Makefile2 pusher +.PHONY : pusher + +# fast build rule for target. +pusher/fast: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/build +.PHONY : pusher/fast + +src/json/json.o: src/json/json.cc.o +.PHONY : src/json/json.o + +# target to build an object file +src/json/json.cc.o: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/json/json.cc.o +.PHONY : src/json/json.cc.o + +src/json/json.i: src/json/json.cc.i +.PHONY : src/json/json.i + +# target to preprocess a source file +src/json/json.cc.i: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/json/json.cc.i +.PHONY : src/json/json.cc.i + +src/json/json.s: src/json/json.cc.s +.PHONY : src/json/json.s + +# target to generate assembly for a file +src/json/json.cc.s: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/json/json.cc.s +.PHONY : src/json/json.cc.s + +src/main.o: src/main.cpp.o +.PHONY : src/main.o + +# target to build an object file +src/main.cpp.o: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/main.cpp.o +.PHONY : src/main.cpp.o + +src/main.i: src/main.cpp.i +.PHONY : src/main.i + +# target to preprocess a source file +src/main.cpp.i: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/main.cpp.i +.PHONY : src/main.cpp.i + +src/main.s: src/main.cpp.s +.PHONY : src/main.s + +# target to generate assembly for a file +src/main.cpp.s: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/main.cpp.s +.PHONY : src/main.cpp.s + +src/pushhandler.o: src/pushhandler.cpp.o +.PHONY : src/pushhandler.o + +# target to build an object file +src/pushhandler.cpp.o: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/pushhandler.cpp.o +.PHONY : src/pushhandler.cpp.o + +src/pushhandler.i: src/pushhandler.cpp.i +.PHONY : src/pushhandler.i + +# target to preprocess a source file +src/pushhandler.cpp.i: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/pushhandler.cpp.i +.PHONY : src/pushhandler.cpp.i + +src/pushhandler.s: src/pushhandler.cpp.s +.PHONY : src/pushhandler.s + +# target to generate assembly for a file +src/pushhandler.cpp.s: + $(MAKE) -f CMakeFiles/pusher.dir/build.make CMakeFiles/pusher.dir/src/pushhandler.cpp.s +.PHONY : src/pushhandler.cpp.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... edit_cache" + @echo "... install" + @echo "... install/local" + @echo "... install/strip" + @echo "... list_install_components" + @echo "... package" + @echo "... package_source" + @echo "... pusher" + @echo "... rebuild_cache" + @echo "... src/json/json.o" + @echo "... src/json/json.i" + @echo "... src/json/json.s" + @echo "... src/main.o" + @echo "... src/main.i" + @echo "... src/main.s" + @echo "... src/pushhandler.o" + @echo "... src/pushhandler.i" + @echo "... src/pushhandler.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/README.md b/README.md index 262e4fc..0011e1a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -![Logo](https://github.com/HackHerz/pusher/blob/master/pusher.png) -[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors) +![Logo](http://hackherz.com/wp-content/uploads/2014/09/Pusher-UNIX.png) # pusher Send push-notifications to your phone from the command line. Uses [PushNotifier](http://pushnotifier.de/) to receive notifications. @@ -13,9 +12,12 @@ Send push-notifications to your phone from the command line. Uses [PushNotifier] ## Installation -Make sure you have **libcurl4-openssl-dev** installed. +### Option 1: Debian package +You can download debian packages from [my website](http://hackherz.com/pusher#download). + +### Option 2: From source ```bash -$ git clone --recursive https://github.com/HackHerz/pusher.git +$ git clone https://github.com/HackHerz/pusher.git $ cd pusher/ $ make $ make install @@ -24,8 +26,6 @@ $ make install ## Usage -If you want some real life use cases make sure to check out our [example section](https://github.com/HackHerz/pusher/tree/master/examples). - ### Basic usage ```bash $ pusher -i 1 "Hello World" # Send Hello World to device-id 1 @@ -71,18 +71,3 @@ pusher uses a few third party libraries. - [json](https://github.com/nlohmann/json) by [Niels Lohmann](http://nlohmann.me/) - [TCLAP](http://tclap.sourceforge.net/) - [simpleini](https://github.com/brofield/simpleini) by Brodie Thiesfield - -## Contributors - - - -| [
Niels Lohmann](http:/nlohmann.me)
[πŸ’»](https://github.com/hackherz/pusher/commits?author=nlohmann "Code") | [
James Rose](http://jameserose.co.uk)
[πŸ“–](https://github.com/hackherz/pusher/commits?author=jamesterjim "Documentation") | [
Daniel Stein](https://hackherz.com/)
[πŸ’»](https://github.com/hackherz/pusher/commits?author=HackHerz "Code") [πŸ“–](https://github.com/hackherz/pusher/commits?author=HackHerz "Documentation") | -| :---: | :---: | :---: | - -Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): - - - - - -This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 525582a..0000000 --- a/examples/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Examples - -This is a collection of examples how you can user pusher. Feel free to contact me or add it yourself if you have a snippet to share. - - -## Server - -- [Notification on SSH login](https://github.com/HackHerz/pusher/blob/master/examples/ssh-notification.md) -- [Notification for fail2ban](https://github.com/HackHerz/pusher/blob/master/examples/fail2ban-notification.md) diff --git a/examples/fail2ban-notification.md b/examples/fail2ban-notification.md deleted file mode 100644 index 372bc81..0000000 --- a/examples/fail2ban-notification.md +++ /dev/null @@ -1,17 +0,0 @@ -# Notification for fail2ban - -Copy [pusher.conf](https://github.com/HackHerz/pusher/blob/master/examples/pusher.conf) to **/etc/fail2ban/action.d/** and insert this snippet in your *jail.local*. - -``` -[ssh] - -enabled = true -port = ssh -filter = sshd -logpath = /var/log/auth.log -maxretry = 6 -action = pusher[name=ssh, dest=ONQ] -``` - - -Example is for SSH and change ONQ to your own Device-ID. diff --git a/examples/pusher.conf b/examples/pusher.conf deleted file mode 100644 index 9ff8e68..0000000 --- a/examples/pusher.conf +++ /dev/null @@ -1,51 +0,0 @@ -# Fail2Ban configuration file -# -# author: hackherz -# - -[Definition] - -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# -actionstart = /usr/local/bin/pusher -i "[Fail2Ban] : started on `uname -n`" - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# -actionstop = /usr/local/bin/pusher -i "[Fail2Ban] : stopped on `uname -n`" - -# Option: actioncheck -# Notes.: command executed once before each actionban command -# Values: CMD -# -actioncheck = - -# Option: actionban -# Notes.: command executed when banning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionban = /usr/local/bin/pusher -i "[Fail2Ban] : banned from `uname -n`" - -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = - -[Init] - -# Default name of the chain -# -name = default - -# Destination/Addressee of the mail -# -dest = default # change this to your default device id - diff --git a/examples/ssh-notification.md b/examples/ssh-notification.md deleted file mode 100644 index 7aced63..0000000 --- a/examples/ssh-notification.md +++ /dev/null @@ -1,16 +0,0 @@ -# Notification on SSH login - -Maybe you want to receive a notification every time someone logs into your server. Just post this snippet at the end of your **/etc/profile**. - - -```bash -# SSH-login notification -if [ -n "${SSH_CLIENT}" ] -then - TEXT="$(date "+%Y-%m-%d %H:%M:%S") ssh login to ${USER}@$(hostname -f)" - TEXT+=" from $(echo "${SSH_CLIENT}" | awk '{print $1}')" - pusher -i 297 "${TEXT}" -fi -``` - -Do not forget to change the id (297 in this example) to the one of your own device. diff --git a/makefile b/makefile deleted file mode 100644 index 5be79b4..0000000 --- a/makefile +++ /dev/null @@ -1,61 +0,0 @@ -#============================================================================= -# Set variables for the build - -TARGET = pusher -INSTALL_DIR = /usr/local/bin -CXX = g++ -CPPFLAGS = -std=c++11 -CDEFS = -DAPI_TOKEN=\"8E7D8B2DDE7DDE7D6C3V52VB52VBD4DDETBTTTKFFB11\" -CDEFS += -DAPP_PACKAGE=\"com.hackherz.pusher\" -BUILDCOMMAND = $(CXX) $(CDEFS) $(CPPFLAGS) -LIBS = `pkg-config libcurl --cflags --libs` - - -#============================================================================= -# Build -all: $(TARGET) - -$(TARGET): simpleini pushnotifier main - $(BUILDCOMMAND) src/simpleini/ConvertUTF.o src/pushnotifier-sdk-cpp/pushnotifier.o src/main.o $(LIBS) -o $(TARGET) - - -# simpleini -.PHONY: simpleini -simpleini: src/simpleini/ConvertUTF.o - -src/simpleini/ConvertUTF.o: src/simpleini/ConvertUTF.c - $(BUILDCOMMAND) -c src/simpleini/ConvertUTF.c -o src/simpleini/ConvertUTF.o - - -# pushnotifier -.PHONY: pushnotifier -pushnotifier: src/pushnotifier-sdk-cpp/pushnotifier.o - -src/pushnotifier-sdk-cpp/pushnotifier.o: src/pushnotifier-sdk-cpp/PushNotifier.cpp - $(BUILDCOMMAND) -c src/pushnotifier-sdk-cpp/PushNotifier.cpp -o src/pushnotifier-sdk-cpp/pushnotifier.o - - -# main -.PHONY: main -main: src/main.o - -src/main.o: src/main.cpp - $(BUILDCOMMAND) -c src/main.cpp -o src/main.o - -#============================================================================= -# Other targets -.PHONY: clean -clean: - rm -f src/*.o - rm -f src/simpleini/*.o - rm -f src/pushnotifier-sdk-cpp/pushnotifier.o - rm -f $(TARGET) - - -install: pusher - cp $(TARGET) $(INSTALL_DIR)/ - - -.PHONY: remove -remove: - rm -f $(INSTALL_DIR)/$(TARGET) diff --git a/pusher.png b/pusher.png deleted file mode 100644 index d373170..0000000 Binary files a/pusher.png and /dev/null differ diff --git a/src/json/json.cc b/src/json/json.cc new file mode 100644 index 0000000..bec3fff --- /dev/null +++ b/src/json/json.cc @@ -0,0 +1,2538 @@ +/*! +@file +@copyright The code is licensed under the MIT License + , + Copyright (c) 2013-2015 Niels Lohmann. + +@author Niels Lohmann + +@see https://github.com/nlohmann/json +*/ + +#include "json.h" + +#include // std::isdigit, std::isspace +#include // std::size_t +#include // std::runtime_error +#include // std::swap, std::move + +namespace nlohmann +{ + +/////////////////////////////////// +// CONSTRUCTORS OF UNION "value" // +/////////////////////////////////// + +json::value::value(array_t* _array): array(_array) {} +json::value::value(object_t* object_): object(object_) {} +json::value::value(string_t* _string): string(_string) {} +json::value::value(boolean_t _boolean) : boolean(_boolean) {} +json::value::value(number_t _number) : number(_number) {} +json::value::value(number_float_t _number_float) : number_float(_number_float) {} + + +///////////////////////////////// +// CONSTRUCTORS AND DESTRUCTOR // +///////////////////////////////// + +/*! +Construct an empty JSON given the type. + +@param t the type from the @ref json::type enumeration. + +@post Memory for array, object, and string are allocated. +*/ +json::json(const value_t t) + : type_(t) +{ + switch (type_) + { + case (value_t::array): + { + value_.array = new array_t(); + break; + } + case (value_t::object): + { + value_.object = new object_t(); + break; + } + case (value_t::string): + { + value_.string = new string_t(); + break; + } + case (value_t::boolean): + { + value_.boolean = boolean_t(); + break; + } + case (value_t::number): + { + value_.number = number_t(); + break; + } + case (value_t::number_float): + { + value_.number_float = number_float_t(); + break; + } + default: + { + break; + } + } +} + +/*! +Construct a null JSON object. +*/ +json::json(std::nullptr_t) noexcept : json() +{} + +/*! +Construct a string JSON object. + +@param s a string to initialize the JSON object with +*/ +json::json(const std::string& s) + : type_(value_t::string), value_(new string_t(s)) +{} + +json::json(std::string&& s) + : type_(value_t::string), value_(new string_t(std::move(s))) +{} + +json::json(const char* s) + : type_(value_t::string), value_(new string_t(s)) +{} + +json::json(const bool b) noexcept + : type_(value_t::boolean), value_(b) +{} + +json::json(const array_t& a) + : type_(value_t::array), value_(new array_t(a)) +{} + +json::json(array_t&& a) + : type_(value_t::array), value_(new array_t(std::move(a))) +{} + +json::json(const object_t& o) + : type_(value_t::object), value_(new object_t(o)) +{} + +json::json(object_t&& o) + : type_(value_t::object), value_(new object_t(std::move(o))) +{} + +/*! +This function is a bit tricky as it uses an initializer list of JSON objects +for both arrays and objects. This is not supported by C++, so we use the +following trick. Both initializer lists for objects and arrays will transform +to a list of JSON objects. The only difference is that in case of an object, +the list will contain JSON array objects with two elements - one for the key +and one for the value. As a result, it is sufficient to check if each element +of the initializer list is an array (1) with two elements (2) whose first +element is of type string (3). If this is the case, we treat the whole +initializer list as list of pairs to construct an object. If not, we pass it +as is to create an array. + +@bug With the described approach, we would fail to recognize an array whose + first element is again an arrays as array. +*/ +json::json(list_init_t a) +{ + // check if each element is an array with two elements whose first element + // is a string + for (const auto& element : a) + { + if (element.type_ != value_t::array or + element.size() != 2 or + element[0].type_ != value_t::string) + { + + // the initializer list describes an array + type_ = value_t::array; + value_ = new array_t(a); + return; + } + } + + // the initializer list is a list of pairs + type_ = value_t::object; + value_ = new object_t(); + for (const json& element : a) + { + const std::string k = element[0]; + value_.object->emplace(std::make_pair(std::move(k), + std::move(element[1]))); + } +} + +/*! +A copy constructor for the JSON class. + +@param o the JSON object to copy +*/ +json::json(const json& o) + : type_(o.type_) +{ + switch (type_) + { + case (value_t::array): + { + value_.array = new array_t(*o.value_.array); + break; + } + case (value_t::object): + { + value_.object = new object_t(*o.value_.object); + break; + } + case (value_t::string): + { + value_.string = new string_t(*o.value_.string); + break; + } + case (value_t::boolean): + { + value_.boolean = o.value_.boolean; + break; + } + case (value_t::number): + { + value_.number = o.value_.number; + break; + } + case (value_t::number_float): + { + value_.number_float = o.value_.number_float; + break; + } + default: + { + break; + } + } +} + +/*! +A move constructor for the JSON class. + +@param o the JSON object to move + +@post The JSON object \p o is invalidated. +*/ +json::json(json&& o) noexcept + : type_(std::move(o.type_)), value_(std::move(o.value_)) +{ + // invalidate payload + o.type_ = value_t::null; + o.value_ = {}; +} + +/*! +A copy assignment operator for the JSON class, following the copy-and-swap +idiom. + +@param o A JSON object to assign to this object. +*/ +json& json::operator=(json o) noexcept +{ + std::swap(type_, o.type_); + std::swap(value_, o.value_); + return *this; +} + +json::~json() noexcept +{ + switch (type_) + { + case (value_t::array): + { + delete value_.array; + break; + } + case (value_t::object): + { + delete value_.object; + break; + } + case (value_t::string): + { + delete value_.string; + break; + } + default: + { + // nothing to do for non-pointer types + break; + } + } +} + +/*! +@param s a string representation of a JSON object +@return a JSON object +*/ +json json::parse(const std::string& s) +{ + return parser(s).parse(); +} + +/*! +@param s a string representation of a JSON object +@return a JSON object +*/ +json json::parse(const char* s) +{ + return parser(s).parse(); +} + + +std::string json::type_name() const noexcept +{ + switch (type_) + { + case (value_t::array): + { + return "array"; + } + case (value_t::object): + { + return "object"; + } + case (value_t::null): + { + return "null"; + } + case (value_t::string): + { + return "string"; + } + case (value_t::boolean): + { + return "boolean"; + } + default: + { + return "number"; + } + } +} + + +/////////////////////////////// +// OPERATORS AND CONVERSIONS // +/////////////////////////////// + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not string +*/ +template<> +std::string json::get() const +{ + switch (type_) + { + case (value_t::string): + return *value_.string; + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON string"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not number (int or float) +*/ +template<> +int json::get() const +{ + switch (type_) + { + case (value_t::number): + return value_.number; + case (value_t::number_float): + return static_cast(value_.number_float); + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON number"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not number (int or float) +*/ +template<> +int64_t json::get() const +{ + switch (type_) + { + case (value_t::number): + return value_.number; + case (value_t::number_float): + return static_cast(value_.number_float); + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON number"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not number (int or float) +*/ +template<> +double json::get() const +{ + switch (type_) + { + case (value_t::number): + return static_cast(value_.number); + case (value_t::number_float): + return value_.number_float; + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON number"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not boolean +*/ +template<> +bool json::get() const +{ + switch (type_) + { + case (value_t::boolean): + return value_.boolean; + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON Boolean"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is an object +*/ +template<> +json::array_t json::get() const +{ + if (type_ == value_t::array) + { + return *value_.array; + } + if (type_ == value_t::object) + { + throw std::logic_error("cannot cast " + type_name() + " to JSON array"); + } + + array_t result; + result.push_back(*this); + return result; +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not object +*/ +template<> +json::object_t json::get() const +{ + if (type_ == value_t::object) + { + return *value_.object; + } + else + { + throw std::logic_error("cannot cast " + type_name() + " to JSON object"); + } +} + +json::operator std::string() const +{ + return get(); +} + +json::operator int() const +{ + return get(); +} + +json::operator int64_t() const +{ + return get(); +} + +json::operator double() const +{ + return get(); +} + +json::operator bool() const +{ + return get(); +} + +json::operator array_t() const +{ + return get(); +} + +json::operator object_t() const +{ + return get(); +} + +/*! +Internal implementation of the serialization function. + +\param prettyPrint whether the output shall be pretty-printed +\param indentStep the indent level +\param currentIndent the current indent level (only used internally) +*/ +std::string json::dump(const bool prettyPrint, const unsigned int indentStep, + unsigned int currentIndent) const noexcept +{ + // helper function to return whitespace as indentation + const auto indent = [prettyPrint, ¤tIndent]() + { + return prettyPrint ? std::string(currentIndent, ' ') : std::string(); + }; + + switch (type_) + { + case (value_t::string): + { + return std::string("\"") + escapeString(*value_.string) + "\""; + } + + case (value_t::boolean): + { + return value_.boolean ? "true" : "false"; + } + + case (value_t::number): + { + return std::to_string(value_.number); + } + + case (value_t::number_float): + { + return std::to_string(value_.number_float); + } + + case (value_t::array): + { + if (value_.array->empty()) + { + return "[]"; + } + + std::string result = "["; + + // increase indentation + if (prettyPrint) + { + currentIndent += indentStep; + result += "\n"; + } + + for (array_t::const_iterator i = value_.array->begin(); i != value_.array->end(); ++i) + { + if (i != value_.array->begin()) + { + result += prettyPrint ? ",\n" : ","; + } + result += indent() + i->dump(prettyPrint, indentStep, currentIndent); + } + + // decrease indentation + if (prettyPrint) + { + currentIndent -= indentStep; + result += "\n"; + } + + return result + indent() + "]"; + } + + case (value_t::object): + { + if (value_.object->empty()) + { + return "{}"; + } + + std::string result = "{"; + + // increase indentation + if (prettyPrint) + { + currentIndent += indentStep; + result += "\n"; + } + + for (object_t::const_iterator i = value_.object->begin(); i != value_.object->end(); ++i) + { + if (i != value_.object->begin()) + { + result += prettyPrint ? ",\n" : ","; + } + result += indent() + "\"" + i->first + "\":" + (prettyPrint ? " " : "") + i->second.dump( + prettyPrint, indentStep, + currentIndent); + } + + // decrease indentation + if (prettyPrint) + { + currentIndent -= indentStep; + result += "\n"; + } + + return result + indent() + "}"; + } + + // actually only value_t::null - but making the compiler happy + default: + { + return "null"; + } + } +} + +/*! +Internal function to replace all occurrences of a character in a given string +with another string. + +\param str the string that contains tokens to replace +\param c the character that needs to be replaced +\param replacement the string that is the replacement for the character +*/ +void json::replaceChar(std::string& str, char c, const std::string& replacement) +const +{ + size_t start_pos = 0; + while ((start_pos = str.find(c, start_pos)) != std::string::npos) + { + str.replace(start_pos, 1, replacement); + start_pos += replacement.length(); + } +} + +/*! +Escapes all special characters in the given string according to ECMA-404. +Necessary as some characters such as quotes, backslashes and so on +can't be used as is when dumping a string value. + +\param str the string that should be escaped. + +\return a copy of the given string with all special characters escaped. +*/ +std::string json::escapeString(const std::string& str) const +{ + std::string result(str); + // we first need to escape the backslashes as all other methods will insert + // legitimate backslashes into the result. + replaceChar(result, '\\', "\\\\"); + // replace all other characters + replaceChar(result, '"', "\\\""); + replaceChar(result, '\n', "\\n"); + replaceChar(result, '\r', "\\r"); + replaceChar(result, '\f', "\\f"); + replaceChar(result, '\b', "\\b"); + replaceChar(result, '\t', "\\t"); + return result; +} + +/*! +Serialization function for JSON objects. The function tries to mimick Python's +\p json.dumps() function, and currently supports its \p indent parameter. + +\param indent if indent is nonnegative, then array elements and object members + will be pretty-printed with that indent level. An indent level + of 0 will only insert newlines. -1 (the default) selects the + most compact representation + +\see https://docs.python.org/2/library/json.html#json.dump +*/ +std::string json::dump(int indent) const noexcept +{ + if (indent >= 0) + { + return dump(true, static_cast(indent)); + } + else + { + return dump(false, 0); + } +} + + +/////////////////////////////////////////// +// ADDING ELEMENTS TO OBJECTS AND ARRAYS // +/////////////////////////////////////////// + +json& json::operator+=(const json& o) +{ + push_back(o); + return *this; +} + +/*! +@todo comment me +*/ +json& json::operator+=(const object_t::value_type& p) +{ + return operator[](p.first) = p.second; +} + +/*! +@todo comment me +*/ +json& json::operator+=(list_init_t a) +{ + push_back(a); + return *this; +} + +/*! +This function implements the actual "adding to array" function and is called +by all other push_back or operator+= functions. If the function is called for +an array, the passed element is added to the array. + +@param o The element to add to the array. + +@pre The JSON object is an array or null. +@post The JSON object is an array whose last element is the passed element o. +@exception std::runtime_error The function was called for a JSON type that + does not support addition to an array (e.g., int or string). + +@note Null objects are silently transformed into an array before the addition. +*/ +void json::push_back(const json& o) +{ + // push_back only works for null objects or arrays + if (not(type_ == value_t::null or type_ == value_t::array)) + { + throw std::runtime_error("cannot add element to " + type_name()); + } + + // transform null object into an array + if (type_ == value_t::null) + { + type_ = value_t::array; + value_.array = new array_t; + } + + // add element to array + value_.array->push_back(o); +} + +/*! +This function implements the actual "adding to array" function and is called +by all other push_back or operator+= functions. If the function is called for +an array, the passed element is added to the array using move semantics. + +@param o The element to add to the array. + +@pre The JSON object is an array or null. +@post The JSON object is an array whose last element is the passed element o. +@post The element o is destroyed. +@exception std::runtime_error The function was called for a JSON type that + does not support addition to an array (e.g., int or string). + +@note Null objects are silently transformed into an array before the addition. +@note This function applies move semantics for the given element. +*/ +void json::push_back(json&& o) +{ + // push_back only works for null objects or arrays + if (not(type_ == value_t::null or type_ == value_t::array)) + { + throw std::runtime_error("cannot add element to " + type_name()); + } + + // transform null object into an array + if (type_ == value_t::null) + { + type_ = value_t::array; + value_.array = new array_t; + } + + // add element to array (move semantics) + value_.array->emplace_back(std::move(o)); + // invalidate object + o.type_ = value_t::null; +} + +/*! +@todo comment me +*/ +void json::push_back(const object_t::value_type& p) +{ + operator[](p.first) = p.second; +} + +/*! +@todo comment me +*/ +void json::push_back(list_init_t a) +{ + bool is_array = false; + + // check if each element is an array with two elements whose first element + // is a string + for (const auto& element : a) + { + if (element.type_ != value_t::array or + element.size() != 2 or + element[0].type_ != value_t::string) + { + // the initializer list describes an array + is_array = true; + break; + } + } + + if (is_array) + { + for (const json& element : a) + { + push_back(element); + } + } + else + { + for (const json& element : a) + { + const object_t::value_type tmp {element[0].get(), element[1]}; + push_back(tmp); + } + } +} + +/*! +This operator realizes read/write access to array elements given an integer +index. Bounds will not be checked. + +@note The "index" variable should be of type size_t as it is compared against + size() and used in the at() function. However, the compiler will have + problems in case integer literals are used. In this case, an implicit + conversion to both size_t and JSON is possible. Therefore, we use int as + type and convert it to size_t where necessary. + +@param index the index of the element to return from the array +@return reference to element for the given index + +@pre Object is an array. +@exception std::domain_error if object is not an array +*/ +json::reference json::operator[](const int index) +{ + // this [] operator only works for arrays + if (type_ != value_t::array) + { + throw std::domain_error("cannot add entry with index " + + std::to_string(index) + " to " + type_name()); + } + + // return reference to element from array at given index + return (*value_.array)[static_cast(index)]; +} + +/*! +This operator realizes read-only access to array elements given an integer +index. Bounds will not be checked. + +@note The "index" variable should be of type size_t as it is compared against + size() and used in the at() function. However, the compiler will have + problems in case integer literals are used. In this case, an implicit + conversion to both size_t and JSON is possible. Therefore, we use int as + type and convert it to size_t where necessary. + +@param index the index of the element to return from the array +@return read-only reference to element for the given index + +@pre Object is an array. +@exception std::domain_error if object is not an array +*/ +json::const_reference json::operator[](const int index) const +{ + // this [] operator only works for arrays + if (type_ != value_t::array) + { + throw std::domain_error("cannot get entry with index " + + std::to_string(index) + " from " + type_name()); + } + + // return element from array at given index + return (*value_.array)[static_cast(index)]; +} + +/*! +This function realizes read/write access to array elements given an integer +index. Bounds will be checked. + +@note The "index" variable should be of type size_t as it is compared against + size() and used in the at() function. However, the compiler will have + problems in case integer literals are used. In this case, an implicit + conversion to both size_t and JSON is possible. Therefore, we use int as + type and convert it to size_t where necessary. + +@param index the index of the element to return from the array +@return reference to element for the given index + +@pre Object is an array. +@exception std::domain_error if object is not an array +@exception std::out_of_range if index is out of range (via std::vector::at) +*/ +json::reference json::at(const int index) +{ + // this function only works for arrays + if (type_ != value_t::array) + { + throw std::domain_error("cannot add entry with index " + + std::to_string(index) + " to " + type_name()); + } + + // return reference to element from array at given index + return value_.array->at(static_cast(index)); +} + +/*! +This operator realizes read-only access to array elements given an integer +index. Bounds will be checked. + +@note The "index" variable should be of type size_t as it is compared against + size() and used in the at() function. However, the compiler will have + problems in case integer literals are used. In this case, an implicit + conversion to both size_t and JSON is possible. Therefore, we use int as + type and convert it to size_t where necessary. + +@param index the index of the element to return from the array +@return read-only reference to element for the given index + +@pre Object is an array. +@exception std::domain_error if object is not an array +@exception std::out_of_range if index is out of range (via std::vector::at) +*/ +json::const_reference json::at(const int index) const +{ + // this function only works for arrays + if (type_ != value_t::array) + { + throw std::domain_error("cannot get entry with index " + + std::to_string(index) + " from " + type_name()); + } + + // return element from array at given index + return value_.array->at(static_cast(index)); +} + +/*! +@copydoc json::operator[](const char* key) +*/ +json::reference json::operator[](const std::string& key) +{ + return operator[](key.c_str()); +} + +/*! +This operator realizes read/write access to object elements given a string +key. + +@param key the key index of the element to return from the object +@return reference to a JSON object for the given key (null if key does not + exist) + +@pre Object is an object or a null object. +@post null objects are silently converted to objects. + +@exception std::domain_error if object is not an object (or null) +*/ +json::reference json::operator[](const char* key) +{ + // implicitly convert null to object + if (type_ == value_t::null) + { + type_ = value_t::object; + value_.object = new object_t; + } + + // this [] operator only works for objects + if (type_ != value_t::object) + { + throw std::domain_error("cannot add entry with key " + + std::string(key) + " to " + type_name()); + } + + // if the key does not exist, create it + if (value_.object->find(key) == value_.object->end()) + { + (*value_.object)[key] = json(); + } + + // return reference to element from array at given index + return (*value_.object)[key]; +} + +/*! +@copydoc json::operator[](const char* key) +*/ +json::const_reference json::operator[](const std::string& key) const +{ + return operator[](key.c_str()); +} + +/*! +This operator realizes read-only access to object elements given a string +key. + +@param key the key index of the element to return from the object +@return read-only reference to element for the given key + +@pre Object is an object. +@exception std::domain_error if object is not an object +@exception std::out_of_range if key is not found in object +*/ +json::const_reference json::operator[](const char* key) const +{ + // this [] operator only works for objects + if (type_ != value_t::object) + { + throw std::domain_error("cannot get entry with key " + + std::string(key) + " from " + type_name()); + } + + // search for the key + const auto it = value_.object->find(key); + + // make sure the key exists in the object + if (it == value_.object->end()) + { + throw std::out_of_range("key " + std::string(key) + " not found"); + } + + // return element from array at given key + return it->second; +} + +/*! +@copydoc json::at(const char* key) +*/ +json::reference json::at(const std::string& key) +{ + return at(key.c_str()); +} + +/*! +This function realizes read/write access to object elements given a string +key. + +@param key the key index of the element to return from the object +@return reference to a JSON object for the given key (exception if key does not + exist) + +@pre Object is an object. + +@exception std::domain_error if object is not an object +@exception std::out_of_range if key was not found (via std::map::at) +*/ +json::reference json::at(const char* key) +{ + // this function operator only works for objects + if (type_ != value_t::object) + { + throw std::domain_error("cannot add entry with key " + + std::string(key) + " to " + type_name()); + } + + // return reference to element from array at given index + return value_.object->at(key); +} + +/*! +@copydoc json::at(const char *key) const +*/ +json::const_reference json::at(const std::string& key) const +{ + return at(key.c_str()); +} + +/*! +This operator realizes read-only access to object elements given a string +key. + +@param key the key index of the element to return from the object +@return read-only reference to element for the given key + +@pre Object is an object. +@exception std::domain_error if object is not an object +@exception std::out_of_range if key is not found (via std::map::at) +*/ +json::const_reference json::at(const char* key) const +{ + // this [] operator only works for objects + if (type_ != value_t::object) + { + throw std::domain_error("cannot get entry with key " + + std::string(key) + " from " + type_name()); + } + + // return element from array at given key + return value_.object->at(key); +} + +/*! +Returns the size of the JSON object. + +@return the size of the JSON object; the size is the number of elements in + compounds (array and object), 1 for value types (true, false, number, + string), and 0 for null. + +@invariant The size is reported as 0 if and only if empty() would return true. +*/ +json::size_type json::size() const noexcept +{ + switch (type_) + { + case (value_t::array): + { + return value_.array->size(); + } + case (value_t::object): + { + return value_.object->size(); + } + case (value_t::null): + { + return 0; + } + default: + { + return 1; + } + } +} + +/*! +Returns the maximal size of the JSON object. + +@return the maximal size of the JSON object; the maximal size is the maximal + number of elements in compounds (array and object), 1 for value types + (true, false, number, string), and 0 for null. +*/ +json::size_type json::max_size() const noexcept +{ + switch (type_) + { + case (value_t::array): + { + return value_.array->max_size(); + } + case (value_t::object): + { + return value_.object->max_size(); + } + case (value_t::null): + { + return 0; + } + default: + { + return 1; + } + } +} + +/*! +Returns whether a JSON object is empty. + +@return true for null objects and empty compounds (array and object); false + for value types (true, false, number, string) and filled compounds + (array and object). + +@invariant Empty would report true if and only if size() would return 0. +*/ +bool json::empty() const noexcept +{ + switch (type_) + { + case (value_t::array): + { + return value_.array->empty(); + } + case (value_t::object): + { + return value_.object->empty(); + } + case (value_t::null): + { + return true; + } + default: + { + return false; + } + } +} + +/*! +Removes all elements from compounds and resets values to default. + +@invariant Clear will set any value type to its default value which is empty + for compounds, false for booleans, 0 for integer numbers, and 0.0 + for floating numbers. +*/ +void json::clear() noexcept +{ + switch (type_) + { + case (value_t::array): + { + value_.array->clear(); + break; + } + case (value_t::object): + { + value_.object->clear(); + break; + } + case (value_t::string): + { + value_.string->clear(); + break; + } + case (value_t::boolean): + { + value_.boolean = {}; + break; + } + case (value_t::number): + { + value_.number = {}; + break; + } + case (value_t::number_float): + { + value_.number_float = {}; + break; + } + default: + { + break; + } + } +} + +void json::swap(json& o) noexcept +{ + std::swap(type_, o.type_); + std::swap(value_, o.value_); +} + +json::value_t json::type() const noexcept +{ + return type_; +} + +json::iterator json::find(const std::string& key) +{ + return find(key.c_str()); +} + +json::const_iterator json::find(const std::string& key) const +{ + return find(key.c_str()); +} + +json::iterator json::find(const char* key) +{ + auto result = end(); + + if (type_ == value_t::object) + { + delete result.oi_; + result.oi_ = new object_t::iterator(value_.object->find(key)); + result.invalid = (*(result.oi_) == value_.object->end()); + } + + return result; +} + +json::const_iterator json::find(const char* key) const +{ + auto result = cend(); + + if (type_ == value_t::object) + { + delete result.oi_; + result.oi_ = new object_t::const_iterator(value_.object->find(key)); + result.invalid = (*(result.oi_) == value_.object->cend()); + } + + return result; +} + +bool json::operator==(const json& o) const noexcept +{ + switch (type_) + { + case (value_t::array): + { + if (o.type_ == value_t::array) + { + return *value_.array == *o.value_.array; + } + break; + } + case (value_t::object): + { + if (o.type_ == value_t::object) + { + return *value_.object == *o.value_.object; + } + break; + } + case (value_t::null): + { + if (o.type_ == value_t::null) + { + return true; + } + break; + } + case (value_t::string): + { + if (o.type_ == value_t::string) + { + return *value_.string == *o.value_.string; + } + break; + } + case (value_t::boolean): + { + if (o.type_ == value_t::boolean) + { + return value_.boolean == o.value_.boolean; + } + break; + } + case (value_t::number): + { + if (o.type_ == value_t::number) + { + return value_.number == o.value_.number; + } + if (o.type_ == value_t::number_float) + { + return value_.number == static_cast(o.value_.number_float); + } + break; + } + case (value_t::number_float): + { + if (o.type_ == value_t::number) + { + return value_.number_float == static_cast(o.value_.number); + } + if (o.type_ == value_t::number_float) + { + return value_.number_float == o.value_.number_float; + } + break; + } + } + + return false; +} + +bool json::operator!=(const json& o) const noexcept +{ + return not operator==(o); +} + + +json::iterator json::begin() noexcept +{ + return json::iterator(this, true); +} + +json::iterator json::end() noexcept +{ + return json::iterator(this, false); +} + +json::const_iterator json::begin() const noexcept +{ + return json::const_iterator(this, true); +} + +json::const_iterator json::end() const noexcept +{ + return json::const_iterator(this, false); +} + +json::const_iterator json::cbegin() const noexcept +{ + return json::const_iterator(this, true); +} + +json::const_iterator json::cend() const noexcept +{ + return json::const_iterator(this, false); +} + +json::reverse_iterator json::rbegin() noexcept +{ + return reverse_iterator(end()); +} + +json::reverse_iterator json::rend() noexcept +{ + return reverse_iterator(begin()); +} + +json::const_reverse_iterator json::crbegin() const noexcept +{ + return const_reverse_iterator(cend()); +} + +json::const_reverse_iterator json::crend() const noexcept +{ + return const_reverse_iterator(cbegin()); +} + + +json::iterator::iterator(json* j, bool begin) + : object_(j), invalid(not begin or j == nullptr) +{ + if (object_ != nullptr) + { + if (object_->type_ == json::value_t::array) + { + if (begin) + { + vi_ = new array_t::iterator(object_->value_.array->begin()); + invalid = (*vi_ == object_->value_.array->end()); + } + else + { + vi_ = new array_t::iterator(object_->value_.array->end()); + } + } + else if (object_->type_ == json::value_t::object) + { + if (begin) + { + oi_ = new object_t::iterator(object_->value_.object->begin()); + invalid = (*oi_ == object_->value_.object->end()); + } + else + { + oi_ = new object_t::iterator(object_->value_.object->end()); + } + } + } +} + +json::iterator::iterator(const json::iterator& o) + : object_(o.object_), invalid(o.invalid) +{ + if (o.vi_ != nullptr) + { + vi_ = new array_t::iterator(*(o.vi_)); + } + + if (o.oi_ != nullptr) + { + oi_ = new object_t::iterator(*(o.oi_)); + } +} + +json::iterator::~iterator() +{ + delete vi_; + delete oi_; +} + +json::iterator& json::iterator::operator=(json::iterator o) +{ + std::swap(object_, o.object_); + std::swap(vi_, o.vi_); + std::swap(oi_, o.oi_); + std::swap(invalid, o.invalid); + return *this; +} + +bool json::iterator::operator==(const json::iterator& o) const +{ + if (object_ != nullptr and o.object_ != nullptr) + { + if (object_->type_ == json::value_t::array and o.object_->type_ == json::value_t::array) + { + return (*vi_ == *(o.vi_)); + } + if (object_->type_ == json::value_t::object and o.object_->type_ == json::value_t::object) + { + return (*oi_ == *(o.oi_)); + } + + if (invalid == o.invalid and object_ == o.object_) + { + return true; + } + } + return false; +} + +bool json::iterator::operator!=(const json::iterator& o) const +{ + return not operator==(o); +} + +json::iterator& json::iterator::operator++() +{ + if (object_ != nullptr) + { + switch (object_->type_) + { + case (json::value_t::array): + { + std::advance(*vi_, 1); + invalid = (*vi_ == object_->value_.array->end()); + break; + } + case (json::value_t::object): + { + std::advance(*oi_, 1); + invalid = (*oi_ == object_->value_.object->end()); + break; + } + default: + { + invalid = true; + break; + } + } + } + + return *this; +} + +json::iterator& json::iterator::operator--() +{ + if (object_ != nullptr) + { + switch (object_->type_) + { + case (json::value_t::array): + { + invalid = (*vi_ == object_->value_.array->begin()); + std::advance(*vi_, -1); + break; + } + case (json::value_t::object): + { + invalid = (*oi_ == object_->value_.object->begin()); + std::advance(*oi_, -1); + break; + } + default: + { + invalid = true; + break; + } + } + } + + return *this; +} + +json& json::iterator::operator*() const +{ + if (object_ == nullptr or invalid) + { + throw std::out_of_range("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_t::array): + { + return **vi_; + } + case (json::value_t::object): + { + return (*oi_)->second; + } + default: + { + return *object_; + } + } +} + +json* json::iterator::operator->() const +{ + if (object_ == nullptr or invalid) + { + throw std::out_of_range("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_t::array): + { + return &(**vi_); + } + case (json::value_t::object): + { + return &((*oi_)->second); + } + default: + { + return object_; + } + } +} + +std::string json::iterator::key() const +{ + if (object_ == nullptr or invalid or object_->type_ != json::value_t::object) + { + throw std::out_of_range("cannot get value"); + } + + return (*oi_)->first; +} + +json& json::iterator::value() const +{ + if (object_ == nullptr or invalid) + { + throw std::out_of_range("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_t::array): + { + return **vi_; + } + case (json::value_t::object): + { + return (*oi_)->second; + } + default: + { + return *object_; + } + } +} + + +json::const_iterator::const_iterator(const json* j, bool begin) + : object_(j), invalid(not begin or j == nullptr) +{ + if (object_ != nullptr) + { + if (object_->type_ == json::value_t::array) + { + if (begin) + { + vi_ = new array_t::const_iterator(object_->value_.array->cbegin()); + invalid = (*vi_ == object_->value_.array->cend()); + } + else + { + vi_ = new array_t::const_iterator(object_->value_.array->cend()); + } + } + else if (object_->type_ == json::value_t::object) + { + if (begin) + { + oi_ = new object_t::const_iterator(object_->value_.object->cbegin()); + invalid = (*oi_ == object_->value_.object->cend()); + } + else + { + oi_ = new object_t::const_iterator(object_->value_.object->cend()); + } + } + } +} + +json::const_iterator::const_iterator(const json::const_iterator& o) + : object_(o.object_), invalid(o.invalid) +{ + if (o.vi_ != nullptr) + { + vi_ = new array_t::const_iterator(*(o.vi_)); + } + if (o.oi_ != nullptr) + { + oi_ = new object_t::const_iterator(*(o.oi_)); + } +} + +json::const_iterator::const_iterator(const json::iterator& o) + : object_(o.object_), invalid(o.invalid) +{ + if (o.vi_ != nullptr) + { + vi_ = new array_t::const_iterator(*(o.vi_)); + } + if (o.oi_ != nullptr) + { + oi_ = new object_t::const_iterator(*(o.oi_)); + } +} + +json::const_iterator::~const_iterator() +{ + delete vi_; + delete oi_; +} + +json::const_iterator& json::const_iterator::operator=(json::const_iterator o) +{ + std::swap(object_, o.object_); + std::swap(vi_, o.vi_); + std::swap(oi_, o.oi_); + std::swap(invalid, o.invalid); + return *this; +} + +bool json::const_iterator::operator==(const json::const_iterator& o) const +{ + if (object_ != nullptr and o.object_ != nullptr) + { + if (object_->type_ == json::value_t::array and o.object_->type_ == json::value_t::array) + { + return (*vi_ == *(o.vi_)); + } + if (object_->type_ == json::value_t::object and o.object_->type_ == json::value_t::object) + { + return (*oi_ == *(o.oi_)); + } + if (invalid == o.invalid and object_ == o.object_) + { + return true; + } + } + + return false; +} + +bool json::const_iterator::operator!=(const json::const_iterator& o) const +{ + return not operator==(o); +} + +json::const_iterator& json::const_iterator::operator++() +{ + if (object_ != nullptr) + { + switch (object_->type_) + { + case (json::value_t::array): + { + std::advance(*vi_, 1); + invalid = (*vi_ == object_->value_.array->end()); + break; + } + case (json::value_t::object): + { + std::advance(*oi_, 1); + invalid = (*oi_ == object_->value_.object->end()); + break; + } + default: + { + invalid = true; + break; + } + } + } + + return *this; +} + +json::const_iterator& json::const_iterator::operator--() +{ + if (object_ != nullptr) + { + switch (object_->type_) + { + case (json::value_t::array): + { + invalid = (*vi_ == object_->value_.array->begin()); + std::advance(*vi_, -1); + break; + } + case (json::value_t::object): + { + invalid = (*oi_ == object_->value_.object->begin()); + std::advance(*oi_, -1); + break; + } + default: + { + invalid = true; + break; + } + } + } + + return *this; +} + +const json& json::const_iterator::operator*() const +{ + if (object_ == nullptr or invalid) + { + throw std::out_of_range("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_t::array): + { + return **vi_; + } + case (json::value_t::object): + { + return (*oi_)->second; + } + default: + { + return *object_; + } + } +} + +const json* json::const_iterator::operator->() const +{ + if (object_ == nullptr or invalid) + { + throw std::out_of_range("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_t::array): + { + return &(**vi_); + } + case (json::value_t::object): + { + return &((*oi_)->second); + } + default: + { + return object_; + } + } +} + +std::string json::const_iterator::key() const +{ + if (object_ == nullptr or invalid or object_->type_ != json::value_t::object) + { + throw std::out_of_range("cannot get value"); + } + + return (*oi_)->first; +} + +const json& json::const_iterator::value() const +{ + if (object_ == nullptr or invalid) + { + throw std::out_of_range("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_t::array): + { + return **vi_; + } + case (json::value_t::object): + { + return (*oi_)->second; + } + default: + { + return *object_; + } + } +} + + +/*! +Initialize the JSON parser given a string \p s. + +@note After initialization, the function @ref parse has to be called manually. + +@param s string to parse + +@post \p s is copied to the buffer @ref buffer_ and the first character is + read. Whitespace is skipped. +*/ +json::parser::parser(const char* s) + : buffer_(s) +{ + // read first character + next(); +} + +/*! +@copydoc json::parser::parser(const char* s) +*/ +json::parser::parser(const std::string& s) + : buffer_(s) +{ + // read first character + next(); +} + +/*! +Initialize the JSON parser given an input stream \p _is. + +@note After initialization, the function @ref parse has to be called manually. + +\param _is input stream to parse + +@post \p _is is copied to the buffer @ref buffer_ and the firsr character is + read. Whitespace is skipped. + +*/ +json::parser::parser(std::istream& _is) +{ + while (_is) + { + std::string input_line; + std::getline(_is, input_line); + buffer_ += input_line; + } + + // read first character + next(); +} + +json json::parser::parse() +{ + switch (current_) + { + case ('{'): + { + // explicitly set result to object to cope with {} + json result(value_t::object); + + next(); + + // process nonempty object + if (current_ != '}') + { + do + { + // key + auto key = parseString(); + + // colon + expect(':'); + + // value + result[std::move(key)] = parse(); + key.clear(); + } + while (current_ == ',' and next()); + } + + // closing brace + expect('}'); + + return result; + } + + case ('['): + { + // explicitly set result to array to cope with [] + json result(value_t::array); + + next(); + + // process nonempty array + if (current_ != ']') + { + do + { + result.push_back(parse()); + } + while (current_ == ',' and next()); + } + + // closing bracket + expect(']'); + + return result; + } + + case ('\"'): + { + return json(parseString()); + } + + case ('t'): + { + parseTrue(); + return json(true); + } + + case ('f'): + { + parseFalse(); + return json(false); + } + + case ('n'): + { + parseNull(); + return json(); + } + + case ('-'): + case ('0'): + case ('1'): + case ('2'): + case ('3'): + case ('4'): + case ('5'): + case ('6'): + case ('7'): + case ('8'): + case ('9'): + { + // remember position of number's first character + const auto _firstpos_ = pos_ - 1; + + while (next() and (std::isdigit(current_) or current_ == '.' + or current_ == 'e' or current_ == 'E' + or current_ == '+' or current_ == '-')); + + try + { + const auto float_val = std::stold(buffer_.substr(_firstpos_, pos_ - _firstpos_)); + const auto int_val = static_cast(float_val); + + // check if conversion loses precision + if (float_val == int_val) + { + // we would not lose precision -> int + return json(int_val); + } + else + { + // we would lose precision -> float + return json(static_cast(float_val)); + } + } + catch (...) + { + error("error translating " + + buffer_.substr(_firstpos_, pos_ - _firstpos_) + " to number"); + } + } + + default: + { + error("unexpected character"); + } + } +} + +/*! +This function reads the next character from the buffer while ignoring all +trailing whitespace. If another character could be read, the function returns +true. If the end of the buffer is reached, false is returned. + +@return whether another non-whitespace character could be read + +@post current_ holds the next character +*/ +bool json::parser::next() +{ + if (pos_ == buffer_.size()) + { + return false; + } + + current_ = buffer_[pos_++]; + + // skip trailing whitespace + while (std::isspace(current_)) + { + if (pos_ == buffer_.size()) + { + return false; + } + + current_ = buffer_[pos_++]; + } + + return true; +} + +/*! +This function encapsulates the error reporting functions of the parser class. +It throws a \p std::invalid_argument exception with a description where the +error occurred (given as the number of characters read), what went wrong (using +the error message \p msg), and the last read token. + +@param msg an error message +@return This function does not return. + +@exception std::invalid_argument whenever the function is called +*/ +void json::parser::error(const std::string& msg) const +{ + throw std::invalid_argument("parse error at position " + + std::to_string(pos_) + ": " + msg + + ", last read: '" + current_ + "'"); +} + +/*! +Parses a string after opening quotes (\p ") where read. + +@return the parsed string + +@pre An opening quote \p " was read in the main parse function @ref parse. + pos_ is the position after the opening quote. + +@post The character after the closing quote \p " is the current character @ref + current_. Whitespace is skipped. + +@todo Unicode escapes such as \uxxxx are missing - see + https://github.com/nlohmann/json/issues/12 +*/ +std::string json::parser::parseString() +{ + // true if and only if the amount of backslashes before the current + // character is even + bool evenAmountOfBackslashes = true; + + // the result of the parse process + std::string result; + + // iterate with pos_ over the whole input until we found the end and return + // or we exit via error() + for (; pos_ < buffer_.size(); pos_++) + { + char currentChar = buffer_[pos_]; + + if (not evenAmountOfBackslashes) + { + // uneven amount of backslashes means the user wants to escape + // something so we know there is a case such as '\X' or '\\\X' but + // we don't know yet what X is. + // at this point in the code, the currentChar has the value of X. + + // slash, backslash and quote are copied as is + if (currentChar == '/' or currentChar == '\\' or currentChar == '"') + { + result += currentChar; + } + else + { + // all other characters are replaced by their respective special + // character + switch (currentChar) + { + case 't': + { + result += '\t'; + break; + } + case 'b': + { + result += '\b'; + break; + } + case 'f': + { + result += '\f'; + break; + } + case 'n': + { + result += '\n'; + break; + } + case 'r': + { + result += '\r'; + break; + } + case 'u': + { + // \uXXXX[\uXXXX] is used for escaping unicode, which + // has it's own subroutine. + result += parseUnicodeEscape(); + // the parsing process has brought us one step behind + // the unicode escape sequence: + // \uXXXX + // ^ + // we need to go one character back or the parser would + // skip the character we are currently pointing at as + // the for-loop will decrement pos_ after this iteration + pos_--; + break; + } + default: + { + error("expected one of \\, /, b, f, n, r, t, u behind backslash."); + } + } + } + } + else + { + if (currentChar == '"') + { + // currentChar is a quote, so we found the end of the string + + // set pos_ behind the trailing quote + pos_++; + // find next char to parse + next(); + + // bring the result of the parsing process back to the caller + return result; + } + else if (currentChar != '\\') + { + // all non-backslash characters are added to the end of the + // result string. The only backslashes we want in the result + // are the ones that are escaped (which happens above). + result += currentChar; + } + } + + // remember if we have an even amount of backslashes before the current + // character + if (currentChar == '\\') + { + // jump between even/uneven for each backslash we encounter + evenAmountOfBackslashes = not evenAmountOfBackslashes; + } + else + { + // zero backslashes are also an even number, so as soon as we + // encounter a non-backslash the chain of backslashes breaks and + // we start again from zero + evenAmountOfBackslashes = true; + } + } + + // we iterated over the whole string without finding a unescaped quote + // so the given string is malformed + error("expected '\"'"); +} + + + +/*! +Turns a code point into it's UTF-8 representation. +You should only pass numbers < 0x10ffff into this function +(everything else is a invalid code point). + +@return the UTF-8 representation of the given code point +*/ +std::string json::parser::codePointToUTF8(unsigned int codePoint) const +{ + // this method contains a lot of bit manipulations to + // build the bytes for UTF-8. + + // the '(... >> S) & 0xHH'-patterns are used to retrieve + // certain bits from the code points. + + // all static casts in this method have boundary checks + + // we initialize all strings with their final length + // (e.g. 1 to 4 bytes) to save the reallocations. + + if (codePoint <= 0x7f) + { + // it's just a ASCII compatible codePoint, + // so we just interpret the point as a character + // and return ASCII + + return std::string(1, static_cast(codePoint)); + } + // if true, we need two bytes to encode this as UTF-8 + else if (codePoint <= 0x7ff) + { + // the 0xC0 enables the two most significant two bits + // to make this a two-byte UTF-8 character. + std::string result(2, static_cast(0xC0 | ((codePoint >> 6) & 0x1F))); + result[1] = static_cast(0x80 | (codePoint & 0x3F)); + return result; + } + // if true, now we need three bytes to encode this as UTF-8 + else if (codePoint <= 0xffff) + { + // the 0xE0 enables the three most significant two bits + // to make this a three-byte UTF-8 character. + std::string result(3, static_cast(0xE0 | ((codePoint >> 12) & 0x0F))); + result[1] = static_cast(0x80 | ((codePoint >> 6) & 0x3F)); + result[2] = static_cast(0x80 | (codePoint & 0x3F)); + return result; + } + // if true, we need maximal four bytes to encode this as UTF-8 + else if (codePoint <= 0x10ffff) + { + // the 0xE0 enables the four most significant two bits + // to make this a three-byte UTF-8 character. + std::string result(4, static_cast(0xF0 | ((codePoint >> 18) & 0x07))); + result[1] = static_cast(0x80 | ((codePoint >> 12) & 0x3F)); + result[2] = static_cast(0x80 | ((codePoint >> 6) & 0x3F)); + result[3] = static_cast(0x80 | (codePoint & 0x3F)); + return result; + } + else + { + // Can't be tested without direct access to this private method. + std::string errorMessage = "Invalid codePoint: "; + errorMessage += codePoint; + error(errorMessage); + } +} + +/*! +Parses 4 hexadecimal characters as a number. + +@return the value of the number the hexadecimal characters represent. + +@pre pos_ is pointing to the first of the 4 hexadecimal characters. + +@post pos_ is pointing to the character after the 4 hexadecimal characters. +*/ +unsigned int json::parser::parse4HexCodePoint() +{ + const auto startPos = pos_; + + // check if the remaining buffer is long enough to even hold 4 characters + if (pos_ + 3 >= buffer_.size()) + { + error("Got end of input while parsing unicode escape sequence \\uXXXX"); + } + + // make a string that can hold the pair + std::string hexCode(4, ' '); + + for (; pos_ < startPos + 4; pos_++) + { + // no boundary check here as we already checked above + char currentChar = buffer_[pos_]; + + // check if we have a hexadecimal character + if ((currentChar >= '0' and currentChar <= '9') + or (currentChar >= 'a' and currentChar <= 'f') + or (currentChar >= 'A' and currentChar <= 'F')) + { + // all is well, we have valid hexadecimal chars + // so we copy that char into our string + hexCode[pos_ - startPos] = currentChar; + } + else + { + error("Found non-hexadecimal character in unicode escape sequence!"); + } + } + // the cast is safe as 4 hex characters can't present more than 16 bits + // the input to stoul was checked to contain only hexadecimal characters + // (see above) + return static_cast(std::stoul(hexCode, nullptr, 16)); +} + +/*! +Parses the unicode escape codes as defined in the ECMA-404. +The escape sequence has two forms: +1. \uXXXX +2. \uXXXX\uYYYY +where X and Y are a hexadecimal character (a-zA-Z0-9). + +Form 1 just contains the unicode code point in the hexadecimal number XXXX. +Form 2 is encoding a UTF-16 surrogate pair. The high surrogate is XXXX, the low +surrogate is YYYY. + +@return the UTF-8 character this unicode escape sequence escaped. + +@pre pos_ is pointing at at the 'u' behind the first backslash. + +@post pos_ is pointing at the character behind the last X (or Y in form 2). +*/ +std::string json::parser::parseUnicodeEscape() +{ + // jump to the first hex value + pos_++; + // parse the hex first hex values + unsigned int firstCodePoint = parse4HexCodePoint(); + + if (firstCodePoint >= 0xD800 and firstCodePoint <= 0xDBFF) + { + // we found invalid code points, which means we either have a malformed + // input or we found a high surrogate. + // we can only find out by seeing if the next character also wants to + // encode a unicode character (so, we have the \uXXXX\uXXXX case here). + + // jump behind the next \u + pos_ += 2; + // try to parse the next hex values. + // the method does boundary checking for us, so no need to do that here + unsigned secondCodePoint = parse4HexCodePoint(); + // ok, we have a low surrogate, check if it is a valid one + if (secondCodePoint >= 0xDC00 and secondCodePoint <= 0xDFFF) + { + // calculate the code point from the pair according to the spec + unsigned int finalCodePoint = + // high surrogate occupies the most significant 22 bits + (firstCodePoint << 10) + // low surrogate occupies the least significant 15 bits + + secondCodePoint + // there is still the 0xD800, 0xDC00 and 0x10000 noise in + // the result + // so we have to substract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + + // we transform the calculated point into UTF-8 + return codePointToUTF8(finalCodePoint); + } + else + { + error("missing low surrogate"); + } + + } + // We have Form 1, so we just interpret the XXXX as a code point + return codePointToUTF8(firstCodePoint); +} + + +/*! +This function is called in case a \p "t" is read in the main parse function +@ref parse. In the standard, the \p "true" token is the only candidate, so the +next three characters are expected to be \p "rue". In case of a mismatch, an +error is raised via @ref error. + +@pre A \p "t" was read in the main parse function @ref parse. +@post The character after the \p "true" is the current character. Whitespace is + skipped. +*/ +void json::parser::parseTrue() +{ + if (buffer_.substr(pos_, 3) != "rue") + { + error("expected true"); + } + + pos_ += 3; + + // read next character + next(); +} + +/*! +This function is called in case an \p "f" is read in the main parse function +@ref parse. In the standard, the \p "false" token is the only candidate, so the +next four characters are expected to be \p "alse". In case of a mismatch, an +error is raised via @ref error. + +@pre An \p "f" was read in the main parse function. +@post The character after the \p "false" is the current character. Whitespace + is skipped. +*/ +void json::parser::parseFalse() +{ + if (buffer_.substr(pos_, 4) != "alse") + { + error("expected false"); + } + + pos_ += 4; + + // read next character + next(); +} + +/*! +This function is called in case an \p "n" is read in the main parse function +@ref parse. In the standard, the \p "null" token is the only candidate, so the +next three characters are expected to be \p "ull". In case of a mismatch, an +error is raised via @ref error. + +@pre An \p "n" was read in the main parse function. +@post The character after the \p "null" is the current character. Whitespace is + skipped. +*/ +void json::parser::parseNull() +{ + if (buffer_.substr(pos_, 3) != "ull") + { + error("expected null"); + } + + pos_ += 3; + + // read next character + next(); +} + +/*! +This function wraps functionality to check whether the current character @ref +current_ matches a given character \p c. In case of a match, the next character +of the buffer @ref buffer_ is read. In case of a mismatch, an error is raised +via @ref error. + +@param c character that is expected + +@post The next chatacter is read. Whitespace is skipped. +*/ +void json::parser::expect(const char c) +{ + if (current_ != c) + { + std::string msg = "expected '"; + msg.append(1, c); + msg += "'"; + error(msg); + } + else + { + next(); + } +} + +} + +/*! +This operator implements a user-defined string literal for JSON objects. It can +be used by adding \p "_json" to a string literal and returns a JSON object if +no parse error occurred. + +@param s a string representation of a JSON object +@return a JSON object +*/ +nlohmann::json operator "" _json(const char* s, std::size_t) +{ + return nlohmann::json::parse(s); +} diff --git a/src/json/json.h b/src/json/json.h new file mode 100644 index 0000000..f6acc76 --- /dev/null +++ b/src/json/json.h @@ -0,0 +1,570 @@ +/*! +@file +@copyright The code is licensed under the MIT License + , + Copyright (c) 2013-2015 Niels Lohmann. + +@author Niels Lohmann + +@see https://github.com/nlohmann/json +*/ + +#pragma once + +#include // std::initializer_list +#include // std::istream, std::ostream +#include // std::map +#include // std::string +#include // std::vector +#include // std::iterator +#include // std::numeric_limits +#include // std::hash + +namespace nlohmann +{ + +/*! +@brief JSON for Modern C++ + +The size of a JSON object is 16 bytes: 8 bytes for the value union whose +largest item is a pointer type and another 8 byte for an element of the +type union. The latter only needs 1 byte - the remaining 7 bytes are wasted +due to alignment. + +@see http://stackoverflow.com/questions/7758580/writing-your-own-stl-container/7759622#7759622 + +@bug Numbers are currently handled too generously. There are several formats + that are forbidden by the standard, but are accepted by the parser. + +@todo Implement json::insert(), json::emplace(), json::emplace_back, json::erase +*/ +class json +{ + public: + // forward declaration to friend this class + class iterator; + class const_iterator; + + public: + // container types + /// the type of elements in a JSON class + using value_type = json; + /// the type of element references + using reference = json&; + /// the type of const element references + using const_reference = const json&; + /// the type of pointers to elements + using pointer = json*; + /// the type of const pointers to elements + using const_pointer = const json*; + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + /// an iterator for a JSON container + using iterator = json::iterator; + /// a const iterator for a JSON container + using const_iterator = json::const_iterator; + /// a reverse iterator for a JSON container + using reverse_iterator = std::reverse_iterator; + /// a const reverse iterator for a JSON container + using const_reverse_iterator = std::reverse_iterator; + + /// a type for an object + using object_t = std::map; + /// a type for an array + using array_t = std::vector; + /// a type for a string + using string_t = std::string; + /// a type for a Boolean + using boolean_t = bool; + /// a type for an integer number + using number_t = int64_t; + /// a type for a floating point number + using number_float_t = double; + /// a type for list initialization + using list_init_t = std::initializer_list; + + /// a JSON value + union value + { + /// array as pointer to array_t + array_t* array; + /// object as pointer to object_t + object_t* object; + /// string as pointer to string_t + string_t* string; + /// Boolean + boolean_t boolean; + /// number (integer) + number_t number; + /// number (float) + number_float_t number_float; + + /// default constructor + value() = default; + /// constructor for arrays + value(array_t*); + /// constructor for objects + value(object_t*); + /// constructor for strings + value(string_t*); + /// constructor for Booleans + value(boolean_t); + /// constructor for numbers (integer) + value(number_t); + /// constructor for numbers (float) + value(number_float_t); + }; + + /// possible types of a JSON object + enum class value_t : uint8_t + { + /// ordered collection of values + array = 0, + /// unordered set of name/value pairs + object, + /// null value + null, + /// string value + string, + /// Boolean value + boolean, + /// number value (integer) + number, + /// number value (float) + number_float + }; + + public: + /// create an object according to given type + json(const value_t); + /// create a null object + json() = default; + /// create a null object + json(std::nullptr_t) noexcept; + /// create a string object from a C++ string + json(const std::string&); + /// create a string object from a C++ string (move) + json(std::string&&); + /// create a string object from a C string + json(const char*); + /// create a Boolean object + json(const bool) noexcept; + /// create an array + json(const array_t&); + /// create an array (move) + json(array_t&&); + /// create an object + json(const object_t&); + /// create an object (move) + json(object_t&&); + /// create from an initializer list (to an array or object) + json(list_init_t); + + /*! + @brief create a number object (integer) + @param n an integer number to wrap in a JSON object + */ + template::is_integer, T>::type + = 0> + json(const T n) noexcept + : type_(value_t::number), + value_(static_cast(n)) + {} + + /*! + @brief create a number object (float) + @param n a floating point number to wrap in a JSON object + */ + template::value>::type + > + json(const T n) noexcept + : type_(value_t::number_float), + value_(static_cast(n)) + {} + + /*! + @brief create an array object + @param v any type of container whose elements can be use to construct + JSON objects (e.g., std::vector, std::set, std::array) + @note For some reason, we need to explicitly forbid JSON iterator types. + */ + template ::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + std::is_constructible::value, int>::type + = 0> + json(const V& v) : json(array_t(v.begin(), v.end())) + {} + + /*! + @brief create a JSON object + @param v any type of associative container whose elements can be use to + construct JSON objects (e.g., std::map) + */ + template ::value and + std::is_constructible::value, int>::type + = 0> + json(const V& v) : json(object_t(v.begin(), v.end())) + {} + + /// copy constructor + json(const json&); + /// move constructor + json(json&&) noexcept; + + /// copy assignment + json& operator=(json) noexcept; + + /// destructor + ~json() noexcept; + + /// create from string representation + static json parse(const std::string&); + /// create from string representation + static json parse(const char*); + + private: + /// return the type as string + std::string type_name() const noexcept; + + /// dump the object (with pretty printer) + std::string dump(const bool, const unsigned int, unsigned int = 0) const noexcept; + /// replaced a character in a string with another string + void replaceChar(std::string& str, char c, const std::string& replacement) const; + /// escapes special characters to safely dump the string + std::string escapeString(const std::string&) const; + + public: + /// explicit value conversion + template + T get() const; + + /// implicit conversion to string representation + operator std::string() const; + /// implicit conversion to integer (only for numbers) + operator int() const; + /// implicit conversion to integer (only for numbers) + operator int64_t() const; + /// implicit conversion to double (only for numbers) + operator double() const; + /// implicit conversion to Boolean (only for Booleans) + operator bool() const; + /// implicit conversion to JSON vector (not for objects) + operator array_t() const; + /// implicit conversion to JSON map (only for objects) + operator object_t() const; + + /// serialize to stream + friend std::ostream& operator<<(std::ostream& o, const json& j) + { + o << j.dump(); + return o; + } + /// serialize to stream + friend std::ostream& operator>>(const json& j, std::ostream& o) + { + o << j.dump(); + return o; + } + + /// deserialize from stream + friend std::istream& operator>>(std::istream& i, json& j) + { + j = parser(i).parse(); + return i; + } + /// deserialize from stream + friend std::istream& operator<<(json& j, std::istream& i) + { + j = parser(i).parse(); + return i; + } + + /// explicit serialization + std::string dump(int = -1) const noexcept; + + /// add constructible objects to an array + template::value>::type = 0> + json & operator+=(const T& o) + { + push_back(json(o)); + return *this; + } + + /// add an object/array to an array + json& operator+=(const json&); + + /// add a pair to an object + json& operator+=(const object_t::value_type&); + /// add a list of elements to array or list of pairs to object + json& operator+=(list_init_t); + + /// add constructible objects to an array + template::value>::type = 0> + void push_back(const T& o) + { + push_back(json(o)); + } + + /// add an object/array to an array + void push_back(const json&); + /// add an object/array to an array (move) + void push_back(json&&); + + /// add a pair to an object + void push_back(const object_t::value_type&); + /// add a list of elements to array or list of pairs to object + void push_back(list_init_t); + + /// operator to set an element in an array + reference operator[](const int); + /// operator to get an element in an array + const_reference operator[](const int) const; + /// operator to get an element in an array + reference at(const int); + /// operator to get an element in an array + const_reference at(const int) const; + + /// operator to set an element in an object + reference operator[](const std::string&); + /// operator to set an element in an object + reference operator[](const char*); + /// operator to get an element in an object + const_reference operator[](const std::string&) const; + /// operator to get an element in an object + const_reference operator[](const char*) const; + /// operator to set an element in an object + reference at(const std::string&); + /// operator to set an element in an object + reference at(const char*); + /// operator to get an element in an object + const_reference at(const std::string&) const; + /// operator to get an element in an object + const_reference at(const char*) const; + + /// return the number of stored values + size_type size() const noexcept; + /// return the maximal number of values that can be stored + size_type max_size() const noexcept; + /// checks whether object is empty + bool empty() const noexcept; + /// removes all elements from compounds and resets values to default + void clear() noexcept; + + /// swaps content with other object + void swap(json&) noexcept; + + /// return the type of the object + value_t type() const noexcept; + + /// find an element in an object (returns end() iterator otherwise) + iterator find(const std::string&); + /// find an element in an object (returns end() iterator otherwise) + const_iterator find(const std::string&) const; + /// find an element in an object (returns end() iterator otherwise) + iterator find(const char*); + /// find an element in an object (returns end() iterator otherwise) + const_iterator find(const char*) const; + + /// lexicographically compares the values + bool operator==(const json&) const noexcept; + /// lexicographically compares the values + bool operator!=(const json&) const noexcept; + + /// returns an iterator to the beginning (array/object) + iterator begin() noexcept; + /// returns an iterator to the end (array/object) + iterator end() noexcept; + /// returns an iterator to the beginning (array/object) + const_iterator begin() const noexcept; + /// returns an iterator to the end (array/object) + const_iterator end() const noexcept; + /// returns an iterator to the beginning (array/object) + const_iterator cbegin() const noexcept; + /// returns an iterator to the end (array/object) + const_iterator cend() const noexcept; + /// returns a reverse iterator to the beginning + reverse_iterator rbegin() noexcept; + /// returns a reverse iterator to the end + reverse_iterator rend() noexcept; + /// returns a reverse iterator to the beginning + const_reverse_iterator crbegin() const noexcept; + /// returns a reverse iterator to the end + const_reverse_iterator crend() const noexcept; + + private: + /// the type of this object + value_t type_ = value_t::null; + + /// the payload + value value_ {}; + + public: + /// an iterator + class iterator : public std::iterator + { + friend class json; + friend class json::const_iterator; + + public: + iterator() = default; + iterator(json*, bool); + iterator(const iterator&); + ~iterator(); + + iterator& operator=(iterator); + bool operator==(const iterator&) const; + bool operator!=(const iterator&) const; + iterator& operator++(); + iterator& operator--(); + json& operator*() const; + json* operator->() const; + + /// getter for the key (in case of objects) + std::string key() const; + /// getter for the value + json& value() const; + + private: + /// a JSON value + json* object_ = nullptr; + /// an iterator for JSON arrays + array_t::iterator* vi_ = nullptr; + /// an iterator for JSON objects + object_t::iterator* oi_ = nullptr; + /// whether iterator points to a valid object + bool invalid = true; + }; + + /// a const iterator + class const_iterator : public std::iterator + { + friend class json; + + public: + const_iterator() = default; + const_iterator(const json*, bool); + const_iterator(const const_iterator&); + const_iterator(const json::iterator&); + ~const_iterator(); + + const_iterator& operator=(const_iterator); + bool operator==(const const_iterator&) const; + bool operator!=(const const_iterator&) const; + const_iterator& operator++(); + const_iterator& operator--(); + const json& operator*() const; + const json* operator->() const; + + /// getter for the key (in case of objects) + std::string key() const; + /// getter for the value + const json& value() const; + + private: + /// a JSON value + const json* object_ = nullptr; + /// an iterator for JSON arrays + array_t::const_iterator* vi_ = nullptr; + /// an iterator for JSON objects + object_t::const_iterator* oi_ = nullptr; + /// whether iterator reached past the end + bool invalid = true; + }; + + private: + /// a helper class to parse a JSON object + class parser + { + public: + /// a parser reading from a C string + parser(const char*); + /// a parser reading from a C++ string + parser(const std::string&); + /// a parser reading from an input stream + parser(std::istream&); + /// destructor of the parser + ~parser() = default; + + // no copy constructor + parser(const parser&) = delete; + // no copy assignment + parser& operator=(parser) = delete; + + /// parse and return a JSON object + json parse(); + + private: + /// read the next character, stripping whitespace + bool next(); + /// raise an exception with an error message + [[noreturn]] inline void error(const std::string&) const; + /// parse a quoted string + inline std::string parseString(); + /// transforms a unicode codepoint to it's UTF-8 presentation + std::string codePointToUTF8(unsigned int codePoint) const; + /// parses 4 hex characters that represent a unicode code point + inline unsigned int parse4HexCodePoint(); + /// parses \uXXXX[\uXXXX] unicode escape characters + inline std::string parseUnicodeEscape(); + /// parse a Boolean "true" + inline void parseTrue(); + /// parse a Boolean "false" + inline void parseFalse(); + /// parse a null object + inline void parseNull(); + /// a helper function to expect a certain character + inline void expect(const char); + + private: + /// a buffer of the input + std::string buffer_ {}; + /// the current character + char current_ {}; + /// the position inside the input buffer + std::size_t pos_ = 0; + }; +}; + +} + +/// user-defined literal operator to create JSON objects from strings +nlohmann::json operator "" _json(const char*, std::size_t); + +// specialization of std::swap, and std::hash +namespace std +{ +template <> +/// swaps the values of two JSON objects +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +template <> +/// hash value for JSON objects +struct hash +{ + size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + return hash()(j.dump()); + } +}; + +} diff --git a/src/main.cpp b/src/main.cpp index 2ad18fd..f662761 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* * pusher - * (c) 2014-2016 Daniel Stein + * (c) 2014-2015 Daniel Stein * https://github.com/HackHerz/pusher * * TODO @@ -11,13 +11,14 @@ #include #include +#include #include #include #include #include "tclap/CmdLine.h" #include "simpleini/SimpleIni.h" -#include "pushnotifier-sdk-cpp/PushNotifier.h" +#include "pushhandler.h" #define CONFIG_FILE "/etc/pusher.conf" @@ -53,16 +54,17 @@ int main(int argc, char **argv) try { - TCLAP::CmdLine cmd("Push notifications to your phone easily.", ' ', "0.4"); + TCLAP::CmdLine cmd("Push notifications to your phone easily.", ' ', "0.3"); // Values - TCLAP::ValueArg idArg("i","id","ID of the device.",false,"0","string"); + TCLAP::ValueArg idArg("i","id","ID of the device.",false,0,"number"); cmd.add(idArg); // Switches TCLAP::SwitchArg tokenSwitch("t", "token", "Request your token.", cmd, false); TCLAP::SwitchArg listSwitch("l", "list", "List all your devices.", cmd, false); TCLAP::SwitchArg pipeSwitch("p", "pipe", "Input via pipe.", cmd, false); + TCLAP::SwitchArg verifySwitch("v","verify","Checks if token is still valid.", cmd, false); // add unlabeled argument @@ -75,16 +77,13 @@ int main(int argc, char **argv) // Variables - string message, id; - CSimpleIniA iniReader; - iniReader.SetUnicode(); - + string message; + int id; // Request token if(tokenSwitch.getValue()) { - string username, password; - PushNotifier::AppToken token; + string username, password, token; // Read username cout << "Username: "; @@ -102,60 +101,84 @@ int main(int argc, char **argv) cout << endl; // pusher instance - PushNotifier buf; - token = buf.login(username, password); + PushHandler buf(username); + token = buf.login(password); - // Build config - iniReader.SetValue("pusher", "username", username.c_str()); - iniReader.SetValue("pusher", "appToken", token.token.c_str()); + // Write config + ofstream dat_aus; + dat_aus.open(CONFIG_FILE, ios_base::out); // Check if file is writable - if(iniReader.SaveFile(CONFIG_FILE) < 0) + if(!dat_aus.is_open()) { cout << "Try running pusher as root or save the following in " << CONFIG_FILE << "\n" << endl; // Data - string strData; - iniReader.Save(strData); - cout << strData << endl; + cout << "[pusher]" << endl; + cout << "username=" << username << endl; + cout << "appToken=" << token << endl; return 1; } + else + { + cout << "Success!" << endl; + } + // Data + dat_aus << "[pusher]\n"; + dat_aus << "username=" << username << "\n"; + dat_aus << "appToken=" << token; + + dat_aus.close(); - cout << "Success!" << endl; return 0; } + // Load conf and check if token is specified + CSimpleIniA iniReader; + iniReader.SetUnicode(); - // Check if reading of config is possible + // Check if reading is possible if(iniReader.LoadFile(CONFIG_FILE) < 0) { - throw runtime_error("You need to login first."); - } - - string username = iniReader.GetValue("pusher", "username", ""); - string appToken = iniReader.GetValue("pusher", "appToken", ""); - - if(username.empty() || appToken.empty()) - { - throw runtime_error("You need to login first."); + cout << "Error" << endl; + return 1; } + string username = iniReader.GetValue("pusher", "username", NULL); + string appToken = iniReader.GetValue("pusher", "appToken", NULL); // Loading values - PushNotifier pusherInstance(username, appToken, 0); + PushHandler pusherInstance(username, appToken); + + + + // Verify token + if(verifySwitch.getValue()) + { + if(pusherInstance.verifyToken()) + { + cout << "appToken is valid" << endl; + return 0; + } + else + { + cout << "appToken is invalid" << endl; + return 1; + } + } // List devices if(listSwitch.getValue()) { - vector devices; + vector devices; devices = pusherInstance.getDevices(); unsigned int titleLength = 5; @@ -169,7 +192,7 @@ int main(int argc, char **argv) if(devices[i].id.length() > idLength) { idLength = devices[i].id.length(); } } - cout + cout << "ID\033[" << (idLength - 2 + 2) << "C" << "Title\033[" << (titleLength - 5 + 2) << "C" << "Model" << endl; @@ -179,9 +202,9 @@ int main(int argc, char **argv) for(unsigned int i = 0; i < devices.size(); i++) { - cout + cout << devices[i].id << "\033[" << (idLength - devices[i].id.length() + 2) << "C" - << devices[i].title << "\033[" << (titleLength - devices[i].title.length() + 2) << "C" + << devices[i].title << "\033[" << (titleLength - devices[i].title.length() + 2) << "C" << devices[i].model << endl; } @@ -191,7 +214,7 @@ int main(int argc, char **argv) // Device id - if(sizeof(idArg) != 0) + if(idArg.getValue() != 0) { id = idArg.getValue(); } @@ -224,7 +247,7 @@ int main(int argc, char **argv) stringstream stringID; stringID << id; - pusherInstance.sendMessage(stringID.str(), message); + pusherInstance.sendToDevice(stringID.str(), message); } @@ -235,7 +258,13 @@ int main(int argc, char **argv) catch (TCLAP::ArgException &e) { cerr << "error: " << e.error() << " for arg " << e.argId() << endl; - return 1; + } + + + // errors thrown by pushhandler + catch(PusherError& e) + { + cout << "Error: " << e.what() << endl; } @@ -243,7 +272,6 @@ int main(int argc, char **argv) catch(exception& e) { cout << "Some kind of error occured: " << e.what() << endl; - return 1; } return 0; diff --git a/src/pushhandler.cpp b/src/pushhandler.cpp new file mode 100644 index 0000000..9785d38 --- /dev/null +++ b/src/pushhandler.cpp @@ -0,0 +1,256 @@ +#include "pushhandler.h" +#include "json/json.h" + + +#include +#include +#include + + +using namespace std; +using json = nlohmann::json; + + +// urlDecode matches +string matches[][2] = { + {"$", "%24"}, + {"&", "%26"}, + {"+", "%2B"}, + {",", "%2C"}, + {"/", "%2F"}, + {":", "%3A"}, + {";", "%3B"}, + {"=", "%3D"}, + {"?", "%3F"}, + {"@", "%40"} +}; + +// needed for urlencoding +string urlDecode(string url) +{ + for(unsigned int i = 0; i < (sizeof(matches)/sizeof(matches[0])); i++) + { + size_t start_pos = 0; + while((start_pos = url.find(matches[i][0], start_pos)) != string::npos) + { + url.replace(start_pos, matches[i][0].length(), matches[i][1]); + start_pos += matches[i][1].length(); + } + } + + return url; +} + + +// needed for handling curl output +static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + + +// curl wrapper +string curlHandler(string data, const char* url) +{ + CURL *curl; + CURLcode res; + string readBuffer; + curl = curl_easy_init(); + + if(curl) + { + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + if(res != CURLE_OK) + { + throw PusherError("Network Error"); + } + } + + return readBuffer; +} + + +// constructor only username +PushHandler::PushHandler(string username) +{ + this->username = username; +} + + +// constructor with appToken +PushHandler::PushHandler(string username, string appToken) +{ + this->username = username; + this->appToken = appToken; +} + + +// login +string PushHandler::login(string password) +{ + // build request data + stringstream requestData; + requestData << "apiToken=" << API_TOKEN; + requestData << "&username=" << this->username; + requestData << "&password=" << password; + + // network request + string readBuffer; + readBuffer = curlHandler(requestData.str(), URL_PN_LOGIN); + + // json parsing + stringstream jsonData; + jsonData << readBuffer; + + json j; + j << jsonData; + + if(j["status"].get() != "ok") + { + throw PusherError("wrong credentials"); + } + + this->appToken = j["appToken"].get(); + return this->appToken; +} + + +// get list of devices +vector PushHandler::getDevices() +{ + // build request data + stringstream requestData; + requestData << "apiToken=" << API_TOKEN; + requestData << "&appToken=" << this->appToken; + + // network request + string readBuffer; + readBuffer = curlHandler(requestData.str(), URL_PN_GET_DEVICES); + + // json parsing + stringstream jsonData; + jsonData << readBuffer; + + json j; + j << jsonData; + + // handle the codes + switch(j["code"].get()) + { + case 1: throw PusherError("Invalid API Token"); + break; + case 2: throw PusherError("App Token missing"); + break; + case 3: throw PusherError("App Token invalid"); + } + + vector buffer; + + for(auto element : j["devices"]) + { + Device buf; + + buf.title = element["title"].get(); + buf.id = element["id"].get(); + buf.model = element["model"].get(); + + buffer.push_back(buf); + } + + return buffer; +} + + +// verify token +bool PushHandler::verifyToken() +{ + // build request data + stringstream requestData; + requestData << "apiToken=" << API_TOKEN; + requestData << "&username=" << this->username; + requestData << "&appToken=" << this->appToken; + + // network request + string readBuffer; + readBuffer = curlHandler(requestData.str(), URL_PN_CHECK_TOKEN); + + // json parser + stringstream jsonData; + jsonData << readBuffer; + + json j; + j << jsonData; + + switch(j["code"].get()) + { + case 0: return true; + break; + case 1: throw PusherError("Invalid API-Token"); + break; + case 2: return false; + default: throw PusherError("Invalid server response"); + } +} + + +// send to device +void PushHandler::sendToDevice(string id, string message) +{ + // analyze content-type + // ...... + + + // actual sending + // build request data + stringstream requestData; + requestData << "apiToken=" << API_TOKEN; + requestData << "&appToken=" << this->appToken; + requestData << "&app=" << APP_PACKAGE; + requestData << "&deviceID=" << id; + requestData << "&type=" << "MESSAGE"; + requestData << "&content=" << urlDecode(message); + + // network request + string readBuffer; + readBuffer = curlHandler(requestData.str(), URL_PN_SEND_TO_DEVICE); + + // json parsing + stringstream jsonData; + jsonData << readBuffer; + + json j; + j << jsonData; + + switch(j["code"].get()) + { + case 0: //return 0; + break; + case 1: throw PusherError("Invalid API Token"); + break; + case 2: throw PusherError("App Token missing"); + break; + case 3: throw PusherError("App Token invalid"); + break; + case 4: throw PusherError("Package Name missing"); + break; + case 5: throw PusherError("Package Name invalid"); + break; + case 6: throw PusherError("Package Name is not linked with the provided API Token"); + break; + case 7: throw PusherError("Device ID missing"); + break; + case 8: throw PusherError("Device ID invalid"); + break; + case 9: throw PusherError("Type missing or invalid"); + break; + default: throw PusherError("Invalid server response"); + } +} diff --git a/src/pushhandler.h b/src/pushhandler.h new file mode 100644 index 0000000..32058ac --- /dev/null +++ b/src/pushhandler.h @@ -0,0 +1,65 @@ +#ifndef H_PUSHHANDLER +#define H_PUSHHANDLER + +#include +#include + +// Change this line if you are using your own API-Token +const char API_TOKEN[] = { + 0x38, 0x45, 0x37, 0x44, 0x38, 0x42, 0x32, + 0x44, 0x44, 0x45, 0x37, 0x44, 0x44, 0x45, + 0x37, 0x44, 0x36, 0x43, 0x33, 0x56, 0x35, + 0x32, 0x56, 0x42, 0x35, 0x32, 0x56, 0x42, + 0x44, 0x34, 0x44, 0x44, 0x45, 0x54, 0x42, + 0x54, 0x54, 0x54, 0x4b, 0x46, 0x46, 0x42 }; + +#define APP_PACKAGE "com.hackherz.pusher" +#define USER_AGENT "pusher/0.2" + +#define URL_PN_LOGIN "http://a.pushnotifier.de/1/login/" +#define URL_PN_CHECK_TOKEN "http://a.pushnotifier.de/1/checkToken/" +#define URL_PN_GET_DEVICES "http://a.pushnotifier.de/1/getDevices/" +#define URL_PN_SEND_TO_DEVICE "http://a.pushnotifier.de/1/sendToDevice/" + + + +class PushHandler +{ +public: + PushHandler(std::string username); + PushHandler(std::string username, std::string appToken); + + + typedef struct + { + std::string title; + std::string id; + std::string model; + } Device; + + + std::string login(std::string password); + std::vector getDevices(); + void sendToDevice(std::string deviceID, std::string message); + bool verifyToken(); + + +private: + std::string username; + std::string appToken; +}; + + +// class for exceptions +class PusherError +{ +public: + PusherError(std::string content) { this->content = content; } + std::string what() { return this->content; } +private: + std::string content; +}; + + +#endif + diff --git a/src/pushnotifier-sdk-cpp b/src/pushnotifier-sdk-cpp deleted file mode 160000 index b1250df..0000000 --- a/src/pushnotifier-sdk-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b1250dfd76f161916745ecbcc82f9b52d3418345 diff --git a/src/simpleini b/src/simpleini deleted file mode 160000 index 2af65fc..0000000 --- a/src/simpleini +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2af65fcc504f8242752755e836709762ef7ce062 diff --git a/src/simpleini/ConvertUTF.c b/src/simpleini/ConvertUTF.c new file mode 100644 index 0000000..9b3deeb --- /dev/null +++ b/src/simpleini/ConvertUTF.c @@ -0,0 +1,539 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + + See the header file "ConvertUTF.h" for complete documentation. + +------------------------------------------------------------------------ */ + + +#include "ConvertUTF.h" +#ifdef CVTUTF_DEBUG +#include +#endif + +static const int halfShift = 10; /* used for shifting by 10 bits */ + +static const UTF32 halfBase = 0x0010000UL; +static const UTF32 halfMask = 0x3FFUL; + +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_HIGH_END (UTF32)0xDBFF +#define UNI_SUR_LOW_START (UTF32)0xDC00 +#define UNI_SUR_LOW_END (UTF32)0xDFFF +#define false 0 +#define true 1 + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; + } + *sourceStart = source; + *targetStart = target; +#ifdef CVTUTF_DEBUG +if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); +} +#endif + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. + */ +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. + */ +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +/* + * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed + * into the first byte, depending on how many bytes follow. There are + * as many entries in this table as there are UTF-8 sequence types. + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * for *legal* UTF-8 will be 4 or fewer bytes total. + */ +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +/* --------------------------------------------------------------------- */ + +/* The interface converts a whole buffer to avoid function-call overhead. + * Constants have been gathered. Loops & conditionals have been removed as + * much as possible for efficiency, in favor of drop-through switches. + * (See "Note A" at the bottom of the file for equivalent code.) + * If your compiler supports it, the "isLegalUTF8" call can be turned + * into an inline function. + */ + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } + + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Utility routine to tell whether a sequence of bytes is legal UTF-8. + * This must be called with the length pre-determined by the first byte. + * If not calling this from ConvertUTF8to*, then the length can be set by: + * length = trailingBytesForUTF8[*source]+1; + * and the sequence is illegal right away if there aren't that many bytes + * available. + * If presented with a length > 4, this returns false. The Unicode + * definition of UTF-8 goes up to 4-byte sequences. + */ + +static Boolean isLegalUTF8(const UTF8 *source, int length) { + UTF8 a; + const UTF8 *srcptr = source+length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } + + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; +} + +/* --------------------------------------------------------------------- */ + +/* + * Exported function to return whether a UTF-8 sequence is legal or not. + * This is not used here; it's just exported. + */ +Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { + int length = trailingBytesForUTF8[*source]+1; + if (source+length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- + + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: + { + int tmpBytesToRead = extraBytesToRead+1; + do { + ch += *source++; + --tmpBytesToRead; + if (tmpBytesToRead) ch <<= 6; + } while (tmpBytesToRead > 0); + } + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. + + --------------------------------------------------------------------- */ diff --git a/src/simpleini/ConvertUTF.h b/src/simpleini/ConvertUTF.h new file mode 100644 index 0000000..14d7b70 --- /dev/null +++ b/src/simpleini/ConvertUTF.h @@ -0,0 +1,149 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + +------------------------------------------------------------------------ */ + +/* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. +------------------------------------------------------------------------ */ + +typedef unsigned int UTF32; /* at least 32 bits */ +typedef unsigned short UTF16; /* at least 16 bits */ +typedef unsigned char UTF8; /* typically 8 bits */ +typedef unsigned char Boolean; /* 0 or 1 */ + +/* Some fundamental constants */ +#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define UNI_MAX_BMP (UTF32)0x0000FFFF +#define UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} ConversionResult; + +typedef enum { + strictConversion = 0, + lenientConversion +} ConversionFlags; + +/* This is for C++ and does no harm in C */ +#ifdef __cplusplus +extern "C" { +#endif + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); + +#ifdef __cplusplus +} +#endif + +/* --------------------------------------------------------------------- */ diff --git a/src/simpleini/SimpleIni.h b/src/simpleini/SimpleIni.h new file mode 100644 index 0000000..2807a8f --- /dev/null +++ b/src/simpleini/SimpleIni.h @@ -0,0 +1,3435 @@ +/** @mainpage + + +
Library SimpleIni +
File SimpleIni.h +
Author Brodie Thiesfield [code at jellycan dot com] +
Source https://github.com/brofield/simpleini +
Version 4.17 +
+ + Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. + + @section intro INTRODUCTION + + This component allows an INI-style configuration file to be used on both + Windows and Linux/Unix. It is fast, simple and source code using this + component will compile unchanged on either OS. + + + @section features FEATURES + + - MIT Licence allows free use in all software (including GPL and commercial) + - multi-platform (Windows 95/98/ME/NT/2K/XP/2003, Windows CE, Linux, Unix) + - loading and saving of INI-style configuration files + - configuration files can have any newline format on all platforms + - liberal acceptance of file format + - key/values with no section + - removal of whitespace around sections, keys and values + - support for multi-line values (values with embedded newline characters) + - optional support for multiple keys with the same name + - optional case-insensitive sections and keys (for ASCII characters only) + - saves files with sections and keys in the same order as they were loaded + - preserves comments on the file, section and keys where possible. + - supports both char or wchar_t programming interfaces + - supports both MBCS (system locale) and UTF-8 file encodings + - system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file + - support for non-ASCII characters in section, keys, values and comments + - support for non-standard character types or file encodings + via user-written converter classes + - support for adding/modifying values programmatically + - compiles cleanly in the following compilers: + - Windows/VC6 (warning level 3) + - Windows/VC.NET 2003 (warning level 4) + - Windows/VC 2005 (warning level 4) + - Linux/gcc (-Wall) + + + @section usage USAGE SUMMARY + + -# Define the appropriate symbol for the converter you wish to use and + include the SimpleIni.h header file. If no specific converter is defined + then the default converter is used. The default conversion mode uses + SI_CONVERT_WIN32 on Windows and SI_CONVERT_GENERIC on all other + platforms. If you are using ICU then SI_CONVERT_ICU is supported on all + platforms. + -# Declare an instance the appropriate class. Note that the following + definitions are just shortcuts for commonly used types. Other types + (PRUnichar, unsigned short, unsigned char) are also possible. + +
Interface Case-sensitive Load UTF-8 Load MBCS Typedef +
SI_CONVERT_GENERIC +
char No Yes Yes #1 CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
wchar_t No Yes Yes CSimpleIniW +
wchar_t Yes Yes Yes CSimpleIniCaseW +
SI_CONVERT_WIN32 +
char No No #2 Yes CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
wchar_t No Yes Yes CSimpleIniW +
wchar_t Yes Yes Yes CSimpleIniCaseW +
SI_CONVERT_ICU +
char No Yes Yes CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
UChar No Yes Yes CSimpleIniW +
UChar Yes Yes Yes CSimpleIniCaseW +
+ #1 On Windows you are better to use CSimpleIniA with SI_CONVERT_WIN32.
+ #2 Only affects Windows. On Windows this uses MBCS functions and + so may fold case incorrectly leading to uncertain results. + -# Call LoadData() or LoadFile() to load and parse the INI configuration file + -# Access and modify the data of the file using the following functions + +
GetAllSections Return all section names +
GetAllKeys Return all key names within a section +
GetAllValues Return all values within a section & key +
GetSection Return all key names and values in a section +
GetSectionSize Return the number of keys in a section +
GetValue Return a value for a section & key +
SetValue Add or update a value for a section & key +
Delete Remove a section, or a key from a section +
+ -# Call Save() or SaveFile() to save the INI configuration data + + @section iostreams IO STREAMS + + SimpleIni supports reading from and writing to STL IO streams. Enable this + by defining SI_SUPPORT_IOSTREAMS before including the SimpleIni.h header + file. Ensure that if the streams are backed by a file (e.g. ifstream or + ofstream) then the flag ios_base::binary has been used when the file was + opened. + + @section multiline MULTI-LINE VALUES + + Values that span multiple lines are created using the following format. + +
+        key = <<
+
+    Note the following:
+    - The text used for ENDTAG can be anything and is used to find
+      where the multi-line text ends.
+    - The newline after ENDTAG in the start tag, and the newline
+      before ENDTAG in the end tag is not included in the data value.
+    - The ending tag must be on it's own line with no whitespace before
+      or after it.
+    - The multi-line value is modified at load so that each line in the value
+      is delimited by a single '\\n' character on all platforms. At save time
+      it will be converted into the newline format used by the current
+      platform.
+
+    @section comments COMMENTS
+
+    Comments are preserved in the file within the following restrictions:
+    - Every file may have a single "file comment". It must start with the
+      first character in the file, and will end with the first non-comment
+      line in the file.
+    - Every section may have a single "section comment". It will start
+      with the first comment line following the file comment, or the last
+      data entry. It ends at the beginning of the section.
+    - Every key may have a single "key comment". This comment will start
+      with the first comment line following the section start, or the file
+      comment if there is no section name.
+    - Comments are set at the time that the file, section or key is first
+      created. The only way to modify a comment on a section or a key is to
+      delete that entry and recreate it with the new comment. There is no
+      way to change the file comment.
+
+    @section save SAVE ORDER
+
+    The sections and keys are written out in the same order as they were
+    read in from the file. Sections and keys added to the data after the
+    file has been loaded will be added to the end of the file when it is
+    written. There is no way to specify the location of a section or key
+    other than in first-created, first-saved order.
+
+    @section notes NOTES
+
+    - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for
+      Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU.
+    - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked.
+    - When using SI_CONVERT_ICU, ICU header files must be on the include
+      path and icuuc.lib must be linked in.
+    - To load a UTF-8 file on Windows AND expose it with SI_CHAR == char,
+      you should use SI_CONVERT_GENERIC.
+    - The collation (sorting) order used for sections and keys returned from
+      iterators is NOT DEFINED. If collation order of the text is important
+      then it should be done yourself by either supplying a replacement
+      SI_STRLESS class, or by sorting the strings external to this library.
+    - Usage of the  header on Windows can be disabled by defining
+      SI_NO_MBCS. This is defined automatically on Windows CE platforms.
+
+    @section contrib CONTRIBUTIONS
+    
+    - 2010/05/03: Tobias Gehrig: added GetDoubleValue()
+
+    @section licence MIT LICENCE
+
+    The licence text below is the boilerplate "MIT Licence" used from:
+    http://www.opensource.org/licenses/mit-license.php
+
+    Copyright (c) 2006-2012, Brodie Thiesfield
+
+    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.
+*/
+
+#ifndef INCLUDED_SimpleIni_h
+#define INCLUDED_SimpleIni_h
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+// Disable these warnings in MSVC:
+//  4127 "conditional expression is constant" as the conversion classes trigger
+//  it with the statement if (sizeof(SI_CHAR) == sizeof(char)). This test will
+//  be optimized away in a release build.
+//  4503 'insert' : decorated name length exceeded, name was truncated
+//  4702 "unreachable code" as the MS STL header causes it in release mode.
+//  Again, the code causing the warning will be cleaned up by the compiler.
+//  4786 "identifier truncated to 256 characters" as this is thrown hundreds
+//  of times VC6 as soon as STL is used.
+#ifdef _MSC_VER
+# pragma warning (push)
+# pragma warning (disable: 4127 4503 4702 4786)
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef SI_SUPPORT_IOSTREAMS
+# include 
+#endif // SI_SUPPORT_IOSTREAMS
+
+#ifdef _DEBUG
+# ifndef assert
+#  include 
+# endif
+# define SI_ASSERT(x)   assert(x)
+#else
+# define SI_ASSERT(x)
+#endif
+
+enum SI_Error {
+    SI_OK       =  0,   //!< No error
+    SI_UPDATED  =  1,   //!< An existing value was updated
+    SI_INSERTED =  2,   //!< A new value was inserted
+
+    // note: test for any error with (retval < 0)
+    SI_FAIL     = -1,   //!< Generic failure
+    SI_NOMEM    = -2,   //!< Out of memory error
+    SI_FILE     = -3    //!< File error (see errno for detail error)
+};
+
+#define SI_UTF8_SIGNATURE     "\xEF\xBB\xBF"
+
+#ifdef _WIN32
+# define SI_NEWLINE_A   "\r\n"
+# define SI_NEWLINE_W   L"\r\n"
+#else // !_WIN32
+# define SI_NEWLINE_A   "\n"
+# define SI_NEWLINE_W   L"\n"
+#endif // _WIN32
+
+#if defined(SI_CONVERT_ICU)
+# include 
+#endif
+
+#if defined(_WIN32)
+# define SI_HAS_WIDE_FILE
+# define SI_WCHAR_T     wchar_t
+#elif defined(SI_CONVERT_ICU)
+# define SI_HAS_WIDE_FILE
+# define SI_WCHAR_T     UChar
+#endif
+
+
+// ---------------------------------------------------------------------------
+//                              MAIN TEMPLATE CLASS
+// ---------------------------------------------------------------------------
+
+/** Simple INI file reader.
+
+    This can be instantiated with the choice of unicode or native characterset,
+    and case sensitive or insensitive comparisons of section and key names.
+    The supported combinations are pre-defined with the following typedefs:
+
+    
+        
Interface Case-sensitive Typedef +
char No CSimpleIniA +
char Yes CSimpleIniCaseA +
wchar_t No CSimpleIniW +
wchar_t Yes CSimpleIniCaseW +
+ + Note that using other types for the SI_CHAR is supported. For instance, + unsigned char, unsigned short, etc. Note that where the alternative type + is a different size to char/wchar_t you may need to supply new helper + classes for SI_STRLESS and SI_CONVERTER. + */ +template +class CSimpleIniTempl +{ +public: + typedef SI_CHAR SI_CHAR_T; + + /** key entry */ + struct Entry { + const SI_CHAR * pItem; + const SI_CHAR * pComment; + int nOrder; + + Entry(const SI_CHAR * a_pszItem = NULL, int a_nOrder = 0) + : pItem(a_pszItem) + , pComment(NULL) + , nOrder(a_nOrder) + { } + Entry(const SI_CHAR * a_pszItem, const SI_CHAR * a_pszComment, int a_nOrder) + : pItem(a_pszItem) + , pComment(a_pszComment) + , nOrder(a_nOrder) + { } + Entry(const Entry & rhs) { operator=(rhs); } + Entry & operator=(const Entry & rhs) { + pItem = rhs.pItem; + pComment = rhs.pComment; + nOrder = rhs.nOrder; + return *this; + } + +#if defined(_MSC_VER) && _MSC_VER <= 1200 + /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ + bool operator<(const Entry & rhs) const { return LoadOrder()(*this, rhs); } + bool operator>(const Entry & rhs) const { return LoadOrder()(rhs, *this); } +#endif + + /** Strict less ordering by name of key only */ + struct KeyOrder : std::binary_function { + bool operator()(const Entry & lhs, const Entry & rhs) const { + const static SI_STRLESS isLess = SI_STRLESS(); + return isLess(lhs.pItem, rhs.pItem); + } + }; + + /** Strict less ordering by order, and then name of key */ + struct LoadOrder : std::binary_function { + bool operator()(const Entry & lhs, const Entry & rhs) const { + if (lhs.nOrder != rhs.nOrder) { + return lhs.nOrder < rhs.nOrder; + } + return KeyOrder()(lhs.pItem, rhs.pItem); + } + }; + }; + + /** map keys to values */ + typedef std::multimap TKeyVal; + + /** map sections to key/value map */ + typedef std::map TSection; + + /** set of dependent string pointers. Note that these pointers are + dependent on memory owned by CSimpleIni. + */ + typedef std::list TNamesDepend; + + /** interface definition for the OutputWriter object to pass to Save() + in order to output the INI file data. + */ + class OutputWriter { + public: + OutputWriter() { } + virtual ~OutputWriter() { } + virtual void Write(const char * a_pBuf) = 0; + private: + OutputWriter(const OutputWriter &); // disable + OutputWriter & operator=(const OutputWriter &); // disable + }; + + /** OutputWriter class to write the INI data to a file */ + class FileWriter : public OutputWriter { + FILE * m_file; + public: + FileWriter(FILE * a_file) : m_file(a_file) { } + void Write(const char * a_pBuf) { + fputs(a_pBuf, m_file); + } + private: + FileWriter(const FileWriter &); // disable + FileWriter & operator=(const FileWriter &); // disable + }; + + /** OutputWriter class to write the INI data to a string */ + class StringWriter : public OutputWriter { + std::string & m_string; + public: + StringWriter(std::string & a_string) : m_string(a_string) { } + void Write(const char * a_pBuf) { + m_string.append(a_pBuf); + } + private: + StringWriter(const StringWriter &); // disable + StringWriter & operator=(const StringWriter &); // disable + }; + +#ifdef SI_SUPPORT_IOSTREAMS + /** OutputWriter class to write the INI data to an ostream */ + class StreamWriter : public OutputWriter { + std::ostream & m_ostream; + public: + StreamWriter(std::ostream & a_ostream) : m_ostream(a_ostream) { } + void Write(const char * a_pBuf) { + m_ostream << a_pBuf; + } + private: + StreamWriter(const StreamWriter &); // disable + StreamWriter & operator=(const StreamWriter &); // disable + }; +#endif // SI_SUPPORT_IOSTREAMS + + /** Characterset conversion utility class to convert strings to the + same format as is used for the storage. + */ + class Converter : private SI_CONVERTER { + public: + Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { + m_scratch.resize(1024); + } + Converter(const Converter & rhs) { operator=(rhs); } + Converter & operator=(const Converter & rhs) { + m_scratch = rhs.m_scratch; + return *this; + } + bool ConvertToStore(const SI_CHAR * a_pszString) { + size_t uLen = SI_CONVERTER::SizeToStore(a_pszString); + if (uLen == (size_t)(-1)) { + return false; + } + while (uLen > m_scratch.size()) { + m_scratch.resize(m_scratch.size() * 2); + } + return SI_CONVERTER::ConvertToStore( + a_pszString, + const_cast(m_scratch.data()), + m_scratch.size()); + } + const char * Data() { return m_scratch.data(); } + private: + std::string m_scratch; + }; + +public: + /*-----------------------------------------------------------------------*/ + + /** Default constructor. + + @param a_bIsUtf8 See the method SetUnicode() for details. + @param a_bMultiKey See the method SetMultiKey() for details. + @param a_bMultiLine See the method SetMultiLine() for details. + */ + CSimpleIniTempl( + bool a_bIsUtf8 = false, + bool a_bMultiKey = false, + bool a_bMultiLine = false + ); + + /** Destructor */ + ~CSimpleIniTempl(); + + /** Deallocate all memory stored by this object */ + void Reset(); + + /** Has any data been loaded */ + bool IsEmpty() const { return m_data.empty(); } + + /*-----------------------------------------------------------------------*/ + /** @{ @name Settings */ + + /** Set the storage format of the INI data. This affects both the loading + and saving of the INI data using all of the Load/Save API functions. + This value cannot be changed after any INI data has been loaded. + + If the file is not set to Unicode (UTF-8), then the data encoding is + assumed to be the OS native encoding. This encoding is the system + locale on Linux/Unix and the legacy MBCS encoding on Windows NT/2K/XP. + If the storage format is set to Unicode then the file will be loaded + as UTF-8 encoded data regardless of the native file encoding. If + SI_CHAR == char then all of the char* parameters take and return UTF-8 + encoded data regardless of the system locale. + + \param a_bIsUtf8 Assume UTF-8 encoding for the source? + */ + void SetUnicode(bool a_bIsUtf8 = true) { + if (!m_pData) m_bStoreIsUtf8 = a_bIsUtf8; + } + + /** Get the storage format of the INI data. */ + bool IsUnicode() const { return m_bStoreIsUtf8; } + + /** Should multiple identical keys be permitted in the file. If set to false + then the last value encountered will be used as the value of the key. + If set to true, then all values will be available to be queried. For + example, with the following input: + +
+        [section]
+        test=value1
+        test=value2
+        
+ + Then with SetMultiKey(true), both of the values "value1" and "value2" + will be returned for the key test. If SetMultiKey(false) is used, then + the value for "test" will only be "value2". This value may be changed + at any time. + + \param a_bAllowMultiKey Allow multi-keys in the source? + */ + void SetMultiKey(bool a_bAllowMultiKey = true) { + m_bAllowMultiKey = a_bAllowMultiKey; + } + + /** Get the storage format of the INI data. */ + bool IsMultiKey() const { return m_bAllowMultiKey; } + + /** Should data values be permitted to span multiple lines in the file. If + set to false then the multi-line construct << + SI_CHAR FORMAT + char same format as when loaded (MBCS or UTF-8) + wchar_t UTF-8 + other UTF-8 + + + Note that comments from the original data is preserved as per the + documentation on comments. The order of the sections and values + from the original file will be preserved. + + Any data prepended or appended to the output device must use the the + same format (MBCS or UTF-8). You may use the GetConverter() method to + convert text to the correct format regardless of the output format + being used by SimpleIni. + + To add a BOM to UTF-8 data, write it out manually at the very beginning + like is done in SaveFile when a_bUseBOM is true. + + @param a_oOutput Output writer to write the data to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the OutputWriter. + + @return SI_Error See error definitions + */ + SI_Error Save( + OutputWriter & a_oOutput, + bool a_bAddSignature = false + ) const; + +#ifdef SI_SUPPORT_IOSTREAMS + /** Save the INI data to an ostream. See Save() for details. + + @param a_ostream String to have the INI data appended to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the stream. + + @return SI_Error See error definitions + */ + SI_Error Save( + std::ostream & a_ostream, + bool a_bAddSignature = false + ) const + { + StreamWriter writer(a_ostream); + return Save(writer, a_bAddSignature); + } +#endif // SI_SUPPORT_IOSTREAMS + + /** Append the INI data to a string. See Save() for details. + + @param a_sBuffer String to have the INI data appended to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the string. + + @return SI_Error See error definitions + */ + SI_Error Save( + std::string & a_sBuffer, + bool a_bAddSignature = false + ) const + { + StringWriter writer(a_sBuffer); + return Save(writer, a_bAddSignature); + } + + /*-----------------------------------------------------------------------*/ + /** @} + @{ @name Accessing INI Data */ + + /** Retrieve all section names. The list is returned as an STL vector of + names and can be iterated or searched as necessary. Note that the + sort order of the returned strings is NOT DEFINED. You can sort + the names into the load order if desired. Search this file for ".sort" + for an example. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these pointers + are in use! + + @param a_names Vector that will receive all of the section + names. See note above! + */ + void GetAllSections( + TNamesDepend & a_names + ) const; + + /** Retrieve all unique key names in a section. The sort order of the + returned strings is NOT DEFINED. You can sort the names into the load + order if desired. Search this file for ".sort" for an example. Only + unique key names are returned. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these strings + are in use! + + @param a_pSection Section to request data for + @param a_names List that will receive all of the key + names. See note above! + + @return true Section was found. + @return false Matching section was not found. + */ + bool GetAllKeys( + const SI_CHAR * a_pSection, + TNamesDepend & a_names + ) const; + + /** Retrieve all values for a specific key. This method can be used when + multiple keys are both enabled and disabled. Note that the sort order + of the returned strings is NOT DEFINED. You can sort the names into + the load order if desired. Search this file for ".sort" for an example. + + NOTE! The returned values are pointers to string data stored in memory + owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed + or Reset while you are using this pointer! + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_values List to return if the key is not found + + @return true Key was found. + @return false Matching section/key was not found. + */ + bool GetAllValues( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + TNamesDepend & a_values + ) const; + + /** Query the number of keys in a specific section. Note that if multiple + keys are enabled, then this value may be different to the number of + keys returned by GetAllKeys. + + @param a_pSection Section to request data for + + @return -1 Section does not exist in the file + @return >=0 Number of keys in the section + */ + int GetSectionSize( + const SI_CHAR * a_pSection + ) const; + + /** Retrieve all key and value pairs for a section. The data is returned + as a pointer to an STL map and can be iterated or searched as + desired. Note that multiple entries for the same key may exist when + multiple keys have been enabled. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these strings + are in use! + + @param a_pSection Name of the section to return + @return boolean Was a section matching the supplied + name found. + */ + const TKeyVal * GetSection( + const SI_CHAR * a_pSection + ) const; + + /** Retrieve the value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + NOTE! The returned value is a pointer to string data stored in memory + owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed + or Reset while you are using this pointer! + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_pDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_pDefault Key was not found in the section + @return other Value of the key + */ + const SI_CHAR * GetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pDefault = NULL, + bool * a_pHasMultiple = NULL + ) const; + + /** Retrieve a numeric value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_nDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_nDefault Key was not found in the section + @return other Value of the key + */ + long GetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nDefault = 0, + bool * a_pHasMultiple = NULL + ) const; + + /** Retrieve a numeric value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_nDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_nDefault Key was not found in the section + @return other Value of the key + */ + double GetDoubleValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + double a_nDefault = 0, + bool * a_pHasMultiple = NULL + ) const; + + /** Retrieve a boolean value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + Strings starting with "t", "y", "on" or "1" are returned as logically true. + Strings starting with "f", "n", "of" or "0" are returned as logically false. + For all other values the default is returned. Character comparisons are + case-insensitive. + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_bDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_nDefault Key was not found in the section + @return other Value of the key + */ + bool GetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bDefault = false, + bool * a_pHasMultiple = NULL + ) const; + + /** Add or update a section or value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. Set to NULL to + create an empty section. + @param a_pValue Value to set. Set to NULL to create an + empty section. + @param a_pComment Comment to be associated with the section or the + key. If a_pKey is NULL then it will be associated + with the section, otherwise the key. Note that a + comment may be set ONLY when the section or key is + first created (i.e. when this function returns the + value SI_INSERTED). If you wish to create a section + with a comment then you need to create the section + separately to the key. The comment string must be + in full comment form already (have a comment + character starting every line). + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetValue and SetValue + with a_bForceReplace = true, is that the load + order and comment will be preserved this way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment = NULL, + bool a_bForceReplace = false + ) + { + return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, a_bForceReplace, true); + } + + /** Add or update a numeric value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. + @param a_nValue Value to set. + @param a_pComment Comment to be associated with the key. See the + notes on SetValue() for comments. + @param a_bUseHex By default the value will be written to the file + in decimal format. Set this to true to write it + as hexadecimal. + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetLongValue and + SetLongValue with a_bForceReplace = true, is that + the load order and comment will be preserved this + way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nValue, + const SI_CHAR * a_pComment = NULL, + bool a_bUseHex = false, + bool a_bForceReplace = false + ); + + /** Add or update a double value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. + @param a_nValue Value to set. + @param a_pComment Comment to be associated with the key. See the + notes on SetValue() for comments. + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetDoubleValue and + SetDoubleValue with a_bForceReplace = true, is that + the load order and comment will be preserved this + way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetDoubleValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + double a_nValue, + const SI_CHAR * a_pComment = NULL, + bool a_bForceReplace = false + ); + + /** Add or update a boolean value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. + @param a_bValue Value to set. + @param a_pComment Comment to be associated with the key. See the + notes on SetValue() for comments. + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetBoolValue and + SetBoolValue with a_bForceReplace = true, is that + the load order and comment will be preserved this + way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bValue, + const SI_CHAR * a_pComment = NULL, + bool a_bForceReplace = false + ); + + /** Delete an entire section, or a key from a section. Note that the + data returned by GetSection is invalid and must not be used after + anything has been deleted from that section using this method. + Note when multiple keys is enabled, this will delete all keys with + that name; to selectively delete individual key/values, use + DeleteValue. + + @param a_pSection Section to delete key from, or if + a_pKey is NULL, the section to remove. + @param a_pKey Key to remove from the section. Set to + NULL to remove the entire section. + @param a_bRemoveEmpty If the section is empty after this key has + been deleted, should the empty section be + removed? + + @return true Key or section was deleted. + @return false Key or section was not found. + */ + bool Delete( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bRemoveEmpty = false + ); + + /** Delete an entire section, or a key from a section. If value is + provided, only remove keys with the value. Note that the data + returned by GetSection is invalid and must not be used after + anything has been deleted from that section using this method. + Note when multiple keys is enabled, all keys with the value will + be deleted. + + @param a_pSection Section to delete key from, or if + a_pKey is NULL, the section to remove. + @param a_pKey Key to remove from the section. Set to + NULL to remove the entire section. + @param a_pValue Value of key to remove from the section. + Set to NULL to remove all keys. + @param a_bRemoveEmpty If the section is empty after this key has + been deleted, should the empty section be + removed? + + @return true Key/value or section was deleted. + @return false Key/value or section was not found. + */ + bool DeleteValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + bool a_bRemoveEmpty = false + ); + + /*-----------------------------------------------------------------------*/ + /** @} + @{ @name Converter */ + + /** Return a conversion object to convert text to the same encoding + as is used by the Save(), SaveFile() and SaveString() functions. + Use this to prepare the strings that you wish to append or prepend + to the output INI data. + */ + Converter GetConverter() const { + return Converter(m_bStoreIsUtf8); + } + + /*-----------------------------------------------------------------------*/ + /** @} */ + +private: + // copying is not permitted + CSimpleIniTempl(const CSimpleIniTempl &); // disabled + CSimpleIniTempl & operator=(const CSimpleIniTempl &); // disabled + + /** Parse the data looking for a file comment and store it if found. + */ + SI_Error FindFileComment( + SI_CHAR *& a_pData, + bool a_bCopyStrings + ); + + /** Parse the data looking for the next valid entry. The memory pointed to + by a_pData is modified by inserting NULL characters. The pointer is + updated to the current location in the block of text. + */ + bool FindEntry( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pSection, + const SI_CHAR *& a_pKey, + const SI_CHAR *& a_pVal, + const SI_CHAR *& a_pComment + ) const; + + /** Add the section/key/value to our data. + + @param a_pSection Section name. Sections will be created if they + don't already exist. + @param a_pKey Key name. May be NULL to create an empty section. + Existing entries will be updated. New entries will + be created. + @param a_pValue Value for the key. + @param a_pComment Comment to be associated with the section or the + key. If a_pKey is NULL then it will be associated + with the section, otherwise the key. This must be + a string in full comment form already (have a + comment character starting every line). + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/AddEntry and AddEntry + with a_bForceReplace = true, is that the load + order and comment will be preserved this way. + @param a_bCopyStrings Should copies of the strings be made or not. + If false then the pointers will be used as is. + */ + SI_Error AddEntry( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace, + bool a_bCopyStrings + ); + + /** Is the supplied character a whitespace character? */ + inline bool IsSpace(SI_CHAR ch) const { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); + } + + /** Does the supplied character start a comment line? */ + inline bool IsComment(SI_CHAR ch) const { + return (ch == ';' || ch == '#'); + } + + + /** Skip over a newline character (or characters) for either DOS or UNIX */ + inline void SkipNewLine(SI_CHAR *& a_pData) const { + a_pData += (*a_pData == '\r' && *(a_pData+1) == '\n') ? 2 : 1; + } + + /** Make a copy of the supplied string, replacing the original pointer */ + SI_Error CopyString(const SI_CHAR *& a_pString); + + /** Delete a string from the copied strings buffer if necessary */ + void DeleteString(const SI_CHAR * a_pString); + + /** Internal use of our string comparison function */ + bool IsLess(const SI_CHAR * a_pLeft, const SI_CHAR * a_pRight) const { + const static SI_STRLESS isLess = SI_STRLESS(); + return isLess(a_pLeft, a_pRight); + } + + bool IsMultiLineTag(const SI_CHAR * a_pData) const; + bool IsMultiLineData(const SI_CHAR * a_pData) const; + bool LoadMultiLineText( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pVal, + const SI_CHAR * a_pTagName, + bool a_bAllowBlankLinesInComment = false + ) const; + bool IsNewLineChar(SI_CHAR a_c) const; + + bool OutputMultiLineText( + OutputWriter & a_oOutput, + Converter & a_oConverter, + const SI_CHAR * a_pText + ) const; + +private: + /** Copy of the INI file data in our character format. This will be + modified when parsed to have NULL characters added after all + interesting string entries. All of the string pointers to sections, + keys and values point into this block of memory. + */ + SI_CHAR * m_pData; + + /** Length of the data that we have stored. Used when deleting strings + to determine if the string is stored here or in the allocated string + buffer. + */ + size_t m_uDataLen; + + /** File comment for this data, if one exists. */ + const SI_CHAR * m_pFileComment; + + /** Parsed INI data. Section -> (Key -> Value). */ + TSection m_data; + + /** This vector stores allocated memory for copies of strings that have + been supplied after the file load. It will be empty unless SetValue() + has been called. + */ + TNamesDepend m_strings; + + /** Is the format of our datafile UTF-8 or MBCS? */ + bool m_bStoreIsUtf8; + + /** Are multiple values permitted for the same key? */ + bool m_bAllowMultiKey; + + /** Are data values permitted to span multiple lines? */ + bool m_bAllowMultiLine; + + /** Should spaces be written out surrounding the equals sign? */ + bool m_bSpaces; + + /** Next order value, used to ensure sections and keys are output in the + same order that they are loaded/added. + */ + int m_nOrder; +}; + +// --------------------------------------------------------------------------- +// IMPLEMENTATION +// --------------------------------------------------------------------------- + +template +CSimpleIniTempl::CSimpleIniTempl( + bool a_bIsUtf8, + bool a_bAllowMultiKey, + bool a_bAllowMultiLine + ) + : m_pData(0) + , m_uDataLen(0) + , m_pFileComment(NULL) + , m_bStoreIsUtf8(a_bIsUtf8) + , m_bAllowMultiKey(a_bAllowMultiKey) + , m_bAllowMultiLine(a_bAllowMultiLine) + , m_bSpaces(true) + , m_nOrder(0) +{ } + +template +CSimpleIniTempl::~CSimpleIniTempl() +{ + Reset(); +} + +template +void +CSimpleIniTempl::Reset() +{ + // remove all data + delete[] m_pData; + m_pData = NULL; + m_uDataLen = 0; + m_pFileComment = NULL; + if (!m_data.empty()) { + m_data.erase(m_data.begin(), m_data.end()); + } + + // remove all strings + if (!m_strings.empty()) { + typename TNamesDepend::iterator i = m_strings.begin(); + for (; i != m_strings.end(); ++i) { + delete[] const_cast(i->pItem); + } + m_strings.erase(m_strings.begin(), m_strings.end()); + } +} + +template +SI_Error +CSimpleIniTempl::LoadFile( + const char * a_pszFile + ) +{ + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + fopen_s(&fp, a_pszFile, "rb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = fopen(a_pszFile, "rb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) { + return SI_FILE; + } + SI_Error rc = LoadFile(fp); + fclose(fp); + return rc; +} + +#ifdef SI_HAS_WIDE_FILE +template +SI_Error +CSimpleIniTempl::LoadFile( + const SI_WCHAR_T * a_pwszFile + ) +{ +#ifdef _WIN32 + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + _wfopen_s(&fp, a_pwszFile, L"rb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = _wfopen(a_pwszFile, L"rb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = LoadFile(fp); + fclose(fp); + return rc; +#else // !_WIN32 (therefore SI_CONVERT_ICU) + char szFile[256]; + u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); + return LoadFile(szFile); +#endif // _WIN32 +} +#endif // SI_HAS_WIDE_FILE + +template +SI_Error +CSimpleIniTempl::LoadFile( + FILE * a_fpFile + ) +{ + // load the raw file data + int retval = fseek(a_fpFile, 0, SEEK_END); + if (retval != 0) { + return SI_FILE; + } + long lSize = ftell(a_fpFile); + if (lSize < 0) { + return SI_FILE; + } + if (lSize == 0) { + return SI_OK; + } + + // allocate and ensure NULL terminated + char * pData = new char[lSize+1]; + if (!pData) { + return SI_NOMEM; + } + pData[lSize] = 0; + + // load data into buffer + fseek(a_fpFile, 0, SEEK_SET); + size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); + if (uRead != (size_t) lSize) { + delete[] pData; + return SI_FILE; + } + + // convert the raw data to unicode + SI_Error rc = LoadData(pData, uRead); + delete[] pData; + return rc; +} + +template +SI_Error +CSimpleIniTempl::LoadData( + const char * a_pData, + size_t a_uDataLen + ) +{ + SI_CONVERTER converter(m_bStoreIsUtf8); + + if (a_uDataLen == 0) { + return SI_OK; + } + + // consume the UTF-8 BOM if it exists + if (m_bStoreIsUtf8 && a_uDataLen >= 3) { + if (memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { + a_pData += 3; + a_uDataLen -= 3; + } + } + + // determine the length of the converted data + size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen); + if (uLen == (size_t)(-1)) { + return SI_FAIL; + } + + // allocate memory for the data, ensure that there is a NULL + // terminator wherever the converted data ends + SI_CHAR * pData = new SI_CHAR[uLen+1]; + if (!pData) { + return SI_NOMEM; + } + memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); + + // convert the data + if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { + delete[] pData; + return SI_FAIL; + } + + // parse it + const static SI_CHAR empty = 0; + SI_CHAR * pWork = pData; + const SI_CHAR * pSection = ∅ + const SI_CHAR * pItem = NULL; + const SI_CHAR * pVal = NULL; + const SI_CHAR * pComment = NULL; + + // We copy the strings if we are loading data into this class when we + // already have stored some. + bool bCopyStrings = (m_pData != NULL); + + // find a file comment if it exists, this is a comment that starts at the + // beginning of the file and continues until the first blank line. + SI_Error rc = FindFileComment(pWork, bCopyStrings); + if (rc < 0) return rc; + + // add every entry in the file to the data table + while (FindEntry(pWork, pSection, pItem, pVal, pComment)) { + rc = AddEntry(pSection, pItem, pVal, pComment, false, bCopyStrings); + if (rc < 0) return rc; + } + + // store these strings if we didn't copy them + if (bCopyStrings) { + delete[] pData; + } + else { + m_pData = pData; + m_uDataLen = uLen+1; + } + + return SI_OK; +} + +#ifdef SI_SUPPORT_IOSTREAMS +template +SI_Error +CSimpleIniTempl::LoadData( + std::istream & a_istream + ) +{ + std::string strData; + char szBuf[512]; + do { + a_istream.get(szBuf, sizeof(szBuf), '\0'); + strData.append(szBuf); + } + while (a_istream.good()); + return LoadData(strData); +} +#endif // SI_SUPPORT_IOSTREAMS + +template +SI_Error +CSimpleIniTempl::FindFileComment( + SI_CHAR *& a_pData, + bool a_bCopyStrings + ) +{ + // there can only be a single file comment + if (m_pFileComment) { + return SI_OK; + } + + // Load the file comment as multi-line text, this will modify all of + // the newline characters to be single \n chars + if (!LoadMultiLineText(a_pData, m_pFileComment, NULL, false)) { + return SI_OK; + } + + // copy the string if necessary + if (a_bCopyStrings) { + SI_Error rc = CopyString(m_pFileComment); + if (rc < 0) return rc; + } + + return SI_OK; +} + +template +bool +CSimpleIniTempl::FindEntry( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pSection, + const SI_CHAR *& a_pKey, + const SI_CHAR *& a_pVal, + const SI_CHAR *& a_pComment + ) const +{ + a_pComment = NULL; + + SI_CHAR * pTrail = NULL; + while (*a_pData) { + // skip spaces and empty lines + while (*a_pData && IsSpace(*a_pData)) { + ++a_pData; + } + if (!*a_pData) { + break; + } + + // skip processing of comment lines but keep a pointer to + // the start of the comment. + if (IsComment(*a_pData)) { + LoadMultiLineText(a_pData, a_pComment, NULL, true); + continue; + } + + // process section names + if (*a_pData == '[') { + // skip leading spaces + ++a_pData; + while (*a_pData && IsSpace(*a_pData)) { + ++a_pData; + } + + // find the end of the section name (it may contain spaces) + // and convert it to lowercase as necessary + a_pSection = a_pData; + while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // if it's an invalid line, just skip it + if (*a_pData != ']') { + continue; + } + + // remove trailing spaces from the section + pTrail = a_pData - 1; + while (pTrail >= a_pSection && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // skip to the end of the line + ++a_pData; // safe as checked that it == ']' above + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + a_pKey = NULL; + a_pVal = NULL; + return true; + } + + // find the end of the key name (it may contain spaces) + // and convert it to lowercase as necessary + a_pKey = a_pData; + while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // if it's an invalid line, just skip it + if (*a_pData != '=') { + continue; + } + + // empty keys are invalid + if (a_pKey == a_pData) { + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + continue; + } + + // remove trailing spaces from the key + pTrail = a_pData - 1; + while (pTrail >= a_pKey && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // skip leading whitespace on the value + ++a_pData; // safe as checked that it == '=' above + while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData)) { + ++a_pData; + } + + // find the end of the value which is the end of this line + a_pVal = a_pData; + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // remove trailing spaces from the value + pTrail = a_pData - 1; + if (*a_pData) { // prepare for the next round + SkipNewLine(a_pData); + } + while (pTrail >= a_pVal && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // check for multi-line entries + if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) { + // skip the "<<<" to get the tag that will end the multiline + const SI_CHAR * pTagName = a_pVal + 3; + return LoadMultiLineText(a_pData, a_pVal, pTagName); + } + + // return the standard entry + return true; + } + + return false; +} + +template +bool +CSimpleIniTempl::IsMultiLineTag( + const SI_CHAR * a_pVal + ) const +{ + // check for the "<<<" prefix for a multi-line entry + if (*a_pVal++ != '<') return false; + if (*a_pVal++ != '<') return false; + if (*a_pVal++ != '<') return false; + return true; +} + +template +bool +CSimpleIniTempl::IsMultiLineData( + const SI_CHAR * a_pData + ) const +{ + // data is multi-line if it has any of the following features: + // * whitespace prefix + // * embedded newlines + // * whitespace suffix + + // empty string + if (!*a_pData) { + return false; + } + + // check for prefix + if (IsSpace(*a_pData)) { + return true; + } + + // embedded newlines + while (*a_pData) { + if (IsNewLineChar(*a_pData)) { + return true; + } + ++a_pData; + } + + // check for suffix + if (IsSpace(*--a_pData)) { + return true; + } + + return false; +} + +template +bool +CSimpleIniTempl::IsNewLineChar( + SI_CHAR a_c + ) const +{ + return (a_c == '\n' || a_c == '\r'); +} + +template +bool +CSimpleIniTempl::LoadMultiLineText( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pVal, + const SI_CHAR * a_pTagName, + bool a_bAllowBlankLinesInComment + ) const +{ + // we modify this data to strip all newlines down to a single '\n' + // character. This means that on Windows we need to strip out some + // characters which will make the data shorter. + // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become + // LINE1-LINE1\nLINE2-LINE2\0 + // The pDataLine entry is the pointer to the location in memory that + // the current line needs to start to run following the existing one. + // This may be the same as pCurrLine in which case no move is needed. + SI_CHAR * pDataLine = a_pData; + SI_CHAR * pCurrLine; + + // value starts at the current line + a_pVal = a_pData; + + // find the end tag. This tag must start in column 1 and be + // followed by a newline. No whitespace removal is done while + // searching for this tag. + SI_CHAR cEndOfLineChar = *a_pData; + for(;;) { + // if we are loading comments then we need a comment character as + // the first character on every line + if (!a_pTagName && !IsComment(*a_pData)) { + // if we aren't allowing blank lines then we're done + if (!a_bAllowBlankLinesInComment) { + break; + } + + // if we are allowing blank lines then we only include them + // in this comment if another comment follows, so read ahead + // to find out. + SI_CHAR * pCurr = a_pData; + int nNewLines = 0; + while (IsSpace(*pCurr)) { + if (IsNewLineChar(*pCurr)) { + ++nNewLines; + SkipNewLine(pCurr); + } + else { + ++pCurr; + } + } + + // we have a comment, add the blank lines to the output + // and continue processing from here + if (IsComment(*pCurr)) { + for (; nNewLines > 0; --nNewLines) *pDataLine++ = '\n'; + a_pData = pCurr; + continue; + } + + // the comment ends here + break; + } + + // find the end of this line + pCurrLine = a_pData; + while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData; + + // move this line down to the location that it should be if necessary + if (pDataLine < pCurrLine) { + size_t nLen = (size_t) (a_pData - pCurrLine); + memmove(pDataLine, pCurrLine, nLen * sizeof(SI_CHAR)); + pDataLine[nLen] = '\0'; + } + + // end the line with a NULL + cEndOfLineChar = *a_pData; + *a_pData = 0; + + // if are looking for a tag then do the check now. This is done before + // checking for end of the data, so that if we have the tag at the end + // of the data then the tag is removed correctly. + if (a_pTagName && + (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine))) + { + break; + } + + // if we are at the end of the data then we just automatically end + // this entry and return the current data. + if (!cEndOfLineChar) { + return true; + } + + // otherwise we need to process this newline to ensure that it consists + // of just a single \n character. + pDataLine += (a_pData - pCurrLine); + *a_pData = cEndOfLineChar; + SkipNewLine(a_pData); + *pDataLine++ = '\n'; + } + + // if we didn't find a comment at all then return false + if (a_pVal == a_pData) { + a_pVal = NULL; + return false; + } + + // the data (which ends at the end of the last line) needs to be + // null-terminated BEFORE before the newline character(s). If the + // user wants a new line in the multi-line data then they need to + // add an empty line before the tag. + *--pDataLine = '\0'; + + // if looking for a tag and if we aren't at the end of the data, + // then move a_pData to the start of the next line. + if (a_pTagName && cEndOfLineChar) { + SI_ASSERT(IsNewLineChar(cEndOfLineChar)); + *a_pData = cEndOfLineChar; + SkipNewLine(a_pData); + } + + return true; +} + +template +SI_Error +CSimpleIniTempl::CopyString( + const SI_CHAR *& a_pString + ) +{ + size_t uLen = 0; + if (sizeof(SI_CHAR) == sizeof(char)) { + uLen = strlen((const char *)a_pString); + } + else if (sizeof(SI_CHAR) == sizeof(wchar_t)) { + uLen = wcslen((const wchar_t *)a_pString); + } + else { + for ( ; a_pString[uLen]; ++uLen) /*loop*/ ; + } + ++uLen; // NULL character + SI_CHAR * pCopy = new SI_CHAR[uLen]; + if (!pCopy) { + return SI_NOMEM; + } + memcpy(pCopy, a_pString, sizeof(SI_CHAR)*uLen); + m_strings.push_back(pCopy); + a_pString = pCopy; + return SI_OK; +} + +template +SI_Error +CSimpleIniTempl::AddEntry( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace, + bool a_bCopyStrings + ) +{ + SI_Error rc; + bool bInserted = false; + + SI_ASSERT(!a_pComment || IsComment(*a_pComment)); + + // if we are copying strings then make a copy of the comment now + // because we will need it when we add the entry. + if (a_bCopyStrings && a_pComment) { + rc = CopyString(a_pComment); + if (rc < 0) return rc; + } + + // create the section entry if necessary + typename TSection::iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + // if the section doesn't exist then we need a copy as the + // string needs to last beyond the end of this function + if (a_bCopyStrings) { + rc = CopyString(a_pSection); + if (rc < 0) return rc; + } + + // only set the comment if this is a section only entry + Entry oSection(a_pSection, ++m_nOrder); + if (a_pComment && (!a_pKey || !a_pValue)) { + oSection.pComment = a_pComment; + } + + typename TSection::value_type oEntry(oSection, TKeyVal()); + typedef typename TSection::iterator SectionIterator; + std::pair i = m_data.insert(oEntry); + iSection = i.first; + bInserted = true; + } + if (!a_pKey || !a_pValue) { + // section only entries are specified with pItem and pVal as NULL + return bInserted ? SI_INSERTED : SI_UPDATED; + } + + // check for existence of the key + TKeyVal & keyval = iSection->second; + typename TKeyVal::iterator iKey = keyval.find(a_pKey); + + // remove all existing entries but save the load order and + // comment of the first entry + int nLoadOrder = ++m_nOrder; + if (iKey != keyval.end() && m_bAllowMultiKey && a_bForceReplace) { + const SI_CHAR * pComment = NULL; + while (iKey != keyval.end() && !IsLess(a_pKey, iKey->first.pItem)) { + if (iKey->first.nOrder < nLoadOrder) { + nLoadOrder = iKey->first.nOrder; + pComment = iKey->first.pComment; + } + ++iKey; + } + if (pComment) { + DeleteString(a_pComment); + a_pComment = pComment; + CopyString(a_pComment); + } + Delete(a_pSection, a_pKey); + iKey = keyval.end(); + } + + // make string copies if necessary + bool bForceCreateNewKey = m_bAllowMultiKey && !a_bForceReplace; + if (a_bCopyStrings) { + if (bForceCreateNewKey || iKey == keyval.end()) { + // if the key doesn't exist then we need a copy as the + // string needs to last beyond the end of this function + // because we will be inserting the key next + rc = CopyString(a_pKey); + if (rc < 0) return rc; + } + + // we always need a copy of the value + rc = CopyString(a_pValue); + if (rc < 0) return rc; + } + + // create the key entry + if (iKey == keyval.end() || bForceCreateNewKey) { + Entry oKey(a_pKey, nLoadOrder); + if (a_pComment) { + oKey.pComment = a_pComment; + } + typename TKeyVal::value_type oEntry(oKey, static_cast(NULL)); + iKey = keyval.insert(oEntry); + bInserted = true; + } + iKey->second = a_pValue; + return bInserted ? SI_INSERTED : SI_UPDATED; +} + +template +const SI_CHAR * +CSimpleIniTempl::GetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pDefault, + bool * a_pHasMultiple + ) const +{ + if (a_pHasMultiple) { + *a_pHasMultiple = false; + } + if (!a_pSection || !a_pKey) { + return a_pDefault; + } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return a_pDefault; + } + typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return a_pDefault; + } + + // check for multiple entries with the same key + if (m_bAllowMultiKey && a_pHasMultiple) { + typename TKeyVal::const_iterator iTemp = iKeyVal; + if (++iTemp != iSection->second.end()) { + if (!IsLess(a_pKey, iTemp->first.pItem)) { + *a_pHasMultiple = true; + } + } + } + + return iKeyVal->second; +} + +template +long +CSimpleIniTempl::GetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nDefault, + bool * a_pHasMultiple + ) const +{ + // return the default if we don't have a value + const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) return a_nDefault; + + // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII + char szValue[64] = { 0 }; + SI_CONVERTER c(m_bStoreIsUtf8); + if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { + return a_nDefault; + } + + // handle the value as hex if prefaced with "0x" + long nValue = a_nDefault; + char * pszSuffix = szValue; + if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { + if (!szValue[2]) return a_nDefault; + nValue = strtol(&szValue[2], &pszSuffix, 16); + } + else { + nValue = strtol(szValue, &pszSuffix, 10); + } + + // any invalid strings will return the default value + if (*pszSuffix) { + return a_nDefault; + } + + return nValue; +} + +template +SI_Error +CSimpleIniTempl::SetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nValue, + const SI_CHAR * a_pComment, + bool a_bUseHex, + bool a_bForceReplace + ) +{ + // use SetValue to create sections + if (!a_pSection || !a_pKey) return SI_FAIL; + + // convert to an ASCII string + char szInput[64]; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); +#else // !__STDC_WANT_SECURE_LIB__ + sprintf(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); +#endif // __STDC_WANT_SECURE_LIB__ + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(szInput, strlen(szInput) + 1, + szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); +} + +template +double +CSimpleIniTempl::GetDoubleValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + double a_nDefault, + bool * a_pHasMultiple + ) const +{ + // return the default if we don't have a value + const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) return a_nDefault; + + // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII + char szValue[64] = { 0 }; + SI_CONVERTER c(m_bStoreIsUtf8); + if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { + return a_nDefault; + } + + char * pszSuffix = NULL; + double nValue = strtod(szValue, &pszSuffix); + + // any invalid strings will return the default value + if (!pszSuffix || *pszSuffix) { + return a_nDefault; + } + + return nValue; +} + +template +SI_Error +CSimpleIniTempl::SetDoubleValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + double a_nValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace + ) +{ + // use SetValue to create sections + if (!a_pSection || !a_pKey) return SI_FAIL; + + // convert to an ASCII string + char szInput[64]; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + sprintf_s(szInput, "%f", a_nValue); +#else // !__STDC_WANT_SECURE_LIB__ + sprintf(szInput, "%f", a_nValue); +#endif // __STDC_WANT_SECURE_LIB__ + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(szInput, strlen(szInput) + 1, + szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); +} + +template +bool +CSimpleIniTempl::GetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bDefault, + bool * a_pHasMultiple + ) const +{ + // return the default if we don't have a value + const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) return a_bDefault; + + // we only look at the minimum number of characters + switch (pszValue[0]) { + case 't': case 'T': // true + case 'y': case 'Y': // yes + case '1': // 1 (one) + return true; + + case 'f': case 'F': // false + case 'n': case 'N': // no + case '0': // 0 (zero) + return false; + + case 'o': case 'O': + if (pszValue[1] == 'n' || pszValue[1] == 'N') return true; // on + if (pszValue[1] == 'f' || pszValue[1] == 'F') return false; // off + break; + } + + // no recognized value, return the default + return a_bDefault; +} + +template +SI_Error +CSimpleIniTempl::SetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace + ) +{ + // use SetValue to create sections + if (!a_pSection || !a_pKey) return SI_FAIL; + + // convert to an ASCII string + const char * pszInput = a_bValue ? "true" : "false"; + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(pszInput, strlen(pszInput) + 1, + szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); +} + +template +bool +CSimpleIniTempl::GetAllValues( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + TNamesDepend & a_values + ) const +{ + a_values.clear(); + + if (!a_pSection || !a_pKey) { + return false; + } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return false; + } + + // insert all values for this key + a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); + if (m_bAllowMultiKey) { + ++iKeyVal; + while (iKeyVal != iSection->second.end() && !IsLess(a_pKey, iKeyVal->first.pItem)) { + a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); + ++iKeyVal; + } + } + + return true; +} + +template +int +CSimpleIniTempl::GetSectionSize( + const SI_CHAR * a_pSection + ) const +{ + if (!a_pSection) { + return -1; + } + + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return -1; + } + const TKeyVal & section = iSection->second; + + // if multi-key isn't permitted then the section size is + // the number of keys that we have. + if (!m_bAllowMultiKey || section.empty()) { + return (int) section.size(); + } + + // otherwise we need to count them + int nCount = 0; + const SI_CHAR * pLastKey = NULL; + typename TKeyVal::const_iterator iKeyVal = section.begin(); + for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { + if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { + ++nCount; + pLastKey = iKeyVal->first.pItem; + } + } + return nCount; +} + +template +const typename CSimpleIniTempl::TKeyVal * +CSimpleIniTempl::GetSection( + const SI_CHAR * a_pSection + ) const +{ + if (a_pSection) { + typename TSection::const_iterator i = m_data.find(a_pSection); + if (i != m_data.end()) { + return &(i->second); + } + } + return 0; +} + +template +void +CSimpleIniTempl::GetAllSections( + TNamesDepend & a_names + ) const +{ + a_names.clear(); + typename TSection::const_iterator i = m_data.begin(); + for (int n = 0; i != m_data.end(); ++i, ++n ) { + a_names.push_back(i->first); + } +} + +template +bool +CSimpleIniTempl::GetAllKeys( + const SI_CHAR * a_pSection, + TNamesDepend & a_names + ) const +{ + a_names.clear(); + + if (!a_pSection) { + return false; + } + + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + + const TKeyVal & section = iSection->second; + const SI_CHAR * pLastKey = NULL; + typename TKeyVal::const_iterator iKeyVal = section.begin(); + for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { + if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { + a_names.push_back(iKeyVal->first); + pLastKey = iKeyVal->first.pItem; + } + } + + return true; +} + +template +SI_Error +CSimpleIniTempl::SaveFile( + const char * a_pszFile, + bool a_bAddSignature + ) const +{ + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + fopen_s(&fp, a_pszFile, "wb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = fopen(a_pszFile, "wb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = SaveFile(fp, a_bAddSignature); + fclose(fp); + return rc; +} + +#ifdef SI_HAS_WIDE_FILE +template +SI_Error +CSimpleIniTempl::SaveFile( + const SI_WCHAR_T * a_pwszFile, + bool a_bAddSignature + ) const +{ +#ifdef _WIN32 + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + _wfopen_s(&fp, a_pwszFile, L"wb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = _wfopen(a_pwszFile, L"wb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = SaveFile(fp, a_bAddSignature); + fclose(fp); + return rc; +#else // !_WIN32 (therefore SI_CONVERT_ICU) + char szFile[256]; + u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); + return SaveFile(szFile, a_bAddSignature); +#endif // _WIN32 +} +#endif // SI_HAS_WIDE_FILE + +template +SI_Error +CSimpleIniTempl::SaveFile( + FILE * a_pFile, + bool a_bAddSignature + ) const +{ + FileWriter writer(a_pFile); + return Save(writer, a_bAddSignature); +} + +template +SI_Error +CSimpleIniTempl::Save( + OutputWriter & a_oOutput, + bool a_bAddSignature + ) const +{ + Converter convert(m_bStoreIsUtf8); + + // add the UTF-8 signature if it is desired + if (m_bStoreIsUtf8 && a_bAddSignature) { + a_oOutput.Write(SI_UTF8_SIGNATURE); + } + + // get all of the sections sorted in load order + TNamesDepend oSections; + GetAllSections(oSections); +#if defined(_MSC_VER) && _MSC_VER <= 1200 + oSections.sort(); +#elif defined(__BORLANDC__) + oSections.sort(Entry::LoadOrder()); +#else + oSections.sort(typename Entry::LoadOrder()); +#endif + + // write the file comment if we have one + bool bNeedNewLine = false; + if (m_pFileComment) { + if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment)) { + return SI_FAIL; + } + bNeedNewLine = true; + } + + // iterate through our sections and output the data + typename TNamesDepend::const_iterator iSection = oSections.begin(); + for ( ; iSection != oSections.end(); ++iSection ) { + // write out the comment if there is one + if (iSection->pComment) { + if (bNeedNewLine) { + a_oOutput.Write(SI_NEWLINE_A); + a_oOutput.Write(SI_NEWLINE_A); + } + if (!OutputMultiLineText(a_oOutput, convert, iSection->pComment)) { + return SI_FAIL; + } + bNeedNewLine = false; + } + + if (bNeedNewLine) { + a_oOutput.Write(SI_NEWLINE_A); + a_oOutput.Write(SI_NEWLINE_A); + bNeedNewLine = false; + } + + // write the section (unless there is no section name) + if (*iSection->pItem) { + if (!convert.ConvertToStore(iSection->pItem)) { + return SI_FAIL; + } + a_oOutput.Write("["); + a_oOutput.Write(convert.Data()); + a_oOutput.Write("]"); + a_oOutput.Write(SI_NEWLINE_A); + } + + // get all of the keys sorted in load order + TNamesDepend oKeys; + GetAllKeys(iSection->pItem, oKeys); +#if defined(_MSC_VER) && _MSC_VER <= 1200 + oKeys.sort(); +#elif defined(__BORLANDC__) + oKeys.sort(Entry::LoadOrder()); +#else + oKeys.sort(typename Entry::LoadOrder()); +#endif + + // write all keys and values + typename TNamesDepend::const_iterator iKey = oKeys.begin(); + for ( ; iKey != oKeys.end(); ++iKey) { + // get all values for this key + TNamesDepend oValues; + GetAllValues(iSection->pItem, iKey->pItem, oValues); + + typename TNamesDepend::const_iterator iValue = oValues.begin(); + for ( ; iValue != oValues.end(); ++iValue) { + // write out the comment if there is one + if (iValue->pComment) { + a_oOutput.Write(SI_NEWLINE_A); + if (!OutputMultiLineText(a_oOutput, convert, iValue->pComment)) { + return SI_FAIL; + } + } + + // write the key + if (!convert.ConvertToStore(iKey->pItem)) { + return SI_FAIL; + } + a_oOutput.Write(convert.Data()); + + // write the value + if (!convert.ConvertToStore(iValue->pItem)) { + return SI_FAIL; + } + a_oOutput.Write(m_bSpaces ? " = " : "="); + if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem)) { + // multi-line data needs to be processed specially to ensure + // that we use the correct newline format for the current system + a_oOutput.Write("<<pItem)) { + return SI_FAIL; + } + a_oOutput.Write("END_OF_TEXT"); + } + else { + a_oOutput.Write(convert.Data()); + } + a_oOutput.Write(SI_NEWLINE_A); + } + } + + bNeedNewLine = true; + } + + return SI_OK; +} + +template +bool +CSimpleIniTempl::OutputMultiLineText( + OutputWriter & a_oOutput, + Converter & a_oConverter, + const SI_CHAR * a_pText + ) const +{ + const SI_CHAR * pEndOfLine; + SI_CHAR cEndOfLineChar = *a_pText; + while (cEndOfLineChar) { + // find the end of this line + pEndOfLine = a_pText; + for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/ ; + cEndOfLineChar = *pEndOfLine; + + // temporarily null terminate, convert and output the line + *const_cast(pEndOfLine) = 0; + if (!a_oConverter.ConvertToStore(a_pText)) { + return false; + } + *const_cast(pEndOfLine) = cEndOfLineChar; + a_pText += (pEndOfLine - a_pText) + 1; + a_oOutput.Write(a_oConverter.Data()); + a_oOutput.Write(SI_NEWLINE_A); + } + return true; +} + +template +bool +CSimpleIniTempl::Delete( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bRemoveEmpty + ) +{ + return DeleteValue(a_pSection, a_pKey, NULL, a_bRemoveEmpty); +} + +template +bool +CSimpleIniTempl::DeleteValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + bool a_bRemoveEmpty + ) +{ + if (!a_pSection) { + return false; + } + + typename TSection::iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + + // remove a single key if we have a keyname + if (a_pKey) { + typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return false; + } + + const static SI_STRLESS isLess = SI_STRLESS(); + + // remove any copied strings and then the key + typename TKeyVal::iterator iDelete; + bool bDeleted = false; + do { + iDelete = iKeyVal++; + + if(a_pValue == NULL || + (isLess(a_pValue, iDelete->second) == false && + isLess(iDelete->second, a_pValue) == false)) { + DeleteString(iDelete->first.pItem); + DeleteString(iDelete->second); + iSection->second.erase(iDelete); + bDeleted = true; + } + } + while (iKeyVal != iSection->second.end() + && !IsLess(a_pKey, iKeyVal->first.pItem)); + + if(!bDeleted) { + return false; + } + + // done now if the section is not empty or we are not pruning away + // the empty sections. Otherwise let it fall through into the section + // deletion code + if (!a_bRemoveEmpty || !iSection->second.empty()) { + return true; + } + } + else { + // delete all copied strings from this section. The actual + // entries will be removed when the section is removed. + typename TKeyVal::iterator iKeyVal = iSection->second.begin(); + for ( ; iKeyVal != iSection->second.end(); ++iKeyVal) { + DeleteString(iKeyVal->first.pItem); + DeleteString(iKeyVal->second); + } + } + + // delete the section itself + DeleteString(iSection->first.pItem); + m_data.erase(iSection); + + return true; +} + +template +void +CSimpleIniTempl::DeleteString( + const SI_CHAR * a_pString + ) +{ + // strings may exist either inside the data block, or they will be + // individually allocated and stored in m_strings. We only physically + // delete those stored in m_strings. + if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { + typename TNamesDepend::iterator i = m_strings.begin(); + for (;i != m_strings.end(); ++i) { + if (a_pString == i->pItem) { + delete[] const_cast(i->pItem); + m_strings.erase(i); + break; + } + } + } +} + +// --------------------------------------------------------------------------- +// CONVERSION FUNCTIONS +// --------------------------------------------------------------------------- + +// Defines the conversion classes for different libraries. Before including +// SimpleIni.h, set the converter that you wish you use by defining one of the +// following symbols. +// +// SI_CONVERT_GENERIC Use the Unicode reference conversion library in +// the accompanying files ConvertUTF.h/c +// SI_CONVERT_ICU Use the IBM ICU conversion library. Requires +// ICU headers on include path and icuuc.lib +// SI_CONVERT_WIN32 Use the Win32 API functions for conversion. + +#if !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) +# ifdef _WIN32 +# define SI_CONVERT_WIN32 +# else +# define SI_CONVERT_GENERIC +# endif +#endif + +/** + * Generic case-sensitive less than comparison. This class returns numerically + * ordered ASCII case-sensitive text for all possible sizes and types of + * SI_CHAR. + */ +template +struct SI_GenericCase { + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + long cmp; + for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { + cmp = (long) *pLeft - (long) *pRight; + if (cmp != 0) { + return cmp < 0; + } + } + return *pRight != 0; + } +}; + +/** + * Generic ASCII case-insensitive less than comparison. This class returns + * numerically ordered ASCII case-insensitive text for all possible sizes + * and types of SI_CHAR. It is not safe for MBCS text comparison where + * ASCII A-Z characters are used in the encoding of multi-byte characters. + */ +template +struct SI_GenericNoCase { + inline SI_CHAR locase(SI_CHAR ch) const { + return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a'); + } + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + long cmp; + for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { + cmp = (long) locase(*pLeft) - (long) locase(*pRight); + if (cmp != 0) { + return cmp < 0; + } + } + return *pRight != 0; + } +}; + +/** + * Null conversion class for MBCS/UTF-8 to char (or equivalent). + */ +template +class SI_ConvertA { + bool m_bStoreIsUtf8; +protected: + SI_ConvertA() { } +public: + SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } + + /* copy and assignment */ + SI_ConvertA(const SI_ConvertA & rhs) { operator=(rhs); } + SI_ConvertA & operator=(const SI_ConvertA & rhs) { + m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + (void)a_pInputData; + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + // ASCII/MBCS/UTF-8 needs no conversion + return a_uInputDataLen; + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + // ASCII/MBCS/UTF-8 needs no conversion + if (a_uInputDataLen > a_uOutputDataSize) { + return false; + } + memcpy(a_pOutputData, a_pInputData, a_uInputDataLen); + return true; + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + // ASCII/MBCS/UTF-8 needs no conversion + return strlen((const char *)a_pInputData) + 1; + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_uOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + // calc input string length (SI_CHAR type and size independent) + size_t uInputLen = strlen((const char *)a_pInputData) + 1; + if (uInputLen > a_uOutputDataSize) { + return false; + } + + // ascii/UTF-8 needs no conversion + memcpy(a_pOutputData, a_pInputData, uInputLen); + return true; + } +}; + + +// --------------------------------------------------------------------------- +// SI_CONVERT_GENERIC +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_GENERIC + +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase + +#include +#include "ConvertUTF.h" + +/** + * Converts UTF-8 to a wchar_t (or equivalent) using the Unicode reference + * library functions. This can be used on all platforms. + */ +template +class SI_ConvertW { + bool m_bStoreIsUtf8; +protected: + SI_ConvertW() { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + if (m_bStoreIsUtf8) { + // worst case scenario for UTF-8 to wchar_t is 1 char -> 1 wchar_t + // so we just return the same number of characters required as for + // the source text. + return a_uInputDataLen; + } + +#if defined(SI_NO_MBSTOWCS_NULL) || (!defined(_MSC_VER) && !defined(_linux)) + // fall back processing for platforms that don't support a NULL dest to mbstowcs + // worst case scenario is 1:1, this will be a sufficient buffer size + (void)a_pInputData; + return a_uInputDataLen; +#else + // get the actual required buffer size + return mbstowcs(NULL, a_pInputData, a_uInputDataLen); +#endif + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + if (m_bStoreIsUtf8) { + // This uses the Unicode reference implementation to do the + // conversion from UTF-8 to wchar_t. The required files are + // ConvertUTF.h and ConvertUTF.c which should be included in + // the distribution but are publically available from unicode.org + // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ + ConversionResult retval; + const UTF8 * pUtf8 = (const UTF8 *) a_pInputData; + if (sizeof(wchar_t) == sizeof(UTF32)) { + UTF32 * pUtf32 = (UTF32 *) a_pOutputData; + retval = ConvertUTF8toUTF32( + &pUtf8, pUtf8 + a_uInputDataLen, + &pUtf32, pUtf32 + a_uOutputDataSize, + lenientConversion); + } + else if (sizeof(wchar_t) == sizeof(UTF16)) { + UTF16 * pUtf16 = (UTF16 *) a_pOutputData; + retval = ConvertUTF8toUTF16( + &pUtf8, pUtf8 + a_uInputDataLen, + &pUtf16, pUtf16 + a_uOutputDataSize, + lenientConversion); + } + return retval == conversionOK; + } + + // convert to wchar_t + size_t retval = mbstowcs(a_pOutputData, + a_pInputData, a_uOutputDataSize); + return retval != (size_t)(-1); + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + if (m_bStoreIsUtf8) { + // worst case scenario for wchar_t to UTF-8 is 1 wchar_t -> 6 char + size_t uLen = 0; + while (a_pInputData[uLen]) { + ++uLen; + } + return (6 * uLen) + 1; + } + else { + size_t uLen = wcstombs(NULL, a_pInputData, 0); + if (uLen == (size_t)(-1)) { + return uLen; + } + return uLen + 1; // include NULL terminator + } + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_uOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize + ) + { + if (m_bStoreIsUtf8) { + // calc input string length (SI_CHAR type and size independent) + size_t uInputLen = 0; + while (a_pInputData[uInputLen]) { + ++uInputLen; + } + ++uInputLen; // include the NULL char + + // This uses the Unicode reference implementation to do the + // conversion from wchar_t to UTF-8. The required files are + // ConvertUTF.h and ConvertUTF.c which should be included in + // the distribution but are publically available from unicode.org + // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ + ConversionResult retval; + UTF8 * pUtf8 = (UTF8 *) a_pOutputData; + if (sizeof(wchar_t) == sizeof(UTF32)) { + const UTF32 * pUtf32 = (const UTF32 *) a_pInputData; + retval = ConvertUTF32toUTF8( + &pUtf32, pUtf32 + uInputLen, + &pUtf8, pUtf8 + a_uOutputDataSize, + lenientConversion); + } + else if (sizeof(wchar_t) == sizeof(UTF16)) { + const UTF16 * pUtf16 = (const UTF16 *) a_pInputData; + retval = ConvertUTF16toUTF8( + &pUtf16, pUtf16 + uInputLen, + &pUtf8, pUtf8 + a_uOutputDataSize, + lenientConversion); + } + return retval == conversionOK; + } + else { + size_t retval = wcstombs(a_pOutputData, + a_pInputData, a_uOutputDataSize); + return retval != (size_t) -1; + } + } +}; + +#endif // SI_CONVERT_GENERIC + + +// --------------------------------------------------------------------------- +// SI_CONVERT_ICU +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_ICU + +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase + +#include + +/** + * Converts MBCS/UTF-8 to UChar using ICU. This can be used on all platforms. + */ +template +class SI_ConvertW { + const char * m_pEncoding; + UConverter * m_pConverter; +protected: + SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { + m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; + } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_pEncoding = rhs.m_pEncoding; + m_pConverter = NULL; + return *this; + } + ~SI_ConvertW() { if (m_pConverter) ucnv_close(m_pConverter); } + + /** Calculate the number of UChar required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to UChar. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of UChar required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return (size_t) -1; + } + } + + nError = U_ZERO_ERROR; + int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0, + a_pInputData, (int32_t) a_uInputDataLen, &nError); + if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { + return (size_t) -1; + } + + return (size_t) nLen; + } + + /** Convert the input string from the storage format to UChar. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to UChar. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in UChar. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + UChar * a_pOutputData, + size_t a_uOutputDataSize) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return false; + } + } + + nError = U_ZERO_ERROR; + ucnv_toUChars(m_pConverter, + a_pOutputData, (int32_t) a_uOutputDataSize, + a_pInputData, (int32_t) a_uInputDataLen, &nError); + if (U_FAILURE(nError)) { + return false; + } + + return true; + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const UChar * a_pInputData) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return (size_t) -1; + } + } + + nError = U_ZERO_ERROR; + int32_t nLen = ucnv_fromUChars(m_pConverter, NULL, 0, + a_pInputData, -1, &nError); + if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { + return (size_t) -1; + } + + return (size_t) nLen + 1; + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_pOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const UChar * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return false; + } + } + + nError = U_ZERO_ERROR; + ucnv_fromUChars(m_pConverter, + a_pOutputData, (int32_t) a_uOutputDataSize, + a_pInputData, -1, &nError); + if (U_FAILURE(nError)) { + return false; + } + + return true; + } +}; + +#endif // SI_CONVERT_ICU + + +// --------------------------------------------------------------------------- +// SI_CONVERT_WIN32 +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_WIN32 + +#define SI_Case SI_GenericCase + +// Windows CE doesn't have errno or MBCS libraries +#ifdef _WIN32_WCE +# ifndef SI_NO_MBCS +# define SI_NO_MBCS +# endif +#endif + +#include +#ifdef SI_NO_MBCS +# define SI_NoCase SI_GenericNoCase +#else // !SI_NO_MBCS +/** + * Case-insensitive comparison class using Win32 MBCS functions. This class + * returns a case-insensitive semi-collation order for MBCS text. It may not + * be safe for UTF-8 text returned in char format as we don't know what + * characters will be folded by the function! Therefore, if you are using + * SI_CHAR == char and SetUnicode(true), then you need to use the generic + * SI_NoCase class instead. + */ +#include +template +struct SI_NoCase { + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + if (sizeof(SI_CHAR) == sizeof(char)) { + return _mbsicmp((const unsigned char *)pLeft, + (const unsigned char *)pRight) < 0; + } + if (sizeof(SI_CHAR) == sizeof(wchar_t)) { + return _wcsicmp((const wchar_t *)pLeft, + (const wchar_t *)pRight) < 0; + } + return SI_GenericNoCase()(pLeft, pRight); + } +}; +#endif // SI_NO_MBCS + +/** + * Converts MBCS and UTF-8 to a wchar_t (or equivalent) on Windows. This uses + * only the Win32 functions and doesn't require the external Unicode UTF-8 + * conversion library. It will not work on Windows 95 without using Microsoft + * Layer for Unicode in your application. + */ +template +class SI_ConvertW { + UINT m_uCodePage; +protected: + SI_ConvertW() { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) { + m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; + } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_uCodePage = rhs.m_uCodePage; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + int retval = MultiByteToWideChar( + m_uCodePage, 0, + a_pInputData, (int) a_uInputDataLen, + 0, 0); + return (size_t)(retval > 0 ? retval : -1); + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + int nSize = MultiByteToWideChar( + m_uCodePage, 0, + a_pInputData, (int) a_uInputDataLen, + (wchar_t *) a_pOutputData, (int) a_uOutputDataSize); + return (nSize > 0); + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + int retval = WideCharToMultiByte( + m_uCodePage, 0, + (const wchar_t *) a_pInputData, -1, + 0, 0, 0, 0); + return (size_t) (retval > 0 ? retval : -1); + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_pOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + int retval = WideCharToMultiByte( + m_uCodePage, 0, + (const wchar_t *) a_pInputData, -1, + a_pOutputData, (int) a_uOutputDataSize, 0, 0); + return retval > 0; + } +}; + +#endif // SI_CONVERT_WIN32 + + +// --------------------------------------------------------------------------- +// TYPE DEFINITIONS +// --------------------------------------------------------------------------- + +typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniA; +typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniCaseA; + +#if defined(SI_CONVERT_ICU) +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; +#else +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; +#endif + +#ifdef _UNICODE +# define CSimpleIni CSimpleIniW +# define CSimpleIniCase CSimpleIniCaseW +# define SI_NEWLINE SI_NEWLINE_W +#else // !_UNICODE +# define CSimpleIni CSimpleIniA +# define CSimpleIniCase CSimpleIniCaseA +# define SI_NEWLINE SI_NEWLINE_A +#endif // _UNICODE + +#ifdef _MSC_VER +# pragma warning (pop) +#endif + +#endif // INCLUDED_SimpleIni_h +