diff --git a/.gitignore b/.gitignore index b24d71e..ad2c8f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,4 @@ -# These are some examples of commonly ignored file patterns. -# You should customize this list as applicable to your project. -# Learn more about .gitignore: -# https://www.atlassian.com/git/tutorials/saving-changes/gitignore - -# Node artifact files -node_modules/ -dist/ - -# Compiled Java class files -*.class - -# Compiled Python bytecode -*.py[cod] - -# Log files -*.log - -# Package files -*.jar - -# Maven -target/ -dist/ - -# JetBrains IDE -.idea/ - -# Unit test reports -TEST*.xml - -# Generated by MacOS -.DS_Store - -# Generated by Windows -Thumbs.db - -# Applications -*.app -*.exe -*.war - -# Large media files -*.mp4 -*.tiff -*.avi -*.flv -*.mov -*.wmv - +.gitignore +/.idea +/cmake-build-* +version.h diff --git a/AutoVersion.cmake b/AutoVersion.cmake new file mode 100644 index 0000000..44b79a7 --- /dev/null +++ b/AutoVersion.cmake @@ -0,0 +1,41 @@ +find_package(Git) + +if (GIT_FOUND) + message(STATUS "Found Git") + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + OUTPUT_VARIABLE GIT_REVISION + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + message(STATUS "Current Git commit hash: " ${GIT_REVISION}) +endif() + +file(READ ${VERSION_FILE} VERSION_TEXT) + +message(STATUS "FILE: ${VERSION_TEXT}") + +string(REGEX MATCH "\"([0-9]+)\\.([0-9]+)\\.([0-9a-zA-Z]+)\\-b([0-9]+)\"" MY_MATCHES "${VERSION_TEXT}") + +set(VAR_MAJOR ${CMAKE_MATCH_1}) +set(VAR_MINOR ${CMAKE_MATCH_2}) +set(VAR_PATCH ${CMAKE_MATCH_3}) +set(VAR_TWEAK ${CMAKE_MATCH_4}) + +message(STATUS "MATCH: ${VAR_MAJOR}.${VAR_MINOR}.${VAR_PATCH}-b${VAR_TWEAK}") + +if (${GIT_REVISION} STREQUAL ${VAR_PATCH}) + math(EXPR VAR_TWEAK "${VAR_TWEAK}+1") +else() + math(EXPR VAR_TWEAK "1") +endif() + +set(VAR_PATCH ${GIT_REVISION}) + +message(STATUS "AUTO_VERSION: ${VAR_MAJOR}.${VAR_MINOR}.${VAR_PATCH}-b${VAR_TWEAK}") + +string(REGEX REPLACE + "\"([0-9]+)\\.([0-9]+)\\.([0-9a-zA-Z]+)\\-b([0-9]+)\"" + "\"${VAR_MAJOR}.${VAR_MINOR}.${VAR_PATCH}-b${VAR_TWEAK}\"" + VERSION_TEXT "${VERSION_TEXT}") + +file(WRITE ${VERSION_FILE} "${VERSION_TEXT}") diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..44a325f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,296 @@ +cmake_minimum_required(VERSION 3.13) +project(apostol-dm) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++ -rdynamic") + +set(PROJECT_NAME "dm") +set(PROJECT_DESCRIPTION "Bitcoin Payment Service (Deal Module)") + +message(STATUS "Project name: ${PROJECT_NAME}") +message(STATUS "Project description: ${PROJECT_DESCRIPTION}") + +# Settings +# ---------------------------------------------------------------------------------------------------------------------- +set(INSTALL_AS_ROOT ON CACHE BOOL "Install as root") +set(WITH_POSTGRESQL OFF CACHE BOOL "Build with PostgreSQL") +set(WITH_SQLITE3 OFF CACHE BOOL "Build with Sqlite3") +set(WITH_CURL OFF CACHE BOOL "Build with cURL") +set(WITH_BITCOIN_CLIENT OFF CACHE BOOL "Build with libbitcoin-client") +# ---------------------------------------------------------------------------------------------------------------------- + +if (INSTALL_AS_ROOT) + set(INSTALL_BIN_PATH "/usr/sbin") + set(PROJECT_PREFIX "/etc/${PROJECT_NAME}") +else() + set(INSTALL_BIN_PATH "/usr/local/sbin") + set(PROJECT_PREFIX "$ENV{HOME}/.config/${PROJECT_NAME}") +endif() + +list( APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake-modules" ) + +add_compile_options("$<$:-D_DEBUG>") + +set(CORE_LIB_NAME apostol-core) +#set(MODULES_LIB_NAME apostol-modules) + +add_custom_target( + auto_increment_version + ${CMAKE_COMMAND} + -D VERSION_FILE=${CMAKE_SOURCE_DIR}/version.h + -P ${CMAKE_SOURCE_DIR}/AutoVersion.cmake +) + +set(PROJECT_LIB_DIR "src/lib") + +# Delphi classes for C++ +# ---------------------------------------------------------------------------------------------------------------------- +set(DELPHI_LIB_NAME delphi) +set(DELPHI_LIB_DIR "${PROJECT_LIB_DIR}/${DELPHI_LIB_NAME}") + +# -Iinclude +include_directories(${DELPHI_LIB_DIR}/include) +include_directories(${DELPHI_LIB_DIR}/src) + +add_compile_options("-DDELPHI_LIB_EXPORTS") +add_compile_options(-DWWWServerName="${PROJECT_DESCRIPTION}") + +if (WITH_POSTGRESQL) + set(PQ_LIB_NAME "pq") + add_compile_options("-DWITH_POSTGRESQL") +endif() + +if (WITH_SSL) + set(SSL_LIB_NAME "ssl") + add_compile_options("-DWITH_SSL") +endif() + +if (WITH_SQLITE3) + set(SQLITE3_LIB_NAME "sqlite3") + add_compile_options("-DWITH_SQLITE3") +endif() + +if (WITH_CURL) + set(CURL_LIB_NAME "curl") + add_compile_options("-DWITH_CURL") +endif() + +add_subdirectory(${DELPHI_LIB_DIR}) + +# Find PkgConfig +#------------------------------------------------------------------------------ +find_package( PkgConfig REQUIRED ) + +set( prefix "${CMAKE_PREFIX_PATH}" ) +set( exec_prefix "\${prefix}" ) +set( libdir "\${exec_prefix}/lib" ) +set( includedir "\${exec_prefix}/include" ) + +set( pkgconfigdir "${libdir}/pkgconfig" CACHE PATH "Path to pkgconfig directory." ) + +# Find dl +#------------------------------------------------------------------------------ +if ((${CMAKE_SYSTEM_NAME} MATCHES "Linux")) + find_package( Dl REQUIRED ) + + message(STATUS "Dl libs: ${dl_LIBS}") + message(STATUS "Dl libraries: ${dl_LIBRARIES}") +endif() + +# rapidxml +# ---------------------------------------------------------------------------------------------------------------------- +include_directories(${PROJECT_LIB_DIR}/rapidxml) +file(GLOB lib_files ${lib_files} ${PROJECT_LIB_DIR}/rapidxml/*.hpp) + +# picojson +# ---------------------------------------------------------------------------------------------------------------------- +include_directories(${PROJECT_LIB_DIR}/picojson) +file(GLOB lib_files ${lib_files} ${PROJECT_LIB_DIR}/picojson/*.h) + +# jwt-cpp +# ---------------------------------------------------------------------------------------------------------------------- +include_directories(${PROJECT_LIB_DIR}/jwt-cpp) +file(GLOB lib_files ${lib_files} ${PROJECT_LIB_DIR}/jwt-cpp/*.h) + +# Find boost +#------------------------------------------------------------------------------ +find_package(Boost 1.62.0 REQUIRED COMPONENTS) + +set( Boost_LIBRARY_DIR "${Boost_LIBRARY_DIR_RELEASE}" ) + +set( boost_CPPFLAGS "-I${Boost_INCLUDE_DIR}" ) +set( boost_LDFLAGS "-L${Boost_LIBRARY_DIR}" ) + +# Find OpenPGP +#------------------------------------------------------------------------------ +find_package(OpenPGP REQUIRED) + +message(STATUS "OpenPGP headers: ${openpgp_INCLUDE_DIRS}") +message(STATUS "OpenPGP directory: ${openpgp_LIBRARY_DIRS}") +message(STATUS "OpenPGP libraries: ${openpgp_LIBRARIES}") + +add_definitions( -DOPENSSL_RNG ) +add_definitions( -DGPG_COMPATIBLE ) + +# Find Bitcoin +#------------------------------------------------------------------------------ + +#set(BITCOIN_VERSION "3.6.0") +set(BITCOIN_VERSION "3.7.0") +#set(BITCOIN_VERSION "4.0.0") + +#add_definitions( -DWITH_ICU ) + +if ( ${BITCOIN_VERSION} VERSION_LESS "3.7.0" ) + + set(BITCOIN_LIB_NAME bitcoin) + + find_package(Bitcoin ${BITCOIN_VERSION} REQUIRED) + + message(STATUS "Bitcoin headers: ${bitcoin_INCLUDE_DIRS}") + message(STATUS "Bitcoin directory: ${bitcoin_LIBRARY_DIRS}") + message(STATUS "Bitcoin libraries: ${bitcoin_LIBRARIES}") + +else() + + if ( ${BITCOIN_VERSION} VERSION_EQUAL "4.0.0" ) + add_definitions(-DBITCOIN_VERSION_4) + else() + add_definitions(-DBITCOIN_VERSION_3_7_x) + endif() + + add_definitions(-DBITCOIN_LIB_NAME="bitcoin-system") + set(BITCOIN_LIB_NAME bitcoin-system) + + find_package(Bitcoin-System ${BITCOIN_VERSION} REQUIRED) + + message(STATUS "Bitcoin-System headers: ${bitcoin_system_INCLUDE_DIRS}") + message(STATUS "Bitcoin-System directory: ${bitcoin_system_LIBRARY_DIRS}") + message(STATUS "Bitcoin-System libraries: ${bitcoin_system_LIBRARIES}") + +endif() + +# Find Bitcoin-Client +#------------------------------------------------------------------------------ +if (WITH_BITCOIN_CLIENT) + add_compile_options("-DWITH_BITCOIN_CLIENT") + + find_package(Bitcoin-Client ${BITCOIN_VERSION} REQUIRED) + + message(STATUS "Bitcoin-Client headers: ${bitcoin_client_INCLUDE_DIRS}") + message(STATUS "Bitcoin-Client directory: ${bitcoin_client_LIBRARY_DIRS}") + message(STATUS "Bitcoin-Client libraries: ${bitcoin_client_LIBRARIES}") +endif() + +# Define project common includes directories +#------------------------------------------------------------------------------ +include_directories( SYSTEM + ${bitcoin_INCLUDE_DIRS} + ${bitcoin_system_INCLUDE_DIRS} + ${bitcoin_client_INCLUDE_DIRS} + ${openpgp_INCLUDE_DIRS} + ${dl_INCLUDE_DIRS} + ) + +# Define project common library directories +#------------------------------------------------------------------------------ +link_directories( + ${bitcoin_LIBRARY_DIRS} + ${bitcoin_system_LIBRARY_DIRS} + ${bitcoin_client_LIBRARY_DIRS} + ${openpgp_LIBRARY_DIRS} + ${dl_LIBRARY_DIRS} +) + +# Define project common libraries/linker flags. +#------------------------------------------------------------------------------ +link_libraries( + "-fstack-protector" + "-fstack-protector-all" + ${bitcoin_LIBRARIES} + ${bitcoin_system_LIBRARIES} + ${bitcoin_client_LIBRARIES} + ${openpgp_LIBRARIES} + ${dl_LIBRARIES} +) + +# Apostol +# ---------------------------------------------------------------------------------------------------------------------- +include_directories(src/app src/core src/modules src/modules/Workers src/modules/Helpers src/processes) + +file(GLOB app_files version.h src/app/*.hpp src/app/*.cpp) +file(GLOB core_files src/core/*.hpp src/core/*.cpp) +file(GLOB modules_files src/modules/Modules.hpp src/modules/*/*.hpp src/modules/*/*/*.hpp src/modules/*/*/*.cpp) +file(GLOB processes_files src/processes/Processes.hpp src/processes/*/*.hpp src/processes/*/*.cpp) + +# Apostol Core +# ---------------------------------------------------------------------------------------------------------------------- +add_library(${CORE_LIB_NAME} STATIC + $ + ${lib_files} + ${core_files} + ${modules_files} + ${processes_files} + ) + +target_compile_definitions(${CORE_LIB_NAME} PUBLIC + APP_NAME="${PROJECT_NAME}" + APP_DESCRIPTION="${PROJECT_DESCRIPTION}" + APP_DEFAULT_LOCALE="en_US.UTF-8" + APP_VAR="${PROJECT_NAME}" + APP_OLDPID_EXT=".oldbin" + APP_DEFAULT_USER="nobody" + APP_DEFAULT_GROUP="nogroup" + APP_DEFAULT_LISTEN="0.0.0.0" + APP_PREFIX="${PROJECT_PREFIX}/" + APP_CONF_PREFIX="conf/" + APP_CACHE_PREFIX="cache/" + APP_SBIN_PATH="sbin/${PROJECT_NAME}" + APP_CONF_FILE="${PROJECT_NAME}.conf" + APP_PID_FILE="logs/${PROJECT_NAME}.pid" + APP_LOCK_FILE="logs/${PROJECT_NAME}.lock" + APP_ERROR_LOG_FILE="logs/error.log" + APP_ACCESS_LOG_FILE="logs/access.log" + APP_POSTGRES_LOG_FILE="logs/postgres.log" + APP_STREAM_LOG_FILE="logs/stream.log" + APP_DOC_ROOT="www/" + ) + +target_link_libraries(${CORE_LIB_NAME} pthread ${PQ_LIB_NAME} ${SQLITE3_LIB_NAME} ${CURL_LIB_NAME} yaml-cpp ssl crypto) + +# Apostol modules +# ---------------------------------------------------------------------------------------------------------------------- +#add_library(${MODULES_LIB_NAME} STATIC ${modules_files}) +#target_link_libraries(${MODULES_LIB_NAME} ${CORE_LIB_NAME}) + +# BitDeals Payment Service (Deal Module) +# ---------------------------------------------------------------------------------------------------------------------- +add_executable(${PROJECT_NAME} ${app_files}) +target_link_libraries(${PROJECT_NAME} ${CORE_LIB_NAME}) + +add_dependencies(${PROJECT_NAME} auto_increment_version) +# ---------------------------------------------------------------------------------------------------------------------- + +# Install +# ---------------------------------------------------------------------------------------------------------------------- +set(INSTALL_PATH "${PROJECT_PREFIX}") + +install(TARGETS ${PROJECT_NAME} DESTINATION ${INSTALL_BIN_PATH}) + +install(DIRECTORY DESTINATION ${INSTALL_PATH}) +install(DIRECTORY DESTINATION ${INSTALL_PATH}/oauth2) +install(DIRECTORY DESTINATION ${INSTALL_PATH}/conf) +install(DIRECTORY DESTINATION ${INSTALL_PATH}/logs) +install(DIRECTORY DESTINATION ${INSTALL_PATH}/sites) +install(DIRECTORY conf/oauth2/ DESTINATION ${INSTALL_PATH}/oauth2) +install(DIRECTORY conf/sites/ DESTINATION ${INSTALL_PATH}/sites) +install(DIRECTORY www/ DESTINATION ${INSTALL_PATH}/www) +install(FILES conf/default.conf DESTINATION ${INSTALL_PATH}/conf) +install(FILES conf/oauth2.conf DESTINATION ${INSTALL_PATH}/conf) +install(FILES conf/sites.conf DESTINATION ${INSTALL_PATH}/conf) +install(FILES conf/default.conf DESTINATION ${INSTALL_PATH} RENAME ${PROJECT_NAME}.conf) + +if (INSTALL_AS_ROOT) + install(FILES auto/daemon DESTINATION /etc/init.d RENAME ${PROJECT_NAME} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + install(CODE "execute_process(COMMAND update-rc.d ${PROJECT_NAME} defaults)") +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index 16eff96..c9fd081 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,182 @@ -**Edit a file, create a new file, and clone from Bitbucket in under 2 minutes** +# BitDeals Payment Service: Deal Module -When you're done, you can delete the content in this README and update the file with details for others getting started with your repository. +**Модуль сделок** системы учёта Bitcoin платежей, исходные коды на C++. -*We recommend that you open this README in another tab as you perform the tasks below. You can [watch our video](https://youtu.be/0ocf7u76WSo) for a full demo of all the steps in this tutorial. Open the video in a new tab to avoid leaving Bitbucket.* +Построено на базе [Апостол](https://github.com/ufocomp/apostol). ---- +СТРУКТУРА КАТАЛОГОВ +- + auto/ содержит файлы со скриптами + cmake-modules/ содержит файлы с модулями CMake + conf/ содержит файлы с настройками + doc/ содержит файлы с документацией + ├─www/ содержит файлы с документацией в формате html + src/ содержит файлы с исходным кодом + ├─app/ содержит файлы с исходным кодом: BitDeals Payment Service (Deal Module) + ├─core/ содержит файлы с исходным кодом: Apostol Core + ├─lib/ содержит файлы с исходным кодом библиотек + | └─delphi/ содержит файлы с исходным кодом библиотеки*: Delphi classes for C++ + └─modules/ содержит файлы с исходным кодом дополнений (модулей) + └─WebService/ содержит файлы с исходным кодом дополнения: Web-сервис -## Edit a file +ОПИСАНИЕ +- -You’ll start by editing this README file to learn how to edit a file in Bitbucket. +**Модуля сделок** (dm) предоставляет интерфейсы для создания и изменения учётных записей пользователей и сделок системы учёта Bitcoin платежей. -1. Click **Source** on the left side. -2. Click the README.md link from the list of files. -3. Click the **Edit** button. -4. Delete the following text: *Delete this line to make a change to the README from Bitbucket.* -5. After making your change, click **Commit** and then **Commit** again in the dialog. The commit page will open and you’ll see the change you just made. -6. Go back to the **Source** page. +REST API +- ---- +[Документация по REST API](./doc/REST-API-ru.md) -## Create a file +Протестировать API **Модуля сделок** можно с помощью встроенного Web-сервера доступного по адресу: [localhost:4999](http://localhost:4999) -Next, you’ll add a new file to this repository. +##### Авторизация: + username: module + password:
-1. Click the **New file** button at the top of the **Source** page. -2. Give the file a filename of **contributors.txt**. -3. Enter your name in the empty file space. -4. Click **Commit** and then **Commit** again in the dialog. -5. Go back to the **Source** page. +Где `
` - Bitcoin адрес модуля сделок. -Before you move on, go ahead and explore the repository. You've already seen the **Source** page, but check out the **Commits**, **Branches**, and **Settings** pages. +СБОРКА И УСТАНОВКА +- +Для установки **Модуля сделок** Вам потребуется: ---- +1. Компилятор C++; +1. [CMake](https://cmake.org) или интегрированная среда разработки (IDE) с поддержкой [CMake](https://cmake.org); +1. Библиотека [libbitcoin-explorer](https://github.com/libbitcoin/libbitcoin-explorer/tree/version3) (Bitcoin Cross-Platform C++ Development Toolkit); +1. Библиотека [OpenPGP](https://github.com/calccrypto/OpenPGP) (OpenPGP in C++) +1. Библиотека [yaml-cpp](https://github.com/jbeder/yaml-cpp) (YAML parser and emitter in C++) -## Clone a repository +### Linux (Debian/Ubuntu) -Use these steps to clone from SourceTree, our client for using the repository command-line free. Cloning allows you to work on your files locally. If you don't yet have SourceTree, [download and install first](https://www.sourcetreeapp.com/). If you prefer to clone from the command line, see [Clone a repository](https://confluence.atlassian.com/x/4whODQ). +Для того чтобы установить компилятор C++ и необходимые библиотеки на Ubuntu выполните: +~~~ +$ sudo apt-get install build-essential libssl-dev libcurl4-openssl-dev make cmake gcc g++ +~~~ -1. You’ll see the clone button under the **Source** heading. Click that button. -2. Now click **Check out in SourceTree**. You may need to create a SourceTree account or log in. -3. When you see the **Clone New** dialog in SourceTree, update the destination path and name if you’d like to and then click **Clone**. -4. Open the directory you just created to see your repository’s files. +###### Подробное описание установки C++, CMake, IDE и иных компонентов необходимых для сборки проекта не входит в данное руководство. -Now that you're more familiar with your Bitbucket repository, go ahead and add a new file locally. You can [push your change back to Bitbucket with SourceTree](https://confluence.atlassian.com/x/iqyBMg), or you can [add, commit,](https://confluence.atlassian.com/x/8QhODQ) and [push from the command line](https://confluence.atlassian.com/x/NQ0zDQ). \ No newline at end of file +### libbitcoin + +Для того чтобы установить libbitcoin выполните: +~~~ +$ sudo apt-get install autoconf automake libtool pkg-config git +~~~ +~~~ +$ wget https://raw.githubusercontent.com/libbitcoin/libbitcoin-explorer/version3/install.sh +$ chmod +x install.sh +~~~ +~~~ +$ ./install.sh --build-boost --build-zmq --disable-shared +~~~ + +#### OpenPGP + +Для сборки **OpenPGP** обратитесь к документации на сайте [OpenPGP](https://github.com/calccrypto/OpenPGP). + +##### CMake Configuration +~~~ +GPG_COMPATIBLE=ON +USE_OPENSSL=ON +~~~ + +###### Обратите внимание на зависимости OpenPGP от других библиотек (GMP, bzip2, zlib, OpenSSL). + +#### yaml-cpp + +Для сборки **yaml-cpp** обратитесь к документации на сайте [yaml-cpp](https://github.com/jbeder/yaml-cpp). + +#### BitDeals Payment Service (Deal Module) + +Для того чтобы установить **Модуля сделок** с помощью Git выполните: +~~~ +$ git clone git@github.com:ufocomp/apostol-dm.git dm +~~~ +Далее: +1. Настроить `CMakeLists.txt` (по необходимости); +1. Собрать и скомпилировать (см. ниже). + +Для того чтобы установить **Модуля сделок** (без Git) необходимо: + +1. Скачать **BPS (DM)** по [ссылке](https://github.com/ufocomp/apostol-dm/archive/master.zip); +1. Распаковать; +1. Настроить `CMakeLists.txt` (по необходимости); +1. Собрать и скомпилировать (см. ниже). + +###### Сборка: +~~~ +$ cd dm +$ ./configure +~~~ + +###### Компиляция и установка: +~~~ +$ cd cmake-build-release +$ make +$ sudo make install +~~~ + +По умолчанию **Модуль сделок** будет установлен в: +~~~ +/usr/sbin +~~~ + +Файл конфигурации и необходимые для работы файлы, в зависимости от варианта установки, будут расположены в: +~~~ +/etc/dm +или +~/dm +~~~ + +ЗАПУСК +- +###### Если `INSTALL_AS_ROOT` установлено в `ON`. + +**`dm`** - это системная служба (демон) Linux. +Для управления **`dm`** используйте стандартные команды управления службами. + +Для запуска Апостол выполните: +~~~ +$ sudo service dm start +~~~ + +Для проверки статуса выполните: +~~~ +$ sudo service dm status +~~~ + +Результат должен быть **примерно** таким: +~~~ +● dm.service - LSB: starts the Deal Module + Loaded: loaded (/etc/init.d/dm; generated; vendor preset: enabled) + Active: active (running) since Thu 2019-08-15 14:11:34 BST; 1h 1min ago + Docs: man:systemd-sysv-generator(8) + Process: 16465 ExecStop=/etc/init.d/dm stop (code=exited, status=0/SUCCESS) + Process: 16509 ExecStart=/etc/init.d/dm start (code=exited, status=0/SUCCESS) + Tasks: 3 (limit: 4915) + CGroup: /system.slice/dm.service + ├─16520 dm: master process /usr/sbin/abc + └─16521 dm: worker process +~~~ + +### **Управление dm**. + +Управлять **`dm`** можно с помощью сигналов. +Номер главного процесса по умолчанию записывается в файл `/run/dm.pid`. +Изменить имя этого файла можно при конфигурации сборки или же в `dm.conf` секция `[daemon]` ключ `pid`. + +Главный процесс поддерживает следующие сигналы: + +|Сигнал |Действие | +|---------|------------------| +|TERM, INT|быстрое завершение| +|QUIT |плавное завершение| +|HUP |изменение конфигурации, запуск новых рабочих процессов с новой конфигурацией, плавное завершение старых рабочих процессов| +|WINCH |плавное завершение рабочих процессов| + +Управлять рабочими процессами по отдельности не нужно. Тем не менее, они тоже поддерживают некоторые сигналы: + +|Сигнал |Действие | +|---------|------------------| +|TERM, INT|быстрое завершение| +|QUIT |плавное завершение| diff --git a/auto/daemon b/auto/daemon new file mode 100755 index 0000000..d805e7b --- /dev/null +++ b/auto/daemon @@ -0,0 +1,210 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: dm +# Required-Start: $local_fs $remote_fs $network $syslog $named +# Required-Stop: $local_fs $remote_fs $network $syslog $named +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts the deal module +# Description: starts Deal Module using start-stop-daemon +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/dm +#DAEMOD_OPTS="-p /etc/dm -c /etc/dm/dm.conf" +NAME=dm +DESC="Deal Module" + +# Include dm defaults if available +if [ -r /etc/default/dm ]; then + . /etc/default/dm +fi + +STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}" + +test -x $DAEMON || exit 0 + +. /lib/init/vars.sh +. /lib/lsb/init-functions + +# Try to extract dm pidfile +PID=$(cat /etc/dm/dm.conf | grep -Ev '^\s*#' | awk 'BEGIN { FS="=" } { if ($1 == "pid") print $2 }' | head -n1) +if [ -z "$PID" ] +then + PID=/run/dm.pid +fi + +# Check if the ULIMIT is set in /etc/default/dm +if [ -n "$ULIMIT" ]; then + # Set the ulimits + ulimit $ULIMIT +fi + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON -- \ + $DAEMON_OPTS 2>/dev/null \ + || return 2 +} + +test_dm_config() { + $DAEMON -t $DAEMON_OPTS >/dev/null 2>&1 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=$STOP_SCHEDULE --pidfile $PID --name $NAME + RETVAL="$?" + + sleep 1 + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + start-stop-daemon --stop --signal HUP --quiet --pidfile $PID --name $NAME + return 0 +} + +# +# Rotate log files +# +do_rotate() { + start-stop-daemon --stop --signal USR1 --quiet --pidfile $PID --name $NAME + return 0 +} + +# +# Online upgrade dm executable +# +# "Upgrading Executable on the Fly" +# +do_upgrade() { + # Return + # 0 if dm has been successfully upgraded + # 1 if dm is not running + # 2 if the pid files were not created on time + # 3 if the old master could not be killed + if start-stop-daemon --stop --signal USR2 --quiet --pidfile $PID --name $NAME; then + # Wait for both old and new master to write their pid file + while [ ! -s "${PID}.oldbin" ] || [ ! -s "${PID}" ]; do + cnt=`expr $cnt + 1` + if [ $cnt -gt 10 ]; then + return 2 + fi + sleep 1 + done + # Everything is ready, gracefully stop the old master + if start-stop-daemon --stop --signal QUIT --quiet --pidfile "${PID}.oldbin" --name $NAME; then + return 0 + else + return 3 + fi + else + return 1 + fi +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + restart) + log_daemon_msg "Restarting $DESC" "$NAME" + + # Check configuration before stopping dm + if ! test_dm_config; then + log_end_msg 1 # Configuration error + exit 0 + fi + + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + reload|force-reload) + log_daemon_msg "Reloading $DESC configuration" "$NAME" + + # Check configuration before reload dm + # + # This is not entirely correct since the on-disk dm binary + # may differ from the in-memory one, but that`s not common. + # We prefer to check the configuration and return an error + # to the administrator. + if ! test_dm_config; then + log_end_msg 1 # Configuration error + exit 0 + fi + + do_reload + log_end_msg $? + ;; + configtest|testconfig) + log_daemon_msg "Testing $DESC configuration" + test_dm_config + log_end_msg $? + ;; + status) + status_of_proc -p $PID "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + upgrade) + log_daemon_msg "Upgrading binary" "$NAME" + do_upgrade + log_end_msg 0 + ;; + rotate) + log_daemon_msg "Re-opening $DESC log files" "$NAME" + do_rotate + log_end_msg $? + ;; + *) + echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest|rotate|upgrade}" >&2 + exit 3 + ;; +esac + +: diff --git a/cmake-modules/FindBitcoin-System.cmake b/cmake-modules/FindBitcoin-System.cmake new file mode 100644 index 0000000..fca1240 --- /dev/null +++ b/cmake-modules/FindBitcoin-System.cmake @@ -0,0 +1,60 @@ +############################################################################### +# Copyright (c) 2014-2019 libbitcoin-system developers (see COPYING). +# +# GENERATED SOURCE CODE, DO NOT EDIT EXCEPT EXPERIMENTALLY +# +############################################################################### +# FindBitcoin-System +# +# Use this module by invoking find_package with the form:: +# +# find_package( Bitcoin-System +# [version] # Minimum version +# [REQUIRED] # Fail with error if bitcoin-system is not found +# ) +# +# Defines the following for use: +# +# bitcoin_system_FOUND - true if headers and requested libraries were found +# bitcoin_system_INCLUDE_DIRS - include directories for bitcoin-system libraries +# bitcoin_system_LIBRARY_DIRS - link directories for bitcoin-system libraries +# bitcoin_system_LIBRARIES - bitcoin-system libraries to be linked +# bitcoin_system_PKG - bitcoin-system pkg-config package specification. +# + +if (MSVC) + if ( Bitcoin-System_FIND_REQUIRED ) + set( _bitcoin_system_MSG_STATUS "SEND_ERROR" ) + else () + set( _bitcoin_system_MSG_STATUS "STATUS" ) + endif() + + set( bitcoin_system_FOUND false ) + message( ${_bitcoin_system_MSG_STATUS} "MSVC environment detection for 'bitcoin-system' not currently supported." ) +else () + # required + if ( Bitcoin-System_FIND_REQUIRED ) + set( _bitcoin_system_REQUIRED "REQUIRED" ) + endif() + + # quiet + if ( Bitcoin-System_FIND_QUIETLY ) + set( _bitcoin_system_QUIET "QUIET" ) + endif() + + # modulespec + if ( Bitcoin-System_FIND_VERSION_COUNT EQUAL 0 ) + set( _bitcoin_system_MODULE_SPEC "libbitcoin-system" ) + else () + if ( Bitcoin-System_FIND_VERSION_EXACT ) + set( _bitcoin_system_MODULE_SPEC_OP "=" ) + else () + set( _bitcoin_system_MODULE_SPEC_OP ">=" ) + endif() + + set( _bitcoin_system_MODULE_SPEC "libbitcoin-system ${_bitcoin_system_MODULE_SPEC_OP} ${Bitcoin-System_FIND_VERSION}" ) + endif() + + pkg_check_modules( bitcoin_system ${_bitcoin_system_REQUIRED} ${_bitcoin_system_QUIET} "${_bitcoin_system_MODULE_SPEC}" ) + set( bitcoin_system_PKG "${_bitcoin_system_MODULE_SPEC}" ) +endif() diff --git a/cmake-modules/FindBitcoin.cmake b/cmake-modules/FindBitcoin.cmake new file mode 100644 index 0000000..50de231 --- /dev/null +++ b/cmake-modules/FindBitcoin.cmake @@ -0,0 +1,60 @@ +############################################################################### +# Copyright (c) 2014-2019 libbitcoin developers (see COPYING). +# +# GENERATED SOURCE CODE, DO NOT EDIT EXCEPT EXPERIMENTALLY +# +############################################################################### +# FindBitcoin +# +# Use this module by invoking find_package with the form:: +# +# find_package( Bitcoin +# [version] # Minimum version +# [REQUIRED] # Fail with error if libbitcoin is not found +# ) +# +# Defines the following for use: +# +# bitcoin_FOUND - true if headers and requested libraries were found +# bitcoin_INCLUDE_DIRS - include directories for bitcoin libraries +# bitcoin_LIBRARY_DIRS - link directories for bitcoin libraries +# bitcoin_LIBRARIES - bitcoin libraries to be linked +# bitcoin_PKG - bitcoin pkg-config package specification. +# + +if (MSVC) + if ( Bitcoin_FIND_REQUIRED ) + set( _bitcoin_MSG_STATUS "SEND_ERROR" ) + else () + set( _bitcoin_MSG_STATUS "STATUS" ) + endif() + + set( bitcoin_FOUND false ) + message( ${_bitcoin_MSG_STATUS} "MSVC environment detection for 'bitcoin' not currently supported." ) +else () + # required + if ( Bitcoin_FIND_REQUIRED ) + set( _bitcoin_REQUIRED "REQUIRED" ) + endif() + + # quiet + if ( Bitcoin_FIND_QUIETLY ) + set( _bitcoin_QUIET "QUIET" ) + endif() + + # modulespec + if ( Bitcoin_FIND_VERSION_COUNT EQUAL 0 ) + set( _bitcoin_MODULE_SPEC "libbitcoin" ) + else () + if ( Bitcoin_FIND_VERSION_EXACT ) + set( _bitcoin_MODULE_SPEC_OP "=" ) + else () + set( _bitcoin_MODULE_SPEC_OP ">=" ) + endif() + + set( _bitcoin_MODULE_SPEC "libbitcoin ${_bitcoin_MODULE_SPEC_OP} ${Bitcoin_FIND_VERSION}" ) + endif() + + pkg_check_modules( bitcoin ${_bitcoin_REQUIRED} ${_bitcoin_QUIET} "${_bitcoin_MODULE_SPEC}" ) + set( bitcoin_PKG "${_bitcoin_MODULE_SPEC}" ) +endif() diff --git a/cmake-modules/FindDelphi.cmake b/cmake-modules/FindDelphi.cmake new file mode 100644 index 0000000..f5f0996 --- /dev/null +++ b/cmake-modules/FindDelphi.cmake @@ -0,0 +1,44 @@ +############################################################################### +# FindDelphi +# +# Use this module by invoking find_package with the form:: +# +# find_package( Delphi +# [REQUIRED] # Fail with error if Delphi is not found +# ) +# +# Defines the following for use: +# +# delphi_FOUND - true if headers and requested libraries were found +# delphi_INCLUDE_DIRS - include directories for Delphi libraries +# delphi_LIBRARY_DIRS - link directories for Delphi libraries +# delphi_LIBRARIES - Delphi libraries to be linked +# delphi_PKG - Delphi pkg-config package specification. +# + +if (MSVC) + if ( Delphi_FIND_REQUIRED ) + set( _delphi_MSG_STATUS "SEND_ERROR" ) + else () + set( _delphi_MSG_STATUS "STATUS" ) + endif() + + set( delphi_FOUND false ) + message( ${_delphi_MSG_STATUS} "MSVC environment detection for 'Delphi' not currently supported." ) +else () + # required + if ( Delphi_FIND_REQUIRED ) + set( _delphi_REQUIRED "REQUIRED" ) + endif() + + # quiet + if ( Delphi_FIND_QUIETLY ) + set( _delphi_QUIET "QUIET" ) + endif() + + # modulespec + set( _delphi_MODULE_SPEC "delphi" ) + + pkg_check_modules( delphi ${_delphi_REQUIRED} ${_delphi_QUIET} "${_delphi_MODULE_SPEC}" ) + set( delphi_PKG "${_delphi_MODULE_SPEC}" ) +endif() diff --git a/cmake-modules/FindDl.cmake b/cmake-modules/FindDl.cmake new file mode 100644 index 0000000..8299520 --- /dev/null +++ b/cmake-modules/FindDl.cmake @@ -0,0 +1,45 @@ +############################################################################### +# Copyright (c) 2014-2019 libbitcoin developers (see COPYING). +# +############################################################################### +# Finddl +# +# Use this module by invoking find_package with the form:: +# +# find_package( dl +# [REQUIRED] # Fail with error if dl is not found +# ) +# +# Defines the following for use: +# +# dl_FOUND - True if headers and requested libraries were found +# dl_LIBRARIES - dl libraries to be linked +# dl_LIBS - dl libraries to be linked +# + +if (DEFINED dl_FIND_VERSION) + message( SEND_ERROR "Library 'dl' unable to process specified version: ${dl_FIND_VERSION}" ) +endif() + +if (MSVC) + message( STATUS "MSVC environment detection for 'dl' not currently supported." ) + set( dl_FOUND false ) +else () + # required + if ( dl_FIND_REQUIRED ) + set( _dl_REQUIRED "REQUIRED" ) + endif() + + find_library(dl_LIBRARIES dl) + + if (dl_LIBRARIES-NOTFOUND) + set( dl_FOUND false ) + else () + set( dl_FOUND true ) + set( dl_LIBS "-ldl" ) + endif() +endif() + +if ( dl_FIND_REQUIRED AND ( NOT dl_FOUND ) ) + message( SEND_ERROR "Library 'dl' not found." ) +endif() diff --git a/cmake-modules/FindOpenPGP.cmake b/cmake-modules/FindOpenPGP.cmake new file mode 100644 index 0000000..932da7e --- /dev/null +++ b/cmake-modules/FindOpenPGP.cmake @@ -0,0 +1,44 @@ +############################################################################### +# FindOpenPGP +# +# Use this module by invoking find_package with the form:: +# +# find_package( OpenPGP +# [REQUIRED] # Fail with error if OpenPGP is not found +# ) +# +# Defines the following for use: +# +# openpgp_FOUND - true if headers and requested libraries were found +# openpgp_INCLUDE_DIRS - include directories for OpenPGP libraries +# openpgp_LIBRARY_DIRS - link directories for OpenPGP libraries +# openpgp_LIBRARIES - OpenPGP libraries to be linked +# openpgp_PKG - OpenPGP pkg-config package specification. +# + +if (MSVC) + if ( OpenPGP_FIND_REQUIRED ) + set( _openpgp_MSG_STATUS "SEND_ERROR" ) + else () + set( _openpgp_MSG_STATUS "STATUS" ) + endif() + + set( openpgp_FOUND false ) + message( ${_openpgp_MSG_STATUS} "MSVC environment detection for 'OpenPGP' not currently supported." ) +else () + # required + if ( OpenPGP_FIND_REQUIRED ) + set( _openpgp_REQUIRED "REQUIRED" ) + endif() + + # quiet + if ( OpenPGP_FIND_QUIETLY ) + set( _openpgp_QUIET "QUIET" ) + endif() + + # modulespec + set( _openpgp_MODULE_SPEC "OpenPGP" ) + + pkg_check_modules( openpgp ${_openpgp_REQUIRED} ${_openpgp_QUIET} "${_openpgp_MODULE_SPEC}" ) + set( openpgp_PKG "${_openpgp_MODULE_SPEC}" ) +endif() diff --git a/conf/bitcoin.conf b/conf/bitcoin.conf new file mode 100644 index 0000000..4010fb2 --- /dev/null +++ b/conf/bitcoin.conf @@ -0,0 +1,30 @@ +[main] +## Bitcoin testnet +## default: false +#testnet=true + +[endpoint] +## default: tcp://mainnet.libbitcoin.net:9091 +#url=tcp://testnet.libbitcoin.net:19091 + +[transaction] +## Transaction minimum output value +## default: 200 +#min_output=200 + +[miner] +## Transaction fee as a percentage of the deal amount +## ATTENTION: If the value is specified as a percentage, you MUST indicate the sign “%”, +## otherwise it will be a FIXED value in satoshi. + +## Transaction fee for miner +## default: 1% +#fee=1% + +## Transaction fee for miner (minimal) +## default: 200 +#min=200 + +## Transaction fee for miner (maximum) +## default: 2000 +#max=2000 diff --git a/conf/default.conf b/conf/default.conf new file mode 100644 index 0000000..cc9b709 --- /dev/null +++ b/conf/default.conf @@ -0,0 +1,92 @@ +## Bitcoin Payment Service (Deal Module) config file + +[main] +#user=nobody +#group=nobody + +#limitnofile=8192 + +## Count worker process +## default: 1 +workers=1 + +## Create helper process +## default: false +#helper=false + +## Create master process +## Master process run processes: +## - worker (if count not equal 0) +## - helper (if value equal true) +## - process/* (if enabled) +## default: true +master=true + +[daemon] +## Run as daemon +## default: true +daemon=true + +## Pid file +## default: logs/dm.pid +pid=/run/dm.pid + +[log] +## Log files +## Available keys: alert, crit, error, warn, notice, info, debug +## default: error=logs/error.log +#alert=logs/error.log +crit=logs/crit.log +error=logs/error.log +#warn=logs/error.log +#notice=logs/error.log +#info=logs/error.log +#debug=logs/debug.log + +## HTTP (Server) config section +[server] +## Listen address +## default: 0.0.0.0 +listen=127.0.0.1 + +## Listen port number +## default: 4999 +port=4999 + +## Connection timeout +## default: 5000 +timeout=5000 + +## Default web server file path +root=www + +## Access log file +log=logs/access.log + +[module] +## Module Bitcoin address +## default: empty +address= + +## OAuth2 configuration file +## default: service.json +oauth2=oauth2/service.json + +## Module transaction fee as a percentage of the deal amount +## ATTENTION: If the value is specified as a percentage, you MUST indicate the sign “%”, +## otherwise it will be a FIXED value in satoshi. +## default: 0.1% +#fee= + +[pgp] +## Path to PGP private file key +## default: empty +private= + +## Path to PGP public file key +## default: empty +public= + +## PGP passphrase +## default: empty +passphrase= diff --git a/conf/oauth2.conf b/conf/oauth2.conf new file mode 100644 index 0000000..0b91dcb --- /dev/null +++ b/conf/oauth2.conf @@ -0,0 +1,2 @@ +[providers] +default = default.json diff --git a/conf/oauth2/default.json b/conf/oauth2/default.json new file mode 100644 index 0000000..709fd34 --- /dev/null +++ b/conf/oauth2/default.json @@ -0,0 +1,19 @@ +{ + "web": { + "issuers": ["accounts.bitdeals.com"], + "scopes": ["api","openid","profile","email"], + "client_id": "web-bitdeals.com", + "client_secret": "", + "algorithm": "HS256", + "auth_uri": "/oauth2/authorize", + "token_uri": "/oauth2/token", + "redirect_uris": [ + "http://localhost:8080/oauth2/code", + "http://localhost:8080/oauth2/callback", + "https://bitdeals.com/oauth2/code", + "https://bitdeals.com/oauth2/callback", + "https://oauthdebugger.com/debug", + "https://auth.advancedrestclient.com/oauth-popup.html" + ] + } +} diff --git a/conf/sites.conf b/conf/sites.conf new file mode 100644 index 0000000..4e24310 --- /dev/null +++ b/conf/sites.conf @@ -0,0 +1,2 @@ +[hosts] +default = default.json diff --git a/conf/sites/default.json b/conf/sites/default.json new file mode 100644 index 0000000..26f624b --- /dev/null +++ b/conf/sites/default.json @@ -0,0 +1,4 @@ +{ + "hosts": ["localhost:4999"], + "root": "/etc/dm/www" +} diff --git a/configure b/configure new file mode 100755 index 0000000..5d5271c --- /dev/null +++ b/configure @@ -0,0 +1,179 @@ +#!/bin/bash + +# Script to configure apostol web service. +# +# Script options: +# --help Display usage, overriding script execution. +# + +# Initialize the build environment. +#============================================================================== +# Exit this script on the first build error. +#------------------------------------------------------------------------------ +set -e + +PROJECT_NAME=apostol-dm +BUILD_TYPE=Release + +pop_directory() +{ + popd >/dev/null +} + +push_directory() +{ + local DIRECTORY="$1" + + pushd "$DIRECTORY" >/dev/null +} + +create_directory() +{ + local DIRECTORY="$1" + + rm -rf "$DIRECTORY" + mkdir "$DIRECTORY" +} + +display_heading_message() +{ + echo + echo "********************** $@ **********************" + echo +} + +display_message() +{ + echo "$@" +} + +display_error() +{ + >&2 echo "$@" +} + +display_help() +{ + display_message "Usage: ./configure [OPTION]..." + display_message "Manage the configure." + display_message "Script options:" + display_message " --build-dir= Location of build files (default: $BUILD_DIR)." + display_message " --release Release build (default)." + display_message " --debug Debug build." + display_message " --update Update build." + display_message " --help Display usage, overriding script execution." + display_message "" +} + +display_configuration() +{ + display_message "Configuration." + display_message "--------------------------------------------------------------------" + display_message "PROJECT_NAME : $PROJECT_NAME" + display_message "BUILD_TYPE : $BUILD_TYPE" + display_message "BUILD_DIR : $BUILD_DIR" + display_message "--------------------------------------------------------------------" +} + +# Download from github. +download_from_github() +{ + local DIRECTORY=$1 + local ACCOUNT=$2 + local REPO=$3 + local BRANCH=$4 + local DIR=$5 + shift 5 + + push_directory "$DIRECTORY" + + FORK="$ACCOUNT/$REPO" + + if ! [ -d $DIR ]; then + # Clone the repository locally. + display_heading_message "Download: $FORK/$BRANCH" + git clone --depth 1 --branch $BRANCH --single-branch "https://github.com/$FORK" $DIR + else + push_directory "$DIR" + display_heading_message "Updating: $FORK/$BRANCH" + git pull + pop_directory + fi + + pop_directory +} + +github() +{ + display_heading_message "Updating the current project" + git pull + + download_from_github src/lib ufocomp libdelphi master delphi + download_from_github src ufocomp apostol-core master core +} + +Success() +{ + display_message "" + echo -e "********************** \e[32;1mSUCCESS\e[0m **********************" + echo -e "\e[32m-- To build the $PROJECT_NAME run:\e[0m" + display_message "--------------------------------------------------------------------" + echo -e "\e[1m$ cd $BUILD_DIR \e[0m" + echo -e "\e[1m$ make \e[0m" + display_message "--------------------------------------------------------------------" + echo -e "\e[32m-- To install the $PROJECT_NAME run:\e[0m" + display_message "--------------------------------------------------------------------" + echo -e "\e[1m$ sudo make install\e[0m" + display_message "--------------------------------------------------------------------" +} + +make_configuration() +{ + if ! [[ $BUILD_UPDATE ]]; then + create_directory $BUILD_DIR + fi + + echo '#define AUTO_VERSION _T("1.0.e00000-b0")' > version.h + + github + + display_heading_message "Make: $PROJECT_NAME" + + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE . -B $BUILD_DIR + + Success +} + +# Parse command line options that are handled by this script. +#------------------------------------------------------------------------------ +for OPTION in "$@"; do + case $OPTION in + # Standard script options. + (--help) DISPLAY_HELP="yes";; + + (--release) BUILD_TYPE="Release";; + (--debug) BUILD_TYPE="Debug";; + + (--update) BUILD_UPDATE="yes";; + + # Unique script options. + (--build-dir=*) BUILD_DIR="${OPTION#*=}";; + esac +done + +if ! [[ $BUILD_DIR ]]; then + if [[ $BUILD_TYPE == Debug ]]; then + BUILD_DIR=cmake-build-debug + else + BUILD_DIR=cmake-build-release + fi +fi + +# Configure. +#============================================================================== +if [[ $DISPLAY_HELP ]]; then + display_help +else + display_configuration + make_configuration +fi diff --git a/doc/REST-API-ru.md b/doc/REST-API-ru.md new file mode 100644 index 0000000..f634392 --- /dev/null +++ b/doc/REST-API-ru.md @@ -0,0 +1,498 @@ +# API "Модуля сделок" системы учёта Bitcoin платежей. + +## Общая информация + * Базовая конечная точка (endpoint): [localhost:4999](http://localhost:4999) + * Все конечные точки возвращают `JSON-объект` + * Все поля, относящиеся ко времени и меткам времени, указаны в **миллисекундах**. + +## HTTP коды возврата + * HTTP `4XX` коды возврата применимы для некорректных запросов - проблема на стороне клиента. + * HTTP `5XX` коды возврата используются для внутренних ошибок - проблема на стороне сервера. Важно **НЕ** рассматривать это как операцию сбоя. Статус выполнения **НЕИЗВЕСТЕН** и может быть успешным. + +## Коды ошибок + * Любая конечная точка может вернуть ошибку. + +**Пример ответа:** +```json +{ + "error": { + "code": 404, + "message": "Not Found" + } +} +``` + +## Общая информация о конечных точках + * Для `GET` конечных точек параметры должны быть отправлены в виде `строки запроса (query string)` . + * Для `POST` конечных точек, некоторые параметры могут быть отправлены в виде `строки запроса (query string)`, а некоторые в виде `тела запроса (request body)`: + * При отправке параметров в виде `тела запроса` допустимы следующие типы контента: + * `application/x-www-form-urlencoded` для `query string`; + * `multipart/form-data` для `HTML-форм`; + * `application/json` для `JSON`; + * `text/plain` для `YAML` или `текста в произвольном формате`. + * Параметры могут быть отправлены в любом порядке. + +## Доступ к API + +Доступ к API возможен только при наличии _**маркера доступа**_ или **_цифровой подписи_** методом HMAC-SHA256. + +[Подробнее](https://github.com/ufocomp/db-platform/wiki/%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF-%D0%BA-API). + +## Общие конечные точки + +### Тест подключения + +```http request +GET /api/v1/ping +``` +Проверить подключение к REST API. + +**Параметры:** +НЕТ + +**Пример ответа:** +```json +{} +``` + +### Проверить время сервера +```http request +GET /api/v1/time +``` +Проверить подключение к REST API и получить текущее время сервера. + +**Параметры:** + НЕТ + +**Пример ответа:** +```json +{ + "serverTime": 1583495795455 +} +``` + +## Конечные точки + +Конечные точки разделены на две категории: `Пользователи (account)` и `Сделки (deal)`. + +Обработка запросов выполняется на стороне `Дополнительного сервера (ДС)`. +Поэтому `Модуль сделок` формирует **транспортный пакет** в виде `JSON-объекта` и отправляет его на `ДС`. + +###### ВАЖНО: Сетевой адрес `Дополнительного сервера (ДС)` можно указать в параметрах запроса в виде `строки запроса`. По умолчанию: `localhost` + +### Формат транспортного пакета: +**Запрос:** +```json +{ + "id": "", + "address": "", + "action": "", + "payload": "" +} +``` +**Ответ:** +```json +{ + "id": "", + "address": "", + "action": "", + "result": { + "success": "", + "message": "" + }, + "payload": "" +} +``` + +**Описание:** + +Ключ | Тип | Значение | Описание +------------ | ------------ | ------------ | ------------ +id | STRING | | Уникальный идентификатор запроса +address | STRING | | Bitcoin адрес пользователя (учётной записи) `ДС`. По умолчанию: Адрес модуля +action | STRING | help, status, new, add, update, delete | Действие +result | JSON | | Результат выполнения запроса +success | BOOLEAN | true, false | Успешно: Да/Нет +message | STRING | | Сообщение (информация об ошибке) +payload | BASE64 | | Полезная нагрузка (строка в формате Base64) + +`Полезная нагрузка` может содержать данные в виде: + * `HTML-документа`; + * `JSON-объекта`; + * `Текста в произвольном формате`. + +### Общие параметры + +#### Для последующих конечных точек можно указать **параметры** в виде `строки запроса (query string)`: + +Имя | Тип | Значение | Описание +------------ | ------------ | ------------ | ------------ +payload | STRING | text, html, json, xml | Получить ответ в виде содержимого поля `payload` с указанием формата +server | STRING | localhost | IP-адрес или Host имя сервера (ДС). По умолчанию: localhost +pgp | BOOLEAN | false, off | Отключить PGP подпись модуля сделок +address | STRING | | Bitcoin адрес пользователя (учётной записи) `ДС`. По умолчанию: Адрес модуля + +## Параметры пользователя + +### Параметры в виде `тела запроса (request body)`: + +Наименование | Тип | Действие | Описание +------------ | ------------ | ------------ | ------------ +address | STRING | * | Bitcoin адрес +bitmessage | STRING | New | Bitmessage адрес +key | STRING | New, Add | Bitcoin ключ (публичный) +pgp | STRING | New, Add | PGP ключ (публичный) +url | STRING | New, Update | Список URL +date | STRING | Update, Delete | Дата в формате: YYYY-MM-DD +sign | STRING | Update, Delete | Bitcoin подпись +flags | STRING | Delete | Флаги + +## Параметры сделки + +### Параметры в виде `тела запроса (request body)`: +#### Для типов контента `application/x-www-form-urlencoded` и `multipart/form-data`: + +Наименование | Тип | Действие | Описание +------------ | ------------ | ------------ | ------------ +type | STRING | * | Тип +at | URL | * | URL сайта +date | DATETIME | * | Дата сделки +seller_address | STRING | * | Продавец: Bitcoin адрес +seller_rating | STRING | Pay, Complete | Продавец: Рейтинг +customer_address | STRING | * | Покупатель: Bitcoin адрес +customer_rating | STRING | Pay, Complete | Покупатель: Рейтинг +payment_address | STRING | Pay, Complete | Оплата: Bitcoin адрес +payment_until | DATETIME | Pay, Complete | Оплата: Срок оплаты +payment_sum | DOUBLE | * | Оплата: Сумма +feedback_leave_before | DATETIME | Pay, Complete | Обратная связь: Срок до... +feedback_status | STRING | Complete | Обратная связь: Статус +feedback_comments | STRING | Complete | Обратная связь: Комментарий + +#### Для типа контента `application/json`: + +```json +{ + "order": "", + "type": "", + "at": "", + "date": "", + "seller": { + "address": "", + "rating": "" + }, + "customer": { + "address": "", + "rating": "" + }, + "payment": { + "address": "", + "until": "", + "sum": "" + }, + "feedback": { + "leave-before": "", + "status": "", + "comments": "" + } +} +``` + +#### Для типа контента `text/plain`: + +```yaml +deal: + order: + type: + at: + date: + seller: + address: + rating: + customer: + address: + rating: + payment: + sum: + address: + until: + feedback: + leave-before: + status: + comments: +``` + +### Пользователь + +#### Помощь +```http request +GET /api/v1/help +``` +```http request +POST /api/v1/help +``` +Получить справочную информацию по командам регистрации и обновления данных учётной записи. + +**Параметры:** +[Строка запроса](#общие-параметры) + +**Пример:** + +Запрос: +```http request +GET /api/v1/help?payload=html +``` + +Ответ (содержимое `payload`): +```html + + + + + + + +
+Bitcoin Payment Service.
+
+Usage: Send a message in accordance with the format.
+
+Available actions: 
+
+  help         : send this help
+  status       : show account data
+  new          : create account
+  add          : adding data to a account (adding missing data)
+  update       : updating account data
+  delete       : delete account
+
+The system has an AI and can determine the action itself.
+But you can help the AI and indicate the action explicitly in the message subject.
+
+Subject: [help|status|new|add|update|delete] | any text
+Body:
+Action         : Message format
+  help         : <ignored>
+  status       : <ignored>
+  new          : <bitcoin><LF>
+                 [<key><LF>] |
+                 [<PGP><LF>] |
+                 [{account:|trusted:}<LF>]
+                 [<url><LF>]
+  add          : [<key><LF>] |
+                 [<PGP><LF>]
+  update       : <yyyy-mm-dd><LF>
+                 [<PGP><LF>] |
+                 [{account:|trusted:}<LF>]
+                 [<url><LF>] |
+                 <signature>
+  delete       : <yyyy-mm-dd><LF>
+                 -account<LF>
+                 <signature>
+Arguments:
+  account      : account URL list (default if not set "trusted")
+  trusted      : trusted URL list
+  -account     : delete account
+
+Templates:
+  <ignored>    : any text will be ignored
+  <LF>         : line feed; format: 0x0a
+  <string>     : single line text
+  <bitcoin>    : bitcoin address; format: [bitcoin:]<string>
+  <key>        : bitcoin public key
+  <PGP>        : PGP public key
+  <yyyy-mm-dd> : current date
+  [+|-]<url>   : add(+) or delete(-) URL; format: http[s]://<string>
+  <signature>  : bitcoin signature for message text (use private key)
+ + + +``` + +###### Этот HTML-документ содержит инструкцию к составлению данных в виде `текста в произвольном формате`. + +#### Статус +```http request +GET /api/v1/account/status +``` +```http request +POST /api/v1/account/status +``` +Получить статус учётной записи пользователя. + +**Параметры:** +[Строка запроса](#общие-параметры) + +**Пример:** + +Запрос: +```http request +GET /api/v1/account/status?address=null +``` + +Ответ: +```json +{ +"id": "A4406-PDF10-OE2BC-SF8AA-T918F-OC9BC-L9EB00", +"action": "Status", +"result": { + "success": false, + "message": "Invalid Bitcoin address: null" +}, +"payload": "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8bWV0YSBuYW1lPSJhdXRob3IiIGNvbnRlbnQ9IkJpdGNvaW4gUGF5bWVudCBTZXJ2aWNlIj4KPC9oZWFkPgo8Ym9keT4KPHByZT5IZWxsbyEKClNvcnJ5LCBzb21ldGhpbmcgd2VudCB3cm9uZyBhbmQgbGVkIHRvIGFuIGVycm9yOgoKPGZvbnQgY29sb3I9IiNDMDM5MkIiPjxiPkludmFsaWQgQml0Y29pbiBhZGRyZXNzOiBudWxsPC9iPjwvZm9udD4KCi0tLS0tClRoYW5rIHlvdSwKUGF5bWVudHMubmV0PC9wcmU+CjwvYm9keT4KPC9odG1sPgo=" +} +``` + +#### Новый +```http request +POST /api/v1/account/new +``` +Зарегистрировать нового пользователя. + +**Параметры:** +[Строка запроса](#общие-параметры), +[Тело запроса](#параметры-пользователя) + +#### Добавить +```http request +POST /api/v1/account/add +``` +Добавить данные в учётную запись пользователя. + +**Параметры:** +[Строка запроса](#общие-параметры), +[Тело запроса](#параметры-пользователя) + +#### Обновить +```http request +POST /api/v1/account/update +``` +Обновить данные в учётной записи пользователя. + +**Параметры:** +[Строка запроса](#общие-параметры), +[Тело запроса](#параметры-пользователя) + +#### Удалить +```http request +POST /api/v1/account/delete +``` +Удалить учётную запись пользователя. + +**Параметры:** +[Строка запроса](#общие-параметры), +[Тело запроса](#параметры-пользователя) + +### Сделка + +#### Создать +```http request +POST /api/v1/deal/create +``` +Создать сделку. + +**Параметры:** +[Строка запроса](#общие-параметры), +[Тело запроса](#параметры-сделки) + +#### Оплатить +```http request +POST /api/v1/deal/pay +``` +Оплатить сделку. + +**Параметры:** +[Строка запроса](#общие-параметры), +[Тело запроса](#параметры-сделки) + +#### Завершить +```http request +POST /api/v1/deal/complete +``` +Завершить сделку. + +**Параметры:** +[Строка запроса](#общие-параметры), +[Тело запроса](#параметры-сделки) + +## Примеры: + +* **HTTP Request:** +```http request +POST /api/v1/account/new HTTP/1.1 +Host: localhost:4999 +Content-Type: application/x-www-form-urlencoded + +address=mynFyJJkRhsbB6y1Q5kTgDGckVz2m9NKH8&key=02ef68c191984433c8a730b8fa48a47ec016a0727e6d26cc0982c8900b39354f61 +``` + +* **curl command:** +```curl +curl "http://localhost:4999/api/v1/account/new?payload=html" \ + -X POST \ + -d "address=mynFyJJkRhsbB6y1Q5kTgDGckVz2m9NKH8&key=02ef68c191984433c8a730b8fa48a47ec016a0727e6d26cc0982c8900b39354f61" \ + -H "Content-Type: application/x-www-form-urlencoded" +``` + +* **HTTP Request:** +```http request +POST /api/v1/account/new HTTP/1.1 +Host: localhost:4999 +Content-Type: application/json + +{"address": "mynFyJJkRhsbB6y1Q5kTgDGckVz2m9NKH8", "key": "02ef68c191984433c8a730b8fa48a47ec016a0727e6d26cc0982c8900b39354f61"} +``` + +* **curl command:** +```curl +curl "http://localhost:4999/api/v1/account/new?payload=html" \ + -X POST \ + -d '{"address": "mynFyJJkRhsbB6y1Q5kTgDGckVz2m9NKH8", "key": "02ef68c191984433c8a730b8fa48a47ec016a0727e6d26cc0982c8900b39354f61"}' \ + -H "Content-Type: application/json" +``` + +* **HTTP Request:** +```http request +POST /api/v1/account/new?payload=html HTTP/1.1 +Host: localhost:4999 +Content-Type: text/plain + +mynFyJJkRhsbB6y1Q5kTgDGckVz2m9NKH8 +02ef68c191984433c8a730b8fa48a47ec016a0727e6d26cc0982c8900b39354f61 +``` + +* **curl command:** +```curl +curl "http://localhost:4999/api/v1/account/new?payload=html" \ + -X POST \ + -d "mynFyJJkRhsbB6y1Q5kTgDGckVz2m9NKH8\n02ef68c191984433c8a730b8fa48a47ec016a0727e6d26cc0982c8900b39354f61" \ + -H "Content-Type: text/plain" +``` + +* **JavaScript:** +```javascript +let headers = new Headers(); +headers.append('Content-Type', 'multipart/form-data'); + +let body = new FormData(); +body.append("address", "mynFyJJkRhsbB6y1Q5kTgDGckVz2m9NKH8"); +body.append("key", "02ef68c191984433c8a730b8fa48a47ec016a0727e6d26cc0982c8900b39354f61"); + +const init = { + method: 'POST', + headers: headers, + body: body, + mode: "cors" +}; + +fetch('http://localhost:4999/api/v1/account/new', init) + .then((response) => { + return response.json(); + }) + .then((json) => { + console.log(json); + console.log(window.atob(json['payload'])); + }) + .catch((e) => { + console.log(e.message); +}); +``` \ No newline at end of file diff --git a/src/app/Bitcoin.cpp b/src/app/Bitcoin.cpp new file mode 100644 index 0000000..e21aee9 --- /dev/null +++ b/src/app/Bitcoin.cpp @@ -0,0 +1,1441 @@ +/*++ + +Module Name: + + Bitcoin.cpp + +Notices: + + Bitcoin library + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#include "Core.hpp" +#include "Bitcoin.hpp" + +#include + +extern "C++" { + +namespace Apostol { + + namespace Bech32 { + + typedef std::vector data; + + /** The Bech32 character set for encoding. */ + const char* charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + + /** The Bech32 character set for decoding. */ + const int8_t charset_rev[128] = { + -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, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 + }; + + /** Concatenate two byte arrays. */ + data cat(data x, const data& y) { + x.insert(x.end(), y.begin(), y.end()); + return x; + } + + /** Find the polynomial with value coefficients mod the generator as 30-bit. */ + uint32_t polymod(const data& values) { + uint32_t chk = 1; + for (size_t i = 0; i < values.size(); ++i) { + uint8_t top = chk >> 25; + chk = (chk & 0x1ffffff) << 5 ^ values[i] ^ + (-((top >> 0) & 1) & 0x3b6a57b2UL) ^ + (-((top >> 1) & 1) & 0x26508e6dUL) ^ + (-((top >> 2) & 1) & 0x1ea119faUL) ^ + (-((top >> 3) & 1) & 0x3d4233ddUL) ^ + (-((top >> 4) & 1) & 0x2a1462b3UL); + } + return chk; + } + + /** Convert to lower case. */ + unsigned char lc(unsigned char c) { + return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c; + } + + /** Expand a HRP for use in checksum computation. */ + data expand_hrp(const std::string& hrp) { + data ret; + ret.resize(hrp.size() * 2 + 1); + for (size_t i = 0; i < hrp.size(); ++i) { + unsigned char c = hrp[i]; + ret[i] = c >> 5; + ret[i + hrp.size() + 1] = c & 0x1f; + } + ret[hrp.size()] = 0; + return ret; + } + + /** Verify a checksum. */ + bool verify_checksum(const std::string& hrp, const data& values) { + return polymod(cat(expand_hrp(hrp), values)) == 1; + } + + /** Create a checksum. */ + data create_checksum(const std::string& hrp, const data& values) { + data enc = cat(expand_hrp(hrp), values); + enc.resize(enc.size() + 6); + uint32_t mod = polymod(enc) ^ 1; + data ret; + ret.resize(6); + for (size_t i = 0; i < 6; ++i) { + ret[i] = (mod >> (5 * (5 - i))) & 31; + } + return ret; + } + + /** Encode a Bech32 string. */ + std::string encode(const std::string& hrp, const data& values) { + data checksum = create_checksum(hrp, values); + data combined = cat(values, checksum); + std::string ret = hrp + '1'; + ret.reserve(ret.size() + combined.size()); + for (size_t i = 0; i < combined.size(); ++i) { + ret += charset[combined[i]]; + } + return ret; + } + + /** Decode a Bech32 string. */ + std::pair decode(const std::string& str) { + bool lower = false, upper = false; + bool ok = true; + for (size_t i = 0; ok && i < str.size(); ++i) { + unsigned char c = str[i]; + if (c < 33 || c > 126) ok = false; + if (c >= 'a' && c <= 'z') lower = true; + if (c >= 'A' && c <= 'Z') upper = true; + } + if (lower && upper) ok = false; + size_t pos = str.rfind('1'); + if (ok && str.size() <= 90 && pos != str.npos && pos >= 1 && pos + 7 <= str.size()) { + data values; + values.resize(str.size() - 1 - pos); + for (size_t i = 0; i < str.size() - 1 - pos; ++i) { + unsigned char c = str[i + pos + 1]; + if (charset_rev[c] == -1) ok = false; + values[i] = charset_rev[c]; + } + if (ok) { + std::string hrp; + for (size_t i = 0; i < pos; ++i) { + hrp += lc(str[i]); + } + if (verify_checksum(hrp, values)) { + return std::make_pair(hrp, data(values.begin(), values.end() - 6)); + } + } + } + return std::make_pair(std::string(), data()); + } + } + + namespace SegWit { + + /** Convert from one power-of-2 number base to another. */ + template + bool convertbits(data& out, const data& in) { + int acc = 0; + int bits = 0; + const int maxv = (1 << tobits) - 1; + const int max_acc = (1 << (frombits + tobits - 1)) - 1; + for (size_t i = 0; i < in.size(); ++i) { + int value = in[i]; + acc = ((acc << frombits) | value) & max_acc; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + out.push_back((acc >> bits) & maxv); + } + } + if (pad) { + if (bits) out.push_back((acc << (tobits - bits)) & maxv); + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return false; + } + return true; + } + + /** Decode a SegWit address. */ + std::pair decode(const std::string& hrp, const std::string& addr) { + std::pair dec = Bech32::decode(addr); + if (dec.first != hrp || dec.second.size() < 1) return std::make_pair(-1, data()); + data conv; + if (!convertbits<5, 8, false>(conv, data(dec.second.begin() + 1, dec.second.end())) || + conv.size() < 2 || conv.size() > 40 || dec.second[0] > 16 || (dec.second[0] == 0 && + conv.size() != 20 && conv.size() != 32)) { + return std::make_pair(-1, data()); + } + return std::make_pair(dec.second[0], conv); + } + + /** Encode a SegWit address. */ + std::string encode(const std::string& hrp, int witver, const data& witprog) { + data enc; + enc.push_back(witver); + convertbits<8, 5, true>(enc, witprog); + std::string ret = Bech32::encode(hrp, enc); + if (decode(hrp, ret).first == -1) return ""; + return ret; + } + + uint32_t checksum(const std::string &hrp, int witver, const std::vector &witprog) { + data enc; + enc.push_back(witver); + convertbits<8, 5, true>(enc, witprog); + data checksum = Bech32::create_checksum(hrp, enc); + const auto checksum_begin = std::begin(checksum); + return from_little_endian_unsafe(checksum_begin); + } + } + + namespace Bitcoin { + + CBitcoinConfig BitcoinConfig; + //-------------------------------------------------------------------------------------------------------------- + + std::string string_to_hex(const std::string& input) + { + static const char* const lut = "0123456789ABCDEF"; + size_t len = input.length(); + + std::string output; + output.reserve(2 * len); + for (size_t i = 0; i < len; ++i) + { + const unsigned char c = input[i]; + output.push_back(lut[c >> 4]); + output.push_back(lut[c & 15]); + } + return output; + } + //-------------------------------------------------------------------------------------------------------------- + + std::string hex_to_string(const std::string& input) + { + static const char* const lut = "0123456789ABCDEF"; + size_t len = input.length(); + if (len & 1) throw std::invalid_argument("odd length"); + + std::string output; + output.reserve(len / 2); + for (size_t i = 0; i < len; i += 2) + { + char a = input[i]; + const char* p = std::lower_bound(lut, lut + 16, a); + if (*p != a) throw std::invalid_argument("not a hex digit"); + + char b = input[i + 1]; + const char* q = std::lower_bound(lut, lut + 16, b); + if (*q != b) throw std::invalid_argument("not a hex digit"); + + output.push_back(((p - lut) << 4) | (q - lut)); + } + return output; + } + //-------------------------------------------------------------------------------------------------------------- + + std::string sha1(const std::string &value) { + const auto& hex = string_to_hex(value); + const config::base16 data(hex); + const auto& hash = sha1_hash(data); + return encode_base16(hash); + } + //-------------------------------------------------------------------------------------------------------------- + + CString sha1(const CString &Value) { + const std::string value(Value); + return sha1(value); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string sha256(const std::string &value) { + const auto& hex = string_to_hex(value); + const config::base16 data(hex); + const auto& hash = sha256_hash(data); + return encode_base16(hash); + } + //-------------------------------------------------------------------------------------------------------------- + + CString sha256(const CString &Value) { + const std::string value(Value); + return sha256(value); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string ripemd160(const std::string &value) { + const auto& hex = string_to_hex(value); + const config::base16 data(hex); + const auto& hash = ripemd160_hash(data); + return encode_base16(hash); + } + //-------------------------------------------------------------------------------------------------------------- + + CString ripemd160(const CString &Value) { + const std::string value(Value); + return ripemd160(value); + } + //-------------------------------------------------------------------------------------------------------------- + + static std::vector segwit_scriptpubkey(int witver, const std::vector& witprog) { + std::vector ret; + ret.push_back(witver ? (0x80 | witver) : 0); + ret.push_back(witprog.size()); + ret.insert(ret.end(), witprog.begin(), witprog.end()); + return ret; + } + //-------------------------------------------------------------------------------------------------------------- + + static bool unwrap(wallet::wrapped_data &data, const data_slice &wrapped) { + if (!verify_checksum(wrapped)) + return false; + + data.version = wrapped.data()[0]; + const auto payload_begin = std::begin(wrapped) + 1; + const auto checksum_begin = std::end(wrapped) - checksum_size; + data.payload.resize(checksum_begin - payload_begin); + std::copy(payload_begin, checksum_begin, data.payload.begin()); + data.checksum = from_little_endian_unsafe(checksum_begin); + return true; + } + //-------------------------------------------------------------------------------------------------------------- + + static data_chunk wrap(const wallet::wrapped_data& data) { + auto bytes = data_chunk(data.version); + extend_data(bytes, data.payload); + append_checksum(bytes); + return bytes; + } + //-------------------------------------------------------------------------------------------------------------- + + uint64_t btc_to_satoshi(double Value) { + return (uint64_t) (Value * 100000000); + }; + //-------------------------------------------------------------------------------------------------------------- + + double satoshi_to_btc(uint64_t Value) { + return ((double) Value) / 100000000; + } + //-------------------------------------------------------------------------------------------------------------- + + bool valid_address(const CString& Address) { + if (Address.IsEmpty()) + return false; + + const std::string address(Address); + + if (IsLegacyAddress(address) && is_base58(address)) { + data_chunk data; + decode_base58(data, address); + return verify_checksum(data); + } + + if (IsSegWitAddress(Address)) { + std::string hrp(address.substr(0, 2)); + std::transform(hrp.begin(), hrp.end(), hrp.begin(), [] (unsigned char c) { return std::tolower(c); }); + const std::pair> &segwit = SegWit::decode(hrp, address); + return segwit.first != -1; + } + + return false; + }; + //-------------------------------------------------------------------------------------------------------------- + + bool valid_public_key(const std::string& key) { + return verify(wallet::ec_public(key)); + }; + //-------------------------------------------------------------------------------------------------------------- + + bool valid_public_key(const CString& Key){ + return valid_public_key(std::string(Key)); + }; + //-------------------------------------------------------------------------------------------------------------- + + bool key_belongs_address(const std::string& key, const std::string& address) { + const wallet::ec_public public_key(key); + + if (!verify(public_key)) + return false; + + if (IsLegacyAddress(address)) { + const wallet::payment_address legacy(address); + const Bitcoin::wrapper wrapped(legacy); + const wallet::wrapped_data& data = wrapped; + + const auto& addr = wallet::payment_address(public_key, data.version).encoded(); + return address == addr; + } +#ifdef BITCOIN_VERSION_4 + if (IsSegWitAddress(address)) { + const auto& addr = wallet::witness_address(public_key).encoded(); + return address == addr; + } +#endif + return false; + }; + //-------------------------------------------------------------------------------------------------------------- + + bool key_belongs_address(const CString& Key, const CString& Address) { + return key_belongs_address(std::string(Key), std::string(Address)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool address_decode(const CString& Str, CString& Result) { + return address_decode(adtHtml, Str, Result); + }; + //-------------------------------------------------------------------------------------------------------------- + + bool address_decode(CAddressDecodeType type, const CString& Address, CString& Result) { + const std::string address(Address); + wallet::wrapped_data data; + + if (IsLegacyAddress(Address)) { + if (!unwrap_legacy(data, address)) + return false; + } else if (IsSegWitAddress(Address)) { + if (!unwrap_segwit(data, address)) + return false; + } else { + return false; + } + + const auto& hex = encode_base16(data.payload); + + switch (type) { + case adtInfo: + Result.Format(" address: %s\n wrapper:\nchecksum: %u\n payload: %s\n version: %u", + address.c_str(), + data.checksum, + hex.c_str(), + data.version); + break; + case adtHtml: + Result.Format(" address: %s\n wrapper:\nchecksum: %u\n payload: %s\n version: %u", + address.c_str(), + data.checksum, + hex.c_str(), + data.version); + break; + case adtJson: + Result.Format(R"({"address": "%s", "wrapper": {"checksum": %u, "payload": "%s", "version": %u}})", + address.c_str(), + data.checksum, + hex.c_str(), + data.version); + break; + } + + return true; + } + //-------------------------------------------------------------------------------------------------------------- + + bool address_decode(const CString& Address, CJSON &Result) { + CString jsonString; + const bool ret = address_decode(adtJson, Address, jsonString); + Result << jsonString; + return ret; + } + //-------------------------------------------------------------------------------------------------------------- + + bool unwrap_legacy(wallet::wrapped_data &data, const wallet::payment_address &address) { + if (!address) + return false; + const Bitcoin::wrapper wrapped(address); + data = wrapped; + return true; + } + //-------------------------------------------------------------------------------------------------------------- + + bool unwrap_legacy(wallet::wrapped_data &data, const std::string &address) { + return unwrap_legacy(data, wallet::payment_address(address)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool unwrap_segwit(wallet::wrapped_data &data, const std::string &address) { + const std::string hrp(address.substr(0, 2)); + const std::pair> &segwit = SegWit::decode(hrp, address); + + if (segwit.first == -1) + return false; + + data.version = segwit.first; + data.payload = segwit.second; + data.checksum = SegWit::checksum(hrp, segwit.first, segwit.second); + + //data.payload = segwit_scriptpubkey(segwit.first, segwit.second); + return true; + } + //-------------------------------------------------------------------------------------------------------------- + + bool IsLegacyAddress(const std::string& address) { + if (address.empty()) return false; + const TCHAR ch = address.at(0); + return (ch == '1' || ch == '2' || ch == '3' || ch == 'm' || ch == 'n') && (address.length() >= 26 && address.length() <= 35); + } + //-------------------------------------------------------------------------------------------------------------- + + bool IsLegacyAddress(const CString& Address) { + if (Address.IsEmpty()) return false; + const TCHAR ch = Address.at(0); + return (ch == '1' || ch == '2' || ch == '3' || ch == 'm' || ch == 'n') && (Address.Length() >= 26 && Address.Length() <= 35); + } + //-------------------------------------------------------------------------------------------------------------- + + bool IsSegWitAddress(const std::string &addr) { + if (addr.empty()) return false; + const std::string hrp(addr.substr(0, 3)); + return (hrp == "bc1" || hrp == "tb1") && (addr.length() == 42 || addr.length() == 62); + } + //-------------------------------------------------------------------------------------------------------------- + + bool IsSegWitAddress(const CString& Addr) { + if (Addr.IsEmpty()) return false; + const CString hrp(Addr.SubString(0, 3).Lower()); + return (hrp == "bc1" || hrp == "tb1") && (Addr.Length() == 42 || Addr.Length() == 62); + } + //-------------------------------------------------------------------------------------------------------------- + + bool IsBitcoinAddress(const std::string &addr) { + return IsLegacyAddress(addr) || IsSegWitAddress(addr); + } + //-------------------------------------------------------------------------------------------------------------- + + bool IsBitcoinAddress(const CString& Addr) { + return IsLegacyAddress(Addr) || IsSegWitAddress(Addr); + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::ek_token token_new(const std::string &passphrase, const data_chunk &salt) { +#ifdef WITH_ICU + if (salt.size() < ek_salt_size) + throw Delphi::Exception::Exception(BX_TOKEN_NEW_SHORT_SALT); + + encrypted_token token = {}; + ek_entropy bytes = {}; + std::copy(salt.begin(), salt.begin() + bytes.size(), bytes.begin()); + create_token(token, passphrase, bytes); + + return ek_token(token); +#else + throw Delphi::Exception::Exception(BX_TOKEN_NEW_REQUIRES_ICU); +#endif + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::ek_public ek_public(const ek_token &token, const data_chunk &seed, uint8_t version) { +#ifdef WITH_ICU + if (seed.size() < ek_seed_size) + throw Delphi::Exception::Exception(BX_EK_ADDRESS_SHORT_SEED); + + ek_seed bytes = {}; + std::copy(seed.begin(), seed.begin() + ek_seed_size, bytes.begin()); + const bool compressed = true; + + encrypted_private unused1 = {}; + encrypted_public key = {}; + ec_compressed unused2; + + // This cannot fail because the token has been validated. + /* bool */ create_key_pair(unused1, key, unused2, token, bytes, version, compressed); + + return wallet::ek_public(key); +#else + throw Delphi::Exception::Exception(BX_TOKEN_NEW_REQUIRES_ICU); +#endif + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::ec_public ek_public_to_ec(const std::string &passphrase, const wallet::ek_public &key) { +#ifdef WITH_ICU + bool compressed; + uint8_t version; + ec_compressed point; + + if (!decrypt(point, version, compressed, key, passphrase)) + throw Delphi::Exception::Exception(BX_EK_PUBLIC_TO_EC_INVALID_PASSPHRASE); + + return ec_public(point, compressed); +#else + throw Delphi::Exception::Exception(BX_TOKEN_NEW_REQUIRES_ICU); +#endif + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::payment_address ek_address(const wallet::ek_token &token, const data_chunk &seed, uint8_t version) { +#ifdef WITH_ICU + if (seed.size() < ek_seed_size) + throw Delphi::Exception::Exception(BX_EK_ADDRESS_SHORT_SEED); + + ek_seed bytes = {}; + std::copy(seed.begin(), seed.begin() + ek_seed_size, bytes.begin()); + const bool compressed = true; + + ec_compressed point; + encrypted_private unused = {}; + + // This cannot fail because the token has been validated. + /* bool */ create_key_pair(unused, point, token, bytes, version, compressed); + const payment_address address({ point, compressed }, version); + + return address; +#else + throw Delphi::Exception::Exception(BX_TOKEN_NEW_REQUIRES_ICU); +#endif + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::hd_private hd_new(const data_chunk &seed, uint64_t prefixes) { + if (seed.size() < minimum_seed_size) + throw Delphi::Exception::Exception(BX_HD_NEW_SHORT_SEED); + + const bc::wallet::hd_private private_key(seed, prefixes); + if (!private_key) + throw Delphi::Exception::Exception(BX_HD_NEW_INVALID_KEY); + + return private_key; + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::ec_private hd_to_ec_private(const wallet::hd_key &key, uint64_t prefixes) { + const auto key_prefixes = from_big_endian_unsafe(key.begin()); + if (key_prefixes != prefixes) + throw Delphi::Exception::Exception("HD Key prefixes version error."); + + // Create the private key from hd_key and the public version. + const auto private_key = bc::wallet::hd_private(key, prefixes); + if (!private_key) + throw Delphi::Exception::Exception(BX_HD_NEW_INVALID_KEY); + + return private_key.secret(); + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::ec_public hd_to_ec_public(const wallet::hd_key &key, uint32_t prefix) { + const auto key_prefix = from_big_endian_unsafe(key.begin()); + if (key_prefix != prefix) + throw Delphi::Exception::Exception("HD Key prefix version error."); + + // Create the public key from hd_key and the public version. + const auto public_key = bc::wallet::hd_public(key, prefix); + if (!public_key) + throw Delphi::Exception::Exception(BX_HD_NEW_INVALID_KEY); + + return wallet::ec_public(public_key); + } + //-------------------------------------------------------------------------------------------------------------- + + bool verify_message(const std::string& msg, const std::string& addr, const std::string& sig) { + + const raw message(msg); + const wallet::payment_address address(addr); + const signature signature(sig); + + return wallet::verify_message(message, address, signature); + } + //-------------------------------------------------------------------------------------------------------------- + + bool VerifyMessage(const CString &Message, const CString &Address, const CString &Signature) { + return verify_message(Message.c_str(), Address.c_str(), Signature.c_str()); + } + //-------------------------------------------------------------------------------------------------------------- +#ifdef BITCOIN_VERSION_4 + + std::string script_to_address(const system::script &script, uint8_t version) { + const auto& address = payment_address(script, version); + return address.encoded(); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string script_to_address(const std::string &script, uint8_t version) { + return script_to_address(system::script(script), version); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string address_to_key(const wallet::payment_address &address) { + return encode_base16(sha256_hash(address.output_script().to_data(false))); + } + //-------------------------------------------------------------------------------------------------------------- +#ifdef WITH_BITCOIN_CLIENT + uint64_t fetch_balance(const wallet::payment_address &address) { + uint64_t result = 0; + + client::connection_settings connection = {}; + connection.retries = 5; + connection.server = config::endpoint(BitcoinConfig.endpoint); + + client::obelisk_client client(connection.retries); + + if (!client.connect(connection)) { + throw Delphi::Exception::Exception(BX_CONNECTION_FAILURE, connection.server); + } +/* + auto on_done = [&result](const code& ec, const history::list& rows) + { + uint64_t balance = balancer(rows); + result = encode_base10(balance, 8); + }; +*/ + auto handler = [&result](const code& ec, const history::list& history) { + if (ec != error::success) + throw Delphi::Exception::ExceptionFrm("Failed to retrieve history: %s", ec.message().c_str()); + else + for (const auto& row: history) + result += row.value; + }; + + const auto key = sha256_hash(address.output_script().to_data(false)); + + client.blockchain_fetch_history4(handler, key); + client.wait(); + + return result; + } + //-------------------------------------------------------------------------------------------------------------- +#endif // WITH_BITCOIN_CLIENT + +#else // BITCOIN_VERSION_4 + + chain::script get_witness_script(const ec_public &key1, const ec_public &key2, const ec_public &key3) { + //make key list + point_list keys {key1.point(), key2.point(), key3.point()}; + //create 2/3 multisig script + return chain::script::to_pay_multisig_pattern(2u, keys); + } + //-------------------------------------------------------------------------------------------------------------- + + chain::script get_redeem_script(const ec_public &key1, const ec_public &key2, const ec_public &key3) { + //create 2/3 multisig script + const auto& multisig = get_witness_script(key1, key2, key3); + + //sha256 the script + const auto& multisig_hash = to_chunk(sha256_hash(multisig.to_data(false))); + + //redeem script + operation::list redeemscript_ops {operation(opcode(0)), operation(multisig_hash)}; + + return script(redeemscript_ops); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string script_to_address(const chain::script &script, uint8_t version) { + const auto& address = payment_address(script, version); + return address.encoded(); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string script_to_address(const std::string &script, uint8_t version) { + return script_to_address(Bitcoin::script(script), version); + } + //-------------------------------------------------------------------------------------------------------------- +#ifdef WITH_BITCOIN_CLIENT + void code_to_json(const code& error, CJSON &Result) { + CString json; + if (error.message().empty()) { + json.Format(R"({"error": {"code": %u, "message": null}})", error.value()); + } else { + json.Format(R"({"error": {"code": %u, "message": "%s"}})", error.value(), error.message().c_str()); + } + Result << json; + }; + //-------------------------------------------------------------------------------------------------------------- + + uint64_t balancer(const history::list& rows) { + uint64_t unspent_balance = 0; + + for (const auto& row: rows) { + // spend unconfirmed (or no spend attempted) + if (row.spend.hash() == null_hash) + unspent_balance += row.value; + } + + return unspent_balance; + } + //-------------------------------------------------------------------------------------------------------------- + + uint64_t fetch_balance(const wallet::payment_address &address) { + uint64_t result = 0; + + client::connection_type connection = {}; + connection.retries = 0; + connection.timeout_seconds = 5; + connection.server = config::endpoint(BitcoinConfig.endpoint); + + client::obelisk_client client(connection); + + if (!client.connect(connection)) { + throw Delphi::Exception::Exception(BX_CONNECTION_FAILURE, connection.server); + } + + auto on_reply = [&result](const history::list& rows) { + result = balancer(rows); + }; + + auto on_error = [](const code& error) { +// if (error != error::success) +// throw Delphi::Exception::ExceptionFrm("Failed to retrieve history: %s", message.c_str()); + }; + + client.blockchain_fetch_history3(on_error, on_reply, address); + client.wait(); + + return result; + } + //-------------------------------------------------------------------------------------------------------------- + + void row_to_json(const chain::history& row, CJSONValue &Value) { + auto& object = Value.Object(); + + CJSONValue received; + CJSONValue spent; + + // missing output implies output cut off by server's history threshold + if (row.output.hash() != null_hash) { + received.Object().AddPair("hash", hash256(row.output.hash()).to_string().c_str()); + + // zeroized received.height implies output unconfirmed (in mempool) + if (row.output_height != 0) + received.Object().AddPair("height", (int) row.output_height); + + received.Object().AddPair("index", (int) row.output.index()); + + object.AddPair("received", received); + } + + // missing input implies unspent + if (row.spend.hash() != null_hash) + { + spent.Object().AddPair("hash", hash256(row.spend.hash()).to_string().c_str()); + + // zeroized input.height implies spend unconfirmed (in mempool) + if (row.spend_height != 0) + spent.Object().AddPair("height", (int) row.spend_height); + + spent.Object().AddPair("index", (int) row.spend.index()); + object.AddPair("spent", spent); + } + + object.AddPair("value", (int) row.value); + } + //-------------------------------------------------------------------------------------------------------------- + + void transfers(const history::list& rows, CJSON& Result) { + CJSONValue Value(jvtArray); + for (const auto& row: rows) { + CJSONValue Object; + row_to_json(row, Object); + Value.Array().Add(Object); + } + Result.Object().AddPair("transfers", Value); + } + //-------------------------------------------------------------------------------------------------------------- + + void fetch_history(const wallet::payment_address &address, CJSON& Result) { + client::connection_type connection = {}; + connection.retries = 0; + connection.timeout_seconds = 5; + connection.server = config::endpoint(BitcoinConfig.endpoint); + + client::obelisk_client client(connection); + + if (!client.connect(connection)) { + throw Delphi::Exception::Exception(BX_CONNECTION_FAILURE, connection.server); + } + + auto on_reply = [&Result](const history::list& rows) { + transfers(rows, Result); + }; + + auto on_error = [&Result](const code& error) { + code_to_json(error, Result); + }; + + client.blockchain_fetch_history3(on_error, on_reply, address); + client.wait(); + } + //-------------------------------------------------------------------------------------------------------------- + + void header_to_json(const header& header, CJSON& Result) { + const chain::header& block_header = header; + + CJSONValue Header(jvtObject); + auto& Object = Header.Object(); + + Object.AddPair("bits", (int) block_header.bits()); + Object.AddPair("hash", hash256(block_header.hash()).to_string().c_str()); + Object.AddPair("merkle_tree_hash", hash256(block_header.merkle()).to_string().c_str()); + Object.AddPair("nonce", (int) block_header.nonce()); + Object.AddPair("previous_block_hash", hash256(block_header.previous_block_hash()).to_string().c_str()); + Object.AddPair("time_stamp", (int) block_header.timestamp()); + Object.AddPair("version", (int) block_header.version()); + + Result.Object().AddPair("header", Header); + } + //-------------------------------------------------------------------------------------------------------------- + + void fetch_header(uint32_t height, CJSON &Result) { + client::connection_type connection = {}; + connection.retries = 0; + connection.timeout_seconds = 5; + connection.server = config::endpoint(BitcoinConfig.endpoint); + + client::obelisk_client client(connection); + + if (!client.connect(connection)) { + throw Delphi::Exception::Exception(BX_CONNECTION_FAILURE, connection.server); + } + + auto on_reply = [&Result](const chain::header& header) { + header_to_json(header, Result); + }; + + auto on_error = [&Result](const code& error) { + code_to_json(error, Result); + }; + + client.blockchain_fetch_block_header(on_error, on_reply, height); + client.wait(); + } + //-------------------------------------------------------------------------------------------------------------- + + void fetch_header(const hash_digest& hash, CJSON &Result) { + client::connection_type connection = {}; + connection.retries = 0; + connection.timeout_seconds = 5; + connection.server = config::endpoint(BitcoinConfig.endpoint); + + client::obelisk_client client(connection); + + if (!client.connect(connection)) { + throw Delphi::Exception::Exception(BX_CONNECTION_FAILURE, connection.server); + } + + auto on_reply = [&Result](const chain::header& header) { + header_to_json(header, Result); + }; + + auto on_error = [&Result](const code& error) { + code_to_json(error, Result); + }; + + client.blockchain_fetch_block_header(on_error, on_reply, hash); + client.wait(); + } + //-------------------------------------------------------------------------------------------------------------- + + void send_tx(const chain::transaction& tx, CJSON& Result) { + client::connection_type connection = {}; + connection.retries = 0; + connection.timeout_seconds = 5; + connection.server = config::endpoint(BitcoinConfig.endpoint); + + client::obelisk_client client(connection); + + if (!client.connect(connection)) { + throw Delphi::Exception::Exception(BX_CONNECTION_FAILURE, connection.server); + } + + auto on_done = [&Result](const code& error) { + code_to_json(error, Result); + }; + + auto on_error = [&Result](const code& error) { + code_to_json(error, Result); + }; + + // This validates the tx, submits it to local tx pool, and notifies peers. + client.transaction_pool_broadcast(on_error, on_done, tx); + client.wait(); + } + //-------------------------------------------------------------------------------------------------------------- + + void send_tx(const std::string &hex, CJSON &Result) { + chain::transaction tx; + + data_chunk data; + if (!decode_base16(data, hex)) + throw Delphi::Exception::Exception("Invalid converting a hex string into transaction data."); + + if (!tx.from_data(data, true, true)) + throw Delphi::Exception::Exception("Invalid transaction data."); + + send_tx(tx, Result); + } +#endif // WITH_BITCOIN_CLIENT +#endif // BITCOIN_VERSION_4 + +#ifdef WITH_BITCOIN_CLIENT + //-------------------------------------------------------------------------------------------------------------- + + //-- CBalance -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + void CBalance::Clear() { + m_Transfers = 0; + m_Height = 0; + + m_Received = 0; + m_Spent = 0; + + m_TimeStamp = 0; + + m_History.Clear(); + m_Header.Clear(); + } + //-------------------------------------------------------------------------------------------------------------- + + void CBalance::Fetch(const CString &Address) { + + Clear(); + + const wallet::payment_address address(std::string(Address.c_str())); + + fetch_history(address, m_History); + + const auto& transfers = m_History["transfers"].Array(); + m_Transfers = transfers.Count(); + + if (m_Transfers == 0) + return; + + for (int i = 0; i < transfers.Count(); i++) { + const auto& transfer = transfers[i].AsObject(); + + if (transfer["received"].ValueType() == jvtObject) { + m_Received += transfer["value"].AsInteger(); + m_Height = transfer["received"]["height"].AsInteger(); + } + + if (transfer["spent"].ValueType() == jvtObject) { + m_Spent += transfer["value"].AsInteger(); + } + } + + if (m_Height != 0) { + fetch_header(m_Height, m_Header); + time_t timestamp = m_Header["header"]["time_stamp"].AsInteger(); + m_TimeStamp = SystemTimeToDateTime(gmtime(×tamp), 0); + } + } +#endif // WITH_BITCOIN_CLIENT + + //-------------------------------------------------------------------------------------------------------------- + + //-- CWitness -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + CWitness::CWitness(): CWitness(2u) { + + } + //-------------------------------------------------------------------------------------------------------------- + + CWitness::CWitness(uint8_t signatures): m_signatures(signatures), m_hash() { + + } + //-------------------------------------------------------------------------------------------------------------- + + CWitness::CWitness(const ec_private &key1, const ec_private &key2): CWitness() { + make(key1, key2); + bind(); + } + //-------------------------------------------------------------------------------------------------------------- + + CWitness::CWitness(const ec_private &key1, const ec_private &key2, const ec_private &key3): CWitness() { + make(key1, key2, key3); + bind(); + } + //-------------------------------------------------------------------------------------------------------------- + + CWitness::CWitness(const ec_private &key1, const ec_private &key2, const ec_private &key3, const ec_public& key4): CWitness() { + make(key1, key2, key3, key4); + bind(); + } + //-------------------------------------------------------------------------------------------------------------- + + CWitness::CWitness(const ec_private &key1, const ec_private &key2, const ec_public &key3): CWitness() { + make(key1, key2, key3); + bind(); + } + //-------------------------------------------------------------------------------------------------------------- + + CWitness::CWitness(const ec_public &key1, const ec_public &key2, const ec_public &key3): CWitness() { + make(key1, key2, key3); + bind(); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWitness::make(const ec_private &key1, const ec_private &key2) { + m_secrets.push_back(key1); + m_secrets.push_back(key2); + + m_keys.push_back(key1.to_public()); + m_keys.push_back(key2.to_public()); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWitness::make(const ec_private &key1, const ec_private &key2, const ec_private &key3) { + m_secrets.push_back(key1); + m_secrets.push_back(key2); + m_secrets.push_back(key3); + + m_keys.push_back(key1.to_public()); + m_keys.push_back(key2.to_public()); + m_keys.push_back(key3.to_public()); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWitness::make(const ec_private &key1, const ec_private &key2, const ec_public &key3) { + m_secrets.push_back(key1); + m_secrets.push_back(key2); + + m_keys.push_back(key1.to_public()); + m_keys.push_back(key2.to_public()); + m_keys.push_back(key3); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWitness::make(const ec_private &key1, const ec_private &key2, const ec_private &key3, const ec_public &key4) { + m_signatures = 3u; + + m_secrets.push_back(key1); + m_secrets.push_back(key2); + m_secrets.push_back(key3); + + m_keys.push_back(key1.to_public()); + m_keys.push_back(key2.to_public()); + m_keys.push_back(key3.to_public()); + m_keys.push_back(key4); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWitness::make(const ec_public &key1, const ec_public &key2, const ec_public &key3) { + m_keys.push_back(key1); + m_keys.push_back(key2); + m_keys.push_back(key3); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWitness::bind_script() { + point_list points; + for (const ec_public &key : m_keys) + points.push_back(key.point()); + m_script = chain::script::to_pay_multisig_pattern(m_signatures, points); + m_hash = sha256_hash(m_script.to_data(false)); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWitness::bind_embedded() { + operation::list operations { operation(opcode::push_size_0), operation(to_chunk(m_hash)) }; + m_embedded = chain::script(operations); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWitness::bind() { + bind_script(); + bind_embedded(); + } + //-------------------------------------------------------------------------------------------------------------- + + const hash_digest& CWitness::hash() const { + return m_hash; + } + //-------------------------------------------------------------------------------------------------------------- + + payment_address CWitness::to_address(uint8_t version) const { + return payment_address(m_embedded, version); + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- raw ------------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + raw::raw(): value_() { + + } + //-------------------------------------------------------------------------------------------------------------- + + raw::raw(const std::string& hexcode) + { + std::stringstream(hexcode) >> *this; + } + //-------------------------------------------------------------------------------------------------------------- + + raw::raw(data_chunk value): value_(std::move(value)) { + + } + //-------------------------------------------------------------------------------------------------------------- + + raw::raw(const raw& other): raw(other.value_) { + + } + //-------------------------------------------------------------------------------------------------------------- + + raw::operator const data_chunk&() const { + return value_; + } + //-------------------------------------------------------------------------------------------------------------- + + raw::operator data_slice() const { + return value_; + } + //-------------------------------------------------------------------------------------------------------------- + + std::istream& operator>>(std::istream& input, raw& argument) { + std::istreambuf_iterator first(input), last; + argument.value_.assign(first, last); + return input; + } + //-------------------------------------------------------------------------------------------------------------- + + std::ostream& operator<<(std::ostream& output, const raw& argument) { + std::ostreambuf_iterator iterator(output); + std::copy(argument.value_.begin(), argument.value_.end(), iterator); + return output; + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- wrapper --------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + wrapper::wrapper() + : value_() + { + } + + wrapper::wrapper(const std::string& wrapped) + { + std::stringstream(wrapped) >> *this; + } + + wrapper::wrapper(const data_chunk& wrapped) + : wrapper(encode_base16(wrapped)) + { + } + + wrapper::wrapper(const wallet::wrapped_data& wrapped) + : value_(wrapped) + { + } + + wrapper::wrapper(const wallet::payment_address& address) + : wrapper(encode_base16(address.to_payment())) + { + } + + wrapper::wrapper(uint8_t version, const data_chunk& payload) + : wrapper(wallet::wrapped_data{ version, payload, 0 }) + { + } + + wrapper::wrapper(const wrapper& other) + : value_(other.value_) + { + } + + const data_chunk wrapper::to_data() const + { + return wrap(value_); + } + + wrapper::operator const wallet::wrapped_data&() const + { + return value_; + } + + std::istream& operator>>(std::istream& input, wrapper& argument) + { + std::string hexcode; + input >> hexcode; + + // The checksum is validated here. + if (!unwrap(argument.value_, base16(hexcode))) + { + throw Delphi::Exception::Exception("Error: Payment address is invalid."); + } + + return input; + } + + std::ostream& operator<<(std::ostream& output, const wrapper& argument) + { + // The checksum is calculated here (value_ checksum is ignored). + const auto bytes = wrap(argument.value_); + output << base16(bytes); + return output; + } + + //-------------------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + // message_signature format is currently private to bx. + static bool decode_signature(wallet::message_signature& signature, const std::string& encoded) { + // There is no bc::decode_base64 array-based override. + data_chunk decoded; + if (!decode_base64(decoded, encoded) || + (decoded.size() != wallet::message_signature_size)) + return false; + + std::copy(decoded.begin(), decoded.end(), signature.begin()); + return true; + } + //-------------------------------------------------------------------------------------------------------------- + + static std::string encode_signature(const wallet::message_signature& signature) { + return encode_base64(signature); + } + //-------------------------------------------------------------------------------------------------------------- + + signature::signature(): value_() { + + } + //-------------------------------------------------------------------------------------------------------------- + + signature::signature(const std::string& hexcode): value_() { + std::stringstream(hexcode) >> *this; + } + //-------------------------------------------------------------------------------------------------------------- + + signature::signature(const wallet::message_signature& value): value_(value) { + + } + //-------------------------------------------------------------------------------------------------------------- + + signature::signature(const signature& other): signature(other.value_) { + + } + //-------------------------------------------------------------------------------------------------------------- + + signature::operator wallet::message_signature&() { + return value_; + } + //-------------------------------------------------------------------------------------------------------------- + + signature::operator const wallet::message_signature&() const { + return value_; + } + //-------------------------------------------------------------------------------------------------------------- + + std::string signature::encoded() const { + return encode_signature(value_); + } + //-------------------------------------------------------------------------------------------------------------- + + std::istream& operator>>(std::istream& input, signature& argument) { + std::string hexcode; + input >> hexcode; + + if (!decode_signature(argument.value_, hexcode)) { + throw Delphi::Exception::Exception("Error: Decode signature."); + } + + return input; + } + //-------------------------------------------------------------------------------------------------------------- + + std::ostream& operator<<(std::ostream& output, const signature& argument) { + output << encode_signature(argument.value_); + return output; + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- script ---------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + script::script(): value_() + { + } + + script::script(const std::string& mnemonic) + { + std::stringstream(mnemonic) >> *this; + } + + script::script(const chain::script& value) + : value_(value) + { + } + + script::script(const data_chunk& value) + { + value_.from_data(value, false); + } + + script::script(const std::vector& tokens) + { + const auto mnemonic = join(tokens); + std::stringstream(mnemonic) >> *this; + } + + script::script(const script& other) + : script(other.value_) + { + } + + const data_chunk script::to_data() const + { + return value_.to_data(false); + } + + const std::string script::to_string() const + { + static constexpr auto flags = machine::rule_fork::all_rules; + return value_.to_string(flags); + } + + script::operator const chain::script&() const + { + return value_; + } + + std::istream& operator>>(std::istream& input, script& argument) + { + std::istreambuf_iterator end; + std::string mnemonic(std::istreambuf_iterator(input), end); + + // Test for invalid result sentinel. + if (!argument.value_.from_string(mnemonic) && mnemonic.length() > 0) { + throw Delphi::Exception::Exception("Error: Invalid script argument."); + } + + return input; + } + + std::ostream& operator<<(std::ostream& output, const script& argument) + { + static constexpr auto flags = machine::rule_fork::all_rules; + output << argument.value_.to_string(flags); + return output; + } + + } // namespace Bitcoin + +} // namespace Apostol +} \ No newline at end of file diff --git a/src/app/Bitcoin.hpp b/src/app/Bitcoin.hpp new file mode 100644 index 0000000..25af8a2 --- /dev/null +++ b/src/app/Bitcoin.hpp @@ -0,0 +1,733 @@ +/*++ + +Module Name: + + Bitcoin.hpp + +Notices: + + Bitcoin library + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_BITCOIN_HPP +#define APOSTOL_BITCOIN_HPP + +#ifdef BITCOIN_VERSION_4 + +//#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +using namespace bc; +using namespace bc::system; +using namespace bc::system::config; +using namespace bc::system::wallet; +using namespace bc::client; + +#else + +#ifdef BITCOIN_VERSION_3_7_x +#include +#else +#include +#endif + +#include + +#include +#include +#include + +using namespace bc; +using namespace bc::chain; +using namespace bc::machine; +using namespace bc::config; +using namespace bc::wallet; +using namespace bc::client; + +#endif + +//---------------------------------------------------------------------------------------------------------------------- + +#define BX_EK_PUBLIC_TO_EC_INVALID_PASSPHRASE \ + "The passphrase is incorrect." +#define BX_EK_ADDRESS_SHORT_SEED \ + "The seed is less than 192 bits long." +#define BX_TOKEN_NEW_SHORT_SALT \ + "The salt is less than 32 bits long." +#define BX_TOKEN_NEW_REQUIRES_ICU \ + "The command requires an ICU build." +#define BX_CONNECTION_FAILURE \ + "Could not connect to server: %1%" +#define BX_HD_NEW_SHORT_SEED \ + "The seed is less than 128 bits long." +#define BX_HD_NEW_INVALID_KEY \ + "The seed produced an invalid key." +//---------------------------------------------------------------------------------------------------------------------- + +#define BX_NO_TRANSFERS_FOUND "No transfers found on account: %s." +#define BX_INVALID_SECRET_KEY_COUNT "Invalid count of secret keys (2 or 3 expected)." +#define BX_INSUFFICIENT_FUNDS "Insufficient funds on account: %s." +#define BX_INSUFFICIENT_FEE "Insufficient transaction fee on account: %s." +//---------------------------------------------------------------------------------------------------------------------- + +/** + * The minimum safe length of a seed in bits (multiple of 8). + */ +BC_CONSTEXPR size_t minimum_seed_bits = 128; + +/** + * The minimum safe length of a seed in bytes (16). + */ +BC_CONSTEXPR size_t minimum_seed_size = minimum_seed_bits / bc::byte_bits; +//---------------------------------------------------------------------------------------------------------------------- + +extern "C++" { + +namespace Apostol { + + namespace Bech32 + { + /** Encode a Bech32 string. Returns the empty string in case of failure. */ + std::string encode(const std::string& hrp, const std::vector& values); + + /** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ + std::pair > decode(const std::string& str); + } + + namespace SegWit + { + /** Decode a SegWit address. Returns (witver, witprog). witver = -1 means failure. */ + std::pair > decode(const std::string& hrp, const std::string& addr); + + /** Encode a SegWit address. Empty string means failure. */ + std::string encode(const std::string& hrp, int witver, const std::vector& witprog); + + uint32_t checksum(const std::string& hrp, int witver, const std::vector& witprog); + } + + namespace Bitcoin { + + typedef TList CPrivateList; + //-------------------------------------------------------------------------------------------------------------- + + struct CTransactionFee { + CString Fee; + uint_t min; + uint_t max; + }; + + struct CBitcoinConfig { + std::string endpoint = "tcp://mainnet.libbitcoin.net:9091"; + + uint64_t version_hd = hd_private::mainnet; + uint16_t version_ec = ec_private::mainnet; + uint8_t version_key = payment_address::mainnet_p2kh; + uint8_t version_script = payment_address::mainnet_p2sh; + + CTransactionFee Miner {"1%", 200, 2000}; + + uint_t min_output = 200; + + CString Symbol = "BTC"; + + }; + //-------------------------------------------------------------------------------------------------------------- + + extern CBitcoinConfig BitcoinConfig; + //-------------------------------------------------------------------------------------------------------------- + + enum CAddressDecodeType { adtInfo, adtHtml, adtJson }; + + std::string string_to_hex(const std::string& input); + std::string hex_to_string(const std::string& input); + //-------------------------------------------------------------------------------------------------------------- + + std::string sha1(const std::string &value); + CString sha1(const CString &Value); + //-------------------------------------------------------------------------------------------------------------- + + std::string sha256(const std::string &value); + CString sha256(const CString &Value); + //-------------------------------------------------------------------------------------------------------------- + + std::string ripemd160(const std::string &value); + CString ripemd160(const CString &Value); + //-------------------------------------------------------------------------------------------------------------- + + double satoshi_to_btc(uint64_t Value); + uint64_t btc_to_satoshi(double Value); + //-------------------------------------------------------------------------------------------------------------- + + bool valid_address(const CString& Address); + //-------------------------------------------------------------------------------------------------------------- + + bool valid_public_key(const std::string& key); + bool valid_public_key(const CString& Key); + //-------------------------------------------------------------------------------------------------------------- + + bool key_belongs_address(const std::string& key, const std::string& address); + bool key_belongs_address(const CString& Key, const CString& Address); + //-------------------------------------------------------------------------------------------------------------- + + bool address_decode(CAddressDecodeType type, const CString& Address, CString& Result); + //-------------------------------------------------------------------------------------------------------------- + + bool address_decode(const CString& Address, CJSON& Result); + bool address_decode(const CString& Address, CString& Result); + //-------------------------------------------------------------------------------------------------------------- + + bool unwrap_legacy(wallet::wrapped_data &data, const wallet::payment_address &address); + bool unwrap_legacy(wallet::wrapped_data &data, const std::string &address); + //-------------------------------------------------------------------------------------------------------------- + + bool unwrap_segwit(wallet::wrapped_data &data, const std::string &address); + //-------------------------------------------------------------------------------------------------------------- + + bool IsLegacyAddress(const std::string& address); + bool IsLegacyAddress(const CString& Address); + + bool IsSegWitAddress(const std::string& addr); + bool IsSegWitAddress(const CString& Addr); + + bool IsBitcoinAddress(const std::string& addr); + bool IsBitcoinAddress(const CString& Addr); + //-------------------------------------------------------------------------------------------------------------- + + wallet::ek_token token_new(const std::string &passphrase, const data_chunk& salt); + wallet::ek_public ek_public(const wallet::ek_token &token, const data_chunk& seed, uint8_t version = payment_address::mainnet_p2kh); + wallet::ec_public ek_public_to_ec(const std::string &passphrase, const wallet::ek_public &key); + wallet::payment_address ek_address(const wallet::ek_token &token, const data_chunk& seed, uint8_t version = payment_address::mainnet_p2kh); + //-------------------------------------------------------------------------------------------------------------- + + wallet::hd_private hd_new(const data_chunk& seed, uint64_t prefixes = hd_private::mainnet); + wallet::ec_private hd_to_ec_private(const wallet::hd_key& key, uint64_t prefixes = hd_private::mainnet); + wallet::ec_public hd_to_ec_public(const wallet::hd_key& key, uint32_t prefix = hd_public::mainnet); + //-------------------------------------------------------------------------------------------------------------- + + bool verify_message(const std::string& msg, const std::string& addr, const std::string& sig); + bool VerifyMessage(const CString& Message, const CString& Address, const CString& Signature); + //-------------------------------------------------------------------------------------------------------------- + +#ifdef BITCOIN_VERSION_4 + std::string address_to_key(const wallet::payment_address &address); +#endif + chain::script get_witness_script(const ec_public& key1, const ec_public& key2, const ec_public& key3); + chain::script get_redeem_script(const ec_public& key1, const ec_public& key2, const ec_public& key3); + + std::string script_to_address(const chain::script &script, uint8_t version = payment_address::mainnet_p2sh); + std::string script_to_address(const std::string &script, uint8_t version = payment_address::mainnet_p2sh); + //-------------------------------------------------------------------------------------------------------------- +#ifdef WITH_BITCOIN_CLIENT + uint64_t fetch_balance(const wallet::payment_address &address); + //-------------------------------------------------------------------------------------------------------------- + + void fetch_history(const wallet::payment_address &address, CJSON& Result); + void fetch_header(uint32_t height, CJSON& Result); + void fetch_header(const hash_digest& hash, CJSON& Result); + + void send_tx(const chain::transaction& tx, CJSON& Result); + void send_tx(const std::string& hex, CJSON& Result); + + //-------------------------------------------------------------------------------------------------------------- + + //-- CBalance -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + class CBalance { + private: + + uint32_t m_Transfers; + uint32_t m_Height; + + uint32_t m_Received; + uint32_t m_Spent; + + CDateTime m_TimeStamp; + + protected: + + CJSON m_History; + CJSON m_Header; + + public: + + CBalance() { + m_Transfers = 0; + m_Height = 0; + + m_Received = 0; + m_Spent = 0; + + m_TimeStamp = 0; + } + + void Clear(); + + void Fetch(const CString &Address); + + const CJSON &History() const { return m_History; }; + const CJSON &Header() const { return m_Header; }; + + uint32_t Transfers() const { return m_Transfers; } + + uint32_t Height() const { return m_Height; } + CDateTime TimeStamp() const { return m_TimeStamp; } + + uint32_t Received() const { return m_Received; } + uint32_t Spent() const { return m_Spent; } + + uint32_t Balance() const { + return m_Received - m_Spent; + } + + }; +#endif + + //-------------------------------------------------------------------------------------------------------------- + + //-- CWitness -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + class CWitness { + private: + + std::vector m_secrets; + std::vector m_keys; + + uint8_t m_signatures; + + hash_digest m_hash; + + chain::script m_script; + chain::script m_embedded; + + void bind_script(); + void bind_embedded(); + + public: + + CWitness(); + explicit CWitness(uint8_t signatures); + + CWitness(const ec_private& key1, const ec_private& key2); + CWitness(const ec_private& key1, const ec_private& key2, const ec_private& key3); + CWitness(const ec_private& key1, const ec_private& key2, const ec_public& key3); + CWitness(const ec_private& key1, const ec_private& key2, const ec_private& key3, const ec_public& key4); + CWitness(const ec_public& key1, const ec_public& key2, const ec_public& key3); + + void make(const ec_private& key1, const ec_private& key2); + void make(const ec_private& key1, const ec_private& key2, const ec_private& key3); + void make(const ec_private& key1, const ec_private& key2, const ec_public& key3); + void make(const ec_private& key1, const ec_private& key2, const ec_private& key3, const ec_public& key4); + void make(const ec_public& key1, const ec_public& key2, const ec_public& key3); + + void bind(); + + const hash_digest& hash() const; + + const chain::script& script() const { return m_script; }; + const chain::script& embedded() const {return m_embedded; }; + + payment_address to_address(uint8_t version = payment_address::mainnet_p2sh) const; + + const std::vector &secrets() const { return m_secrets; }; + const std::vector &keys() const { return m_keys; }; + + }; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CRecipient ------------------------------------------------------------------------------------------------ + + //-------------------------------------------------------------------------------------------------------------- + + typedef struct CRecipient { + + CString Address {}; + uint64_t Amount; + + CRecipient() { + Amount = 0; + } + + CRecipient(const CRecipient &Other) { + if (this != &Other) { + this->Address = Other.Address; + this->Amount = Other.Amount; + } + }; + + CRecipient(const CString &Address, uint64_t Amount) { + this->Address = Address; + this->Amount = Amount; + } + + CRecipient &operator=(const CRecipient &Other) { + if (this != &Other) { + this->Address = Other.Address; + this->Amount = Other.Amount; + } + return *this; + }; + + inline bool operator!=(const CRecipient &Value) { return Address != Value.Address; }; + + inline bool operator==(const CRecipient &Value) { return Address == Value.Address; }; + + } CRecipient, *PRecipient; + //-------------------------------------------------------------------------------------------------------------- + + typedef TList CRecipients; + + //-------------------------------------------------------------------------------------------------------------- + + //-- raw ------------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + /** + * Serialization helper to convert between a byte stream and data_chunk. + */ + class raw + { + public: + + /** + * Default constructor. + */ + raw(); + + /** + * Initialization constructor. + * @param[in] text The value to initialize with. + */ + raw(const std::string& text); + + /** + * Initialization constructor. + * @param[in] value The value to initialize with. + */ + raw(data_chunk value); + + /** + * Copy constructor. + * @param[in] other The object to copy into self on construct. + */ + raw(const raw& other); + + /** + * Overload cast to internal type. + * @return This object's value cast to internal type. + */ + operator const data_chunk&() const; + + /** + * Overload cast to generic data reference. + * @return This object's value cast to a generic data reference. + */ + operator data_slice() const; + + /** + * Overload stream in. Throws if input is invalid. + * @param[in] input The input stream to read the value from. + * @param[out] argument The object to receive the read value. + * @return The input stream reference. + */ + friend std::istream& operator>>(std::istream& input, + raw& argument); + + /** + * Overload stream out. + * @param[in] output The output stream to write the value to. + * @param[out] argument The object from which to obtain the value. + * @return The output stream reference. + */ + friend std::ostream& operator<<(std::ostream& output, + const raw& argument); + + private: + + /** + * The state of this object's raw data. + */ + data_chunk value_; + }; + + class wrapper + { + public: + + /** + * Default constructor. + */ + wrapper(); + + /** + * Initialization constructor. + * + * @param[in] wrapped The value to initialize with. + */ + wrapper(const std::string& wrapped); + + /** + * Initialization constructor. + * @param[in] wrapped The wrapped value to initialize with. + */ + wrapper(const data_chunk& wrapped); + + /** + * Initialization constructor. + * @param[in] wrapped The wrapped value to initialize with. + */ + wrapper(const wallet::wrapped_data& wrapped); + + /** + * Initialization constructor. + * @param[in] address The payment address to initialize with. + */ + wrapper(const wallet::payment_address& address); + + /** + * Initialization constructor. + * @param[in] version The version for the new wrapped value. + * @param[in] payload The payload for the new wrapped value. + */ + wrapper(uint8_t version, const data_chunk& payload); + + /** + * Copy constructor. + * @param[in] other The object to copy into self on construct. + */ + wrapper(const wrapper& other); + + /** + * Serialize the wrapper to bytes according to the wire protocol. + * @return The byte serialized copy of the wrapper. + */ + const data_chunk to_data() const; + + /** + * Overload cast to internal type. + * @return This object's value cast to internal type. + */ + operator const wallet::wrapped_data&() const; + + /** + * Overload stream in. Throws if input is invalid. + * @param[in] input The input stream to read the value from. + * @param[out] argument The object to receive the read value. + * @return The input stream reference. + */ + friend std::istream& operator>>(std::istream& input, + wrapper& argument); + + /** + * Overload stream out. + * @param[in] output The output stream to write the value to. + * @param[out] argument The object from which to obtain the value. + * @return The output stream reference. + */ + friend std::ostream& operator<<(std::ostream& output, + const wrapper& argument); + + private: + + /** + * The state of this object's data. + */ + wallet::wrapped_data value_; + }; + + /** + * Serialization helper to convert between string and message_signature. + */ + class signature + { + public: + + /** + * Default constructor. + */ + signature(); + + /** + * Initialization constructor. + * @param[in] hexcode The value to initialize with. + */ + signature(const std::string& hexcode); + + /** + * Initialization constructor. + * @param[in] value The value to initialize with. + */ + signature(const wallet::message_signature& value); + + /** + * Copy constructor. + * @param[in] other The object to copy into self on construct. + */ + signature(const signature& other); + + /** + * Overload cast to internal type. + * @return This object's value cast to internal type. + */ + operator wallet::message_signature&(); + + /** + * Overload cast to internal type. + * @return This object's value cast to internal type. + */ + operator const wallet::message_signature&() const; + + /// Serializer. + std::string encoded() const; + + /** + * Overload stream in. If input is invalid sets no bytes in argument. + * @param[in] input The input stream to read the value from. + * @param[out] argument The object to receive the read value. + * @return The input stream reference. + */ + friend std::istream& operator>>(std::istream& input, signature& argument); + + /** + * Overload stream out. + * @param[in] output The output stream to write the value to. + * @param[out] argument The object from which to obtain the value. + * @return The output stream reference. + */ + friend std::ostream& operator<<(std::ostream& output, const signature& argument); + + private: + + /** + * The state of this object. + */ + wallet::message_signature value_; + }; + + /** + * Serialization helper to convert between base16/raw script and script_type. + */ + class script + { + public: + + /** + * Default constructor. + */ + script(); + + /** + * Initialization constructor. + * @param[in] mnemonic The value to initialize with. + */ + script(const std::string& mnemonic); + + /** + * Initialization constructor. + * @param[in] value The value to initialize with. + */ + script(const chain::script& value); + + /** + * Initialization constructor. + * @param[in] value The value to initialize with. + */ + script(const data_chunk& value); + + /** + * Initialization constructor. + * @param[in] tokens The mnemonic tokens to initialize with. + */ + script(const std::vector& tokens); + + /** + * Copy constructor. + * @param[in] other The object to copy into self on construct. + */ + script(const script& other); + + /** + * Serialize the script to bytes according to the wire protocol. + * @return The byte serialized copy of the script. + */ + const bc::data_chunk to_data() const; + + /** + * Return a pretty-printed copy of the script. + * @return A mnemonic-printed copy of the internal script. + */ + const std::string to_string() const; + + /** + * Overload cast to internal type. + * @return This object's value cast to internal type. + */ + operator const chain::script&() const; + + /** + * Overload stream in. Throws if input is invalid. + * @param[in] input The input stream to read the value from. + * @param[out] argument The object to receive the read value. + * @return The input stream reference. + */ + friend std::istream& operator>>(std::istream& input, + script& argument); + + /** + * Overload stream out. + * @param[in] output The output stream to write the value to. + * @param[out] argument The object from which to obtain the value. + * @return The output stream reference. + */ + friend std::ostream& operator<<(std::ostream& output, + const script& argument); + + private: + + /** + * The state of this object. + */ + chain::script value_; + }; + + } // namespace Bitcoin + +} // namespace Apostol + +using namespace Apostol::Bitcoin; +using namespace Apostol::Bech32; +using namespace Apostol::SegWit; +} +#endif //APOSTOL_BITCOIN_HPP diff --git a/src/app/Deal.cpp b/src/app/Deal.cpp new file mode 100644 index 0000000..4e678cf --- /dev/null +++ b/src/app/Deal.cpp @@ -0,0 +1,532 @@ +/*++ + +Program name: + + BitDeals + +Module Name: + + Deal.cpp + +Notices: + + Apostol Core (Deal) + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#include "Core.hpp" +#include "Deal.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#ifdef __cplusplus +extern "C++" { +#endif // __cplusplus + +namespace Apostol { + + namespace Deal { + + CString UTCFormat(const CString& Value) { + return Value.SubString(0, 19) << " UTC"; + } + //-------------------------------------------------------------------------------------------------------------- + + CString BTCFormat(const CString& Value) { + if (Value.Find(BitcoinConfig.Symbol) == CString::npos) + return Value.TrimRight('0') << " " << BitcoinConfig.Symbol; + return Value.TrimRight('0'); + } + //-------------------------------------------------------------------------------------------------------------- + + double BTCToDouble(const CString &Value) { + const size_t Pos = Value.Find(BitcoinConfig.Symbol); + if (Pos == CString::npos) + return StrToDouble(Value.c_str()); + return StrToDouble(Value.SubString(0, Pos).TrimRight().c_str()); + } + //-------------------------------------------------------------------------------------------------------------- + + CString DoubleToBTC(const double &Value, const CString &Format) { + TCHAR Buffer[25] = {0}; + FloatToStr(Value, Buffer, sizeof(Buffer), Format.c_str()); + return Buffer; + } + //-------------------------------------------------------------------------------------------------------------- + + CDateTime StringToDate(const CString &Value, const CString &Format) { + return StrToDateTimeDef(Value.c_str(), 0, Format.c_str()); + } + //-------------------------------------------------------------------------------------------------------------- + + CString DateToString(const CDateTime &Value, const CString &Format) { + TCHAR Buffer[25] = {0}; + DateTimeToStr(Value, Buffer, sizeof(Buffer), Format.c_str()); + return Buffer; + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- DealData -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + CString DealData::GetStringData() const { + CString Data; + + Data.Format("Type: %d;", (int) Type); + Data.Format("URL: %s;", At.c_str()); + Data.Format("Date: %s;", Date.c_str()); + Data.Format("Seller: %s;", Seller.Address.c_str()); + Data.Format("Customer: %s;", Customer.Address.c_str()); + Data.Format("Until: %s;", Payment.Until.c_str()); + Data.Format("Sum: %s;", Payment.Sum.c_str()); + Data.Format("LeaveBefore: %s", FeedBack.LeaveBefore.c_str()); + + return Data; + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- CRating --------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + void CRating::Parse(const CString &Value) { + size_t Pos = 0; + + TCHAR ch = Value.at(Pos++); + if (Value.Size() < 4 || !IsNumeral(ch)) + throw ExceptionFrm("Invalid rating value."); + + CString Item; + while (!IsCtl(ch)) { + if (ch == '+' && !Item.IsEmpty()) { + Count = StrToIntDef(Item.c_str(), 0); + Count++; + Item.Clear(); + } else if (ch == ',' && !Item.IsEmpty()) { + Count = StrToIntDef(Item.c_str(), 0); + Item.Clear(); + } else if (ch == '%' && !Item.IsEmpty()) { + Positive = StrToIntDef(Item.c_str(), 0); + Item.Clear(); + } else { + if (!(ch == ' ' || ch == ',')) { + if (!IsNumeral(ch)) + throw ExceptionFrm("Invalid rating value."); + Item << ch; + } + } + + ch = Value.at(Pos++); + } + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- CDeal ----------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + CDeal::CDeal() { + m_Data.Order = doCreate; + m_Data.FeedBack.Status = fsPositive; + } + //-------------------------------------------------------------------------------------------------------------- + + CDeal::CDeal(const YAML::Node &node) : CDeal() { + Parse(node); + } + //-------------------------------------------------------------------------------------------------------------- + + CDealOrder CDeal::StringToOrder(const CString &Value) { + const CString &S = Value.Lower(); + + if (S == "create") { + return doCreate; + } else if (S == "created") { + return doCreated; + } else if (S == "pay") { + return doPay; + } else if (S == "paid") { + return doPaid; + } else if (S == "complete") { + return doComplete; + } else if (S == "completed") { + return doCompleted; + } else if (S == "cancel") { + return doCancel; + } else if (S == "canceled") { + return doCanceled; + } else if (S == "execute") { + return doExecute; + } else if (S == "executed") { + return doExecuted; + } else if (S == "delete") { + return doDelete; + } else if (S == "deleted") { + return doDeleted; + } else if (S == "fail") { + return doFail; + } else if (S == "failed") { + return doFailed; + } else if (S == "feedback") { + return doFeedback; + } else { + throw ExceptionFrm("Invalid deal order value: \"%s\".", Value.c_str()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::OrderToString(CDealOrder Order) { + switch (Order) { + case doCreate: + return "Create"; + case doCreated: + return "Created"; + case doPay: + return "Pay"; + case doPaid: + return "Paid"; + case doComplete: + return "Complete"; + case doCompleted: + return "Completed"; + case doCancel: + return "Cancel"; + case doCanceled: + return "Canceled"; + case doExecute: + return "Execute"; + case doExecuted: + return "Executed"; + case doDelete: + return "Delete"; + case doDeleted: + return "Deleted"; + case doFail: + return "Fail"; + case doFailed: + return "Failed"; + case doFeedback: + return "Feedback"; + default: + return "Unknown"; + } + } + //-------------------------------------------------------------------------------------------------------------- + + CDealType CDeal::StringToType(const CString &Value) { + const auto &status = Value.Lower(); + + if (status.Find("prepaid") != CString::npos || status.Find("prepayment") != CString::npos) { + return dtPrepaid; + } else if (status.Find("postpaid") != CString::npos || status.Find("postpayment") != CString::npos) { + return dtPostpaid; + } else { + throw ExceptionFrm(R"(Invalid order type value: "%s".)", status.c_str()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::TypeToString(CDealType Type) { + switch (Type) { + case dtPrepaid: + return "Prepaid"; + case dtPostpaid: + return "Postpaid"; + default: + return "Unknown"; + } + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::TypeCodeToString(const CString &Code) { + if (Code == "prepaid.deal") { + return CString("Prepaid"); + } else if (Code == "postpaid.deal") { + return CString("Postpaid"); + } else { + throw ExceptionFrm(R"(Invalid type code value: "%s".)", Code.c_str()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + CFeedBackStatus CDeal::StringToFeedBackStatus(const CString &Value) { + const CString &Status = Value.Lower(); + + if (Status == "negative") { + return fsNegative; + } else if (Status == "neutral") { + return fsNeutral; + } else if (Status == "positive") { + return fsPositive; + } else { + throw ExceptionFrm(R"(Invalid feedback status value: "%s".)", Status.c_str()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::FeedBackStatusToString(CFeedBackStatus Status) { + switch (Status) { + case fsNegative: + return "Negative"; + case fsNeutral: + return "Neutral"; + case fsPositive: + return "Positive"; + default: + return "Unknown"; + } + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::GetHashData() { + return m_Data.GetStringData(); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string CDeal::get_hash() { + const std::string str(GetHashData().c_str()); + return sha256(str); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string CDeal::get_code() { + const std::string value(GetHashData().c_str()); + const auto& hex = string_to_hex(value); + const config::base16 data(hex); + const auto& hash = ripemd160_hash(sha256_hash(data)); + return encode_base16(hash); + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::GetCode() { + return get_code(); + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::GetHash() { + return sha256(GetHashData()); + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::ec_public CDeal::to_public_ek(uint8_t version) { + const auto& hash = get_hash(); + const std::string seed(hash.substr(0, 48)); + const std::string salt(hash.substr(48, 16)); + const auto& token = Bitcoin::token_new(hash, base16(salt)); + const auto& key = Bitcoin::ek_public(token, base16(seed), version); + return ek_public_to_ec(hash, key); + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::payment_address CDeal::to_address_ek(uint8_t version) { + const auto& hash = get_hash(); + const std::string seed(hash.substr(0, 48)); + const std::string salt(hash.substr(48, 16)); + const auto& token = Bitcoin::token_new(hash, base16(salt)); + return Bitcoin::ek_address(token, base16(seed), version); + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::ec_public CDeal::to_public_hd(uint64_t prefixes) { + const auto& hash = get_hash(); + const auto& key = hd_new(base16(hash), prefixes); + const auto& public_key = key.to_public(); + return public_key.point(); + } + //-------------------------------------------------------------------------------------------------------------- + + wallet::payment_address CDeal::to_address_hd(uint64_t prefixes) { + const auto& key = to_public_hd(prefixes); + return key.to_payment_address(); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string CDeal::get_payment_ek(const std::string &key1, const std::string &key2, std::string &key3, + uint8_t version_key, uint8_t version_script) { + + CWitness Witness(ec_public(key1), ec_public(key2), key3.empty() ? to_public_ek(version_key) : ec_public(key3)); + + if (key3.empty()) + key3 = Witness.keys()[2].encoded(); + + const auto& address = Witness.to_address(version_script); + + return address.encoded(); + } + //-------------------------------------------------------------------------------------------------------------- + + std::string CDeal::get_payment_hd(const std::string &key1, const std::string &key2, std::string &key3, + uint64_t version_key, uint8_t version_script) { + + CWitness Witness(ec_public(key1), ec_public(key2), key3.empty() ? to_public_hd(version_key) : ec_public(key3)); + + if (key3.empty()) + key3 = Witness.keys()[2].encoded(); + + const auto& address = Witness.to_address(version_script); + + return address.encoded(); + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::GetPaymentEK(const CString &Key1, const CString &Key2, CString &Key3, + uint8_t version_key, uint8_t version_script) { + + std::string key3(Key3); + + const auto& payment = get_payment_ek(Key1.c_str(), Key2.c_str(), key3, version_key, version_script); + + Key3 = key3; + + return payment; + } + //-------------------------------------------------------------------------------------------------------------- + + CString CDeal::GetPaymentHD(const CString &Key1, const CString &Key2, CString &Key3, uint64_t version_key, + uint8_t version_script) { + + std::string key3(Key3); + + const auto& payment = get_payment_hd(Key1.c_str(), Key2.c_str(), key3, version_key, version_script); + + Key3 = key3; + + return payment; + } + //-------------------------------------------------------------------------------------------------------------- + + void CDeal::Parse(const YAML::Node &node) { + + const auto& deal = node["deal"]; + + if (deal) { + m_Data.Order = StringToOrder(deal["order"].as()); + m_Data.Type = StringToType(deal["type"].as()); + + m_Data.At = deal["at"].as(); + m_Data.Date = UTCFormat(deal["date"].as()); + + const auto& seller = deal["seller"]; + m_Data.Seller.Address = seller["address"].as(); + + if (!valid_address(m_Data.Seller.Address)) + throw ExceptionFrm("Invalid seller address: %s.", m_Data.Seller.Address.c_str()); + + if (seller["rating"]) + m_Data.Seller.Rating = seller["rating"].as(); + + if (seller["signature"]) + m_Data.Seller.Signature = seller["signature"].as(); + + const auto& customer = deal["customer"]; + m_Data.Customer.Address = customer["address"].as(); + + if (!valid_address(m_Data.Customer.Address)) + throw ExceptionFrm("Invalid customer address: %s.", m_Data.Customer.Address.c_str()); + + if (customer["rating"]) + m_Data.Customer.Rating = customer["rating"].as(); + + if (customer["signature"]) + m_Data.Customer.Signature = customer["signature"].as(); + + const auto& payment = deal["payment"]; + if (payment) { + if (payment["address"]) + m_Data.Payment.Address = payment["address"].as(); + + if (payment["until"]) + m_Data.Payment.Until = UTCFormat(payment["until"].as()); + + m_Data.Payment.Sum = BTCFormat(payment["sum"].as()); + } + + const auto& feedback = deal["feedback"]; + if (feedback) { + if (feedback["leave-before"]) + m_Data.FeedBack.LeaveBefore = UTCFormat(feedback["leave-before"].as()); + + if (feedback["status"]) + m_Data.FeedBack.Status = StringToFeedBackStatus(feedback["status"].as()); + + if (feedback["refund"]) + m_Data.FeedBack.Refund = feedback["refund"].as(); + + if (feedback["comments"]) + m_Data.FeedBack.Comments = feedback["comments"].as(); + } + + const auto& transaction = deal["transaction"]; + if (transaction) { + m_Data.Transaction.Hex = transaction["hex"].as(); + } + + const auto& error = deal["error"]; + if (error) { + m_Data.Error.Message = error["message"].as(); + } + + const CDateTime Date = StringToDate(m_Data.Date); + + if (m_Data.Payment.Until.IsEmpty()) + m_Data.Payment.Until = DateToString(Date + (CDateTime) 3600 / SecsPerDay); // 60 min + + if (m_Data.FeedBack.LeaveBefore.IsEmpty()) + m_Data.FeedBack.LeaveBefore = DateToString(Date + 1); + + m_Data.Code = GetCode(); + } else + throw ExceptionFrm("Invalid YAML format: Need node \"deal\"."); + } + //-------------------------------------------------------------------------------------------------------------- + + void CDeal::SetOrder(YAML::Node &Node, CDealOrder Order) { + Node["deal"]["order"] = CDeal::OrderToString(Order).c_str(); + } + //-------------------------------------------------------------------------------------------------------------- + + void CDeal::AddFeedback(YAML::Node &Node, CFeedBackStatus Status, const CString &Comments) { + YAML::Node Deal = Node["deal"]; + YAML::Node Feedback = Deal["feedback"]; + Feedback["status"] = CDeal::FeedBackStatusToString(Status).c_str(); + if (!Comments.IsEmpty()) + Feedback["comments"] = Comments.c_str(); + } + //-------------------------------------------------------------------------------------------------------------- + + void CDeal::AddTransaction(YAML::Node &Node, const transaction &tx) { + + YAML::Node Deal = Node["deal"]; + YAML::Node Transaction = Deal["transaction"]; + + const auto& hex = encode_base16(tx.to_data(true, true)); + + Transaction["hex"] = hex; + + DebugMessage("tx: %s\n", hex.c_str()); + } + //-------------------------------------------------------------------------------------------------------------- + + void CDeal::AddError(YAML::Node &Node, const CString &Message) { + YAML::Node Deal = Node["deal"]; + YAML::Node Error = Deal["error"]; + Error["message"] = Message.c_str(); + } + + } +} + +using namespace Apostol::Deal; +#ifdef __cplusplus +} +#endif // __cplusplus diff --git a/src/app/Deal.hpp b/src/app/Deal.hpp new file mode 100644 index 0000000..a1aa776 --- /dev/null +++ b/src/app/Deal.hpp @@ -0,0 +1,308 @@ +/*++ + +Program name: + + BitDeals + +Module Name: + + Deal.hpp + +Notices: + + Apostol Core (Deal) + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_DEAL_HPP +#define APOSTOL_DEAL_HPP + +#include "yaml-cpp/yaml.h" +//---------------------------------------------------------------------------------------------------------------------- + +#ifdef __cplusplus +extern "C++" { +#endif // __cplusplus + +namespace Apostol { + + namespace Deal { + + CString UTCFormat(const CString& Value); + CString BTCFormat(const CString& Value); + //-------------------------------------------------------------------------------------------------------------- + + double BTCToDouble(const CString &Value); + CString DoubleToBTC(const double &Value, const CString &Format = _T("%f BTC")); + + CDateTime StringToDate(const CString &Value, const CString &Format = _T("%04d-%02d-%02d %02d:%02d:%02d")); + CString DateToString(const CDateTime &Value, const CString &Format = _T("%Y-%m-%d %H:%M:%S UTC")); + + //-------------------------------------------------------------------------------------------------------------- + + //-- CRating --------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + typedef struct CRating { + + int Count; + int Positive; + + CRating(): Count(0), Positive(0) { + + } + + CRating(const CRating &Rating): CRating() { + Assign(Rating); + } + + void Assign(const CRating& Rating) { + this->Count = Rating.Count; + this->Positive = Rating.Positive; + }; + + int Compare(const CRating& Rating) const { + if (this->Count == Rating.Count) + return 0; + if (this->Count > Rating.Count) + return 1; + return -1; + }; + + void ToString(CString &String) const { + String.Clear(); + + int q = 1; + int c = Count; + + while ((c = div(c, 10).quot > 10)) + q++; + + c = c * (int) pow(10, q); + + if (Count == c) + String.Format("%d, %d%%", c, Positive); + else + String.Format("%d+, %d%%", c, Positive); + } + + void Parse(const CString &Value); + + CString GetString() const { + CString S; + ToString(S); + return S; + }; + + bool operator== (const CRating& R) const { + return Compare(R) == 0; + }; + + bool operator!= (const CRating& R) const { + return Compare(R) != 0; + }; + + bool operator< (const CRating& R) const { + return Compare(R) < 0; + }; + + bool operator<= (const CRating& R) const { + return Compare(R) <= 0; + }; + + bool operator> (const CRating& R) const { + return Compare(R) > 0; + }; + + bool operator>= (const CRating& R) const { + return Compare(R) >= 0; + }; + + CRating& operator= (const CRating& R) { + if (this != &R) { + Assign(R); + } + return *this; + }; + + CRating& operator= (const CString& S) { + Parse(S); + return *this; + }; + + CRating& operator<< (const CString& S) { + Parse(S); + return *this; + }; + + friend CString& operator>> (const CRating &LS, CString &RS) { + LS.ToString(RS); + return RS; + } + + } CRating; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CDealData ------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + enum CDealType { dtPrepaid = 0, dtPostpaid }; + //-------------------------------------------------------------------------------------------------------------- + + enum CFeedBackStatus { fsNegative = -1, fsNeutral = 0, fsPositive = 1 }; + //-------------------------------------------------------------------------------------------------------------- + + enum CDealOrder { doCreate = 0, doCreated, doPay, doPaid, doComplete, doCompleted, doCancel, doCanceled, + doExecute, doExecuted, doDelete, doDeleted, doFail, doFailed, doFeedback }; + //-------------------------------------------------------------------------------------------------------------- + + typedef struct DealData { + + CDealOrder Order = doCreate; + CDealType Type = dtPrepaid; + + // Hidden value + CString Code {}; + + CString At {}; + CString Date {}; + + struct seller { + CString Address; + CRating Rating; + CString Signature; + } Seller; + + struct customer { + CString Address; + CRating Rating; + CString Signature; + } Customer; + + struct payment { + CString Address {}; + CString Until {}; + CString Sum {}; + } Payment; + + struct feedback { + CString LeaveBefore {}; + CFeedBackStatus Status = fsNeutral; + CString Refund {}; + CString Comments {}; + } FeedBack; + + struct transaction { + CString Fee {}; + CString Key {}; + CString Hex {}; + } Transaction; + + struct error { + CString Message {}; + } Error; + + CString GetStringData() const; + + } CDealData, *PDealData; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CDeal ----------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + class CDeal { + private: + + CDealData m_Data; + + CString GetHashData(); + + protected: + + std::string get_hash(); + std::string get_code(); + + wallet::ec_public to_public_ek(uint8_t version = payment_address::mainnet_p2kh); + wallet::payment_address to_address_ek(uint8_t version = payment_address::mainnet_p2kh); + + wallet::ec_public to_public_hd(uint64_t prefixes = hd_private::mainnet); + wallet::payment_address to_address_hd(uint64_t prefixes = hd_private::mainnet); + + std::string get_payment_ek(const std::string &key1, const std::string &key2, std::string &key3, + uint8_t version_key = payment_address::mainnet_p2kh, + uint8_t version_script = payment_address::mainnet_p2sh); + + std::string get_payment_hd(const std::string &key1, const std::string &key2, std::string &key3, + uint64_t version_key = hd_private::mainnet, + uint8_t version_script = payment_address::mainnet_p2sh); + + public: + + CDeal(); + + explicit CDeal(const YAML::Node &node); + + void Parse(const YAML::Node &node); + + CString GetHash(); + CString GetCode(); + + CString GetPaymentEK(const CString &Key1, const CString &Key2, CString &Key3, + uint8_t version_key = payment_address::mainnet_p2kh, + uint8_t version_script = payment_address::mainnet_p2sh); + + CString GetPaymentHD(const CString &Key1, const CString &Key2, CString &Key3, + uint64_t version_key = hd_private::mainnet, + uint8_t version_script = payment_address::mainnet_p2sh); + + static CDealOrder StringToOrder(const CString &Value); + static CString OrderToString(CDealOrder Order); + + static CDealType StringToType(const CString &Value); + static CString TypeToString(CDealType Type); + static CString TypeCodeToString(const CString &Code); + + static CFeedBackStatus StringToFeedBackStatus(const CString &Value); + static CString FeedBackStatusToString(CFeedBackStatus Status); + + CDealOrder Order() const { return m_Data.Order; } + void Order(CDealOrder Value) { m_Data.Order = Value; } + + CDealData &Data() { return m_Data; } + const CDealData &Data() const { return m_Data; } + + static void SetOrder(YAML::Node &Node, CDealOrder Order); + + static void AddFeedback(YAML::Node &Node, CFeedBackStatus Status, const CString &Comments); + static void AddTransaction(YAML::Node &Node, const transaction &tx); + static void AddError(YAML::Node &Node, const CString &Message); + + CDeal& operator<< (const YAML::Node &node) { + Parse(node); + return *this; + } + + }; + //-------------------------------------------------------------------------------------------------------------- + + } +} + +using namespace Apostol::Deal; +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif //APOSTOL_DEAL_HPP diff --git a/src/app/Header.hpp b/src/app/Header.hpp new file mode 100644 index 0000000..31c70c9 --- /dev/null +++ b/src/app/Header.hpp @@ -0,0 +1,46 @@ +/*++ + +Program name: + + ship-safety + +Module Name: + + Header.hpp + +Notices: + + Ship Safety Service + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_HEADER_HPP +#define APOSTOL_HEADER_HPP +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +//---------------------------------------------------------------------------------------------------------------------- + +//using namespace std; +//---------------------------------------------------------------------------------------------------------------------- + +#include "delphi.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#include "PGP.hpp" +#include "Bitcoin.hpp" +#include "Deal.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#include "Core.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#endif //APOSTOL_HEADER_HPP diff --git a/src/app/PGP.cpp b/src/app/PGP.cpp new file mode 100644 index 0000000..dc29408 --- /dev/null +++ b/src/app/PGP.cpp @@ -0,0 +1,1050 @@ +/*++ + +Module Name: + + PGP.cpp + +Notices: + + PGP library + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#include "Core.hpp" +#include "PGP.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#include +//---------------------------------------------------------------------------------------------------------------------- + +extern "C++" { + +namespace Apostol { + + namespace PGP { + + const std::map Public_Key_Type = { + std::make_pair(Packet::SECRET_KEY, "sec"), + std::make_pair(Packet::PUBLIC_KEY, "pub"), + std::make_pair(Packet::SECRET_SUBKEY, "ssb"), + std::make_pair(Packet::PUBLIC_SUBKEY, "sub"), + }; + + //-------------------------------------------------------------------------------------------------------------- + + //-- Key::Key -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + Key::Key(): OpenPGP::Key() { + + } + //-------------------------------------------------------------------------------------------------------------- + + Key::Key(const OpenPGP::PGP ©): OpenPGP::Key(copy) { + + } + //-------------------------------------------------------------------------------------------------------------- + + Key::Key(const Key ©): OpenPGP::Key(copy) { + + } + //-------------------------------------------------------------------------------------------------------------- + + Key::Key(const std::string &data): OpenPGP::Key(data) { + + } + //-------------------------------------------------------------------------------------------------------------- + + Key::Key(std::istream &stream): OpenPGP::Key(stream) { + + } + //-------------------------------------------------------------------------------------------------------------- + + std::string Key::ListKeys(std::size_t indents, std::size_t indent_size) const { + if (!meaningful()) { + return "Key data not meaningful."; + } + + const std::string indent(indents * indent_size, ' '); + + // print Key and User packets + std::stringstream out; + for (Packet::Tag::Ptr const & p : packets) { + // primary key/subkey + if (Packet::is_key_packet(p -> get_tag())) { + const Packet::Key::Ptr key = std::static_pointer_cast (p); + + if (Packet::is_subkey(p -> get_tag())) { + out << "\n"; + } + + out << indent << Apostol::PGP::Public_Key_Type.at(p -> get_tag()) << " " << std::setfill(' ') << std::setw(4) << std::to_string(bitsize(key -> get_mpi()[0])) + << indent << PKA::SHORT.at(key -> get_pka()) << "/" + << indent << hexlify(key -> get_keyid()) << " " + << indent << show_date(key -> get_time()); + } + // User ID + else if (p -> get_tag() == Packet::USER_ID) { + out << "\n" + << indent << "uid " << std::static_pointer_cast (p) -> get_contents(); + } + // User Attribute + else if (p -> get_tag() == Packet::USER_ATTRIBUTE) { + for(Subpacket::Tag17::Sub::Ptr const & s : std::static_pointer_cast (p) -> get_attributes()) { + // since only subpacket type 1 is defined + out << "\n" + << indent << "att att [jpeg image of size " << std::static_pointer_cast (s) -> get_image().size() << "]"; + } + } + // Signature + else if (p -> get_tag() == Packet::SIGNATURE) { + out << indent << "sig "; + + const Packet::Tag2::Ptr sig = std::static_pointer_cast (p); + if (Signature_Type::is_revocation(sig -> get_type())) { + out << "revok"; + } + else if (sig -> get_type() == Signature_Type::SUBKEY_BINDING_SIGNATURE) { + out << "sbind"; + } + else{ + out << " sig "; + } + + const std::array times = sig -> get_times(); // {signature creation time, signature expiration time, key expiration time} + out << " " << hexlify(sig -> get_keyid()); + + // signature creation time (should always exist) + if (times[0]) { + out << " " << show_date(times[0]); + } + // else{ + // out << " " << std::setfill(' ') << std::setw(10); + // } + + // if the signature expires + if (times[1]) { + out << " " << show_date(times[1]); + } + else{ + out << " " << std::setfill(' ') << std::setw(10); + } + + // if the key expires + if (times[2]) { + out << " " << show_date(times[2]); + } + else{ + out << " " << std::setfill(' ') << std::setw(10); + } + } + else{} + + out << "\n"; + } + + return out.str(); + } + //-------------------------------------------------------------------------------------------------------------- + + void Key::ExportUID(CPGPUserIdList &List) const { + if (!meaningful()) { + return; + } + + for (Packet::Tag::Ptr const & p : packets) { + if (p->get_tag() == Packet::USER_ID) { + const auto& uid = std::static_pointer_cast (p)->get_contents(); + + List.Add(CPGPUserId()); + auto& UserId = List.Last(); + + int Index = 0; + bool Append; + for (char ch : uid) { + Append = false; + switch (ch) { + case '<': + Index = 1; + break; + case '>': + Index = 0; + break; + case '(': + Index = 2; + break; + case ')': + Index = 0; + break; + default: + Append = true; + break; + } + + if (Append) { + if (Index == 0) { + UserId.Name.Append(ch); + } else if (Index == 1) { + UserId.Mail.Append(ch); + } else { + UserId.Desc.Append(ch); + } + } + } + + UserId.Name = UserId.Name.TrimRight(); + } + } + } + + //-------------------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + bool TryCleartextSignature(const CString &Key, const CString &Pass, const CString &Hash, const CString &ClearText, + CString &SignText) { + + const std::string key(Key); + const std::string pass(Pass); + const std::string text(ClearText); + const std::string hash(Hash.IsEmpty() ? "SHA256" : Hash); + + if (OpenPGP::Hash::NUMBER.find(hash) == OpenPGP::Hash::NUMBER.end()) { + throw ExceptionFrm("PGP: Bad Hash Algorithm: %s.", hash.c_str()); + } + + const OpenPGP::Sign::Args SignArgs(OpenPGP::SecretKey(key), pass,4, OpenPGP::Hash::NUMBER.at(hash)); + const OpenPGP::CleartextSignature signature = OpenPGP::Sign::cleartext_signature(SignArgs, text); + + if (signature.meaningful()) { + SignText << signature.write(); + return true; + } + + return false; + } + //-------------------------------------------------------------------------------------------------------------- + + void CleartextSignature(const CString &Key, const CString &Pass, const CString &Hash, const CString &ClearText, + CString &SignText) { + if (!TryCleartextSignature(Key, Pass, Hash, ClearText, SignText)) + throw Delphi::Exception::Exception("PGP: Generated bad cleartext signature."); + } + //-------------------------------------------------------------------------------------------------------------- + +#ifdef USE_LIB_GCRYPT + static int GInstanceCount = 0; + + pthread_mutex_t GPGPCriticalSection; + CPGP *GPGP = nullptr; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPComponent --------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + inline void AddPGP() { + + if (GInstanceCount == 0) + pthread_mutex_init(&GPGPCriticalSection, nullptr); + + pthread_mutex_lock(&GPGPCriticalSection); + + try { + if (GInstanceCount == 0) { + GPGP = CPGP::CreatePGP(); + } + + GInstanceCount++; + } catch (...) { + } + + pthread_mutex_unlock(&GPGPCriticalSection); + }; + //-------------------------------------------------------------------------------------------------------------- + + inline void RemovePGP() { + + pthread_mutex_lock(&GPGPCriticalSection); + + try { + GInstanceCount--; + + if (GInstanceCount == 0) + { + CPGP::DeletePGP(); + GPGP = nullptr; + } + } catch (...) { + } + + pthread_mutex_unlock(&GPGPCriticalSection); + + if (GInstanceCount == 0) + pthread_mutex_destroy(&GPGPCriticalSection); + }; + //-------------------------------------------------------------------------------------------------------------- + + CPGPComponent::CPGPComponent() { + AddPGP(); + }; + //-------------------------------------------------------------------------------------------------------------- + + CPGPComponent::~CPGPComponent() { + RemovePGP(); + }; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGP ------------------------------------------------------------------------------------------------------ + + //-------------------------------------------------------------------------------------------------------------- + + CPGP::CPGP(): CObject() { + m_LastError = GPG_ERR_NO_ERROR; + m_OnVerbose = nullptr; + + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + Initialize(); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGP::Initialize() { + /* Version check should be the very first call because it + makes sure that important subsystems are initialized. */ + if (!gcry_check_version(GCRYPT_VERSION)) + { + throw EPGPError("Error: libgcrypt version mismatch"); + } + + /* We don't want to see any warnings, e.g. because we have not yet + parsed program options which might be used to suppress such + warnings. */ + //gcry_control(GCRYCTL_SUSPEND_SECMEM_WARN); + + /* Allocate a pool of 16k secure memory. This makes the secure memory + available and also drops privileges where needed. Note that by + using functions like gcry_xmalloc_secure and gcry_mpi_snew Libgcrypt + may expand the secure memory pool with memory which lacks the + property of not being swapped out to disk. */ + //gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + + /* It is now okay to let Libgcrypt complain when there was/is + a problem with the secure memory. */ + //gcry_control(GCRYCTL_RESUME_SECMEM_WARN); + + /* Disable secure memory. */ + gcry_control(GCRYCTL_DISABLE_SECMEM, 0); + + /* No valuable keys are create, so we can speed up our RNG. */ + gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM, 0); + + /* Tell Libgcrypt that initialization has completed. */ + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGP::CheckForCipherError(gcry_error_t error) { + int ignore[] = {0}; + return CheckForCipherError(error, ignore, 0); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGP::CheckForCipherError(gcry_error_t err, const int *ignore, int count) { + if (err != GPG_ERR_NO_ERROR) { + m_LastError = err; + for (int i = 0; i < count; ++i) + if (m_LastError == ignore[i]) + return true; + RaiseCipherError(m_LastError); + } + return false; + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGP::RaiseCipherError(gcry_error_t err) { + throw EPGPError("Error: %s/%s", gcry_strsource(err), gcry_strerror(err)); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGP::Verbose(LPCSTR AFormat, ...) { + va_list args; + va_start(args, AFormat); + DoVerbose(AFormat, args); + va_end(args); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGP::Verbose(LPCSTR AFormat, va_list args) { + DoVerbose(AFormat, args); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGP::DoVerbose(LPCTSTR AFormat, va_list args) { + if (m_OnVerbose != nullptr) { + m_OnVerbose(this, AFormat, args); + } + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPCipher ------------------------------------------------------------------------------------------------ + + //-------------------------------------------------------------------------------------------------------------- + + CPGPCipher::CPGPCipher(): CPGPComponent() { + m_Handle = nullptr; + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Open(int algo, int mode, unsigned int flags) { + return !GPGP->CheckForCipherError(gcry_cipher_open(&m_Handle, algo, mode, flags)); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPCipher::Close() { + if (m_Handle != nullptr) { + gcry_cipher_close(m_Handle); + } + m_Handle = nullptr; + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::SetKey(const void *key, size_t keylen) { + return !GPGP->CheckForCipherError(gcry_cipher_setkey(m_Handle, key, keylen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::SetIV(const void *iv, size_t ivlen) { + return !GPGP->CheckForCipherError(gcry_cipher_setiv(m_Handle, iv, ivlen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::SetCTR(const void *ctr, size_t ctrlen) { + return !GPGP->CheckForCipherError(gcry_cipher_setctr(m_Handle, ctr, ctrlen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Reset() { + return !GPGP->CheckForCipherError(gcry_cipher_reset(m_Handle)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Authenticate(const void *abuf, size_t abuflen) { + return !GPGP->CheckForCipherError(gcry_cipher_authenticate(m_Handle, abuf, abuflen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::GetTag(void *tag, size_t taglen) { + return !GPGP->CheckForCipherError(gcry_cipher_gettag(m_Handle, tag, taglen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::CheckTag(gcry_cipher_hd_t h, const void *tag, size_t taglen) { + return !GPGP->CheckForCipherError(gcry_cipher_checktag(m_Handle, tag, taglen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Encrypt(unsigned char *out, size_t outsize, const unsigned char *in, size_t inlen) { + return !GPGP->CheckForCipherError(gcry_cipher_encrypt(m_Handle, out, outsize, in, inlen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Decrypt(unsigned char *out, size_t outsize, const unsigned char *in, size_t inlen) { + return !GPGP->CheckForCipherError(gcry_cipher_decrypt(m_Handle, out, outsize, in, inlen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Final() { + return !GPGP->CheckForCipherError(gcry_cipher_final(m_Handle)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Sync() { + return !GPGP->CheckForCipherError(gcry_cipher_sync(m_Handle)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Ctl(int cmd, void *buffer, size_t buflen) { + return !GPGP->CheckForCipherError(gcry_cipher_ctl(m_Handle, cmd, buffer, buflen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPCipher::Info(gcry_cipher_hd_t h, int what, void *buffer, size_t *nbytes) { + return !GPGP->CheckForCipherError(gcry_cipher_info(m_Handle, what, buffer, nbytes)); + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPAlgo -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + CPGPAlgo::CPGPAlgo(): CPGPComponent() { + + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPAlgo::Info(int algo, int what, void *buffer, size_t *nbytes) { + return !GPGP->CheckForCipherError(gcry_cipher_algo_info(algo, what, buffer, nbytes)); + } + //-------------------------------------------------------------------------------------------------------------- + + size_t CPGPAlgo::KeyLen(int algo) { + return !GPGP->CheckForCipherError(gcry_cipher_get_algo_keylen(algo)); + } + //-------------------------------------------------------------------------------------------------------------- + + size_t CPGPAlgo::BlkLen(int algo) { + return !GPGP->CheckForCipherError(gcry_cipher_get_algo_blklen(algo)); + } + //-------------------------------------------------------------------------------------------------------------- + + const char *CPGPAlgo::Name(int algo) { + return gcry_cipher_algo_name(algo); + } + //-------------------------------------------------------------------------------------------------------------- + + int CPGPAlgo::MapName(const char *name) { + return gcry_cipher_map_name(name); + } + //-------------------------------------------------------------------------------------------------------------- + + int CPGPAlgo::ModeFromOid(const char *string) { + return gcry_cipher_mode_from_oid(string); + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPMPI ---------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + CPGPMPI::CPGPMPI() { + m_Handle = nullptr; + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::New(unsigned int nbits) { + m_Handle = gcry_mpi_new(nbits); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::SNew(unsigned int nbits) { + m_Handle = gcry_mpi_snew(nbits); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::Copy(const gcry_mpi_t a) { + m_Handle = gcry_mpi_copy(a); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::Release() { + gcry_mpi_release(m_Handle); + } + //-------------------------------------------------------------------------------------------------------------- + + gcry_mpi_t CPGPMPI::Set(const gcry_mpi_t u) { + return gcry_mpi_set(m_Handle, u); + } + //-------------------------------------------------------------------------------------------------------------- + + gcry_mpi_t CPGPMPI::SetUI(unsigned long u) { + return gcry_mpi_set_ui(m_Handle, u); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::Swap(gcry_mpi_t b) { + gcry_mpi_swap(m_Handle, b); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::Snatch(const gcry_mpi_t u) { + gcry_mpi_snatch(m_Handle, u); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::Neg(gcry_mpi_t u) { + gcry_mpi_neg(m_Handle, u); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::Abs() { + gcry_mpi_abs(m_Handle); + } + //-------------------------------------------------------------------------------------------------------------- + + int CPGPMPI::Cmp(const gcry_mpi_t v) const { + return gcry_mpi_cmp(m_Handle, v); + } + //-------------------------------------------------------------------------------------------------------------- + + int CPGPMPI::CmpUI(unsigned long v) const { + return gcry_mpi_cmp_ui(m_Handle, v); + } + //-------------------------------------------------------------------------------------------------------------- + + int CPGPMPI::IsNeg() const { + return gcry_mpi_is_neg(m_Handle); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPMPI::Scan(enum gcry_mpi_format format, const unsigned char *buffer, size_t buflen, size_t *nscanned) { + return !GPGP->CheckForCipherError(gcry_mpi_scan(&m_Handle, format, buffer, buflen, nscanned)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPMPI::Print(enum gcry_mpi_format format, unsigned char *buffer, size_t buflen, size_t *nwritten) const { + return !GPGP->CheckForCipherError(gcry_mpi_print(format, buffer, buflen, nwritten, m_Handle)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPMPI::APrint(enum gcry_mpi_format format, unsigned char **buffer, size_t *nbytes) const { + return !GPGP->CheckForCipherError(gcry_mpi_aprint(format, buffer, nbytes, m_Handle)); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPMPI::Dump() { + gcry_mpi_dump(m_Handle); + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPSexp -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + CPGPSexp::CPGPSexp() { + m_Handle = nullptr; + } + //-------------------------------------------------------------------------------------------------------------- + + CPGPSexp::~CPGPSexp() { + Release(); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPSexp::New(const void *buffer, size_t length, int autodetect) { + return !GPGP->CheckForCipherError(gcry_sexp_new(&m_Handle, buffer, length, autodetect)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPSexp::Create(void *buffer, size_t length, int autodetect, void (*freefnc)(void *)) { + return !GPGP->CheckForCipherError(gcry_sexp_create(&m_Handle, buffer, length, autodetect, freefnc)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPSexp::Build(size_t *erroff, const char *format, ...) { + bool result; + + va_list argList; + va_start(argList, format); + result = !GPGP->CheckForCipherError(gcry_sexp_build_array(&m_Handle, erroff, format, (void **) &argList)); + va_end(argList); + + return result; + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPSexp::Sscan(size_t *erroff, const char *buffer, size_t length) { + return !GPGP->CheckForCipherError(gcry_sexp_sscan(&m_Handle, erroff, buffer, length)); + } + //-------------------------------------------------------------------------------------------------------------- + + size_t CPGPSexp::Sprint(int mode, char *buffer, size_t maxlength) { + return gcry_sexp_sprint(m_Handle, mode, buffer, maxlength); + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPSexp::Release() { + if (m_Handle != nullptr) + gcry_sexp_release(m_Handle); + m_Handle = nullptr; + } + //-------------------------------------------------------------------------------------------------------------- + + void CPGPSexp::Dump() { + gcry_sexp_dump(m_Handle); + } + //-------------------------------------------------------------------------------------------------------------- + + size_t CPGPSexp::CanonLen(const unsigned char *buffer, size_t length, size_t *erroff, gcry_error_t *errcode) { + return gcry_sexp_canon_len(buffer, length, erroff, errcode); + } + //-------------------------------------------------------------------------------------------------------------- + + gcry_sexp_t CPGPSexp::FindToken(const char *token, size_t toklen) { + return gcry_sexp_find_token(m_Handle, token, toklen); + } + //-------------------------------------------------------------------------------------------------------------- + + int CPGPSexp::Length() { + return gcry_sexp_length(m_Handle); + } + //-------------------------------------------------------------------------------------------------------------- + + gcry_sexp_t CPGPSexp::Nth(int number) { + return gcry_sexp_nth(m_Handle, number); + } + //-------------------------------------------------------------------------------------------------------------- + + gcry_sexp_t CPGPSexp::Car() { + return gcry_sexp_car(m_Handle); + } + //-------------------------------------------------------------------------------------------------------------- + + gcry_sexp_t CPGPSexp::Cdr() { + return gcry_sexp_cdr(m_Handle); + } + //-------------------------------------------------------------------------------------------------------------- + + const char *CPGPSexp::NthData(int number, size_t *datalen) { + return gcry_sexp_nth_data(m_Handle, number, datalen); + } + //-------------------------------------------------------------------------------------------------------------- + + void *CPGPSexp::NthBuffer(int number, size_t *rlength) { + return gcry_sexp_nth_buffer(m_Handle, number, rlength); + } + //-------------------------------------------------------------------------------------------------------------- + + char *CPGPSexp::NthString(int number) { + return gcry_sexp_nth_string(m_Handle, number); + } + //-------------------------------------------------------------------------------------------------------------- + + gcry_mpi_t CPGPSexp::NthMPI(int number, int mpifmt) { + return gcry_sexp_nth_mpi(m_Handle, number, mpifmt); + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPKey --------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::Encrypt(gcry_sexp_t data, gcry_sexp_t pkey) { + return !GPGP->CheckForCipherError(gcry_pk_encrypt(&m_Handle, data, pkey)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::Decrypt(gcry_sexp_t data, gcry_sexp_t skey) { + return !GPGP->CheckForCipherError(gcry_pk_decrypt(&m_Handle, data, skey)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::Sign(gcry_sexp_t data, gcry_sexp_t skey) { + return !GPGP->CheckForCipherError(gcry_pk_sign(&m_Handle, data, skey)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::Verify(gcry_sexp_t data, gcry_sexp_t pkey) { + return !GPGP->CheckForCipherError(gcry_pk_verify(m_Handle, data, pkey)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::TestKey() { + return !GPGP->CheckForCipherError(gcry_pk_testkey(m_Handle)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::AlgoInfo(int algo, int what, void *buffer, size_t *nbytes) { + return !GPGP->CheckForCipherError(gcry_pk_algo_info(algo, what, buffer, nbytes)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::Ctl(int cmd, void *buffer, size_t buflen) { + return !GPGP->CheckForCipherError(gcry_pk_ctl(cmd, buffer, buflen)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::GenKey(gcry_sexp_t parms) { + return !GPGP->CheckForCipherError(gcry_pk_genkey(&m_Handle, parms)); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CPGPKey::PubKeyGetSexp(int mode, gcry_ctx_t ctx) { + return !GPGP->CheckForCipherError(gcry_pubkey_get_sexp(&m_Handle, mode, ctx)); + } + //-------------------------------------------------------------------------------------------------------------- + + const char *CPGPKey::AlgoName(int algo) { + return gcry_pk_algo_name(algo); + } + //-------------------------------------------------------------------------------------------------------------- + + int CPGPKey::MapName(const char *name) { + return gcry_pk_map_name(name); + } + //-------------------------------------------------------------------------------------------------------------- + + int CPGPKey::TestAlgo(int algo) { + return gcry_pk_test_algo(algo); + } + //-------------------------------------------------------------------------------------------------------------- + + unsigned int CPGPKey::GetNBits() { + return gcry_pk_get_nbits(m_Handle); + } + //-------------------------------------------------------------------------------------------------------------- + + unsigned char *CPGPKey::KeyGrip(unsigned char *array) { + return gcry_pk_get_keygrip (m_Handle, array); + } + + //-------------------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + static void print_mpi(const char *text, const CPGPMPI& Mpi) + { + char *buf; + void *bufaddr = &buf; + + try { + Mpi.APrint(GCRYMPI_FMT_HEX, (unsigned char **) bufaddr, nullptr); + } catch (...) { + + } + + if (GPGP->LastError()) { + GPGP->Verbose("%s=[error printing number: %s]\n", + text, gpg_strerror(GPGP->LastError())); + } else { + GPGP->Verbose("%s=0x%s\n", text, buf); + gcry_free (buf); + } + } + //-------------------------------------------------------------------------------------------------------------- + + static void check_generated_rsa_key(CPGPKey& Key, unsigned long expected_e) { + + CPGPKey Public(Key.FindToken("public-key", 0)); + if (!Public.Handle()) + throw EPGPError("public part missing in return value"); + + CPGPSexp List(Public.FindToken("e", 0)); + CPGPMPI Mpi(List.NthMPI(1, 0)); + + if (!List.Handle() || !Mpi.Handle()) { + throw EPGPError("public exponent not found"); + } else if (!expected_e) { + print_mpi("e", Mpi); + } else if (Mpi.CmpUI(expected_e)) { + print_mpi("e", Mpi); + throw EPGPError("public exponent is not %lu", expected_e); + } + + Mpi.Release(); + List.Release(); + Public.Release(); + + CPGPKey Secret(Key.FindToken("private-key", 0)); + if (!Secret.Handle()) + throw EPGPError("private part missing in return value"); + + Secret.TestKey(); + Secret.Release(); + } + //-------------------------------------------------------------------------------------------------------------- + + void check_rsa_keys(COnPGPVerboseEvent && Verbose) { + + CPGPSexp KeyParm; + CPGPKey Key; + + GPGP->OnVerbose(static_cast(Verbose)); + + /* Check that DSA generation works and that it can grok the qbits + argument. */ + GPGP->Verbose("Creating 5 1024 bit DSA keys"); + + for (int i = 0; i < 5; i++) + { + KeyParm.New("(genkey\n" + " (dsa\n" + " (nbits 4:1024)\n" + " ))", 0, 1); + + Key.GenKey(KeyParm.Handle()); + + KeyParm.Release(); + Key.Release(); + + GPGP->Verbose("Done"); + } + + GPGP->Verbose("Creating 1536 bit DSA key"); + + KeyParm.New("(genkey\n" + " (dsa\n" + " (nbits 4:1536)\n" + " (qbits 3:224)\n" + " ))", 0, 1); + + Key.GenKey(KeyParm.Handle()); + KeyParm.Release(); + + char buffer[20000]; + Key.Sprint(GCRYSEXP_FMT_ADVANCED, buffer, sizeof buffer); + GPGP->Verbose("=============================\n%s\n" + "=============================\n", buffer); + + Key.Release(); + + GPGP->Verbose("Creating 1024 bit RSA key"); + + KeyParm.New("(genkey\n" + " (rsa\n" + " (nbits 4:1024)\n" + " ))", 0, 1); + + Key.GenKey(KeyParm.Handle()); + KeyParm.Release(); + + check_generated_rsa_key (Key, 65537); + Key.Release(); + + GPGP->Verbose("Creating 512 bit RSA key with e=257"); + + KeyParm.New("(genkey\n" + " (rsa\n" + " (nbits 3:512)\n" + " (rsa-use-e 3:257)\n" + " ))", 0, 1); + + Key.GenKey(KeyParm.Handle()); + KeyParm.Release(); + + check_generated_rsa_key (Key, 257); + Key.Release(); + + GPGP->Verbose("Creating 512 bit RSA key with default e"); + + KeyParm.New("(genkey\n" + " (rsa\n" + " (nbits 3:512)\n" + " (rsa-use-e 1:0)\n" + " ))", 0, 1); + + Key.GenKey(KeyParm.Handle()); + KeyParm.Release(); + + check_generated_rsa_key (Key, 0); /* We don't expect a constant exponent. */ + Key.Release(); + } + + void get_aes_ctx(const char* passwd, CPGPCipher *Cipher) + { + const size_t keylen = 16; + char passwd_hash[keylen]; + + size_t pass_len = strlen(passwd); + + Cipher->Open(GCRY_CIPHER_AES128,GCRY_CIPHER_MODE_CFB, 0); + + gcry_md_hash_buffer(GCRY_MD_MD5, (void*) &passwd_hash, + (const void*) passwd, pass_len); + + Cipher->SetKey((const void *) &passwd_hash, keylen); + Cipher->SetIV((const void *) &passwd_hash, keylen); + } + + size_t get_keypair_size(int nbits) + { + size_t aes_blklen = gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128); + + size_t keypair_nbits = 4 * (2 * nbits); + + size_t rem = keypair_nbits % aes_blklen; + return (keypair_nbits + rem) / 8; + } + + void load_key(const char *KeyFile, const char* Passwd, COnPGPVerboseEvent &&Verbose) { + + CString keyFile; + keyFile.LoadFromFile(KeyFile); + + /* Grab a key pair password and create an AES context with it. */ + CPGPCipher Cipher; + get_aes_ctx("Aliens04", &Cipher); + + /* Read and decrypt the key pair from disk. */ + size_t rsa_len = get_keypair_size(2048); + + void* rsa_buf = calloc(1, rsa_len); + if (!rsa_buf) { + throw Delphi::Exception::Exception("malloc: could not allocate rsa buffer"); + } + + keyFile.Read(rsa_buf, rsa_len); + + Cipher.Decrypt((unsigned char*) rsa_buf, rsa_len, nullptr, 0); + + /* Load the key pair components into sexps. */ + CPGPSexp KeyPair; + KeyPair.New(rsa_buf, rsa_len, 0); + + free(rsa_buf); + + CPGPKey Public(KeyPair.FindToken("public-key", 0)); + CPGPKey Secret(KeyPair.FindToken("private-key", 0)); + + /* Create a message. */ + CPGPMPI Msg; + CPGPSexp Data; + + const auto* s = (const unsigned char*) "Hello world."; + Msg.Scan(GCRYMPI_FMT_USG, s, strlen((const char*) s), nullptr); + + Data.Build(nullptr,"(data (flags raw) (value %m))", Msg.Handle()); + + /* Encrypt the message. */ + CPGPKey Ciph; + Ciph.Encrypt(Data.Handle(), Public.Handle()); + + /* Decrypt the message. */ + CPGPKey Plain; + Plain.Decrypt(Ciph.Handle(), Secret.Handle()); + + /* Pretty-print the results. */ + CPGPMPI outMsg(Plain.NthMPI(0, GCRYMPI_FMT_USG)); + + GPGP->Verbose("Original:"); + + Msg.Dump(); + + GPGP->Verbose("Decrypted:"); + + outMsg.Dump(); + + if (Msg.Cmp(outMsg.Handle())) { + throw Delphi::Exception::Exception("Data corruption!"); + } + + GPGP->Verbose("Messages match."); + + unsigned char obuf[64] = { 0 }; + + outMsg.Print(GCRYMPI_FMT_USG, (unsigned char*) &obuf, sizeof(obuf), nullptr); + + GPGP->Verbose("-> %s", (char *) obuf); + } +#endif + } +} +} diff --git a/src/app/PGP.hpp b/src/app/PGP.hpp new file mode 100644 index 0000000..9615a3f --- /dev/null +++ b/src/app/PGP.hpp @@ -0,0 +1,403 @@ +/*++ + +Module Name: + + PGP.hpp + +Notices: + + PGP library + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#include "OpenPGP.h" +using namespace OpenPGP; +//---------------------------------------------------------------------------------------------------------------------- +#ifdef USE_LIB_GCRYPT +#include +//---------------------------------------------------------------------------------------------------------------------- +#endif + +#ifndef APOSTOL_PGP_HPP +#define APOSTOL_PGP_HPP + +extern "C++" { + +namespace Apostol { + + namespace PGP { + + typedef struct CPGPUserId { + CString Name; + CString Mail; + CString Desc; + } CPGPUserId, *PPGPUserId; + + typedef TList CPGPUserIdList; + + class Key: public OpenPGP::Key { + public: + + Key(); + Key(const PGP & copy); + Key(const Key & copy); + Key(const std::string & data); + Key(std::istream & stream); + ~Key() override = default; + + // output style inspired by gpg and SKS Keyserver/pgp.mit.edu + std::string ListKeys(std::size_t indents = 0, std::size_t indent_size = 4) const; + + void ExportUID(CPGPUserIdList &List) const; + }; + + bool TryCleartextSignature(const CString& Key, const CString &Pass, const CString &Hash, const CString& ClearText, + CString& SignText); + + void CleartextSignature(const CString& Key, const CString &Pass, const CString &Hash, const CString& ClearText, + CString& SignText); + +#ifdef USE_LIB_GCRYPT + class CPGP; + + extern CPGP *GPGP; + //-------------------------------------------------------------------------------------------------------------- + + typedef std::function COnPGPVerboseEvent; + //-------------------------------------------------------------------------------------------------------------- + + class EPGPError: public Delphi::Exception::Exception { + typedef Delphi::Exception::Exception inherited; + + private: + + int m_LastError; + + public: + + EPGPError() : inherited(), m_LastError(GPG_ERR_NO_ERROR) {}; + + EPGPError(int Error, LPCTSTR lpFormat) : inherited(lpFormat) { + m_LastError = Error; + }; + + explicit EPGPError(LPCTSTR lpFormat, ...): m_LastError(GPG_ERR_NO_ERROR), inherited() { + va_list argList; + va_start(argList, lpFormat); + FormatMessage(lpFormat, argList); + va_end(argList); + }; + + ~EPGPError() override = default; + + int GetLastError() { return m_LastError; } + }; + //-------------------------------------------------------------------------------------------------------------- + + class CPGPComponent { + public: + + CPGPComponent(); + + ~CPGPComponent(); + + }; // CPGPComponent + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGP ------------------------------------------------------------------------------------------------------ + + //-------------------------------------------------------------------------------------------------------------- + + class CPGP: public CObject { + private: + + COnPGPVerboseEvent m_OnVerbose; + + protected: + + gcry_error_t m_LastError; + + virtual void DoVerbose(LPCTSTR AFormat, va_list args); + + public: + + CPGP(); + + ~CPGP() override = default; + + inline static class CPGP *CreatePGP() { return GPGP = new CPGP(); }; + + inline static void DeletePGP() { delete GPGP; }; + + static void Initialize(); + + bool CheckForCipherError(gcry_error_t err); + + bool CheckForCipherError(gcry_error_t err, int const ignore[], int count); + + void RaiseCipherError(gcry_error_t err); + + void Verbose(LPCSTR AFormat, ...); + void Verbose(LPCSTR AFormat, va_list args); + + gcry_error_t LastError() const { return m_LastError; }; + + const COnPGPVerboseEvent &OnVerbose() const { return m_OnVerbose; } + void OnVerbose(COnPGPVerboseEvent && Value) { m_OnVerbose = Value; } + + }; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPCipher ------------------------------------------------------------------------------------------------ + + //-------------------------------------------------------------------------------------------------------------- + + class CPGPCipher: public CPGPComponent { + private: + + gcry_cipher_hd_t m_Handle; + + public: + + CPGPCipher(); + + gcry_cipher_hd_t Handle() { return m_Handle; }; + gcry_cipher_hd_t *Ptr() { return &m_Handle; }; + + bool Open(int algo, int mode, unsigned int flags); + + void Close(); + + bool SetKey(const void *key, size_t keylen); + bool SetIV(const void *iv, size_t ivlen); + bool SetCTR(const void *ctr, size_t ctrlen); + + bool Reset(); + + bool Authenticate(const void *abuf, size_t abuflen); + + bool GetTag(void *tag, size_t taglen); + bool CheckTag(gcry_cipher_hd_t h, const void *tag, size_t taglen); + + bool Encrypt(unsigned char *out, size_t outsize, const unsigned char *in, size_t inlen); + bool Decrypt(unsigned char *out, size_t outsize, const unsigned char *in, size_t inlen); + + bool Final(); + bool Sync(); + + bool Ctl(int cmd, void *buffer, size_t buflen); + bool Info(gcry_cipher_hd_t h, int what, void *buffer, size_t *nbytes); + + }; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPAlgo -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + class CPGPAlgo: public CPGPComponent { + public: + + CPGPAlgo(); + + bool Info(int algo, int what, void *buffer, size_t *nbytes); + + size_t KeyLen(int algo); + + size_t BlkLen(int algo); + + const char *Name(int algo); + + int MapName(const char *name); + + int ModeFromOid(const char *string); + + }; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPMPI --------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + class CPGPMPI: public CPGPComponent { + protected: + + gcry_mpi_t m_Handle; + + public: + + CPGPMPI(); + + CPGPMPI(const CPGPMPI &Value) { + m_Handle = Value.m_Handle; + }; + + explicit CPGPMPI(gcry_mpi_t AHandle) { + m_Handle = AHandle; + }; + + ~CPGPMPI() = default; + + gcry_mpi_t Handle() { return m_Handle; }; + + void New(unsigned int nbits); + + void SNew(unsigned int nbits); + + void Copy(const gcry_mpi_t a); + + void Release(); + + gcry_mpi_t Set(const gcry_mpi_t u); + + gcry_mpi_t SetUI(unsigned long u); + + void Swap(gcry_mpi_t b); + + void Snatch(const gcry_mpi_t u); + + void Neg(gcry_mpi_t u); + + void Abs(); + + int Cmp(const gcry_mpi_t v) const; + int CmpUI(unsigned long v) const; + int IsNeg() const; + + bool Scan (enum gcry_mpi_format format, const unsigned char *buffer, size_t buflen, size_t *nscanned); + + bool Print (enum gcry_mpi_format format, unsigned char *buffer, size_t buflen, size_t *nwritten) const; + + bool APrint(enum gcry_mpi_format format, unsigned char **buffer, size_t *nbytes) const; + + void Dump(); + + + }; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPSexp -------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + class CPGPSexp: public CPGPComponent { + protected: + + gcry_sexp_t m_Handle; + + public: + + CPGPSexp(); + + CPGPSexp(const CPGPSexp& Value) { + m_Handle = Value.m_Handle; + }; + + explicit CPGPSexp(gcry_sexp_t AHandle) { + m_Handle = AHandle; + }; + + ~CPGPSexp(); + + gcry_sexp_t Handle() { return m_Handle; }; + + bool New(const void *buffer, size_t length, int autodetect); + + bool Create(void *buffer, size_t length, int autodetect, void (*freefnc)(void*)); + + bool Sscan(size_t *erroff, const char *buffer, size_t length); + + bool Build(size_t *erroff, const char *format, ...); + + size_t Sprint(int mode, char *buffer, size_t maxlength); + + void Release(); + + void Dump(); + + static size_t CanonLen(const unsigned char *buffer, size_t length, size_t *erroff, gcry_error_t *errcode); + + gcry_sexp_t FindToken(const char *token, size_t toklen); + + int Length(); + + gcry_sexp_t Nth(int number); + + gcry_sexp_t Car(); + + gcry_sexp_t Cdr(); + + const char *NthData(int number, size_t *datalen); + + void *NthBuffer(int number, size_t *rlength); + + char *NthString(int number); + + gcry_mpi_t NthMPI(int number, int mpifmt); + + }; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CPGPKey --------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + class CPGPKey: public CPGPSexp { + public: + + CPGPKey(): CPGPSexp() { + + }; + + CPGPKey(const CPGPKey& Value) = default; + + explicit CPGPKey(gcry_sexp_t AHandle): CPGPSexp(AHandle) { + + }; + + bool TestKey (); + static bool AlgoInfo (int algo, int what, void *buffer, size_t *nbytes); + static bool Ctl (int cmd, void *buffer, size_t buflen); + bool GenKey (gcry_sexp_t parms); + bool PubKeyGetSexp(int mode, gcry_ctx_t ctx); + + unsigned int GetNBits (); + unsigned char * KeyGrip (unsigned char *array); + + bool Encrypt(gcry_sexp_t data, gcry_sexp_t pkey); + bool Decrypt(gcry_sexp_t data, gcry_sexp_t skey); + bool Sign(gcry_sexp_t data, gcry_sexp_t skey); + bool Verify(gcry_sexp_t data, gcry_sexp_t pkey); + + static const char * AlgoName(int algo); + static int MapName (const char *name); + static int TestAlgo (int algo); + + }; + + //-------------------------------------------------------------------------------------------------------------- + + void check_rsa_keys(COnPGPVerboseEvent && Verbose = nullptr); + void load_key(const char *KeyFile, const char* Passwd, COnPGPVerboseEvent &&Verbose = nullptr); +#endif + + } +} + +using namespace Apostol::PGP; +} + +#endif //APOSTOL_PGP_HPP diff --git a/src/app/dm.cpp b/src/app/dm.cpp new file mode 100644 index 0000000..b465f3b --- /dev/null +++ b/src/app/dm.cpp @@ -0,0 +1,264 @@ +/*++ + +Program name: + + deal-module + +Module Name: + + DealModule.cpp + +Notices: + + Bitcoin Payment Service (Deal Module) + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#include "dm.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#define exit_failure(msg) { \ + if (GLog != nullptr) \ + GLog->Error(APP_LOG_EMERG, 0, msg); \ + else \ + std::cerr << APP_NAME << ": " << (msg) << std::endl; \ + exitcode = EXIT_FAILURE; \ +} +//---------------------------------------------------------------------------------------------------------------------- + +extern "C++" { + +namespace Apostol { + + namespace DealModule { + + void CDealModule::ShowVersionInfo() { + + std::cerr << APP_NAME " version: " APP_VERSION " (" APP_DESCRIPTION ")" LINEFEED << std::endl; + + if (Config()->Flags().show_help) { + std::cerr << "Usage: " APP_NAME << " [-?hvVt] [-s signal] [-c filename]" + " [-p prefix] [-g directives]" LINEFEED + LINEFEED + "Options:" LINEFEED + " -?,-h : this help" LINEFEED + " -v : show version and exit" LINEFEED + " -V : show version and configure options then exit" LINEFEED + " -t : test configuration and exit" LINEFEED + " -s signal : send signal to a master process: stop, quit, reopen, reload" LINEFEED + #ifdef APP_PREFIX + " -p prefix : set prefix path (default: " APP_PREFIX ")" LINEFEED + #else + " -p prefix : set prefix path (default: NONE)" LINEFEED + #endif + " -c filename : set configuration file (default: " APP_CONF_FILE ")" LINEFEED + " -g directives : set global directives out of configuration file" LINEFEED + " -l locale : set locale (default: " APP_DEFAULT_LOCALE ")" LINEFEED + << std::endl; + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CDealModule::ParseCmdLine() { + + LPCTSTR P; + + for (int i = 1; i < argc(); ++i) { + + P = argv()[i].c_str(); + + if (*P++ != '-') { + throw Delphi::Exception::ExceptionFrm(_T("invalid option: \"%s\""), P); + } + + while (*P) { + + switch (*P++) { + + case '?': + case 'h': + Config()->Flags().show_version = true; + Config()->Flags().show_help = true; + break; + + case 'v': + Config()->Flags().show_version = true; + break; + + case 'V': + Config()->Flags().show_version = true; + Config()->Flags().show_configure = true; + break; + + case 't': + Config()->Flags().test_config = true; + break; + + case 'p': + if (*P) { + Config()->Prefix(P); + goto next; + } + + if (!argv()[++i].empty()) { + Config()->Prefix(argv()[i]); + goto next; + } + + throw Delphi::Exception::Exception(_T("option \"-p\" requires directory name")); + + case 'c': + if (*P) { + Config()->ConfFile(P); + goto next; + } + + if (!argv()[++i].empty()) { + Config()->ConfFile(argv()[i]); + goto next; + } + + throw Delphi::Exception::Exception(_T("option \"-c\" requires file name")); + + case 'g': + if (*P) { + Config()->ConfParam(P); + goto next; + } + + if (!argv()[++i].empty()) { + Config()->ConfParam(argv()[i]); + goto next; + } + + throw Delphi::Exception::Exception(_T("option \"-g\" requires parameter")); + + case 's': + if (*P) { + Config()->Signal(P); + } else if (!argv()[++i].empty()) { + Config()->Signal(argv()[i]); + } else { + throw Delphi::Exception::Exception(_T("option \"-s\" requires parameter")); + } + + if ( Config()->Signal() == "stop" + || Config()->Signal() == "quit" + || Config()->Signal() == "reopen" + || Config()->Signal() == "reload") + { + ProcessType(ptSignaller); + goto next; + } + + throw Delphi::Exception::ExceptionFrm(_T("invalid option: \"-s %s\""), Config()->Signal().c_str()); + + case 'l': + if (*P) { + Config()->Locale(P); + goto next; + } + + if (!argv()[++i].empty()) { + Config()->Locale(argv()[i]); + goto next; + } + + throw Delphi::Exception::Exception(_T("option \"-l\" requires locale")); + + default: + throw Delphi::Exception::ExceptionFrm(_T("invalid option: \"%c\""), *(P - 1)); + } + } + + next: + + continue; + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CDealModule::StartProcess() { + if (Config()->Helper()) { + m_ProcessType = ptHelper; + } + + CIniFile Bitcoin(Config()->ConfPrefix() + "bitcoin.conf"); + + if (Bitcoin.ReadBool("main", "testnet", false)) { + BitcoinConfig.endpoint = "tcp://testnet.libbitcoin.net:19091"; + BitcoinConfig.version_hd = hd_private::testnet; + BitcoinConfig.version_ec = ec_private::testnet; + BitcoinConfig.version_key = payment_address::testnet_p2kh; + BitcoinConfig.version_script = payment_address::testnet_p2sh; + BitcoinConfig.min_output = 1000; + BitcoinConfig.Miner.Fee = "3000"; + BitcoinConfig.Symbol = "tBTC"; + } else { + BitcoinConfig.endpoint = Bitcoin.ReadString("endpoint", "url", BitcoinConfig.endpoint); + BitcoinConfig.Miner.Fee = Bitcoin.ReadString("miner", "fee", "1%"); + BitcoinConfig.Miner.min = Bitcoin.ReadInteger("miner", "min", 200); + BitcoinConfig.Miner.max = Bitcoin.ReadInteger("miner", "max", 2000); + BitcoinConfig.min_output = Bitcoin.ReadInteger("transaction", "min_output", 200); + + if (BitcoinConfig.Miner.min < 0) + BitcoinConfig.Miner.min = 0; + + if (BitcoinConfig.Miner.max < 0) + BitcoinConfig.Miner.max = 0; + + if (BitcoinConfig.Miner.min > BitcoinConfig.Miner.max) + BitcoinConfig.Miner.min = BitcoinConfig.Miner.max; + } + + CApplication::StartProcess(); + } + //-------------------------------------------------------------------------------------------------------------- + + void CDealModule::Run() { + CApplication::Run(); + } + //-------------------------------------------------------------------------------------------------------------- + } +} +} +//---------------------------------------------------------------------------------------------------------------------- + +int main(int argc, char *argv[]) { + + int exitcode; + + DefaultLocale.SetLocale(""); + + CDealModule dm(argc, argv); + + try + { + dm.Name() = APP_NAME; + dm.Description() = APP_DESCRIPTION; + dm.Version() = APP_VERSION; + dm.Title() = APP_VER; + + dm.Run(); + + exitcode = dm.ExitCode(); + } + catch (std::exception& e) + { + exit_failure(e.what()); + } + catch (...) + { + exit_failure("Unknown error..."); + } + + exit(exitcode); +} +//---------------------------------------------------------------------------------------------------------------------- diff --git a/src/app/dm.hpp b/src/app/dm.hpp new file mode 100644 index 0000000..10fe2bc --- /dev/null +++ b/src/app/dm.hpp @@ -0,0 +1,75 @@ +/*++ + +Program name: + + dm + +Module Name: + + dm.hpp + +Notices: + + BitDeals Payment Service (Deal Module) + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_APOSTOL_HPP +#define APOSTOL_APOSTOL_HPP + +#include "../../version.h" +//---------------------------------------------------------------------------------------------------------------------- + +#define APP_VERSION AUTO_VERSION +#define APP_VER APP_NAME "/" APP_VERSION +//---------------------------------------------------------------------------------------------------------------------- + +#include "Header.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +extern "C++" { + +namespace Apostol { + + namespace DealModule { + + class CDealModule: public CApplication { + protected: + + void ParseCmdLine() override; + void ShowVersionInfo() override; + + void StartProcess() override; + + public: + + CDealModule(int argc, char *const *argv): CApplication(argc, argv) { + + }; + + ~CDealModule() override = default; + + static class CDealModule *Create(int argc, char *const *argv) { + return new CDealModule(argc, argv); + }; + + inline void Destroy() override { delete this; }; + + void Run() override; + + }; + } +} + +using namespace Apostol::DealModule; +} + +#endif //APOSTOL_APOSTOL_HPP + diff --git a/src/core b/src/core new file mode 160000 index 0000000..a748414 --- /dev/null +++ b/src/core @@ -0,0 +1 @@ +Subproject commit a748414372940c16bd015eb9a74a0fb05cf3123a diff --git a/src/lib/jwt-cpp/base.h b/src/lib/jwt-cpp/base.h new file mode 100644 index 0000000..375e0eb --- /dev/null +++ b/src/lib/jwt-cpp/base.h @@ -0,0 +1,210 @@ +#pragma once +#include +#include + +#ifdef __has_cpp_attribute +#if __has_cpp_attribute(fallthrough) +#define JWT_FALLTHROUGH [[fallthrough]] +#endif +#endif + +#ifndef JWT_FALLTHROUGH +#define JWT_FALLTHROUGH +#endif + +namespace jwt { + namespace alphabet { + struct base64 { + static const std::array& data() { + static std::array data = { + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; + return data; + }; + static const std::string& fill() { + static std::string fill = "="; + return fill; + } + }; + struct base64url { + static const std::array& data() { + static std::array data = { + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; + return data; + }; + static const std::string& fill() { + static std::string fill = "%3d"; + return fill; + } + }; + } + + class base { + public: + template + static std::string encode(const std::string& bin) { + return encode(bin, T::data(), T::fill()); + } + template + static std::string decode(const std::string& base) { + return decode(base, T::data(), T::fill()); + } + template + static std::string pad(const std::string& base) { + return pad(base, T::fill()); + } + template + static std::string trim(const std::string& base) { + return trim(base, T::fill()); + } + + private: + static std::string encode(const std::string& bin, const std::array& alphabet, const std::string& fill) { + size_t size = bin.size(); + std::string res; + + // clear incomplete bytes + size_t fast_size = size - size % 3; + for (size_t i = 0; i < fast_size;) { + uint32_t octet_a = (unsigned char)bin[i++]; + uint32_t octet_b = (unsigned char)bin[i++]; + uint32_t octet_c = (unsigned char)bin[i++]; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += alphabet[(triple >> 0 * 6) & 0x3F]; + } + + if (fast_size == size) + return res; + + size_t mod = size % 3; + + uint32_t octet_a = fast_size < size ? (unsigned char)bin[fast_size++] : 0; + uint32_t octet_b = fast_size < size ? (unsigned char)bin[fast_size++] : 0; + uint32_t octet_c = fast_size < size ? (unsigned char)bin[fast_size++] : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + switch (mod) { + case 1: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += fill; + res += fill; + break; + case 2: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += fill; + break; + default: + break; + } + + return res; + } + + static std::string decode(const std::string& base, const std::array& alphabet, const std::string& fill) { + size_t size = base.size(); + + size_t fill_cnt = 0; + while (size > fill.size()) { + if (base.substr(size - fill.size(), fill.size()) == fill) { + fill_cnt++; + size -= fill.size(); + if(fill_cnt > 2) + throw std::runtime_error("Invalid input"); + } + else break; + } + + if ((size + fill_cnt) % 4 != 0) + throw std::runtime_error("Invalid input"); + + size_t out_size = size / 4 * 3; + std::string res; + res.reserve(out_size); + + auto get_sextet = [&](size_t offset) { + for (size_t i = 0; i < alphabet.size(); i++) { + if (alphabet[i] == base[offset]) + return static_cast(i); + } + throw std::runtime_error("Invalid input"); + }; + + + size_t fast_size = size - size % 4; + for (size_t i = 0; i < fast_size;) { + uint32_t sextet_a = get_sextet(i++); + uint32_t sextet_b = get_sextet(i++); + uint32_t sextet_c = get_sextet(i++); + uint32_t sextet_d = get_sextet(i++); + + uint32_t triple = (sextet_a << 3 * 6) + + (sextet_b << 2 * 6) + + (sextet_c << 1 * 6) + + (sextet_d << 0 * 6); + + res += (triple >> 2 * 8) & 0xFF; + res += (triple >> 1 * 8) & 0xFF; + res += (triple >> 0 * 8) & 0xFF; + } + + if (fill_cnt == 0) + return res; + + uint32_t triple = (get_sextet(fast_size) << 3 * 6) + + (get_sextet(fast_size + 1) << 2 * 6); + + switch (fill_cnt) { + case 1: + triple |= (get_sextet(fast_size + 2) << 1 * 6); + res += (triple >> 2 * 8) & 0xFF; + res += (triple >> 1 * 8) & 0xFF; + break; + case 2: + res += (triple >> 2 * 8) & 0xFF; + break; + default: + break; + } + + return res; + } + + static std::string pad(const std::string& base, const std::string& fill) { + std::string padding; + switch (base.size() % 4) { + case 1: + padding += fill; + JWT_FALLTHROUGH; + case 2: + padding += fill; + JWT_FALLTHROUGH; + case 3: + padding += fill; + JWT_FALLTHROUGH; + default: + break; + } + + return base + padding; + } + + static std::string trim(const std::string& base, const std::string& fill) { + auto pos = base.find(fill); + return base.substr(0, pos); + } + }; +} diff --git a/src/lib/jwt-cpp/jwt.h b/src/lib/jwt-cpp/jwt.h new file mode 100644 index 0000000..eb88fa3 --- /dev/null +++ b/src/lib/jwt-cpp/jwt.h @@ -0,0 +1,1612 @@ +#pragma once +#define PICOJSON_USE_INT64 +#include "picojson.h" +#include "base.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//If openssl version less than 1.1 +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define OPENSSL10 +#endif + +#ifndef JWT_CLAIM_EXPLICIT +#define JWT_CLAIM_EXPLICIT explicit +#endif + +namespace jwt { + using date = std::chrono::system_clock::time_point; + + struct signature_exception : public std::runtime_error { + signature_exception() + : std::runtime_error("Signature failed.") + {} + explicit signature_exception(const std::string& msg) + : std::runtime_error(msg) + {} + explicit signature_exception(const char* msg) + : std::runtime_error(msg) + {} + }; + + struct signature_verification_exception : public signature_exception { + signature_verification_exception() + : signature_exception("Signature verification failed.") + {} + explicit signature_verification_exception(const std::string& msg) + : signature_exception(msg) + {} + explicit signature_verification_exception(const char* msg) + : signature_exception(msg) + {} + }; + + struct signature_generation_exception : public signature_exception { + signature_generation_exception() + : signature_exception("Signature generation failed.") + {} + explicit signature_generation_exception(const std::string& msg) + : signature_exception(msg) + {} + explicit signature_generation_exception(const char* msg) + : signature_exception(msg) + {} + }; + + struct rsa_exception : public signature_exception { + explicit rsa_exception(const std::string& msg) + : signature_exception(msg) + {} + explicit rsa_exception(const char* msg) + : signature_exception(msg) + {} + }; + + struct ecdsa_exception : public signature_exception { + explicit ecdsa_exception(const std::string& msg) + : signature_exception(msg) + {} + explicit ecdsa_exception(const char* msg) + : signature_exception(msg) + {} + }; + + struct token_verification_exception : public std::runtime_error { + token_verification_exception() + : std::runtime_error("Verification failed.") + {} + explicit token_verification_exception(const std::string& msg) + : std::runtime_error("Verification failed: " + msg) + {} + }; + + struct token_expired_exception : public token_verification_exception { + token_expired_exception() + : token_verification_exception("Token expired.") + {} + explicit token_expired_exception(const std::string& msg) + : token_verification_exception("Token expired (" + msg + ").") + {} + }; + + namespace helper { + inline + std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { +#if OPENSSL_VERSION_NUMBER <= 0x10100003L + std::unique_ptr certbio(BIO_new_mem_buf(const_cast(certstr.data()), certstr.size()), BIO_free_all); +#else + std::unique_ptr certbio(BIO_new_mem_buf(certstr.data(), static_cast(certstr.size())), BIO_free_all); +#endif + std::unique_ptr keybio(BIO_new(BIO_s_mem()), BIO_free_all); + + std::unique_ptr cert(PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); + if (!cert) throw rsa_exception("Error loading cert into memory"); + std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); + if(!key) throw rsa_exception("Error getting public key from certificate"); + if(!PEM_write_bio_PUBKEY(keybio.get(), key.get())) throw rsa_exception("Error writing public key data in PEM format"); + char* ptr = nullptr; + auto len = BIO_get_mem_data(keybio.get(), &ptr); + if(len <= 0 || ptr == nullptr) throw rsa_exception("Failed to convert pubkey to pem"); + std::string res(ptr, len); + return res; + } + + inline + std::shared_ptr load_public_key_from_string(const std::string& key, const std::string& password = "") { + std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if(key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(key, password); + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) + throw rsa_exception("failed to load public key: bio_write failed"); + } else { + const int len = static_cast(key.size()); + if (BIO_write(pubkey_bio.get(), key.data(), len) != len) + throw rsa_exception("failed to load public key: bio_write failed"); + } + + std::shared_ptr pkey(PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)password.c_str()), EVP_PKEY_free); + if (!pkey) + throw rsa_exception("failed to load public key: PEM_read_bio_PUBKEY failed:" + std::string(ERR_error_string(ERR_get_error(), NULL))); + return pkey; + } + + inline + std::shared_ptr load_private_key_from_string(const std::string& key, const std::string& password = "") { + std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + const int len = static_cast(key.size()); + if (BIO_write(privkey_bio.get(), key.data(), len) != len) + throw rsa_exception("failed to load private key: bio_write failed"); + std::shared_ptr pkey(PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str())), EVP_PKEY_free); + if (!pkey) + throw rsa_exception("failed to load private key: PEM_read_bio_PrivateKey failed"); + return pkey; + } + + /** + * Convert a OpenSSL BIGNUM to a std::string + * \param bn BIGNUM to convert + * \return bignum as string + */ + inline +#ifdef OPENSSL10 + static std::string bn2raw(BIGNUM* bn) +#else + static std::string bn2raw(const BIGNUM* bn) +#endif + { + std::string res; + res.resize(BN_num_bytes(bn)); + BN_bn2bin(bn, (unsigned char*)res.data()); + return res; + } + /** + * Convert an std::string to a OpenSSL BIGNUM + * \param raw String to convert + * \return BIGNUM representation + */ + inline + static std::unique_ptr raw2bn(const std::string& raw) { + return std::unique_ptr(BN_bin2bn((const unsigned char*)raw.data(), static_cast(raw.size()), nullptr), BN_free); + } + } + + namespace algorithm { + /** + * "base" algorithm. + * + * Returns and empty signature and checks if the given signature is empty. + */ + struct algorithm { + public: + + algorithm(): md(nullptr), alg_name("none") { + + } + + algorithm(const EVP_MD*(*md)(), std::string name): md(md), alg_name(std::move(name)) { + + } + /// Return an empty string + virtual std::string sign(const std::string& data) const { + return ""; + } + /// Check if the given signature is empty. JWT's with "none" algorithm should not contain a signature. + virtual void verify(const std::string& data, const std::string& signature) const { + if (!signature.empty()) + throw signature_verification_exception(); + } + /** + * Returns the algorithm name provided to the constructor + * \return Algorithmname + */ + const std::string& name() const { + return alg_name; + } + + protected: + /// Hash generator function + const EVP_MD*(*md)(); + + private: + /// Algorithmname + const std::string alg_name; + }; + /** + * Base class for HMAC family of algorithms + */ + struct hmacsha: public algorithm { + public: + /** + * Construct new hmac algorithm + * \param key Key to use for HMAC + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + hmacsha(std::string key, const EVP_MD*(*md)(), const std::string& name) + : algorithm(md, name), secret(std::move(key)) + {} + /** + * Sign jwt data + * \param data The data to sign + * \return HMAC signature for the given data + * \throws signature_generation_exception + */ + std::string sign(const std::string& data) const override { + std::string res; + res.resize(static_cast(EVP_MAX_MD_SIZE)); + auto len = static_cast(res.size()); + if (HMAC(md(), secret.data(), static_cast(secret.size()), (const unsigned char*)data.data(), static_cast(data.size()), (unsigned char*)res.data(), &len) == nullptr) + throw signature_generation_exception(); + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \throws signature_verification_exception If the provided signature does not match + */ + void verify(const std::string& data, const std::string& signature) const override { + try { + auto res = sign(data); + bool matched = true; + for (size_t i = 0; i < std::min(res.size(), signature.size()); i++) + if (res[i] != signature[i]) + matched = false; + if (res.size() != signature.size()) + matched = false; + if (!matched) + throw signature_verification_exception(); + } + catch (const signature_generation_exception&) { + throw signature_verification_exception(); + } + } + private: + /// HMAC secrect + const std::string secret; + }; + /** + * Base class for RSA family of algorithms + */ + struct rsa: public algorithm { + public: + /** + * Construct new rsa algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name) + : algorithm(md, name) + { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if(!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw rsa_exception("at least one of public or private key need to be present"); + } + /** + * Sign jwt data + * \param data The data to sign + * \return RSA signature for the given data + * \throws signature_generation_exception + */ + std::string sign(const std::string& data) const override { +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); +#endif + if (!ctx) + throw signature_generation_exception("failed to create signature: could not create context"); + if (!EVP_SignInit(ctx.get(), md())) + throw signature_generation_exception("failed to create signature: SignInit failed"); + + std::string res; + res.resize(EVP_PKEY_size(pkey.get())); + unsigned int len = 0; + + if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) + throw signature_generation_exception(); + if (!EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get())) + throw signature_generation_exception(); + + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \throws signature_verification_exception If the provided signature does not match + */ + void verify(const std::string& data, const std::string& signature) const override { +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); +#endif + if (!ctx) + throw signature_verification_exception("failed to verify signature: could not create context"); + if (!EVP_VerifyInit(ctx.get(), md())) + throw signature_verification_exception("failed to verify signature: VerifyInit failed"); + if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) + throw signature_verification_exception("failed to verify signature: VerifyUpdate failed"); + auto res = EVP_VerifyFinal(ctx.get(), (const unsigned char*)signature.data(), static_cast(signature.size()), pkey.get()); + if (res != 1) + throw signature_verification_exception("evp verify final failed: " + std::to_string(res) + " " + ERR_error_string(ERR_get_error(), NULL)); + } + private: + /// OpenSSL structure containing converted keys + std::shared_ptr pkey; + }; + /** + * Base class for ECDSA family of algorithms + */ + struct ecdsa: public algorithm { + public: + /** + * Construct new ecdsa algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name, size_t siglen) + : algorithm(md, name), signature_length(siglen) + { + if (!public_key.empty()) { + std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if(public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(public_key, public_key_password); + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) + throw ecdsa_exception("failed to load public key: bio_write failed"); + } else { + const int len = static_cast(public_key.size()); + if (BIO_write(pubkey_bio.get(), public_key.data(), len) != len) + throw ecdsa_exception("failed to load public key: bio_write failed"); + } + + pkey.reset(PEM_read_bio_EC_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EC_KEY_free); + if (!pkey) + throw ecdsa_exception("failed to load public key: PEM_read_bio_EC_PUBKEY failed:" + std::string(ERR_error_string(ERR_get_error(), NULL))); + size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get())); + if(keysize != signature_length*4 && (signature_length != 132 || keysize != 521)) + throw ecdsa_exception("invalid key size"); + } + + if (!private_key.empty()) { + std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + const int len = static_cast(private_key.size()); + if (BIO_write(privkey_bio.get(), private_key.data(), len) != len) + throw ecdsa_exception("failed to load private key: bio_write failed"); + pkey.reset(PEM_read_bio_ECPrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(private_key_password.c_str())), EC_KEY_free); + if (!pkey) + throw ecdsa_exception("failed to load private key: PEM_read_bio_ECPrivateKey failed"); + size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get())); + if(keysize != signature_length*4 && (signature_length != 132 || keysize != 521)) + throw ecdsa_exception("invalid key size"); + } + if(!pkey) + throw ecdsa_exception("at least one of public or private key need to be present"); + + if(EC_KEY_check_key(pkey.get()) == 0) + throw ecdsa_exception("failed to load key: key is invalid"); + } + /** + * Sign jwt data + * \param data The data to sign + * \return ECDSA signature for the given data + * \throws signature_generation_exception + */ + std::string sign(const std::string& data) const override { + const std::string hash = generate_hash(data); + + std::unique_ptr + sig(ECDSA_do_sign((const unsigned char*)hash.data(), static_cast(hash.size()), pkey.get()), ECDSA_SIG_free); + if(!sig) + throw signature_generation_exception(); +#ifdef OPENSSL10 + + auto rr = helper::bn2raw(sig->r); + auto rs = helper::bn2raw(sig->s); +#else + const BIGNUM *r; + const BIGNUM *s; + ECDSA_SIG_get0(sig.get(), &r, &s); + auto rr = helper::bn2raw(r); + auto rs = helper::bn2raw(s); +#endif + if(rr.size() > signature_length/2 || rs.size() > signature_length/2) + throw std::logic_error("bignum size exceeded expected length"); + while(rr.size() != signature_length/2) rr = '\0' + rr; + while(rs.size() != signature_length/2) rs = '\0' + rs; + return rr + rs; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \throws signature_verification_exception If the provided signature does not match + */ + void verify(const std::string& data, const std::string& signature) const override { + const std::string hash = generate_hash(data); + auto r = helper::raw2bn(signature.substr(0, signature.size() / 2)); + auto s = helper::raw2bn(signature.substr(signature.size() / 2)); + +#ifdef OPENSSL10 + ECDSA_SIG sig; + sig.r = r.get(); + sig.s = s.get(); + + if(ECDSA_do_verify((const unsigned char*)hash.data(), hash.size(), &sig, pkey.get()) != 1) + throw signature_verification_exception("Invalid signature"); +#else + std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); + + ECDSA_SIG_set0(sig.get(), r.release(), s.release()); + + if(ECDSA_do_verify((const unsigned char*)hash.data(), static_cast(hash.size()), sig.get(), pkey.get()) != 1) + throw signature_verification_exception("Invalid signature"); +#endif + } + private: + /** + * Hash the provided data using the hash function specified in constructor + * \param data Data to hash + * \return Hash of data + */ + std::string generate_hash(const std::string& data) const { +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); +#endif + if(EVP_DigestInit(ctx.get(), md()) == 0) + throw signature_generation_exception("EVP_DigestInit failed"); + if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) + throw signature_generation_exception("EVP_DigestUpdate failed"); + unsigned int len = 0; + std::string res; + res.resize(EVP_MD_CTX_size(ctx.get())); + if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0) + throw signature_generation_exception("EVP_DigestFinal failed"); + res.resize(len); + return res; + } + + /// OpenSSL struct containing keys + std::shared_ptr pkey; + /// Length of the resulting signature + const size_t signature_length; + }; + + /** + * Base class for PSS-RSA family of algorithms + */ + struct pss: public algorithm { + public: + /** + * Construct new pss algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name) + : algorithm(md, name) + { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if(!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw rsa_exception("at least one of public or private key need to be present"); + } + /** + * Sign jwt data + * \param data The data to sign + * \return ECDSA signature for the given data + * \throws signature_generation_exception + */ + std::string sign(const std::string& data) const override { + auto hash = this->generate_hash(data); + + std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); + const int size = RSA_size(key.get()); + + std::string padded(size, 0x00); + if (!RSA_padding_add_PKCS1_PSS_mgf1(key.get(), (unsigned char*)padded.data(), (const unsigned char*)hash.data(), md(), md(), -1)) + throw signature_generation_exception("failed to create signature: RSA_padding_add_PKCS1_PSS_mgf1 failed"); + + std::string res(size, 0x00); + if (RSA_private_encrypt(size, (const unsigned char*)padded.data(), (unsigned char*)res.data(), key.get(), RSA_NO_PADDING) < 0) + throw signature_generation_exception("failed to create signature: RSA_private_encrypt failed"); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \throws signature_verification_exception If the provided signature does not match + */ + void verify(const std::string& data, const std::string& signature) const override { + auto hash = this->generate_hash(data); + + std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); + const int size = RSA_size(key.get()); + + std::string sig(size, 0x00); + if(!RSA_public_decrypt(static_cast(signature.size()), (const unsigned char*)signature.data(), (unsigned char*)sig.data(), key.get(), RSA_NO_PADDING)) + throw signature_verification_exception("Invalid signature"); + + if(!RSA_verify_PKCS1_PSS_mgf1(key.get(), (const unsigned char*)hash.data(), md(), md(), (const unsigned char*)sig.data(), -1)) + throw signature_verification_exception("Invalid signature"); + } + private: + /** + * Hash the provided data using the hash function specified in constructor + * \param data Data to hash + * \return Hash of data + */ + std::string generate_hash(const std::string& data) const { +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); +#endif + if(EVP_DigestInit(ctx.get(), md()) == 0) + throw signature_generation_exception("EVP_DigestInit failed"); + if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) + throw signature_generation_exception("EVP_DigestUpdate failed"); + unsigned int len = 0; + std::string res; + res.resize(EVP_MD_CTX_size(ctx.get())); + if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0) + throw signature_generation_exception("EVP_DigestFinal failed"); + res.resize(len); + return res; + } + + /// OpenSSL structure containing keys + std::shared_ptr pkey; + }; + + /** + * HS256 algorithm + */ + struct hs256 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs256(std::string key) + : hmacsha(std::move(key), EVP_sha256, "HS256") + {} + }; + /** + * HS384 algorithm + */ + struct hs384 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs384(std::string key) + : hmacsha(std::move(key), EVP_sha384, "HS384") + {} + }; + /** + * HS512 algorithm + */ + struct hs512 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs512(std::string key) + : hmacsha(std::move(key), EVP_sha512, "HS512") + {} + }; + /** + * RS256 algorithm + */ + struct rs256 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit rs256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") + {} + }; + /** + * RS384 algorithm + */ + struct rs384 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit rs384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") + {} + }; + /** + * RS512 algorithm + */ + struct rs512 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit rs512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") + {} + }; + /** + * ES256 algorithm + */ + struct es256 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit es256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256", 64) + {} + }; + /** + * ES384 algorithm + */ + struct es384 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit es384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384", 96) + {} + }; + /** + * ES512 algorithm + */ + struct es512 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit es512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512", 132) + {} + }; + + /** + * PS256 algorithm + */ + struct ps256 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit ps256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") + {} + }; + /** + * PS384 algorithm + */ + struct ps384 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit ps384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") + {} + }; + /** + * PS512 algorithm + */ + struct ps512 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param privat_key_password Password to decrypt private key pem. + */ + explicit ps512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") + {} + }; + } + + /** + * Convenience wrapper for JSON value + */ + class claim { + picojson::value val; + public: + enum class type { + null, + boolean, + number, + string, + array, + object, + int64 + }; + + claim() + : val() + {} + JWT_CLAIM_EXPLICIT claim(std::string s) + : val(std::move(s)) + {} + JWT_CLAIM_EXPLICIT claim(const date& s) + : val(int64_t(std::chrono::system_clock::to_time_t(s))) + {} + JWT_CLAIM_EXPLICIT claim(const std::set& s) + : val(picojson::array(s.cbegin(), s.cend())) + {} + JWT_CLAIM_EXPLICIT claim(const picojson::value& val) + : val(val) + {} + + template + claim(Iterator start, Iterator end) + : val(picojson::array()) + { + auto& arr = val.get(); + for(; start != end; start++) { + arr.push_back(picojson::value(*start)); + } + } + + /** + * Get wrapped json object + * \return Wrapped json object + */ + picojson::value to_json() const { + return val; + } + + /** + * Parse input stream into wrapped json object + * \return input stream + */ + inline std::istream& operator>>(std::istream& is) + { + return is >> val; + } + + /** + * Get type of contained object + * \return Type + * \throws std::logic_error An internal error occured + */ + type get_type() const { + if (val.is()) return type::null; + else if (val.is()) return type::boolean; + else if (val.is()) return type::int64; + else if (val.is()) return type::number; + else if (val.is()) return type::string; + else if (val.is()) return type::array; + else if (val.is()) return type::object; + else throw std::logic_error("internal error"); + } + + /** + * Get the contained object as a string + * \return content as string + * \throws std::bad_cast Content was not a string + */ + const std::string& as_string() const { + if (!val.is()) + throw std::bad_cast(); + return val.get(); + } + /** + * Get the contained object as a date + * \return content as date + * \throws std::bad_cast Content was not a date + */ + date as_date() const { + return std::chrono::system_clock::from_time_t(as_int()); + } + /** + * Get the contained object as an array + * \return content as array + * \throws std::bad_cast Content was not an array + */ + const picojson::array& as_array() const { + if (!val.is()) + throw std::bad_cast(); + return val.get(); + } + /** + * Get the contained object as a set of strings + * \return content as set of strings + * \throws std::bad_cast Content was not a set + */ + std::set as_set() const { + std::set res; + for(auto& e : as_array()) { + if(!e.is()) + throw std::bad_cast(); + res.insert(e.get()); + } + return res; + } + /** + * Get the contained object as an integer + * \return content as int + * \throws std::bad_cast Content was not an int + */ + int64_t as_int() const { + if (!val.is()) + throw std::bad_cast(); + return val.get(); + } + /** + * Get the contained object as a bool + * \return content as bool + * \throws std::bad_cast Content was not a bool + */ + bool as_bool() const { + if (!val.is()) + throw std::bad_cast(); + return val.get(); + } + /** + * Get the contained object as a number + * \return content as double + * \throws std::bad_cast Content was not a number + */ + double as_number() const { + if (!val.is()) + throw std::bad_cast(); + return val.get(); + } + }; + + /** + * Base class that represents a token payload. + * Contains Convenience accessors for common claims. + */ + class payload { + protected: + std::unordered_map payload_claims; + public: + /** + * Check if issuer is present ("iss") + * \return true if present, false otherwise + */ + bool has_issuer() const noexcept { return has_payload_claim("iss"); } + /** + * Check if subject is present ("sub") + * \return true if present, false otherwise + */ + bool has_subject() const noexcept { return has_payload_claim("sub"); } + /** + * Check if audience is present ("aud") + * \return true if present, false otherwise + */ + bool has_audience() const noexcept { return has_payload_claim("aud"); } + /** + * Check if expires is present ("exp") + * \return true if present, false otherwise + */ + bool has_expires_at() const noexcept { return has_payload_claim("exp"); } + /** + * Check if not before is present ("nbf") + * \return true if present, false otherwise + */ + bool has_not_before() const noexcept { return has_payload_claim("nbf"); } + /** + * Check if issued at is present ("iat") + * \return true if present, false otherwise + */ + bool has_issued_at() const noexcept { return has_payload_claim("iat"); } + /** + * Check if token id is present ("jti") + * \return true if present, false otherwise + */ + bool has_id() const noexcept { return has_payload_claim("jti"); } + /** + * Get issuer claim + * \return issuer as string + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + const std::string& get_issuer() const { return get_payload_claim("iss").as_string(); } + /** + * Get subject claim + * \return subject as string + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + const std::string& get_subject() const { return get_payload_claim("sub").as_string(); } + /** + * Get audience claim + * \return audience as strings + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a set (Should not happen in a valid token) + */ + std::string get_audience() const { return get_payload_claim("aud").as_string(); } + /** + * Get audiences claim + * \return audience as a set of strings + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a set (Should not happen in a valid token) + */ + std::set get_audiences() const { + auto aud = get_payload_claim("aud"); + if (aud.get_type() == jwt::claim::type::string) + return { aud.as_string() }; + else + return aud.as_set(); + } + /** + * Get expires claim + * \return expires as a date in utc + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_expires_at() const { return get_payload_claim("exp").as_date(); } + /** + * Get not valid before claim + * \return nbf date in utc + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_not_before() const { return get_payload_claim("nbf").as_date(); } + /** + * Get issued at claim + * \return issued at as date in utc + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_issued_at() const { return get_payload_claim("iat").as_date(); } + /** + * Get id claim + * \return id as string + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + const std::string& get_id() const { return get_payload_claim("jti").as_string(); } + /** + * Check if a payload claim is present + * \return true if claim was present, false otherwise + */ + bool has_payload_claim(const std::string& name) const noexcept { return payload_claims.count(name) != 0; } + /** + * Get payload claim + * \return Requested claim + * \throws std::runtime_error If claim was not present + */ + const claim& get_payload_claim(const std::string& name) const { + if (!has_payload_claim(name)) + throw std::runtime_error("claim not found"); + return payload_claims.at(name); + } + /** + * Get all payload claims + * \return map of claims + */ + std::unordered_map get_payload_claims() const { return payload_claims; } + }; + + /** + * Base class that represents a token header. + * Contains Convenience accessors for common claims. + */ + class header { + protected: + std::unordered_map header_claims; + public: + /** + * Check if algortihm is present ("alg") + * \return true if present, false otherwise + */ + bool has_algorithm() const noexcept { return has_header_claim("alg"); } + /** + * Check if type is present ("typ") + * \return true if present, false otherwise + */ + bool has_type() const noexcept { return has_header_claim("typ"); } + /** + * Check if content type is present ("cty") + * \return true if present, false otherwise + */ + bool has_content_type() const noexcept { return has_header_claim("cty"); } + /** + * Check if key id is present ("kid") + * \return true if present, false otherwise + */ + bool has_key_id() const noexcept { return has_header_claim("kid"); } + /** + * Get algorithm claim + * \return algorithm as string + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + const std::string& get_algorithm() const { return get_header_claim("alg").as_string(); } + /** + * Get type claim + * \return type as a string + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + const std::string& get_type() const { return get_header_claim("typ").as_string(); } + /** + * Get content type claim + * \return content type as string + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + const std::string& get_content_type() const { return get_header_claim("cty").as_string(); } + /** + * Get key id claim + * \return key id as string + * \throws std::runtime_error If claim was not present + * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + const std::string& get_key_id() const { return get_header_claim("kid").as_string(); } + /** + * Check if a header claim is present + * \return true if claim was present, false otherwise + */ + bool has_header_claim(const std::string& name) const noexcept { return header_claims.count(name) != 0; } + /** + * Get header claim + * \return Requested claim + * \throws std::runtime_error If claim was not present + */ + const claim& get_header_claim(const std::string& name) const { + if (!has_header_claim(name)) + throw std::runtime_error("claim not found"); + return header_claims.at(name); + } + /** + * Get all header claims + * \return map of claims + */ + std::unordered_map get_header_claims() const { return header_claims; } + }; + + /** + * Class containing all information about a decoded token + */ + class decoded_jwt : public header, public payload { + protected: + /// Unmodifed token, as passed to constructor + const std::string token; + /// Header part decoded from base64 + std::string header; + /// Unmodified header part in base64 + std::string header_base64; + /// Payload part decoded from base64 + std::string payload; + /// Unmodified payload part in base64 + std::string payload_base64; + /// Signature part decoded from base64 + std::string signature; + /// Unmodified signature part in base64 + std::string signature_base64; + public: + /** + * Constructor + * Parses a given token + * \param token The token to parse + * \throws std::invalid_argument Token is not in correct format + * \throws std::runtime_error Base64 decoding failed or invalid json + */ + explicit decoded_jwt(const std::string& token) + : token(token) + { + auto hdr_end = token.find('.'); + if (hdr_end == std::string::npos) + throw std::invalid_argument("invalid token supplied"); + auto payload_end = token.find('.', hdr_end + 1); + if (payload_end == std::string::npos) + throw std::invalid_argument("invalid token supplied"); + header = header_base64 = token.substr(0, hdr_end); + payload = payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); + signature = signature_base64 = token.substr(payload_end + 1); + + // Fix padding: JWT requires padding to get removed + header = base::pad(header); + payload = base::pad(payload); + signature = base::pad(signature); + + header = base::decode(header); + payload = base::decode(payload); + signature = base::decode(signature); + + auto parse_claims = [](const std::string& str) { + std::unordered_map res; + picojson::value val; + if (!picojson::parse(val, str).empty()) + throw std::runtime_error("Invalid json"); + + for (auto& e : val.get()) { res.insert({ e.first, claim(e.second) }); } + + return res; + }; + + header_claims = parse_claims(header); + payload_claims = parse_claims(payload); + } + + /** + * Get token string, as passed to constructor + * \return token as passed to constructor + */ + const std::string& get_token() const noexcept { return token; } + /** + * Get header part as json string + * \return header part after base64 decoding + */ + const std::string& get_header() const noexcept { return header; } + /** + * Get payload part as json string + * \return payload part after base64 decoding + */ + const std::string& get_payload() const noexcept { return payload; } + /** + * Get signature part as json string + * \return signature part after base64 decoding + */ + const std::string& get_signature() const noexcept { return signature; } + /** + * Get header part as base64 string + * \return header part before base64 decoding + */ + const std::string& get_header_base64() const noexcept { return header_base64; } + /** + * Get payload part as base64 string + * \return payload part before base64 decoding + */ + const std::string& get_payload_base64() const noexcept { return payload_base64; } + /** + * Get signature part as base64 string + * \return signature part before base64 decoding + */ + const std::string& get_signature_base64() const noexcept { return signature_base64; } + + }; + + /** + * Builder class to build and sign a new token + * Use jwt::create() to get an instance of this class. + */ + class builder { + std::unordered_map header_claims; + std::unordered_map payload_claims; + + builder() = default; + friend builder create(); + public: + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const std::string& id, claim c) { header_claims[id] = std::move(c); return *this; } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const std::string& id, claim c) { payload_claims[id] = std::move(c); return *this; } + /** + * Set algorithm claim + * You normally don't need to do this, as the algorithm is automatically set if you don't change it. + * \param str Name of algorithm + * \return *this to allow for method chaining + */ + builder& set_algorithm(const std::string& str) { return set_header_claim("alg", claim(str)); } + /** + * Set type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_type(const std::string& str) { return set_header_claim("typ", claim(str)); } + /** + * Set content type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_content_type(const std::string& str) { return set_header_claim("cty", claim(str)); } + /** + * Set key id claim + * \param str Key id to set + * \return *this to allow for method chaining + */ + builder& set_key_id(const std::string& str) { return set_header_claim("kid", claim(str)); } + /** + * Set issuer claim + * \param str Issuer to set + * \return *this to allow for method chaining + */ + builder& set_issuer(const std::string& str) { return set_payload_claim("iss", claim(str)); } + /** + * Set subject claim + * \param str Subject to set + * \return *this to allow for method chaining + */ + builder& set_subject(const std::string& str) { return set_payload_claim("sub", claim(str)); } + /** + * Set audience claim + * \param l Audience set + * \return *this to allow for method chaining + */ + builder& set_audience(const std::set& l) { return set_payload_claim("aud", claim(l)); } + /** + * Set audience claim + * \param aud Single audience + * \return *this to allow for method chaining + */ + builder& set_audience(const std::string& aud) { return set_payload_claim("aud", claim(aud)); } + /** + * Set expires at claim + * \param d Expires time + * \return *this to allow for method chaining + */ + builder& set_expires_at(const date& d) { return set_payload_claim("exp", claim(d)); } + /** + * Set not before claim + * \param d First valid time + * \return *this to allow for method chaining + */ + builder& set_not_before(const date& d) { return set_payload_claim("nbf", claim(d)); } + /** + * Set issued at claim + * \param d Issued at time, should be current time + * \return *this to allow for method chaining + */ + builder& set_issued_at(const date& d) { return set_payload_claim("iat", claim(d)); } + /** + * Set id claim + * \param str ID to set + * \return *this to allow for method chaining + */ + builder& set_id(const std::string& str) { return set_payload_claim("jti", claim(str)); } + + /** + * Sign token and return result + * \param algo Instance of an algorithm to sign the token with + * \return Final token as a string + */ + template + std::string sign(const T& algo) const { + picojson::object obj_header; + obj_header["alg"] = picojson::value(algo.name()); + for (auto& e : header_claims) { + obj_header[e.first] = e.second.to_json(); + } + picojson::object obj_payload; + for (auto& e : payload_claims) { + obj_payload.insert({ e.first, e.second.to_json() }); + } + + auto encode = [](const std::string& data) { + return base::trim(base::encode(data)); + }; + + std::string header = encode(picojson::value(obj_header).serialize()); + std::string payload = encode(picojson::value(obj_payload).serialize()); + + std::string token = header + "." + payload; + + return token + "." + encode(algo.sign(token)); + } + }; + + /** + * Verifier class used to check if a decoded token contains all claims required by your application and has a valid signature. + */ + template + class verifier { + struct algo_base { + virtual ~algo_base() = default; + virtual void verify(const std::string& data, const std::string& sig) = 0; + }; + template + struct algo : public algo_base { + T alg; + explicit algo(T a) : alg(a) {} + void verify(const std::string& data, const std::string& sig) override { + alg.verify(data, sig); + } + }; + + /// Required claims + std::unordered_map claims; + /// Leeway time for exp, nbf and iat + size_t default_leeway = 0; + /// Instance of clock type + Clock clock; + /// Supported algorithms + std::unordered_map> algs; + public: + /** + * Constructor for building a new verifier instance + * \param c Clock instance + */ + explicit verifier(Clock c) : clock(c) {} + + /** + * Set default leeway to use. + * \param leeway Default leeway to use if not specified otherwise + * \return *this to allow chaining + */ + verifier& leeway(size_t leeway) { default_leeway = leeway; return *this; } + /** + * Set leeway for expires at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for expires at. + * \return *this to allow chaining + */ + verifier& expires_at_leeway(size_t leeway) { return with_claim("exp", claim(std::chrono::system_clock::from_time_t(leeway))); } + /** + * Set leeway for not before. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for not before. + * \return *this to allow chaining + */ + verifier& not_before_leeway(size_t leeway) { return with_claim("nbf", claim(std::chrono::system_clock::from_time_t(leeway))); } + /** + * Set leeway for issued at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for issued at. + * \return *this to allow chaining + */ + verifier& issued_at_leeway(size_t leeway) { return with_claim("iat", claim(std::chrono::system_clock::from_time_t(leeway))); } + /** + * Set an issuer to check for. + * Check is casesensitive. + * \param iss Issuer to check for. + * \return *this to allow chaining + */ + verifier& with_issuer(const std::string& iss) { return with_claim("iss", claim(iss)); } + /** + * Set a subject to check for. + * Check is casesensitive. + * \param sub Subject to check for. + * \return *this to allow chaining + */ + verifier& with_subject(const std::string& sub) { return with_claim("sub", claim(sub)); } + /** + * Set an audience to check for. + * If any of the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const std::set& aud) { return with_claim("aud", claim(aud)); } + /** + * Set an audience to check for. + * If the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const std::string& aud) { return with_claim("aud", claim(aud)); } + /** + * Set an id to check for. + * Check is casesensitive. + * \param id ID to check for. + * \return *this to allow chaining + */ + verifier& with_id(const std::string& id) { return with_claim("jti", claim(id)); } + /** + * Specify a claim to check for. + * \param name Name of the claim to check for + * \param c Claim to check for + * \return *this to allow chaining + */ + verifier& with_claim(const std::string& name, claim c) { claims[name] = c; return *this; } + + /** + * Add an algorithm available for checking. + * \param alg Algorithm to allow + * \return *this to allow chaining + */ + template + verifier& allow_algorithm(Algorithm alg) { + algs[alg.name()] = std::make_shared>(alg); + return *this; + } + + /** + * Verify the given token. + * \param jwt Token to check + * \throws token_verification_exception Verification failed + */ + void verify(const decoded_jwt& jwt) const { + const std::string data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); + const auto& sig = jwt.get_signature(); + const auto& algo = jwt.get_algorithm(); + if (algs.count(algo) == 0) + throw token_verification_exception("Wrong algorithm"); + algs.at(algo)->verify(data, sig); + + auto assert_claim_eq = [](const decoded_jwt& jwt, const std::string& key, const claim& c) { + if (!jwt.has_payload_claim(key)) + throw token_verification_exception("Missing " + key + " claim"); + const auto& jc = jwt.get_payload_claim(key); + if (jc.get_type() != c.get_type()) + throw token_verification_exception("Claim " + key + " type mismatch"); + if (c.get_type() == claim::type::int64) { + if (c.as_date() != jc.as_date()) + throw token_verification_exception("Claim " + key + " does not match expected"); + } else if (c.get_type() == claim::type::array) { + const auto& s1 = c.as_set(); + const auto& s2 = jc.as_set(); + if (s1.size() != s2.size()) + throw token_verification_exception("Claim " + key + " does not match expected"); + auto it1 = s1.cbegin(); + auto it2 = s2.cbegin(); + while (it1 != s1.cend() && it2 != s2.cend()) { + if (*it1++ != *it2++) + throw token_verification_exception("Claim " + key + " does not match expected"); + } + } else if (c.get_type() == claim::type::object) { + if( c.to_json().serialize() != jc.to_json().serialize()) + throw token_verification_exception("Claim " + key + " does not match expected"); + } else if (c.get_type() == claim::type::string) { + if (c.as_string() != jc.as_string()) + throw token_verification_exception("Claim " + key + " does not match expected"); + } else + throw token_verification_exception("Internal error"); + }; + + const auto time = clock.now(); + + if (jwt.has_expires_at()) { + const auto leeway = claims.count("exp") == 1 ? std::chrono::system_clock::to_time_t(claims.at("exp").as_date()) : default_leeway; + const auto exp = jwt.get_expires_at(); + if (time > exp + std::chrono::seconds(leeway)) + throw token_expired_exception(); + } + if (jwt.has_issued_at()) { + const auto leeway = claims.count("iat") == 1 ? std::chrono::system_clock::to_time_t(claims.at("iat").as_date()) : default_leeway; + const auto iat = jwt.get_issued_at(); + if (time < iat - std::chrono::seconds(leeway)) + throw token_expired_exception(); + } + if (jwt.has_not_before()) { + const auto leeway = claims.count("nbf") == 1 ? std::chrono::system_clock::to_time_t(claims.at("nbf").as_date()) : default_leeway; + const auto nbf = jwt.get_not_before(); + if (time < nbf - std::chrono::seconds(leeway)) + throw token_expired_exception(); + } + for (auto& c : claims) { + if (c.first == "exp" || c.first == "iat" || c.first == "nbf") { + // Nothing to do here, already checked + } else if (c.first == "aud") { + if (!jwt.has_audience()) + throw token_verification_exception("Token doesn't contain the required audience"); + if (c.second.get_type() == jwt::claim::type::array) { + const auto& aud = jwt.get_audiences(); + const auto& expected = c.second.as_set(); + for (auto& e : expected) + if (aud.count(e) == 0) + throw token_verification_exception("Token doesn't contain the required audience"); + } else { + const auto& aud = jwt.get_audience(); + if (aud != c.second.as_string()) + throw token_verification_exception("Token doesn't contain the required audience"); + } + } else { + assert_claim_eq(jwt, c.first, c.second); + } + } + } + }; + + /** + * Create a verifier using the given clock + * \param c Clock instance to use + * \return verifier instance + */ + template + verifier verify(Clock c) { + return verifier(c); + } + + /** + * Default clock class using std::chrono::system_clock as a backend. + */ + struct default_clock { + std::chrono::system_clock::time_point now() const { + return std::chrono::system_clock::now(); + } + }; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline + verifier verify() { + return verify({}); + } + + /** + * Return a builder instance to create a new token + */ + inline + builder create() { + return builder(); + } + + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throws std::invalid_argument Token is not in correct format + * \throws std::runtime_error Base64 decoding failed or invalid json + */ + inline + decoded_jwt decode(const std::string& token) { + return decoded_jwt(token); + } +} + +inline std::istream& operator>>(std::istream& is, jwt::claim& c) +{ + return c.operator>>(is); +} + +inline std::ostream& operator<<(std::ostream& os, const jwt::claim& c) +{ + return os << c.to_json(); +} diff --git a/src/lib/picojson/picojson.h b/src/lib/picojson/picojson.h new file mode 100644 index 0000000..ef4b903 --- /dev/null +++ b/src/lib/picojson/picojson.h @@ -0,0 +1,1174 @@ +/* + * Copyright 2009-2010 Cybozu Labs, Inc. + * Copyright 2011-2014 Kazuho Oku + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. + */ +#ifndef picojson_h +#define picojson_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// for isnan/isinf +#if __cplusplus >= 201103L +#include +#else +extern "C" { +#ifdef _MSC_VER +#include +#elif defined(__INTEL_COMPILER) +#include +#else +#include +#endif +} +#endif + +#ifndef PICOJSON_USE_RVALUE_REFERENCE +#if (defined(__cpp_rvalue_references) && __cpp_rvalue_references >= 200610) || (defined(_MSC_VER) && _MSC_VER >= 1600) +#define PICOJSON_USE_RVALUE_REFERENCE 1 +#else +#define PICOJSON_USE_RVALUE_REFERENCE 0 +#endif +#endif // PICOJSON_USE_RVALUE_REFERENCE + +#ifndef PICOJSON_NOEXCEPT +#if PICOJSON_USE_RVALUE_REFERENCE +#define PICOJSON_NOEXCEPT noexcept +#else +#define PICOJSON_NOEXCEPT throw() +#endif +#endif + +// experimental support for int64_t (see README.mkdn for detail) +#ifdef PICOJSON_USE_INT64 +#define __STDC_FORMAT_MACROS +#include +#if __cplusplus >= 201103L +#include +#else +extern "C" { +#include +} +#endif +#endif + +// to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 +#ifndef PICOJSON_USE_LOCALE +#define PICOJSON_USE_LOCALE 1 +#endif +#if PICOJSON_USE_LOCALE +extern "C" { +#include +} +#endif + +#ifndef PICOJSON_ASSERT +#define PICOJSON_ASSERT(e) \ + do { \ + if (!(e)) \ + throw std::runtime_error(#e); \ + } while (0) +#endif + +#ifdef _MSC_VER +#define SNPRINTF _snprintf_s +#pragma warning(push) +#pragma warning(disable : 4244) // conversion from int to char +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4702) // unreachable code +#else +#define SNPRINTF snprintf +#endif + +namespace picojson { + +enum { + null_type, + boolean_type, + number_type, + string_type, + array_type, + object_type +#ifdef PICOJSON_USE_INT64 + , + int64_type +#endif +}; + +enum { INDENT_WIDTH = 2 }; + +struct null {}; + +class value { +public: + typedef std::vector array; + typedef std::map object; + union _storage { + bool boolean_; + double number_; +#ifdef PICOJSON_USE_INT64 + int64_t int64_; +#endif + std::string *string_; + array *array_; + object *object_; + }; + +protected: + int type_; + _storage u_; + +public: + value(); + value(int type, bool); + explicit value(bool b); +#ifdef PICOJSON_USE_INT64 + explicit value(int64_t i); +#endif + explicit value(double n); + explicit value(const std::string &s); + explicit value(const array &a); + explicit value(const object &o); +#if PICOJSON_USE_RVALUE_REFERENCE + explicit value(std::string &&s); + explicit value(array &&a); + explicit value(object &&o); +#endif + explicit value(const char *s); + value(const char *s, size_t len); + ~value(); + value(const value &x); + value &operator=(const value &x); +#if PICOJSON_USE_RVALUE_REFERENCE + value(value &&x) PICOJSON_NOEXCEPT; + value &operator=(value &&x) PICOJSON_NOEXCEPT; +#endif + void swap(value &x) PICOJSON_NOEXCEPT; + template bool is() const; + template const T &get() const; + template T &get(); + template void set(const T &); +#if PICOJSON_USE_RVALUE_REFERENCE + template void set(T &&); +#endif + bool evaluate_as_boolean() const; + const value &get(const size_t idx) const; + const value &get(const std::string &key) const; + value &get(const size_t idx); + value &get(const std::string &key); + + bool contains(const size_t idx) const; + bool contains(const std::string &key) const; + std::string to_str() const; + template void serialize(Iter os, bool prettify = false) const; + std::string serialize(bool prettify = false) const; + +private: + template value(const T *); // intentionally defined to block implicit conversion of pointer to bool + template static void _indent(Iter os, int indent); + template void _serialize(Iter os, int indent) const; + std::string _serialize(int indent) const; + void clear(); +}; + +typedef value::array array; +typedef value::object object; + +inline value::value() : type_(null_type), u_() { +} + +inline value::value(int type, bool) : type_(type), u_() { + switch (type) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(boolean_, false); + INIT(number_, 0.0); +#ifdef PICOJSON_USE_INT64 + INIT(int64_, 0); +#endif + INIT(string_, new std::string()); + INIT(array_, new array()); + INIT(object_, new object()); +#undef INIT + default: + break; + } +} + +inline value::value(bool b) : type_(boolean_type), u_() { + u_.boolean_ = b; +} + +#ifdef PICOJSON_USE_INT64 +inline value::value(int64_t i) : type_(int64_type), u_() { + u_.int64_ = i; +} +#endif + +inline value::value(double n) : type_(number_type), u_() { + if ( +#ifdef _MSC_VER + !_finite(n) +#elif __cplusplus >= 201103L + std::isnan(n) || std::isinf(n) +#else + isnan(n) || isinf(n) +#endif + ) { + throw std::overflow_error(""); + } + u_.number_ = n; +} + +inline value::value(const std::string &s) : type_(string_type), u_() { + u_.string_ = new std::string(s); +} + +inline value::value(const array &a) : type_(array_type), u_() { + u_.array_ = new array(a); +} + +inline value::value(const object &o) : type_(object_type), u_() { + u_.object_ = new object(o); +} + +#if PICOJSON_USE_RVALUE_REFERENCE +inline value::value(std::string &&s) : type_(string_type), u_() { + u_.string_ = new std::string(std::move(s)); +} + +inline value::value(array &&a) : type_(array_type), u_() { + u_.array_ = new array(std::move(a)); +} + +inline value::value(object &&o) : type_(object_type), u_() { + u_.object_ = new object(std::move(o)); +} +#endif + +inline value::value(const char *s) : type_(string_type), u_() { + u_.string_ = new std::string(s); +} + +inline value::value(const char *s, size_t len) : type_(string_type), u_() { + u_.string_ = new std::string(s, len); +} + +inline void value::clear() { + switch (type_) { +#define DEINIT(p) \ + case p##type: \ + delete u_.p; \ + break + DEINIT(string_); + DEINIT(array_); + DEINIT(object_); +#undef DEINIT + default: + break; + } +} + +inline value::~value() { + clear(); +} + +inline value::value(const value &x) : type_(x.type_), u_() { + switch (type_) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(string_, new std::string(*x.u_.string_)); + INIT(array_, new array(*x.u_.array_)); + INIT(object_, new object(*x.u_.object_)); +#undef INIT + default: + u_ = x.u_; + break; + } +} + +inline value &value::operator=(const value &x) { + if (this != &x) { + value t(x); + swap(t); + } + return *this; +} + +#if PICOJSON_USE_RVALUE_REFERENCE +inline value::value(value &&x) PICOJSON_NOEXCEPT : type_(null_type), u_() { + swap(x); +} +inline value &value::operator=(value &&x) PICOJSON_NOEXCEPT { + swap(x); + return *this; +} +#endif +inline void value::swap(value &x) PICOJSON_NOEXCEPT { + std::swap(type_, x.type_); + std::swap(u_, x.u_); +} + +#define IS(ctype, jtype) \ + template <> inline bool value::is() const { \ + return type_ == jtype##_type; \ + } +IS(null, null) +IS(bool, boolean) +#ifdef PICOJSON_USE_INT64 +IS(int64_t, int64) +#endif +IS(std::string, string) +IS(array, array) +IS(object, object) +#undef IS +template <> inline bool value::is() const { + return type_ == number_type +#ifdef PICOJSON_USE_INT64 + || type_ == int64_type +#endif + ; +} + +#define GET(ctype, var) \ + template <> inline const ctype &value::get() const { \ + PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ + return var; \ + } \ + template <> inline ctype &value::get() { \ + PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ + return var; \ + } +GET(bool, u_.boolean_) +GET(std::string, *u_.string_) +GET(array, *u_.array_) +GET(object, *u_.object_) +#ifdef PICOJSON_USE_INT64 +GET(double, + (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), + u_.number_)) +GET(int64_t, u_.int64_) +#else +GET(double, u_.number_) +#endif +#undef GET + +#define SET(ctype, jtype, setter) \ + template <> inline void value::set(const ctype &_val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ + } +SET(bool, boolean, u_.boolean_ = _val;) +SET(std::string, string, u_.string_ = new std::string(_val);) +SET(array, array, u_.array_ = new array(_val);) +SET(object, object, u_.object_ = new object(_val);) +SET(double, number, u_.number_ = _val;) +#ifdef PICOJSON_USE_INT64 +SET(int64_t, int64, u_.int64_ = _val;) +#endif +#undef SET + +#if PICOJSON_USE_RVALUE_REFERENCE +#define MOVESET(ctype, jtype, setter) \ + template <> inline void value::set(ctype && _val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ + } +MOVESET(std::string, string, u_.string_ = new std::string(std::move(_val));) +MOVESET(array, array, u_.array_ = new array(std::move(_val));) +MOVESET(object, object, u_.object_ = new object(std::move(_val));) +#undef MOVESET +#endif + +inline bool value::evaluate_as_boolean() const { + switch (type_) { + case null_type: + return false; + case boolean_type: + return u_.boolean_; + case number_type: + return u_.number_ != 0; +#ifdef PICOJSON_USE_INT64 + case int64_type: + return u_.int64_ != 0; +#endif + case string_type: + return !u_.string_->empty(); + default: + return true; + } +} + +inline const value &value::get(const size_t idx) const { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +inline value &value::get(const size_t idx) { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +inline const value &value::get(const std::string &key) const { + static value s_null; + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +inline value &value::get(const std::string &key) { + static value s_null; + PICOJSON_ASSERT(is()); + object::iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +inline bool value::contains(const size_t idx) const { + PICOJSON_ASSERT(is()); + return idx < u_.array_->size(); +} + +inline bool value::contains(const std::string &key) const { + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end(); +} + +inline std::string value::to_str() const { + switch (type_) { + case null_type: + return "null"; + case boolean_type: + return u_.boolean_ ? "true" : "false"; +#ifdef PICOJSON_USE_INT64 + case int64_type: { + char buf[sizeof("-9223372036854775808")]; + SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); + return buf; + } +#endif + case number_type: { + char buf[256]; + double tmp; + SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); +#if PICOJSON_USE_LOCALE + char *decimal_point = localeconv()->decimal_point; + if (strcmp(decimal_point, ".") != 0) { + size_t decimal_point_len = strlen(decimal_point); + for (char *p = buf; *p != '\0'; ++p) { + if (strncmp(p, decimal_point, decimal_point_len) == 0) { + return std::string(buf, p) + "." + (p + decimal_point_len); + } + } + } +#endif + return buf; + } + case string_type: + return *u_.string_; + case array_type: + return "array"; + case object_type: + return "object"; + default: + PICOJSON_ASSERT(0); +#ifdef _MSC_VER + __assume(0); +#endif + } + return std::string(); +} + +template void copy(const std::string &s, Iter oi) { + std::copy(s.begin(), s.end(), oi); +} + +template struct serialize_str_char { + Iter oi; + void operator()(char c) { + switch (c) { +#define MAP(val, sym) \ + case val: \ + copy(sym, oi); \ + break + MAP('"', "\\\""); + MAP('\\', "\\\\"); + MAP('/', "\\/"); + MAP('\b', "\\b"); + MAP('\f', "\\f"); + MAP('\n', "\\n"); + MAP('\r', "\\r"); + MAP('\t', "\\t"); +#undef MAP + default: + if (static_cast(c) < 0x20 || c == 0x7f) { + char buf[7]; + SNPRINTF(buf, sizeof(buf), "\\u%04x", c & 0xff); + copy(buf, buf + 6, oi); + } else { + *oi++ = c; + } + break; + } + } +}; + +template void serialize_str(const std::string &s, Iter oi) { + *oi++ = '"'; + serialize_str_char process_char = {oi}; + std::for_each(s.begin(), s.end(), process_char); + *oi++ = '"'; +} + +template void value::serialize(Iter oi, bool prettify) const { + return _serialize(oi, prettify ? 0 : -1); +} + +inline std::string value::serialize(bool prettify) const { + return _serialize(prettify ? 0 : -1); +} + +template void value::_indent(Iter oi, int indent) { + *oi++ = '\n'; + for (int i = 0; i < indent * INDENT_WIDTH; ++i) { + *oi++ = ' '; + } +} + +template void value::_serialize(Iter oi, int indent) const { + switch (type_) { + case string_type: + serialize_str(*u_.string_, oi); + break; + case array_type: { + *oi++ = '['; + if (indent != -1) { + ++indent; + } + for (array::const_iterator i = u_.array_->begin(); i != u_.array_->end(); ++i) { + if (i != u_.array_->begin()) { + *oi++ = ','; + } + if (indent != -1) { + _indent(oi, indent); + } + i->_serialize(oi, indent); + } + if (indent != -1) { + --indent; + if (!u_.array_->empty()) { + _indent(oi, indent); + } + } + *oi++ = ']'; + break; + } + case object_type: { + *oi++ = '{'; + if (indent != -1) { + ++indent; + } + for (object::const_iterator i = u_.object_->begin(); i != u_.object_->end(); ++i) { + if (i != u_.object_->begin()) { + *oi++ = ','; + } + if (indent != -1) { + _indent(oi, indent); + } + serialize_str(i->first, oi); + *oi++ = ':'; + if (indent != -1) { + *oi++ = ' '; + } + i->second._serialize(oi, indent); + } + if (indent != -1) { + --indent; + if (!u_.object_->empty()) { + _indent(oi, indent); + } + } + *oi++ = '}'; + break; + } + default: + copy(to_str(), oi); + break; + } + if (indent == 0) { + *oi++ = '\n'; + } +} + +inline std::string value::_serialize(int indent) const { + std::string s; + _serialize(std::back_inserter(s), indent); + return s; +} + +template class input { +protected: + Iter cur_, end_; + bool consumed_; + int line_; + +public: + input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1) { + } + int getc() { + if (consumed_) { + if (*cur_ == '\n') { + ++line_; + } + ++cur_; + } + if (cur_ == end_) { + consumed_ = false; + return -1; + } + consumed_ = true; + return *cur_ & 0xff; + } + void ungetc() { + consumed_ = false; + } + Iter cur() const { + if (consumed_) { + input *self = const_cast *>(this); + self->consumed_ = false; + ++self->cur_; + } + return cur_; + } + int line() const { + return line_; + } + void skip_ws() { + while (1) { + int ch = getc(); + if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { + ungetc(); + break; + } + } + } + bool expect(const int expected) { + skip_ws(); + if (getc() != expected) { + ungetc(); + return false; + } + return true; + } + bool match(const std::string &pattern) { + for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { + if (getc() != *pi) { + ungetc(); + return false; + } + } + return true; + } +}; + +template inline int _parse_quadhex(input &in) { + int uni_ch = 0, hex; + for (int i = 0; i < 4; i++) { + if ((hex = in.getc()) == -1) { + return -1; + } + if ('0' <= hex && hex <= '9') { + hex -= '0'; + } else if ('A' <= hex && hex <= 'F') { + hex -= 'A' - 0xa; + } else if ('a' <= hex && hex <= 'f') { + hex -= 'a' - 0xa; + } else { + in.ungetc(); + return -1; + } + uni_ch = uni_ch * 16 + hex; + } + return uni_ch; +} + +template inline bool _parse_codepoint(String &out, input &in) { + int uni_ch; + if ((uni_ch = _parse_quadhex(in)) == -1) { + return false; + } + if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { + if (0xdc00 <= uni_ch) { + // a second 16-bit of a surrogate pair appeared + return false; + } + // first 16-bit of surrogate pair, get the next one + if (in.getc() != '\\' || in.getc() != 'u') { + in.ungetc(); + return false; + } + int second = _parse_quadhex(in); + if (!(0xdc00 <= second && second <= 0xdfff)) { + return false; + } + uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); + uni_ch += 0x10000; + } + if (uni_ch < 0x80) { + out.push_back(static_cast(uni_ch)); + } else { + if (uni_ch < 0x800) { + out.push_back(static_cast(0xc0 | (uni_ch >> 6))); + } else { + if (uni_ch < 0x10000) { + out.push_back(static_cast(0xe0 | (uni_ch >> 12))); + } else { + out.push_back(static_cast(0xf0 | (uni_ch >> 18))); + out.push_back(static_cast(0x80 | ((uni_ch >> 12) & 0x3f))); + } + out.push_back(static_cast(0x80 | ((uni_ch >> 6) & 0x3f))); + } + out.push_back(static_cast(0x80 | (uni_ch & 0x3f))); + } + return true; +} + +template inline bool _parse_string(String &out, input &in) { + while (1) { + int ch = in.getc(); + if (ch < ' ') { + in.ungetc(); + return false; + } else if (ch == '"') { + return true; + } else if (ch == '\\') { + if ((ch = in.getc()) == -1) { + return false; + } + switch (ch) { +#define MAP(sym, val) \ + case sym: \ + out.push_back(val); \ + break + MAP('"', '\"'); + MAP('\\', '\\'); + MAP('/', '/'); + MAP('b', '\b'); + MAP('f', '\f'); + MAP('n', '\n'); + MAP('r', '\r'); + MAP('t', '\t'); +#undef MAP + case 'u': + if (!_parse_codepoint(out, in)) { + return false; + } + break; + default: + return false; + } + } else { + out.push_back(static_cast(ch)); + } + } + return false; +} + +template inline bool _parse_array(Context &ctx, input &in) { + if (!ctx.parse_array_start()) { + return false; + } + size_t idx = 0; + if (in.expect(']')) { + return ctx.parse_array_stop(idx); + } + do { + if (!ctx.parse_array_item(in, idx)) { + return false; + } + idx++; + } while (in.expect(',')); + return in.expect(']') && ctx.parse_array_stop(idx); +} + +template inline bool _parse_object(Context &ctx, input &in) { + if (!ctx.parse_object_start()) { + return false; + } + if (in.expect('}')) { + return true; + } + do { + std::string key; + if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) { + return false; + } + if (!ctx.parse_object_item(in, key)) { + return false; + } + } while (in.expect(',')); + return in.expect('}'); +} + +template inline std::string _parse_number(input &in) { + std::string num_str; + while (1) { + int ch = in.getc(); + if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { + num_str.push_back(static_cast(ch)); + } else if (ch == '.') { +#if PICOJSON_USE_LOCALE + num_str += localeconv()->decimal_point; +#else + num_str.push_back('.'); +#endif + } else { + in.ungetc(); + break; + } + } + return num_str; +} + +template inline bool _parse(Context &ctx, input &in) { + in.skip_ws(); + int ch = in.getc(); + switch (ch) { +#define IS(ch, text, op) \ + case ch: \ + if (in.match(text) && op) { \ + return true; \ + } else { \ + return false; \ + } + IS('n', "ull", ctx.set_null()); + IS('f', "alse", ctx.set_bool(false)); + IS('t', "rue", ctx.set_bool(true)); +#undef IS + case '"': + return ctx.parse_string(in); + case '[': + return _parse_array(ctx, in); + case '{': + return _parse_object(ctx, in); + default: + if (('0' <= ch && ch <= '9') || ch == '-') { + double f; + char *endp; + in.ungetc(); + std::string num_str(_parse_number(in)); + if (num_str.empty()) { + return false; + } +#ifdef PICOJSON_USE_INT64 + { + errno = 0; + intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); + if (errno == 0 && std::numeric_limits::min() <= ival && ival <= std::numeric_limits::max() && + endp == num_str.c_str() + num_str.size()) { + ctx.set_int64(ival); + return true; + } + } +#endif + f = strtod(num_str.c_str(), &endp); + if (endp == num_str.c_str() + num_str.size()) { + ctx.set_number(f); + return true; + } + return false; + } + break; + } + in.ungetc(); + return false; +} + +class deny_parse_context { +public: + bool set_null() { + return false; + } + bool set_bool(bool) { + return false; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t) { + return false; + } +#endif + bool set_number(double) { + return false; + } + template bool parse_string(input &) { + return false; + } + bool parse_array_start() { + return false; + } + template bool parse_array_item(input &, size_t) { + return false; + } + bool parse_array_stop(size_t) { + return false; + } + bool parse_object_start() { + return false; + } + template bool parse_object_item(input &, const std::string &) { + return false; + } +}; + +class default_parse_context { +protected: + value *out_; + +public: + default_parse_context(value *out) : out_(out) { + } + bool set_null() { + *out_ = value(); + return true; + } + bool set_bool(bool b) { + *out_ = value(b); + return true; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t i) { + *out_ = value(i); + return true; + } +#endif + bool set_number(double f) { + *out_ = value(f); + return true; + } + template bool parse_string(input &in) { + *out_ = value(string_type, false); + return _parse_string(out_->get(), in); + } + bool parse_array_start() { + *out_ = value(array_type, false); + return true; + } + template bool parse_array_item(input &in, size_t) { + array &a = out_->get(); + a.push_back(value()); + default_parse_context ctx(&a.back()); + return _parse(ctx, in); + } + bool parse_array_stop(size_t) { + return true; + } + bool parse_object_start() { + *out_ = value(object_type, false); + return true; + } + template bool parse_object_item(input &in, const std::string &key) { + object &o = out_->get(); + default_parse_context ctx(&o[key]); + return _parse(ctx, in); + } + +private: + default_parse_context(const default_parse_context &); + default_parse_context &operator=(const default_parse_context &); +}; + +class null_parse_context { +public: + struct dummy_str { + void push_back(int) { + } + }; + +public: + null_parse_context() { + } + bool set_null() { + return true; + } + bool set_bool(bool) { + return true; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t) { + return true; + } +#endif + bool set_number(double) { + return true; + } + template bool parse_string(input &in) { + dummy_str s; + return _parse_string(s, in); + } + bool parse_array_start() { + return true; + } + template bool parse_array_item(input &in, size_t) { + return _parse(*this, in); + } + bool parse_array_stop(size_t) { + return true; + } + bool parse_object_start() { + return true; + } + template bool parse_object_item(input &in, const std::string &) { + return _parse(*this, in); + } + +private: + null_parse_context(const null_parse_context &); + null_parse_context &operator=(const null_parse_context &); +}; + +// obsolete, use the version below +template inline std::string parse(value &out, Iter &pos, const Iter &last) { + std::string err; + pos = parse(out, pos, last, &err); + return err; +} + +template inline Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err) { + input in(first, last); + if (!_parse(ctx, in) && err != NULL) { + char buf[64]; + SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); + *err = buf; + while (1) { + int ch = in.getc(); + if (ch == -1 || ch == '\n') { + break; + } else if (ch >= ' ') { + err->push_back(static_cast(ch)); + } + } + } + return in.cur(); +} + +template inline Iter parse(value &out, const Iter &first, const Iter &last, std::string *err) { + default_parse_context ctx(&out); + return _parse(ctx, first, last, err); +} + +inline std::string parse(value &out, const std::string &s) { + std::string err; + parse(out, s.begin(), s.end(), &err); + return err; +} + +inline std::string parse(value &out, std::istream &is) { + std::string err; + parse(out, std::istreambuf_iterator(is.rdbuf()), std::istreambuf_iterator(), &err); + return err; +} + +template struct last_error_t { static std::string s; }; +template std::string last_error_t::s; + +inline void set_last_error(const std::string &s) { + last_error_t::s = s; +} + +inline const std::string &get_last_error() { + return last_error_t::s; +} + +inline bool operator==(const value &x, const value &y) { + if (x.is()) + return y.is(); +#define PICOJSON_CMP(type) \ + if (x.is()) \ + return y.is() && x.get() == y.get() + PICOJSON_CMP(bool); + PICOJSON_CMP(double); + PICOJSON_CMP(std::string); + PICOJSON_CMP(array); + PICOJSON_CMP(object); +#undef PICOJSON_CMP + PICOJSON_ASSERT(0); +#ifdef _MSC_VER + __assume(0); +#endif + return false; +} + +inline bool operator!=(const value &x, const value &y) { + return !(x == y); +} +} + +#if !PICOJSON_USE_RVALUE_REFERENCE +namespace std { +template <> inline void swap(picojson::value &x, picojson::value &y) { + x.swap(y); +} +} +#endif + +inline std::istream &operator>>(std::istream &is, picojson::value &x) { + picojson::set_last_error(std::string()); + const std::string err(picojson::parse(x, is)); + if (!err.empty()) { + picojson::set_last_error(err); + is.setstate(std::ios::failbit); + } + return is; +} + +inline std::ostream &operator<<(std::ostream &os, const picojson::value &x) { + x.serialize(std::ostream_iterator(os)); + return os; +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/src/lib/rapidxml/license.txt b/src/lib/rapidxml/license.txt new file mode 100644 index 0000000..1409831 --- /dev/null +++ b/src/lib/rapidxml/license.txt @@ -0,0 +1,52 @@ +Use of this software is granted under one of the following two licenses, +to be chosen freely by the user. + +1. Boost Software License - Version 1.0 - August 17th, 2003 +=============================================================================== + +Copyright (c) 2006, 2007 Marcin Kalicinski + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +2. The MIT License +=============================================================================== + +Copyright (c) 2006, 2007 Marcin Kalicinski + +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. diff --git a/src/lib/rapidxml/manual.html b/src/lib/rapidxml/manual.html new file mode 100644 index 0000000..2c42270 --- /dev/null +++ b/src/lib/rapidxml/manual.html @@ -0,0 +1,406 @@ +

RAPIDXML Manual

Version 1.13

Copyright (C) 2006, 2009 Marcin Kalicinski
See accompanying file license.txt for license information.

Table of Contents

1. What is RapidXml?
1.1 Dependencies And Compatibility
1.2 Character Types And Encodings
1.3 Error Handling
1.4 Memory Allocation
1.5 W3C Compliance
1.6 API Design
1.7 Reliability
1.8 Acknowledgements
2. Two Minute Tutorial
2.1 Parsing
2.2 Accessing The DOM Tree
2.3 Modifying The DOM Tree
2.4 Printing XML
3. Differences From Regular XML Parsers
3.1 Lifetime Of Source Text
3.2 Ownership Of Strings
3.3 Destructive Vs Non-Destructive Mode
4. Performance
4.1 Comparison With Other Parsers
5. Reference

1. What is RapidXml?

RapidXml is an attempt to create the fastest XML DOM parser possible, while retaining useability, portability and reasonable W3C compatibility. It is an in-situ parser written in C++, with parsing speed approaching that of strlen() function executed on the same data.

+ Entire parser is contained in a single header file, so no building or linking is neccesary. To use it you just need to copy rapidxml.hpp file to a convenient place (such as your project directory), and include it where needed. You may also want to use printing functions contained in header rapidxml_print.hpp.

1.1 Dependencies And Compatibility

RapidXml has no dependencies other than a very small subset of standard C++ library (<cassert>, <cstdlib>, <new> and <exception>, unless exceptions are disabled). It should compile on any reasonably conformant compiler, and was tested on Visual C++ 2003, Visual C++ 2005, Visual C++ 2008, gcc 3, gcc 4, and Comeau 4.3.3. Care was taken that no warnings are produced on these compilers, even with highest warning levels enabled.

1.2 Character Types And Encodings

RapidXml is character type agnostic, and can work both with narrow and wide characters. Current version does not fully support UTF-16 or UTF-32, so use of wide characters is somewhat incapacitated. However, it should succesfully parse wchar_t strings containing UTF-16 or UTF-32 if endianness of the data matches that of the machine. UTF-8 is fully supported, including all numeric character references, which are expanded into appropriate UTF-8 byte sequences (unless you enable parse_no_utf8 flag).

+ Note that RapidXml performs no decoding - strings returned by name() and value() functions will contain text encoded using the same encoding as source file. Rapidxml understands and expands the following character references: &apos; &amp; &quot; &lt; &gt; &#...; Other character references are not expanded.

1.3 Error Handling

By default, RapidXml uses C++ exceptions to report errors. If this behaviour is undesirable, RAPIDXML_NO_EXCEPTIONS can be defined to suppress exception code. See parse_error class and parse_error_handler() function for more information.

1.4 Memory Allocation

RapidXml uses a special memory pool object to allocate nodes and attributes, because direct allocation using new operator would be far too slow. Underlying memory allocations performed by the pool can be customized by use of memory_pool::set_allocator() function. See class memory_pool for more information.

1.5 W3C Compliance

RapidXml is not a W3C compliant parser, primarily because it ignores DOCTYPE declarations. There is a number of other, minor incompatibilities as well. Still, it can successfully parse and produce complete trees of all valid XML files in W3C conformance suite (over 1000 files specially designed to find flaws in XML processors). In destructive mode it performs whitespace normalization and character entity substitution for a small set of built-in entities.

1.6 API Design

RapidXml API is minimalistic, to reduce code size as much as possible, and facilitate use in embedded environments. Additional convenience functions are provided in separate headers: rapidxml_utils.hpp and rapidxml_print.hpp. Contents of these headers is not an essential part of the library, and is currently not documented (otherwise than with comments in code).

1.7 Reliability

RapidXml is very robust and comes with a large harness of unit tests. Special care has been taken to ensure stability of the parser no matter what source text is thrown at it. One of the unit tests produces 100,000 randomly corrupted variants of XML document, which (when uncorrupted) contains all constructs recognized by RapidXml. RapidXml passes this test when it correctly recognizes that errors have been introduced, and does not crash or loop indefinitely.

+ Another unit test puts RapidXml head-to-head with another, well estabilished XML parser, and verifies that their outputs match across a wide variety of small and large documents.

+ Yet another test feeds RapidXml with over 1000 test files from W3C compliance suite, and verifies that correct results are obtained. There are also additional tests that verify each API function separately, and test that various parsing modes work as expected.

1.8 Acknowledgements

I would like to thank Arseny Kapoulkine for his work on pugixml, which was an inspiration for this project. Additional thanks go to Kristen Wegner for creating pugxml, from which pugixml was derived. Janusz Wohlfeil kindly ran RapidXml speed tests on hardware that I did not have access to, allowing me to expand performance comparison table.

2. Two Minute Tutorial

2.1 Parsing

The following code causes RapidXml to parse a zero-terminated string named text:
using namespace rapidxml;
+xml_document<> doc;    // character type defaults to char
+doc.parse<0>(text);    // 0 means default parse flags
+
doc object is now a root of DOM tree containing representation of the parsed XML. Because all RapidXml interface is contained inside namespace rapidxml, users must either bring contents of this namespace into scope, or fully qualify all the names. Class xml_document represents a root of the DOM hierarchy. By means of public inheritance, it is also an xml_node and a memory_pool. Template parameter of xml_document::parse() function is used to specify parsing flags, with which you can fine-tune behaviour of the parser. Note that flags must be a compile-time constant.

2.2 Accessing The DOM Tree

To access the DOM tree, use methods of xml_node and xml_attribute classes:
cout << "Name of my first node is: " << doc.first_node()->name() << "\n";
+xml_node<> *node = doc.first_node("foobar");
+cout << "Node foobar has value " << node->value() << "\n";
+for (xml_attribute<> *attr = node->first_attribute();
+     attr; attr = attr->next_attribute())
+{
+    cout << "Node foobar has attribute " << attr->name() << " ";
+    cout << "with value " << attr->value() << "\n";
+}
+

2.3 Modifying The DOM Tree

DOM tree produced by the parser is fully modifiable. Nodes and attributes can be added/removed, and their contents changed. The below example creates a HTML document, whose sole contents is a link to google.com website:
xml_document<> doc;
+xml_node<> *node = doc.allocate_node(node_element, "a", "Google");
+doc.append_node(node);
+xml_attribute<> *attr = doc.allocate_attribute("href", "google.com");
+node->append_attribute(attr);
+
One quirk is that nodes and attributes do not own the text of their names and values. This is because normally they only store pointers to the source text. So, when assigning a new name or value to the node, care must be taken to ensure proper lifetime of the string. The easiest way to achieve it is to allocate the string from the xml_document memory pool. In the above example this is not necessary, because we are only assigning character constants. But the code below uses memory_pool::allocate_string() function to allocate node name (which will have the same lifetime as the document), and assigns it to a new node:
xml_document<> doc;
+char *node_name = doc.allocate_string(name);        // Allocate string and copy name into it
+xml_node<> *node = doc.allocate_node(node_element, node_name);  // Set node name to node_name
+
Check Reference section for description of the entire interface.

2.4 Printing XML

You can print xml_document and xml_node objects into an XML string. Use print() function or operator <<, which are defined in rapidxml_print.hpp header.
using namespace rapidxml;
+xml_document<> doc;    // character type defaults to char
+// ... some code to fill the document
+
+// Print to stream using operator <<
+std::cout << doc;   
+
+// Print to stream using print function, specifying printing flags
+print(std::cout, doc, 0);   // 0 means default printing flags
+
+// Print to string using output iterator
+std::string s;
+print(std::back_inserter(s), doc, 0);
+
+// Print to memory buffer using output iterator
+char buffer[4096];                      // You are responsible for making the buffer large enough!
+char *end = print(buffer, doc, 0);      // end contains pointer to character after last printed character
+*end = 0;                               // Add string terminator after XML
+

3. Differences From Regular XML Parsers

RapidXml is an in-situ parser, which allows it to achieve very high parsing speed. In-situ means that parser does not make copies of strings. Instead, it places pointers to the source text in the DOM hierarchy.

3.1 Lifetime Of Source Text

In-situ parsing requires that source text lives at least as long as the document object. If source text is destroyed, names and values of nodes in DOM tree will become destroyed as well. Additionally, whitespace processing, character entity translation, and zero-termination of strings require that source text be modified during parsing (but see non-destructive mode). This makes the text useless for further processing once it was parsed by RapidXml.

+ In many cases however, these are not serious issues.

3.2 Ownership Of Strings

Nodes and attributes produced by RapidXml do not own their name and value strings. They merely hold the pointers to them. This means you have to be careful when setting these values manually, by using xml_base::name(const Ch *) or xml_base::value(const Ch *) functions. Care must be taken to ensure that lifetime of the string passed is at least as long as lifetime of the node/attribute. The easiest way to achieve it is to allocate the string from memory_pool owned by the document. Use memory_pool::allocate_string() function for this purpose.

3.3 Destructive Vs Non-Destructive Mode

By default, the parser modifies source text during the parsing process. This is required to achieve character entity translation, whitespace normalization, and zero-termination of strings.

+ In some cases this behaviour may be undesirable, for example if source text resides in read only memory, or is mapped to memory directly from file. By using appropriate parser flags (parse_non_destructive), source text modifications can be disabled. However, because RapidXml does in-situ parsing, it obviously has the following side-effects:

4. Performance

RapidXml achieves its speed through use of several techniques:
  • In-situ parsing. When building DOM tree, RapidXml does not make copies of string data, such as node names and values. Instead, it stores pointers to interior of the source text.
  • Use of template metaprogramming techniques. This allows it to move much of the work to compile time. Through magic of the templates, C++ compiler generates a separate copy of parsing code for any combination of parser flags you use. In each copy, all possible decisions are made at compile time and all unused code is omitted.
  • Extensive use of lookup tables for parsing.
  • Hand-tuned C++ with profiling done on several most popular CPUs.
This results in a very small and fast code: a parser which is custom tailored to exact needs with each invocation.

4.1 Comparison With Other Parsers

The table below compares speed of RapidXml to some other parsers, and to strlen() function executed on the same data. On a modern CPU (as of 2007), you can expect parsing throughput to be close to 1 GB/s. As a rule of thumb, parsing speed is about 50-100x faster than Xerces DOM, 30-60x faster than TinyXml, 3-12x faster than pugxml, and about 5% - 30% faster than pugixml, the fastest XML parser I know of.
  • The test file is a real-world, 50kB large, moderately dense XML file.
  • All timing is done by using RDTSC instruction present in Pentium-compatible CPUs.
  • No profile-guided optimizations are used.
  • All parsers are running in their fastest modes.
  • The results are given in CPU cycles per character, so frequency of CPUs is irrelevant.
  • The results are minimum values from a large number of runs, to minimize effects of operating system activity, task switching, interrupt handling etc.
  • A single parse of the test file takes about 1/10th of a millisecond, so with large number of runs there is a good chance of hitting at least one no-interrupt streak, and obtaining undisturbed results.
Platform
Compiler
strlen() RapidXml pugixml 0.3 pugxml TinyXml
Pentium 4
MSVC 8.0
2.5
5.4
7.0
61.7
298.8
Pentium 4
gcc 4.1.1
0.8
6.1
9.5
67.0
413.2
Core 2
MSVC 8.0
1.0
4.5
5.0
24.6
154.8
Core 2
gcc 4.1.1
0.6
4.6
5.4
28.3
229.3
Athlon XP
MSVC 8.0
3.1
7.7
8.0
25.5
182.6
Athlon XP
gcc 4.1.1
0.9
8.2
9.2
33.7
265.2
Pentium 3
MSVC 8.0
2.0
6.3
7.0
30.9
211.9
Pentium 3
gcc 4.1.1
1.0
6.7
8.9
35.3
316.0
(*) All results are in CPU cycles per character of source text

5. Reference

This section lists all classes, functions, constants etc. and describes them in detail.
class + template + rapidxml::memory_pool
+ constructor + memory_pool()
+ destructor + ~memory_pool()
function allocate_node(node_type type, const Ch *name=0, const Ch *value=0, std::size_t name_size=0, std::size_t value_size=0)
function allocate_attribute(const Ch *name=0, const Ch *value=0, std::size_t name_size=0, std::size_t value_size=0)
function allocate_string(const Ch *source=0, std::size_t size=0)
function clone_node(const xml_node< Ch > *source, xml_node< Ch > *result=0)
function clear()
function set_allocator(alloc_func *af, free_func *ff)

class rapidxml::parse_error
+ constructor + parse_error(const char *what, void *where)
function what() const
function where() const

class + template + rapidxml::xml_attribute
+ constructor + xml_attribute()
function document() const
function previous_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function next_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const

class + template + rapidxml::xml_base
+ constructor + xml_base()
function name() const
function name_size() const
function value() const
function value_size() const
function name(const Ch *name, std::size_t size)
function name(const Ch *name)
function value(const Ch *value, std::size_t size)
function value(const Ch *value)
function parent() const

class + template + rapidxml::xml_document
+ constructor + xml_document()
function parse(Ch *text)
function clear()

class + template + rapidxml::xml_node
+ constructor + xml_node(node_type type)
function type() const
function document() const
function first_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function last_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function previous_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function next_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function first_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function last_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function type(node_type type)
function prepend_node(xml_node< Ch > *child)
function append_node(xml_node< Ch > *child)
function insert_node(xml_node< Ch > *where, xml_node< Ch > *child)
function remove_first_node()
function remove_last_node()
function remove_node(xml_node< Ch > *where)
function remove_all_nodes()
function prepend_attribute(xml_attribute< Ch > *attribute)
function append_attribute(xml_attribute< Ch > *attribute)
function insert_attribute(xml_attribute< Ch > *where, xml_attribute< Ch > *attribute)
function remove_first_attribute()
function remove_last_attribute()
function remove_attribute(xml_attribute< Ch > *where)
function remove_all_attributes()

namespace rapidxml
enum node_type
function parse_error_handler(const char *what, void *where)
function print(OutIt out, const xml_node< Ch > &node, int flags=0)
function print(std::basic_ostream< Ch > &out, const xml_node< Ch > &node, int flags=0)
function operator<<(std::basic_ostream< Ch > &out, const xml_node< Ch > &node)
+ constant + parse_no_data_nodes
+ constant + parse_no_element_values
+ constant + parse_no_string_terminators
+ constant + parse_no_entity_translation
+ constant + parse_no_utf8
+ constant + parse_declaration_node
+ constant + parse_comment_nodes
+ constant + parse_doctype_node
+ constant + parse_pi_nodes
+ constant + parse_validate_closing_tags
+ constant + parse_trim_whitespace
+ constant + parse_normalize_whitespace
+ constant + parse_default
+ constant + parse_non_destructive
+ constant + parse_fastest
+ constant + parse_full
+ constant + print_no_indenting


class + template + rapidxml::memory_pool

+ + Defined in rapidxml.hpp
+ Base class for + xml_document

Description

This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation. In most cases, you will not need to use this class directly. However, if you need to create nodes manually or modify names/values of nodes, you are encouraged to use memory_pool of relevant xml_document to allocate the memory. Not only is this faster than allocating them by using new operator, but also their lifetime will be tied to the lifetime of document, possibly simplyfing memory management.

+ Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool. You can also call allocate_string() function to allocate strings. Such strings can then be used as names or values of nodes without worrying about their lifetime. Note that there is no free() function -- all allocations are freed at once when clear() function is called, or when the pool is destroyed.

+ It is also possible to create a standalone memory_pool, and use it to allocate nodes, whose lifetime will not be tied to any document.

+ Pool maintains RAPIDXML_STATIC_POOL_SIZE bytes of statically allocated memory. Until static memory is exhausted, no dynamic memory allocations are done. When static memory is exhausted, pool allocates additional blocks of memory of size RAPIDXML_DYNAMIC_POOL_SIZE each, by using global new[] and delete[] operators. This behaviour can be changed by setting custom allocation routines. Use set_allocator() function to set them.

+ Allocations for nodes, attributes and strings are aligned at RAPIDXML_ALIGNMENT bytes. This value defaults to the size of pointer on target architecture.

+ To obtain absolutely top performance from the parser, it is important that all nodes are allocated from a single, contiguous block of memory. Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably. If required, you can tweak RAPIDXML_STATIC_POOL_SIZE, RAPIDXML_DYNAMIC_POOL_SIZE and RAPIDXML_ALIGNMENT to obtain best wasted memory to performance compromise. To do it, define their values before rapidxml.hpp file is included.

Parameters

Ch
Character type of created nodes.

+ constructor + memory_pool::memory_pool

Synopsis

memory_pool(); +

Description

Constructs empty pool with default allocator functions.

+ destructor + memory_pool::~memory_pool

Synopsis

~memory_pool(); +

Description

Destroys pool and frees all the memory. This causes memory occupied by nodes allocated by the pool to be freed. Nodes allocated from the pool are no longer valid.

function memory_pool::allocate_node

Synopsis

xml_node<Ch>* allocate_node(node_type type, const Ch *name=0, const Ch *value=0, std::size_t name_size=0, std::size_t value_size=0); +

Description

Allocates a new node from the pool, and optionally assigns name and value to it. If the allocation request cannot be accomodated, this function will throw std::bad_alloc. If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function will call rapidxml::parse_error_handler() function.

Parameters

type
Type of node to create.
name
Name to assign to the node, or 0 to assign no name.
value
Value to assign to the node, or 0 to assign no value.
name_size
Size of name to assign, or 0 to automatically calculate size from name string.
value_size
Size of value to assign, or 0 to automatically calculate size from value string.

Returns

Pointer to allocated node. This pointer will never be NULL.

function memory_pool::allocate_attribute

Synopsis

xml_attribute<Ch>* allocate_attribute(const Ch *name=0, const Ch *value=0, std::size_t name_size=0, std::size_t value_size=0); +

Description

Allocates a new attribute from the pool, and optionally assigns name and value to it. If the allocation request cannot be accomodated, this function will throw std::bad_alloc. If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function will call rapidxml::parse_error_handler() function.

Parameters

name
Name to assign to the attribute, or 0 to assign no name.
value
Value to assign to the attribute, or 0 to assign no value.
name_size
Size of name to assign, or 0 to automatically calculate size from name string.
value_size
Size of value to assign, or 0 to automatically calculate size from value string.

Returns

Pointer to allocated attribute. This pointer will never be NULL.

function memory_pool::allocate_string

Synopsis

Ch* allocate_string(const Ch *source=0, std::size_t size=0); +

Description

Allocates a char array of given size from the pool, and optionally copies a given string to it. If the allocation request cannot be accomodated, this function will throw std::bad_alloc. If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function will call rapidxml::parse_error_handler() function.

Parameters

source
String to initialize the allocated memory with, or 0 to not initialize it.
size
Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated.

Returns

Pointer to allocated char array. This pointer will never be NULL.

function memory_pool::clone_node

Synopsis

xml_node<Ch>* clone_node(const xml_node< Ch > *source, xml_node< Ch > *result=0); +

Description

Clones an xml_node and its hierarchy of child nodes and attributes. Nodes and attributes are allocated from this memory pool. Names and values are not cloned, they are shared between the clone and the source. Result node can be optionally specified as a second parameter, in which case its contents will be replaced with cloned source node. This is useful when you want to clone entire document.

Parameters

source
Node to clone.
result
Node to put results in, or 0 to automatically allocate result node

Returns

Pointer to cloned node. This pointer will never be NULL.

function memory_pool::clear

Synopsis

void clear(); +

Description

Clears the pool. This causes memory occupied by nodes allocated by the pool to be freed. Any nodes or strings allocated from the pool will no longer be valid.

function memory_pool::set_allocator

Synopsis

void set_allocator(alloc_func *af, free_func *ff); +

Description

Sets or resets the user-defined memory allocation functions for the pool. This can only be called when no memory is allocated from the pool yet, otherwise results are undefined. Allocation function must not return invalid pointer on failure. It should either throw, stop the program, or use longjmp() function to pass control to other place of program. If it returns invalid pointer, results are undefined.

+ User defined allocation functions must have the following forms:

+void *allocate(std::size_t size);
+void free(void *pointer);

Parameters

af
Allocation function, or 0 to restore default function
ff
Free function, or 0 to restore default function

class rapidxml::parse_error

+ + Defined in rapidxml.hpp

Description

Parse error exception. This exception is thrown by the parser when an error occurs. Use what() function to get human-readable error message. Use where() function to get a pointer to position within source text where error was detected.

+ If throwing exceptions by the parser is undesirable, it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included. This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception. This function must be defined by the user.

+ This class derives from std::exception class.

+ constructor + parse_error::parse_error

Synopsis

parse_error(const char *what, void *where); +

Description

Constructs parse error.

function parse_error::what

Synopsis

virtual const char* what() const; +

Description

Gets human readable description of error.

Returns

Pointer to null terminated description of the error.

function parse_error::where

Synopsis

Ch* where() const; +

Description

Gets pointer to character data where error happened. Ch should be the same as char type of xml_document that produced the error.

Returns

Pointer to location within the parsed string where error occured.

class + template + rapidxml::xml_attribute

+ + Defined in rapidxml.hpp
+ Inherits from + xml_base

Description

Class representing attribute node of XML document. Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base). Note that after parse, both name and value of attribute will point to interior of source text used for parsing. Thus, this text must persist in memory for the lifetime of attribute.

Parameters

Ch
Character type to use.

+ constructor + xml_attribute::xml_attribute

Synopsis

xml_attribute(); +

Description

Constructs an empty attribute with the specified type. Consider using memory_pool of appropriate xml_document if allocating attributes manually.

function xml_attribute::document

Synopsis

xml_document<Ch>* document() const; +

Description

Gets document of which attribute is a child.

Returns

Pointer to document that contains this attribute, or 0 if there is no parent document.

function xml_attribute::previous_attribute

Synopsis

xml_attribute<Ch>* previous_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets previous attribute, optionally matching attribute name.

Parameters

name
Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found attribute, or 0 if not found.

function xml_attribute::next_attribute

Synopsis

xml_attribute<Ch>* next_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets next attribute, optionally matching attribute name.

Parameters

name
Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found attribute, or 0 if not found.

class + template + rapidxml::xml_base

+ + Defined in rapidxml.hpp
+ Base class for + xml_attribute xml_node

Description

Base class for xml_node and xml_attribute implementing common functions: name(), name_size(), value(), value_size() and parent().

Parameters

Ch
Character type to use

+ constructor + xml_base::xml_base

Synopsis

xml_base(); +

function xml_base::name

Synopsis

Ch* name() const; +

Description

Gets name of the node. Interpretation of name depends on type of node. Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.

+ Use name_size() function to determine length of the name.

Returns

Name of node, or empty string if node has no name.

function xml_base::name_size

Synopsis

std::size_t name_size() const; +

Description

Gets size of node name, not including terminator character. This function works correctly irrespective of whether name is or is not zero terminated.

Returns

Size of node name, in characters.

function xml_base::value

Synopsis

Ch* value() const; +

Description

Gets value of node. Interpretation of value depends on type of node. Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.

+ Use value_size() function to determine length of the value.

Returns

Value of node, or empty string if node has no value.

function xml_base::value_size

Synopsis

std::size_t value_size() const; +

Description

Gets size of node value, not including terminator character. This function works correctly irrespective of whether value is or is not zero terminated.

Returns

Size of node value, in characters.

function xml_base::name

Synopsis

void name(const Ch *name, std::size_t size); +

Description

Sets name of node to a non zero-terminated string. See Ownership Of Strings .

+ Note that node does not own its name or value, it only stores a pointer to it. It will not delete or otherwise free the pointer on destruction. It is reponsibility of the user to properly manage lifetime of the string. The easiest way to achieve it is to use memory_pool of the document to allocate the string - on destruction of the document the string will be automatically freed.

+ Size of name must be specified separately, because name does not have to be zero terminated. Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated).

Parameters

name
Name of node to set. Does not have to be zero terminated.
size
Size of name, in characters. This does not include zero terminator, if one is present.

function xml_base::name

Synopsis

void name(const Ch *name); +

Description

Sets name of node to a zero-terminated string. See also Ownership Of Strings and xml_node::name(const Ch *, std::size_t).

Parameters

name
Name of node to set. Must be zero terminated.

function xml_base::value

Synopsis

void value(const Ch *value, std::size_t size); +

Description

Sets value of node to a non zero-terminated string. See Ownership Of Strings .

+ Note that node does not own its name or value, it only stores a pointer to it. It will not delete or otherwise free the pointer on destruction. It is reponsibility of the user to properly manage lifetime of the string. The easiest way to achieve it is to use memory_pool of the document to allocate the string - on destruction of the document the string will be automatically freed.

+ Size of value must be specified separately, because it does not have to be zero terminated. Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated).

+ If an element has a child node of type node_data, it will take precedence over element value when printing. If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser.

Parameters

value
value of node to set. Does not have to be zero terminated.
size
Size of value, in characters. This does not include zero terminator, if one is present.

function xml_base::value

Synopsis

void value(const Ch *value); +

Description

Sets value of node to a zero-terminated string. See also Ownership Of Strings and xml_node::value(const Ch *, std::size_t).

Parameters

value
Vame of node to set. Must be zero terminated.

function xml_base::parent

Synopsis

xml_node<Ch>* parent() const; +

Description

Gets node parent.

Returns

Pointer to parent node, or 0 if there is no parent.

class + template + rapidxml::xml_document

+ + Defined in rapidxml.hpp
+ Inherits from + xml_node memory_pool

Description

This class represents root of the DOM hierarchy. It is also an xml_node and a memory_pool through public inheritance. Use parse() function to build a DOM tree from a zero-terminated XML text string. parse() function allocates memory for nodes and attributes by using functions of xml_document, which are inherited from memory_pool. To access root node of the document, use the document itself, as if it was an xml_node.

Parameters

Ch
Character type to use.

+ constructor + xml_document::xml_document

Synopsis

xml_document(); +

Description

Constructs empty XML document.

function xml_document::parse

Synopsis

void parse(Ch *text); +

Description

Parses zero-terminated XML string according to given flags. Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used. The string must persist for the lifetime of the document. In case of error, rapidxml::parse_error exception will be thrown.

+ If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning. Make sure that data is zero-terminated.

+ Document can be parsed into multiple times. Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool.

Parameters

text
XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser.

function xml_document::clear

Synopsis

void clear(); +

Description

Clears the document by deleting all nodes and clearing the memory pool. All nodes owned by document pool are destroyed.

class + template + rapidxml::xml_node

+ + Defined in rapidxml.hpp
+ Inherits from + xml_base
+ Base class for + xml_document

Description

Class representing a node of XML document. Each node may have associated name and value strings, which are available through name() and value() functions. Interpretation of name and value depends on type of the node. Type of node can be determined by using type() function.

+ Note that after parse, both name and value of node, if any, will point interior of source text used for parsing. Thus, this text must persist in the memory for the lifetime of node.

Parameters

Ch
Character type to use.

+ constructor + xml_node::xml_node

Synopsis

xml_node(node_type type); +

Description

Constructs an empty node with the specified type. Consider using memory_pool of appropriate document to allocate nodes manually.

Parameters

type
Type of node to construct.

function xml_node::type

Synopsis

node_type type() const; +

Description

Gets type of node.

Returns

Type of node.

function xml_node::document

Synopsis

xml_document<Ch>* document() const; +

Description

Gets document of which node is a child.

Returns

Pointer to document that contains this node, or 0 if there is no parent document.

function xml_node::first_node

Synopsis

xml_node<Ch>* first_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets first child node, optionally matching node name.

Parameters

name
Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found child, or 0 if not found.

function xml_node::last_node

Synopsis

xml_node<Ch>* last_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets last child node, optionally matching node name. Behaviour is undefined if node has no children. Use first_node() to test if node has children.

Parameters

name
Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found child, or 0 if not found.

function xml_node::previous_sibling

Synopsis

xml_node<Ch>* previous_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets previous sibling node, optionally matching node name. Behaviour is undefined if node has no parent. Use parent() to test if node has a parent.

Parameters

name
Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found sibling, or 0 if not found.

function xml_node::next_sibling

Synopsis

xml_node<Ch>* next_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets next sibling node, optionally matching node name. Behaviour is undefined if node has no parent. Use parent() to test if node has a parent.

Parameters

name
Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found sibling, or 0 if not found.

function xml_node::first_attribute

Synopsis

xml_attribute<Ch>* first_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets first attribute of node, optionally matching attribute name.

Parameters

name
Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found attribute, or 0 if not found.

function xml_node::last_attribute

Synopsis

xml_attribute<Ch>* last_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets last attribute of node, optionally matching attribute name.

Parameters

name
Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found attribute, or 0 if not found.

function xml_node::type

Synopsis

void type(node_type type); +

Description

Sets type of node.

Parameters

type
Type of node to set.

function xml_node::prepend_node

Synopsis

void prepend_node(xml_node< Ch > *child); +

Description

Prepends a new child node. The prepended child becomes the first child, and all existing children are moved one position back.

Parameters

child
Node to prepend.

function xml_node::append_node

Synopsis

void append_node(xml_node< Ch > *child); +

Description

Appends a new child node. The appended child becomes the last child.

Parameters

child
Node to append.

function xml_node::insert_node

Synopsis

void insert_node(xml_node< Ch > *where, xml_node< Ch > *child); +

Description

Inserts a new child node at specified place inside the node. All children after and including the specified node are moved one position back.

Parameters

where
Place where to insert the child, or 0 to insert at the back.
child
Node to insert.

function xml_node::remove_first_node

Synopsis

void remove_first_node(); +

Description

Removes first child node. If node has no children, behaviour is undefined. Use first_node() to test if node has children.

function xml_node::remove_last_node

Synopsis

void remove_last_node(); +

Description

Removes last child of the node. If node has no children, behaviour is undefined. Use first_node() to test if node has children.

function xml_node::remove_node

Synopsis

void remove_node(xml_node< Ch > *where); +

Description

Removes specified child from the node.

function xml_node::remove_all_nodes

Synopsis

void remove_all_nodes(); +

Description

Removes all child nodes (but not attributes).

function xml_node::prepend_attribute

Synopsis

void prepend_attribute(xml_attribute< Ch > *attribute); +

Description

Prepends a new attribute to the node.

Parameters

attribute
Attribute to prepend.

function xml_node::append_attribute

Synopsis

void append_attribute(xml_attribute< Ch > *attribute); +

Description

Appends a new attribute to the node.

Parameters

attribute
Attribute to append.

function xml_node::insert_attribute

Synopsis

void insert_attribute(xml_attribute< Ch > *where, xml_attribute< Ch > *attribute); +

Description

Inserts a new attribute at specified place inside the node. All attributes after and including the specified attribute are moved one position back.

Parameters

where
Place where to insert the attribute, or 0 to insert at the back.
attribute
Attribute to insert.

function xml_node::remove_first_attribute

Synopsis

void remove_first_attribute(); +

Description

Removes first attribute of the node. If node has no attributes, behaviour is undefined. Use first_attribute() to test if node has attributes.

function xml_node::remove_last_attribute

Synopsis

void remove_last_attribute(); +

Description

Removes last attribute of the node. If node has no attributes, behaviour is undefined. Use first_attribute() to test if node has attributes.

function xml_node::remove_attribute

Synopsis

void remove_attribute(xml_attribute< Ch > *where); +

Description

Removes specified attribute from node.

Parameters

where
Pointer to attribute to be removed.

function xml_node::remove_all_attributes

Synopsis

void remove_all_attributes(); +

Description

Removes all attributes of node.

enum node_type

Description

Enumeration listing all node types produced by the parser. Use xml_node::type() function to query node type.

Values

node_document
A document node. Name and value are empty.
node_element
An element node. Name contains element name. Value contains text of first data node.
node_data
A data node. Name is empty. Value contains data text.
node_cdata
A CDATA node. Name is empty. Value contains data text.
node_comment
A comment node. Name is empty. Value contains comment text.
node_declaration
A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes.
node_doctype
A DOCTYPE node. Name is empty. Value contains DOCTYPE text.
node_pi
A PI node. Name contains target. Value contains instructions.

function parse_error_handler

Synopsis

void rapidxml::parse_error_handler(const char *what, void *where); +

Description

When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function is called to notify user about the error. It must be defined by the user.

+ This function cannot return. If it does, the results are undefined.

+ A very simple definition might look like that: + void rapidxml::parse_error_handler(const char *what, void *where) + { + std::cout << "Parse error: " << what << "\n"; + std::abort(); + } +

Parameters

what
Human readable description of the error.
where
Pointer to character data where error was detected.

function print

Synopsis

OutIt rapidxml::print(OutIt out, const xml_node< Ch > &node, int flags=0); +

Description

Prints XML to given output iterator.

Parameters

out
Output iterator to print to.
node
Node to be printed. Pass xml_document to print entire document.
flags
Flags controlling how XML is printed.

Returns

Output iterator pointing to position immediately after last character of printed text.

function print

Synopsis

std::basic_ostream<Ch>& rapidxml::print(std::basic_ostream< Ch > &out, const xml_node< Ch > &node, int flags=0); +

Description

Prints XML to given output stream.

Parameters

out
Output stream to print to.
node
Node to be printed. Pass xml_document to print entire document.
flags
Flags controlling how XML is printed.

Returns

Output stream.

function operator<<

Synopsis

std::basic_ostream<Ch>& rapidxml::operator<<(std::basic_ostream< Ch > &out, const xml_node< Ch > &node); +

Description

Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.

Parameters

out
Output stream to print to.
node
Node to be printed.

Returns

Output stream.

+ constant + parse_no_data_nodes

Synopsis

const int parse_no_data_nodes + = 0x1; +

Description

Parse flag instructing the parser to not create data nodes. Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_no_element_values

Synopsis

const int parse_no_element_values + = 0x2; +

Description

Parse flag instructing the parser to not use text of first data node as a value of parent element. Can be combined with other flags by use of | operator. Note that child data nodes of element node take precendence over its value when printing. That is, if element has one or more child data nodes and a value, the value will be ignored. Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements.

+ See xml_document::parse() function.

+ constant + parse_no_string_terminators

Synopsis

const int parse_no_string_terminators + = 0x4; +

Description

Parse flag instructing the parser to not place zero terminators after strings in the source text. By default zero terminators are placed, modifying source text. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_no_entity_translation

Synopsis

const int parse_no_entity_translation + = 0x8; +

Description

Parse flag instructing the parser to not translate entities in the source text. By default entities are translated, modifying source text. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_no_utf8

Synopsis

const int parse_no_utf8 + = 0x10; +

Description

Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters. By default, UTF-8 handling is enabled. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_declaration_node

Synopsis

const int parse_declaration_node + = 0x20; +

Description

Parse flag instructing the parser to create XML declaration node. By default, declaration node is not created. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_comment_nodes

Synopsis

const int parse_comment_nodes + = 0x40; +

Description

Parse flag instructing the parser to create comments nodes. By default, comment nodes are not created. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_doctype_node

Synopsis

const int parse_doctype_node + = 0x80; +

Description

Parse flag instructing the parser to create DOCTYPE node. By default, doctype node is not created. Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_pi_nodes

Synopsis

const int parse_pi_nodes + = 0x100; +

Description

Parse flag instructing the parser to create PI nodes. By default, PI nodes are not created. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_validate_closing_tags

Synopsis

const int parse_validate_closing_tags + = 0x200; +

Description

Parse flag instructing the parser to validate closing tag names. If not set, name inside closing tag is irrelevant to the parser. By default, closing tags are not validated. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_trim_whitespace

Synopsis

const int parse_trim_whitespace + = 0x400; +

Description

Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes. By default, whitespace is not trimmed. This flag does not cause the parser to modify source text. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_normalize_whitespace

Synopsis

const int parse_normalize_whitespace + = 0x800; +

Description

Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character. Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag. By default, whitespace is not normalized. If this flag is specified, source text will be modified. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_default

Synopsis

const int parse_default + = 0; +

Description

Parse flags which represent default behaviour of the parser. This is always equal to 0, so that all other flags can be simply ored together. Normally there is no need to inconveniently disable flags by anding with their negated (~) values. This also means that meaning of each flag is a negation of the default setting. For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is enabled by default, and using the flag will disable it.

+ See xml_document::parse() function.

+ constant + parse_non_destructive

Synopsis

const int parse_non_destructive + = parse_no_string_terminators | parse_no_entity_translation; +

Description

A combination of parse flags that forbids any modifications of the source text. This also results in faster parsing. However, note that the following will occur:
  • names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends
  • entities will not be translated
  • whitespace will not be normalized
+See xml_document::parse() function.

+ constant + parse_fastest

Synopsis

const int parse_fastest + = parse_non_destructive | parse_no_data_nodes; +

Description

A combination of parse flags resulting in fastest possible parsing, without sacrificing important data.

+ See xml_document::parse() function.

+ constant + parse_full

Synopsis

const int parse_full + = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags; +

Description

A combination of parse flags resulting in largest amount of data being extracted. This usually results in slowest parsing.

+ See xml_document::parse() function.

+ constant + print_no_indenting

Synopsis

const int print_no_indenting + = 0x1; +

Description

Printer flag instructing the printer to suppress indenting of XML. See print() function.

\ No newline at end of file diff --git a/src/lib/rapidxml/rapidxml.hpp b/src/lib/rapidxml/rapidxml.hpp new file mode 100644 index 0000000..ae91e08 --- /dev/null +++ b/src/lib/rapidxml/rapidxml.hpp @@ -0,0 +1,2596 @@ +#ifndef RAPIDXML_HPP_INCLUDED +#define RAPIDXML_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml.hpp This file contains rapidxml parser and DOM implementation + +// If standard library is disabled, user must provide implementations of required functions and typedefs +#if !defined(RAPIDXML_NO_STDLIB) + #include // For std::size_t + #include // For assert + #include // For placement new +#endif + +// On MSVC, disable "conditional expression is constant" warning (level 4). +// This warning is almost impossible to avoid with certain types of templated code +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4127) // Conditional expression is constant +#endif + +/////////////////////////////////////////////////////////////////////////// +// RAPIDXML_PARSE_ERROR + +#if defined(RAPIDXML_NO_EXCEPTIONS) + +#define RAPIDXML_PARSE_ERROR(what, where) { parse_error_handler(what, where); assert(0); } + +namespace rapidxml +{ + //! When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, + //! this function is called to notify user about the error. + //! It must be defined by the user. + //!

+ //! This function cannot return. If it does, the results are undefined. + //!

+ //! A very simple definition might look like that: + //!

+    //! void %rapidxml::%parse_error_handler(const char *what, void *where)
+    //! {
+    //!     std::cout << "Parse error: " << what << "\n";
+    //!     std::abort();
+    //! }
+    //! 
+ //! \param what Human readable description of the error. + //! \param where Pointer to character data where error was detected. + void parse_error_handler(const char *what, void *where); +} + +#else + +#include // For std::exception + +#define RAPIDXML_PARSE_ERROR(what, where) throw parse_error(what, where) + +namespace rapidxml +{ + + //! Parse error exception. + //! This exception is thrown by the parser when an error occurs. + //! Use what() function to get human-readable error message. + //! Use where() function to get a pointer to position within source text where error was detected. + //!

+ //! If throwing exceptions by the parser is undesirable, + //! it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included. + //! This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception. + //! This function must be defined by the user. + //!

+ //! This class derives from std::exception class. + class parse_error: public std::exception + { + + public: + + //! Constructs parse error + parse_error(const char *what, void *where) + : m_what(what) + , m_where(where) + { + } + + //! Gets human readable description of error. + //! \return Pointer to null terminated description of the error. + virtual const char *what() const throw() + { + return m_what; + } + + //! Gets pointer to character data where error happened. + //! Ch should be the same as char type of xml_document that produced the error. + //! \return Pointer to location within the parsed string where error occured. + template + Ch *where() const + { + return reinterpret_cast(m_where); + } + + private: + + const char *m_what; + void *m_where; + + }; +} + +#endif + +/////////////////////////////////////////////////////////////////////////// +// Pool sizes + +#ifndef RAPIDXML_STATIC_POOL_SIZE + // Size of static memory block of memory_pool. + // Define RAPIDXML_STATIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value. + // No dynamic memory allocations are performed by memory_pool until static memory is exhausted. + #define RAPIDXML_STATIC_POOL_SIZE (64 * 1024) +#endif + +#ifndef RAPIDXML_DYNAMIC_POOL_SIZE + // Size of dynamic memory block of memory_pool. + // Define RAPIDXML_DYNAMIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value. + // After the static block is exhausted, dynamic blocks with approximately this size are allocated by memory_pool. + #define RAPIDXML_DYNAMIC_POOL_SIZE (64 * 1024) +#endif + +#ifndef RAPIDXML_ALIGNMENT + // Memory allocation alignment. + // Define RAPIDXML_ALIGNMENT before including rapidxml.hpp if you want to override the default value, which is the size of pointer. + // All memory allocations for nodes, attributes and strings will be aligned to this value. + // This must be a power of 2 and at least 1, otherwise memory_pool will not work. + #define RAPIDXML_ALIGNMENT sizeof(void *) +#endif + +namespace rapidxml +{ + // Forward declarations + template class xml_node; + template class xml_attribute; + template class xml_document; + + //! Enumeration listing all node types produced by the parser. + //! Use xml_node::type() function to query node type. + enum node_type + { + node_document, //!< A document node. Name and value are empty. + node_element, //!< An element node. Name contains element name. Value contains text of first data node. + node_data, //!< A data node. Name is empty. Value contains data text. + node_cdata, //!< A CDATA node. Name is empty. Value contains data text. + node_comment, //!< A comment node. Name is empty. Value contains comment text. + node_declaration, //!< A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes. + node_doctype, //!< A DOCTYPE node. Name is empty. Value contains DOCTYPE text. + node_pi //!< A PI node. Name contains target. Value contains instructions. + }; + + /////////////////////////////////////////////////////////////////////// + // Parsing flags + + //! Parse flag instructing the parser to not create data nodes. + //! Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_no_data_nodes = 0x1; + + //! Parse flag instructing the parser to not use text of first data node as a value of parent element. + //! Can be combined with other flags by use of | operator. + //! Note that child data nodes of element node take precendence over its value when printing. + //! That is, if element has one or more child data nodes and a value, the value will be ignored. + //! Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements. + //!

+ //! See xml_document::parse() function. + const int parse_no_element_values = 0x2; + + //! Parse flag instructing the parser to not place zero terminators after strings in the source text. + //! By default zero terminators are placed, modifying source text. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_no_string_terminators = 0x4; + + //! Parse flag instructing the parser to not translate entities in the source text. + //! By default entities are translated, modifying source text. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_no_entity_translation = 0x8; + + //! Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters. + //! By default, UTF-8 handling is enabled. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_no_utf8 = 0x10; + + //! Parse flag instructing the parser to create XML declaration node. + //! By default, declaration node is not created. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_declaration_node = 0x20; + + //! Parse flag instructing the parser to create comments nodes. + //! By default, comment nodes are not created. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_comment_nodes = 0x40; + + //! Parse flag instructing the parser to create DOCTYPE node. + //! By default, doctype node is not created. + //! Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_doctype_node = 0x80; + + //! Parse flag instructing the parser to create PI nodes. + //! By default, PI nodes are not created. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_pi_nodes = 0x100; + + //! Parse flag instructing the parser to validate closing tag names. + //! If not set, name inside closing tag is irrelevant to the parser. + //! By default, closing tags are not validated. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_validate_closing_tags = 0x200; + + //! Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes. + //! By default, whitespace is not trimmed. + //! This flag does not cause the parser to modify source text. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_trim_whitespace = 0x400; + + //! Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character. + //! Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag. + //! By default, whitespace is not normalized. + //! If this flag is specified, source text will be modified. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_normalize_whitespace = 0x800; + + // Compound flags + + //! Parse flags which represent default behaviour of the parser. + //! This is always equal to 0, so that all other flags can be simply ored together. + //! Normally there is no need to inconveniently disable flags by anding with their negated (~) values. + //! This also means that meaning of each flag is a negation of the default setting. + //! For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is enabled by default, + //! and using the flag will disable it. + //!

+ //! See xml_document::parse() function. + const int parse_default = 0; + + //! A combination of parse flags that forbids any modifications of the source text. + //! This also results in faster parsing. However, note that the following will occur: + //!
    + //!
  • names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends
  • + //!
  • entities will not be translated
  • + //!
  • whitespace will not be normalized
  • + //!
+ //! See xml_document::parse() function. + const int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation; + + //! A combination of parse flags resulting in fastest possible parsing, without sacrificing important data. + //!

+ //! See xml_document::parse() function. + const int parse_fastest = parse_non_destructive | parse_no_data_nodes; + + //! A combination of parse flags resulting in largest amount of data being extracted. + //! This usually results in slowest parsing. + //!

+ //! See xml_document::parse() function. + const int parse_full = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags; + + /////////////////////////////////////////////////////////////////////// + // Internals + + //! \cond internal + namespace internal + { + + // Struct that contains lookup tables for the parser + // It must be a template to allow correct linking (because it has static data members, which are defined in a header file). + template + struct lookup_tables + { + static const unsigned char lookup_whitespace[256]; // Whitespace table + static const unsigned char lookup_node_name[256]; // Node name table + static const unsigned char lookup_text[256]; // Text table + static const unsigned char lookup_text_pure_no_ws[256]; // Text table + static const unsigned char lookup_text_pure_with_ws[256]; // Text table + static const unsigned char lookup_attribute_name[256]; // Attribute name table + static const unsigned char lookup_attribute_data_1[256]; // Attribute data table with single quote + static const unsigned char lookup_attribute_data_1_pure[256]; // Attribute data table with single quote + static const unsigned char lookup_attribute_data_2[256]; // Attribute data table with double quotes + static const unsigned char lookup_attribute_data_2_pure[256]; // Attribute data table with double quotes + static const unsigned char lookup_digits[256]; // Digits + static const unsigned char lookup_upcase[256]; // To uppercase conversion table for ASCII characters + }; + + // Find length of the string + template + inline std::size_t measure(const Ch *p) + { + const Ch *tmp = p; + while (*tmp) + ++tmp; + return tmp - p; + } + + // Compare strings for equality + template + inline bool compare(const Ch *p1, std::size_t size1, const Ch *p2, std::size_t size2, bool case_sensitive) + { + if (size1 != size2) + return false; + if (case_sensitive) + { + for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2) + if (*p1 != *p2) + return false; + } + else + { + for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2) + if (lookup_tables<0>::lookup_upcase[static_cast(*p1)] != lookup_tables<0>::lookup_upcase[static_cast(*p2)]) + return false; + } + return true; + } + } + //! \endcond + + /////////////////////////////////////////////////////////////////////// + // Memory pool + + //! This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation. + //! In most cases, you will not need to use this class directly. + //! However, if you need to create nodes manually or modify names/values of nodes, + //! you are encouraged to use memory_pool of relevant xml_document to allocate the memory. + //! Not only is this faster than allocating them by using new operator, + //! but also their lifetime will be tied to the lifetime of document, + //! possibly simplyfing memory management. + //!

+ //! Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool. + //! You can also call allocate_string() function to allocate strings. + //! Such strings can then be used as names or values of nodes without worrying about their lifetime. + //! Note that there is no free() function -- all allocations are freed at once when clear() function is called, + //! or when the pool is destroyed. + //!

+ //! It is also possible to create a standalone memory_pool, and use it + //! to allocate nodes, whose lifetime will not be tied to any document. + //!

+ //! Pool maintains RAPIDXML_STATIC_POOL_SIZE bytes of statically allocated memory. + //! Until static memory is exhausted, no dynamic memory allocations are done. + //! When static memory is exhausted, pool allocates additional blocks of memory of size RAPIDXML_DYNAMIC_POOL_SIZE each, + //! by using global new[] and delete[] operators. + //! This behaviour can be changed by setting custom allocation routines. + //! Use set_allocator() function to set them. + //!

+ //! Allocations for nodes, attributes and strings are aligned at RAPIDXML_ALIGNMENT bytes. + //! This value defaults to the size of pointer on target architecture. + //!

+ //! To obtain absolutely top performance from the parser, + //! it is important that all nodes are allocated from a single, contiguous block of memory. + //! Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably. + //! If required, you can tweak RAPIDXML_STATIC_POOL_SIZE, RAPIDXML_DYNAMIC_POOL_SIZE and RAPIDXML_ALIGNMENT + //! to obtain best wasted memory to performance compromise. + //! To do it, define their values before rapidxml.hpp file is included. + //! \param Ch Character type of created nodes. + template + class memory_pool + { + + public: + + //! \cond internal + typedef void *(alloc_func)(std::size_t); // Type of user-defined function used to allocate memory + typedef void (free_func)(void *); // Type of user-defined function used to free memory + //! \endcond + + //! Constructs empty pool with default allocator functions. + memory_pool() + : m_alloc_func(0) + , m_free_func(0) + { + init(); + } + + //! Destroys pool and frees all the memory. + //! This causes memory occupied by nodes allocated by the pool to be freed. + //! Nodes allocated from the pool are no longer valid. + ~memory_pool() + { + clear(); + } + + //! Allocates a new node from the pool, and optionally assigns name and value to it. + //! If the allocation request cannot be accomodated, this function will throw std::bad_alloc. + //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function + //! will call rapidxml::parse_error_handler() function. + //! \param type Type of node to create. + //! \param name Name to assign to the node, or 0 to assign no name. + //! \param value Value to assign to the node, or 0 to assign no value. + //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string. + //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string. + //! \return Pointer to allocated node. This pointer will never be NULL. + xml_node *allocate_node(node_type type, + const Ch *name = 0, const Ch *value = 0, + std::size_t name_size = 0, std::size_t value_size = 0) + { + void *memory = allocate_aligned(sizeof(xml_node)); + xml_node *node = new(memory) xml_node(type); + if (name) + { + if (name_size > 0) + node->name(name, name_size); + else + node->name(name); + } + if (value) + { + if (value_size > 0) + node->value(value, value_size); + else + node->value(value); + } + return node; + } + + //! Allocates a new attribute from the pool, and optionally assigns name and value to it. + //! If the allocation request cannot be accomodated, this function will throw std::bad_alloc. + //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function + //! will call rapidxml::parse_error_handler() function. + //! \param name Name to assign to the attribute, or 0 to assign no name. + //! \param value Value to assign to the attribute, or 0 to assign no value. + //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string. + //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string. + //! \return Pointer to allocated attribute. This pointer will never be NULL. + xml_attribute *allocate_attribute(const Ch *name = 0, const Ch *value = 0, + std::size_t name_size = 0, std::size_t value_size = 0) + { + void *memory = allocate_aligned(sizeof(xml_attribute)); + xml_attribute *attribute = new(memory) xml_attribute; + if (name) + { + if (name_size > 0) + attribute->name(name, name_size); + else + attribute->name(name); + } + if (value) + { + if (value_size > 0) + attribute->value(value, value_size); + else + attribute->value(value); + } + return attribute; + } + + //! Allocates a char array of given size from the pool, and optionally copies a given string to it. + //! If the allocation request cannot be accomodated, this function will throw std::bad_alloc. + //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function + //! will call rapidxml::parse_error_handler() function. + //! \param source String to initialize the allocated memory with, or 0 to not initialize it. + //! \param size Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated. + //! \return Pointer to allocated char array. This pointer will never be NULL. + Ch *allocate_string(const Ch *source = 0, std::size_t size = 0) + { + assert(source || size); // Either source or size (or both) must be specified + if (size == 0) + size = internal::measure(source) + 1; + Ch *result = static_cast(allocate_aligned(size * sizeof(Ch))); + if (source) + for (std::size_t i = 0; i < size; ++i) + result[i] = source[i]; + return result; + } + + //! Clones an xml_node and its hierarchy of child nodes and attributes. + //! Nodes and attributes are allocated from this memory pool. + //! Names and values are not cloned, they are shared between the clone and the source. + //! Result node can be optionally specified as a second parameter, + //! in which case its contents will be replaced with cloned source node. + //! This is useful when you want to clone entire document. + //! \param source Node to clone. + //! \param result Node to put results in, or 0 to automatically allocate result node + //! \return Pointer to cloned node. This pointer will never be NULL. + xml_node *clone_node(const xml_node *source, xml_node *result = 0) + { + // Prepare result node + if (result) + { + result->remove_all_attributes(); + result->remove_all_nodes(); + result->type(source->type()); + } + else + result = allocate_node(source->type()); + + // Clone name and value + result->name(source->name(), source->name_size()); + result->value(source->value(), source->value_size()); + + // Clone child nodes and attributes + for (xml_node *child = source->first_node(); child; child = child->next_sibling()) + result->append_node(clone_node(child)); + for (xml_attribute *attr = source->first_attribute(); attr; attr = attr->next_attribute()) + result->append_attribute(allocate_attribute(attr->name(), attr->value(), attr->name_size(), attr->value_size())); + + return result; + } + + //! Clears the pool. + //! This causes memory occupied by nodes allocated by the pool to be freed. + //! Any nodes or strings allocated from the pool will no longer be valid. + void clear() + { + while (m_begin != m_static_memory) + { + char *previous_begin = reinterpret_cast
(align(m_begin))->previous_begin; + if (m_free_func) + m_free_func(m_begin); + else + delete[] m_begin; + m_begin = previous_begin; + } + init(); + } + + //! Sets or resets the user-defined memory allocation functions for the pool. + //! This can only be called when no memory is allocated from the pool yet, otherwise results are undefined. + //! Allocation function must not return invalid pointer on failure. It should either throw, + //! stop the program, or use longjmp() function to pass control to other place of program. + //! If it returns invalid pointer, results are undefined. + //!

+ //! User defined allocation functions must have the following forms: + //!
+ //!
void *allocate(std::size_t size); + //!
void free(void *pointer); + //!

+ //! \param af Allocation function, or 0 to restore default function + //! \param ff Free function, or 0 to restore default function + void set_allocator(alloc_func *af, free_func *ff) + { + assert(m_begin == m_static_memory && m_ptr == align(m_begin)); // Verify that no memory is allocated yet + m_alloc_func = af; + m_free_func = ff; + } + + private: + + struct header + { + char *previous_begin; + }; + + void init() + { + m_begin = m_static_memory; + m_ptr = align(m_begin); + m_end = m_static_memory + sizeof(m_static_memory); + } + + char *align(char *ptr) + { + std::size_t alignment = ((RAPIDXML_ALIGNMENT - (std::size_t(ptr) & (RAPIDXML_ALIGNMENT - 1))) & (RAPIDXML_ALIGNMENT - 1)); + return ptr + alignment; + } + + char *allocate_raw(std::size_t size) + { + // Allocate + void *memory; + if (m_alloc_func) // Allocate memory using either user-specified allocation function or global operator new[] + { + memory = m_alloc_func(size); + assert(memory); // Allocator is not allowed to return 0, on failure it must either throw, stop the program or use longjmp + } + else + { + memory = new char[size]; +#ifdef RAPIDXML_NO_EXCEPTIONS + if (!memory) // If exceptions are disabled, verify memory allocation, because new will not be able to throw bad_alloc + RAPIDXML_PARSE_ERROR("out of memory", 0); +#endif + } + return static_cast(memory); + } + + void *allocate_aligned(std::size_t size) + { + // Calculate aligned pointer + char *result = align(m_ptr); + + // If not enough memory left in current pool, allocate a new pool + if (result + size > m_end) + { + // Calculate required pool size (may be bigger than RAPIDXML_DYNAMIC_POOL_SIZE) + std::size_t pool_size = RAPIDXML_DYNAMIC_POOL_SIZE; + if (pool_size < size) + pool_size = size; + + // Allocate + std::size_t alloc_size = sizeof(header) + (2 * RAPIDXML_ALIGNMENT - 2) + pool_size; // 2 alignments required in worst case: one for header, one for actual allocation + char *raw_memory = allocate_raw(alloc_size); + + // Setup new pool in allocated memory + char *pool = align(raw_memory); + header *new_header = reinterpret_cast
(pool); + new_header->previous_begin = m_begin; + m_begin = raw_memory; + m_ptr = pool + sizeof(header); + m_end = raw_memory + alloc_size; + + // Calculate aligned pointer again using new pool + result = align(m_ptr); + } + + // Update pool and return aligned pointer + m_ptr = result + size; + return result; + } + + char *m_begin; // Start of raw memory making up current pool + char *m_ptr; // First free byte in current pool + char *m_end; // One past last available byte in current pool + char m_static_memory[RAPIDXML_STATIC_POOL_SIZE]; // Static raw memory + alloc_func *m_alloc_func; // Allocator function, or 0 if default is to be used + free_func *m_free_func; // Free function, or 0 if default is to be used + }; + + /////////////////////////////////////////////////////////////////////////// + // XML base + + //! Base class for xml_node and xml_attribute implementing common functions: + //! name(), name_size(), value(), value_size() and parent(). + //! \param Ch Character type to use + template + class xml_base + { + + public: + + /////////////////////////////////////////////////////////////////////////// + // Construction & destruction + + // Construct a base with empty name, value and parent + xml_base() + : m_name(0) + , m_value(0) + , m_parent(0) + { + } + + /////////////////////////////////////////////////////////////////////////// + // Node data access + + //! Gets name of the node. + //! Interpretation of name depends on type of node. + //! Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse. + //!

+ //! Use name_size() function to determine length of the name. + //! \return Name of node, or empty string if node has no name. + Ch *name() const + { + return m_name ? m_name : nullstr(); + } + + //! Gets size of node name, not including terminator character. + //! This function works correctly irrespective of whether name is or is not zero terminated. + //! \return Size of node name, in characters. + std::size_t name_size() const + { + return m_name ? m_name_size : 0; + } + + //! Gets value of node. + //! Interpretation of value depends on type of node. + //! Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse. + //!

+ //! Use value_size() function to determine length of the value. + //! \return Value of node, or empty string if node has no value. + Ch *value() const + { + return m_value ? m_value : nullstr(); + } + + //! Gets size of node value, not including terminator character. + //! This function works correctly irrespective of whether value is or is not zero terminated. + //! \return Size of node value, in characters. + std::size_t value_size() const + { + return m_value ? m_value_size : 0; + } + + /////////////////////////////////////////////////////////////////////////// + // Node modification + + //! Sets name of node to a non zero-terminated string. + //! See \ref ownership_of_strings. + //!

+ //! Note that node does not own its name or value, it only stores a pointer to it. + //! It will not delete or otherwise free the pointer on destruction. + //! It is reponsibility of the user to properly manage lifetime of the string. + //! The easiest way to achieve it is to use memory_pool of the document to allocate the string - + //! on destruction of the document the string will be automatically freed. + //!

+ //! Size of name must be specified separately, because name does not have to be zero terminated. + //! Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated). + //! \param name Name of node to set. Does not have to be zero terminated. + //! \param size Size of name, in characters. This does not include zero terminator, if one is present. + void name(const Ch *name, std::size_t size) + { + m_name = const_cast(name); + m_name_size = size; + } + + //! Sets name of node to a zero-terminated string. + //! See also \ref ownership_of_strings and xml_node::name(const Ch *, std::size_t). + //! \param name Name of node to set. Must be zero terminated. + void name(const Ch *name) + { + this->name(name, internal::measure(name)); + } + + //! Sets value of node to a non zero-terminated string. + //! See \ref ownership_of_strings. + //!

+ //! Note that node does not own its name or value, it only stores a pointer to it. + //! It will not delete or otherwise free the pointer on destruction. + //! It is reponsibility of the user to properly manage lifetime of the string. + //! The easiest way to achieve it is to use memory_pool of the document to allocate the string - + //! on destruction of the document the string will be automatically freed. + //!

+ //! Size of value must be specified separately, because it does not have to be zero terminated. + //! Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated). + //!

+ //! If an element has a child node of type node_data, it will take precedence over element value when printing. + //! If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser. + //! \param value value of node to set. Does not have to be zero terminated. + //! \param size Size of value, in characters. This does not include zero terminator, if one is present. + void value(const Ch *value, std::size_t size) + { + m_value = const_cast(value); + m_value_size = size; + } + + //! Sets value of node to a zero-terminated string. + //! See also \ref ownership_of_strings and xml_node::value(const Ch *, std::size_t). + //! \param value Vame of node to set. Must be zero terminated. + void value(const Ch *value) + { + this->value(value, internal::measure(value)); + } + + /////////////////////////////////////////////////////////////////////////// + // Related nodes access + + //! Gets node parent. + //! \return Pointer to parent node, or 0 if there is no parent. + xml_node *parent() const + { + return m_parent; + } + + protected: + + // Return empty string + static Ch *nullstr() + { + static Ch zero = Ch('\0'); + return &zero; + } + + Ch *m_name; // Name of node, or 0 if no name + Ch *m_value; // Value of node, or 0 if no value + std::size_t m_name_size; // Length of node name, or undefined of no name + std::size_t m_value_size; // Length of node value, or undefined if no value + xml_node *m_parent; // Pointer to parent node, or 0 if none + + }; + + //! Class representing attribute node of XML document. + //! Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base). + //! Note that after parse, both name and value of attribute will point to interior of source text used for parsing. + //! Thus, this text must persist in memory for the lifetime of attribute. + //! \param Ch Character type to use. + template + class xml_attribute: public xml_base + { + + friend class xml_node; + + public: + + /////////////////////////////////////////////////////////////////////////// + // Construction & destruction + + //! Constructs an empty attribute with the specified type. + //! Consider using memory_pool of appropriate xml_document if allocating attributes manually. + xml_attribute() + { + } + + /////////////////////////////////////////////////////////////////////////// + // Related nodes access + + //! Gets document of which attribute is a child. + //! \return Pointer to document that contains this attribute, or 0 if there is no parent document. + xml_document *document() const + { + if (xml_node *node = this->parent()) + { + while (node->parent()) + node = node->parent(); + return node->type() == node_document ? static_cast *>(node) : 0; + } + else + return 0; + } + + //! Gets previous attribute, optionally matching attribute name. + //! \param name Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found attribute, or 0 if not found. + xml_attribute *previous_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_attribute *attribute = m_prev_attribute; attribute; attribute = attribute->m_prev_attribute) + if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } + else + return this->m_parent ? m_prev_attribute : 0; + } + + //! Gets next attribute, optionally matching attribute name. + //! \param name Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found attribute, or 0 if not found. + xml_attribute *next_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_attribute *attribute = m_next_attribute; attribute; attribute = attribute->m_next_attribute) + if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } + else + return this->m_parent ? m_next_attribute : 0; + } + + private: + + xml_attribute *m_prev_attribute; // Pointer to previous sibling of attribute, or 0 if none; only valid if parent is non-zero + xml_attribute *m_next_attribute; // Pointer to next sibling of attribute, or 0 if none; only valid if parent is non-zero + + }; + + /////////////////////////////////////////////////////////////////////////// + // XML node + + //! Class representing a node of XML document. + //! Each node may have associated name and value strings, which are available through name() and value() functions. + //! Interpretation of name and value depends on type of the node. + //! Type of node can be determined by using type() function. + //!

+ //! Note that after parse, both name and value of node, if any, will point interior of source text used for parsing. + //! Thus, this text must persist in the memory for the lifetime of node. + //! \param Ch Character type to use. + template + class xml_node: public xml_base + { + + public: + + /////////////////////////////////////////////////////////////////////////// + // Construction & destruction + + //! Constructs an empty node with the specified type. + //! Consider using memory_pool of appropriate document to allocate nodes manually. + //! \param type Type of node to construct. + xml_node(node_type type) + : m_type(type) + , m_first_node(0) + , m_first_attribute(0) + { + } + + /////////////////////////////////////////////////////////////////////////// + // Node data access + + //! Gets type of node. + //! \return Type of node. + node_type type() const + { + return m_type; + } + + /////////////////////////////////////////////////////////////////////////// + // Related nodes access + + //! Gets document of which node is a child. + //! \return Pointer to document that contains this node, or 0 if there is no parent document. + xml_document *document() const + { + xml_node *node = const_cast *>(this); + while (node->parent()) + node = node->parent(); + return node->type() == node_document ? static_cast *>(node) : 0; + } + + //! Gets first child node, optionally matching node name. + //! \param name Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found child, or 0 if not found. + xml_node *first_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_node *child = m_first_node; child; child = child->next_sibling()) + if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive)) + return child; + return 0; + } + else + return m_first_node; + } + + //! Gets last child node, optionally matching node name. + //! Behaviour is undefined if node has no children. + //! Use first_node() to test if node has children. + //! \param name Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found child, or 0 if not found. + xml_node *last_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + assert(m_first_node); // Cannot query for last child if node has no children + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_node *child = m_last_node; child; child = child->previous_sibling()) + if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive)) + return child; + return 0; + } + else + return m_last_node; + } + + //! Gets previous sibling node, optionally matching node name. + //! Behaviour is undefined if node has no parent. + //! Use parent() to test if node has a parent. + //! \param name Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found sibling, or 0 if not found. + xml_node *previous_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + assert(this->m_parent); // Cannot query for siblings if node has no parent + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_node *sibling = m_prev_sibling; sibling; sibling = sibling->m_prev_sibling) + if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive)) + return sibling; + return 0; + } + else + return m_prev_sibling; + } + + //! Gets next sibling node, optionally matching node name. + //! Behaviour is undefined if node has no parent. + //! Use parent() to test if node has a parent. + //! \param name Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found sibling, or 0 if not found. + xml_node *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + assert(this->m_parent); // Cannot query for siblings if node has no parent + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_node *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling) + if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive)) + return sibling; + return 0; + } + else + return m_next_sibling; + } + + //! Gets first attribute of node, optionally matching attribute name. + //! \param name Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found attribute, or 0 if not found. + xml_attribute *first_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_attribute *attribute = m_first_attribute; attribute; attribute = attribute->m_next_attribute) + if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } + else + return m_first_attribute; + } + + //! Gets last attribute of node, optionally matching attribute name. + //! \param name Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found attribute, or 0 if not found. + xml_attribute *last_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_attribute *attribute = m_last_attribute; attribute; attribute = attribute->m_prev_attribute) + if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } + else + return m_first_attribute ? m_last_attribute : 0; + } + + /////////////////////////////////////////////////////////////////////////// + // Node modification + + //! Sets type of node. + //! \param type Type of node to set. + void type(node_type type) + { + m_type = type; + } + + /////////////////////////////////////////////////////////////////////////// + // Node manipulation + + //! Prepends a new child node. + //! The prepended child becomes the first child, and all existing children are moved one position back. + //! \param child Node to prepend. + void prepend_node(xml_node *child) + { + assert(child && !child->parent() && child->type() != node_document); + if (first_node()) + { + child->m_next_sibling = m_first_node; + m_first_node->m_prev_sibling = child; + } + else + { + child->m_next_sibling = 0; + m_last_node = child; + } + m_first_node = child; + child->m_parent = this; + child->m_prev_sibling = 0; + } + + //! Appends a new child node. + //! The appended child becomes the last child. + //! \param child Node to append. + void append_node(xml_node *child) + { + assert(child && !child->parent() && child->type() != node_document); + if (first_node()) + { + child->m_prev_sibling = m_last_node; + m_last_node->m_next_sibling = child; + } + else + { + child->m_prev_sibling = 0; + m_first_node = child; + } + m_last_node = child; + child->m_parent = this; + child->m_next_sibling = 0; + } + + //! Inserts a new child node at specified place inside the node. + //! All children after and including the specified node are moved one position back. + //! \param where Place where to insert the child, or 0 to insert at the back. + //! \param child Node to insert. + void insert_node(xml_node *where, xml_node *child) + { + assert(!where || where->parent() == this); + assert(child && !child->parent() && child->type() != node_document); + if (where == m_first_node) + prepend_node(child); + else if (where == 0) + append_node(child); + else + { + child->m_prev_sibling = where->m_prev_sibling; + child->m_next_sibling = where; + where->m_prev_sibling->m_next_sibling = child; + where->m_prev_sibling = child; + child->m_parent = this; + } + } + + //! Removes first child node. + //! If node has no children, behaviour is undefined. + //! Use first_node() to test if node has children. + void remove_first_node() + { + assert(first_node()); + xml_node *child = m_first_node; + m_first_node = child->m_next_sibling; + if (child->m_next_sibling) + child->m_next_sibling->m_prev_sibling = 0; + else + m_last_node = 0; + child->m_parent = 0; + } + + //! Removes last child of the node. + //! If node has no children, behaviour is undefined. + //! Use first_node() to test if node has children. + void remove_last_node() + { + assert(first_node()); + xml_node *child = m_last_node; + if (child->m_prev_sibling) + { + m_last_node = child->m_prev_sibling; + child->m_prev_sibling->m_next_sibling = 0; + } + else + m_first_node = 0; + child->m_parent = 0; + } + + //! Removes specified child from the node + // \param where Pointer to child to be removed. + void remove_node(xml_node *where) + { + assert(where && where->parent() == this); + assert(first_node()); + if (where == m_first_node) + remove_first_node(); + else if (where == m_last_node) + remove_last_node(); + else + { + where->m_prev_sibling->m_next_sibling = where->m_next_sibling; + where->m_next_sibling->m_prev_sibling = where->m_prev_sibling; + where->m_parent = 0; + } + } + + //! Removes all child nodes (but not attributes). + void remove_all_nodes() + { + for (xml_node *node = first_node(); node; node = node->m_next_sibling) + node->m_parent = 0; + m_first_node = 0; + } + + //! Prepends a new attribute to the node. + //! \param attribute Attribute to prepend. + void prepend_attribute(xml_attribute *attribute) + { + assert(attribute && !attribute->parent()); + if (first_attribute()) + { + attribute->m_next_attribute = m_first_attribute; + m_first_attribute->m_prev_attribute = attribute; + } + else + { + attribute->m_next_attribute = 0; + m_last_attribute = attribute; + } + m_first_attribute = attribute; + attribute->m_parent = this; + attribute->m_prev_attribute = 0; + } + + //! Appends a new attribute to the node. + //! \param attribute Attribute to append. + void append_attribute(xml_attribute *attribute) + { + assert(attribute && !attribute->parent()); + if (first_attribute()) + { + attribute->m_prev_attribute = m_last_attribute; + m_last_attribute->m_next_attribute = attribute; + } + else + { + attribute->m_prev_attribute = 0; + m_first_attribute = attribute; + } + m_last_attribute = attribute; + attribute->m_parent = this; + attribute->m_next_attribute = 0; + } + + //! Inserts a new attribute at specified place inside the node. + //! All attributes after and including the specified attribute are moved one position back. + //! \param where Place where to insert the attribute, or 0 to insert at the back. + //! \param attribute Attribute to insert. + void insert_attribute(xml_attribute *where, xml_attribute *attribute) + { + assert(!where || where->parent() == this); + assert(attribute && !attribute->parent()); + if (where == m_first_attribute) + prepend_attribute(attribute); + else if (where == 0) + append_attribute(attribute); + else + { + attribute->m_prev_attribute = where->m_prev_attribute; + attribute->m_next_attribute = where; + where->m_prev_attribute->m_next_attribute = attribute; + where->m_prev_attribute = attribute; + attribute->m_parent = this; + } + } + + //! Removes first attribute of the node. + //! If node has no attributes, behaviour is undefined. + //! Use first_attribute() to test if node has attributes. + void remove_first_attribute() + { + assert(first_attribute()); + xml_attribute *attribute = m_first_attribute; + if (attribute->m_next_attribute) + { + attribute->m_next_attribute->m_prev_attribute = 0; + } + else + m_last_attribute = 0; + attribute->m_parent = 0; + m_first_attribute = attribute->m_next_attribute; + } + + //! Removes last attribute of the node. + //! If node has no attributes, behaviour is undefined. + //! Use first_attribute() to test if node has attributes. + void remove_last_attribute() + { + assert(first_attribute()); + xml_attribute *attribute = m_last_attribute; + if (attribute->m_prev_attribute) + { + attribute->m_prev_attribute->m_next_attribute = 0; + m_last_attribute = attribute->m_prev_attribute; + } + else + m_first_attribute = 0; + attribute->m_parent = 0; + } + + //! Removes specified attribute from node. + //! \param where Pointer to attribute to be removed. + void remove_attribute(xml_attribute *where) + { + assert(first_attribute() && where->parent() == this); + if (where == m_first_attribute) + remove_first_attribute(); + else if (where == m_last_attribute) + remove_last_attribute(); + else + { + where->m_prev_attribute->m_next_attribute = where->m_next_attribute; + where->m_next_attribute->m_prev_attribute = where->m_prev_attribute; + where->m_parent = 0; + } + } + + //! Removes all attributes of node. + void remove_all_attributes() + { + for (xml_attribute *attribute = first_attribute(); attribute; attribute = attribute->m_next_attribute) + attribute->m_parent = 0; + m_first_attribute = 0; + } + + private: + + /////////////////////////////////////////////////////////////////////////// + // Restrictions + + // No copying + xml_node(const xml_node &); + void operator =(const xml_node &); + + /////////////////////////////////////////////////////////////////////////// + // Data members + + // Note that some of the pointers below have UNDEFINED values if certain other pointers are 0. + // This is required for maximum performance, as it allows the parser to omit initialization of + // unneded/redundant values. + // + // The rules are as follows: + // 1. first_node and first_attribute contain valid pointers, or 0 if node has no children/attributes respectively + // 2. last_node and last_attribute are valid only if node has at least one child/attribute respectively, otherwise they contain garbage + // 3. prev_sibling and next_sibling are valid only if node has a parent, otherwise they contain garbage + + node_type m_type; // Type of node; always valid + xml_node *m_first_node; // Pointer to first child node, or 0 if none; always valid + xml_node *m_last_node; // Pointer to last child node, or 0 if none; this value is only valid if m_first_node is non-zero + xml_attribute *m_first_attribute; // Pointer to first attribute of node, or 0 if none; always valid + xml_attribute *m_last_attribute; // Pointer to last attribute of node, or 0 if none; this value is only valid if m_first_attribute is non-zero + xml_node *m_prev_sibling; // Pointer to previous sibling of node, or 0 if none; this value is only valid if m_parent is non-zero + xml_node *m_next_sibling; // Pointer to next sibling of node, or 0 if none; this value is only valid if m_parent is non-zero + + }; + + /////////////////////////////////////////////////////////////////////////// + // XML document + + //! This class represents root of the DOM hierarchy. + //! It is also an xml_node and a memory_pool through public inheritance. + //! Use parse() function to build a DOM tree from a zero-terminated XML text string. + //! parse() function allocates memory for nodes and attributes by using functions of xml_document, + //! which are inherited from memory_pool. + //! To access root node of the document, use the document itself, as if it was an xml_node. + //! \param Ch Character type to use. + template + class xml_document: public xml_node, public memory_pool + { + + public: + + //! Constructs empty XML document + xml_document() + : xml_node(node_document) + { + } + + //! Parses zero-terminated XML string according to given flags. + //! Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used. + //! The string must persist for the lifetime of the document. + //! In case of error, rapidxml::parse_error exception will be thrown. + //!

+ //! If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning. + //! Make sure that data is zero-terminated. + //!

+ //! Document can be parsed into multiple times. + //! Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool. + //! \param text XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser. + template + void parse(Ch *text) + { + assert(text); + + // Remove current contents + this->remove_all_nodes(); + this->remove_all_attributes(); + + // Parse BOM, if any + parse_bom(text); + + // Parse children + while (1) + { + // Skip whitespace before node + skip(text); + if (*text == 0) + break; + + // Parse and append new child + if (*text == Ch('<')) + { + ++text; // Skip '<' + if (xml_node *node = parse_node(text)) + this->append_node(node); + } + else + RAPIDXML_PARSE_ERROR("expected <", text); + } + + } + + //! Clears the document by deleting all nodes and clearing the memory pool. + //! All nodes owned by document pool are destroyed. + void clear() + { + this->remove_all_nodes(); + this->remove_all_attributes(); + memory_pool::clear(); + } + + private: + + /////////////////////////////////////////////////////////////////////// + // Internal character utility functions + + // Detect whitespace character + struct whitespace_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_whitespace[static_cast(ch)]; + } + }; + + // Detect node name character + struct node_name_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_node_name[static_cast(ch)]; + } + }; + + // Detect attribute name character + struct attribute_name_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_attribute_name[static_cast(ch)]; + } + }; + + // Detect text character (PCDATA) + struct text_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_text[static_cast(ch)]; + } + }; + + // Detect text character (PCDATA) that does not require processing + struct text_pure_no_ws_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_text_pure_no_ws[static_cast(ch)]; + } + }; + + // Detect text character (PCDATA) that does not require processing + struct text_pure_with_ws_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_text_pure_with_ws[static_cast(ch)]; + } + }; + + // Detect attribute value character + template + struct attribute_value_pred + { + static unsigned char test(Ch ch) + { + if (Quote == Ch('\'')) + return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast(ch)]; + if (Quote == Ch('\"')) + return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast(ch)]; + return 0; // Should never be executed, to avoid warnings on Comeau + } + }; + + // Detect attribute value character + template + struct attribute_value_pure_pred + { + static unsigned char test(Ch ch) + { + if (Quote == Ch('\'')) + return internal::lookup_tables<0>::lookup_attribute_data_1_pure[static_cast(ch)]; + if (Quote == Ch('\"')) + return internal::lookup_tables<0>::lookup_attribute_data_2_pure[static_cast(ch)]; + return 0; // Should never be executed, to avoid warnings on Comeau + } + }; + + // Insert coded character, using UTF8 or 8-bit ASCII + template + static void insert_coded_character(Ch *&text, unsigned long code) + { + if (Flags & parse_no_utf8) + { + // Insert 8-bit ASCII character + // Todo: possibly verify that code is less than 256 and use replacement char otherwise? + text[0] = static_cast(code); + text += 1; + } + else + { + // Insert UTF8 sequence + if (code < 0x80) // 1 byte sequence + { + text[0] = static_cast(code); + text += 1; + } + else if (code < 0x800) // 2 byte sequence + { + text[1] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[0] = static_cast(code | 0xC0); + text += 2; + } + else if (code < 0x10000) // 3 byte sequence + { + text[2] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[1] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[0] = static_cast(code | 0xE0); + text += 3; + } + else if (code < 0x110000) // 4 byte sequence + { + text[3] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[2] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[1] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[0] = static_cast(code | 0xF0); + text += 4; + } + else // Invalid, only codes up to 0x10FFFF are allowed in Unicode + { + RAPIDXML_PARSE_ERROR("invalid numeric character entity", text); + } + } + } + + // Skip characters until predicate evaluates to true + template + static void skip(Ch *&text) + { + Ch *tmp = text; + while (StopPred::test(*tmp)) + ++tmp; + text = tmp; + } + + // Skip characters until predicate evaluates to true while doing the following: + // - replacing XML character entity references with proper characters (' & " < > &#...;) + // - condensing whitespace sequences to single space character + template + static Ch *skip_and_expand_character_refs(Ch *&text) + { + // If entity translation, whitespace condense and whitespace trimming is disabled, use plain skip + if (Flags & parse_no_entity_translation && + !(Flags & parse_normalize_whitespace) && + !(Flags & parse_trim_whitespace)) + { + skip(text); + return text; + } + + // Use simple skip until first modification is detected + skip(text); + + // Use translation skip + Ch *src = text; + Ch *dest = src; + while (StopPred::test(*src)) + { + // If entity translation is enabled + if (!(Flags & parse_no_entity_translation)) + { + // Test if replacement is needed + if (src[0] == Ch('&')) + { + switch (src[1]) + { + + // & ' + case Ch('a'): + if (src[2] == Ch('m') && src[3] == Ch('p') && src[4] == Ch(';')) + { + *dest = Ch('&'); + ++dest; + src += 5; + continue; + } + if (src[2] == Ch('p') && src[3] == Ch('o') && src[4] == Ch('s') && src[5] == Ch(';')) + { + *dest = Ch('\''); + ++dest; + src += 6; + continue; + } + break; + + // " + case Ch('q'): + if (src[2] == Ch('u') && src[3] == Ch('o') && src[4] == Ch('t') && src[5] == Ch(';')) + { + *dest = Ch('"'); + ++dest; + src += 6; + continue; + } + break; + + // > + case Ch('g'): + if (src[2] == Ch('t') && src[3] == Ch(';')) + { + *dest = Ch('>'); + ++dest; + src += 4; + continue; + } + break; + + // < + case Ch('l'): + if (src[2] == Ch('t') && src[3] == Ch(';')) + { + *dest = Ch('<'); + ++dest; + src += 4; + continue; + } + break; + + // &#...; - assumes ASCII + case Ch('#'): + if (src[2] == Ch('x')) + { + unsigned long code = 0; + src += 3; // Skip &#x + while (1) + { + unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast(*src)]; + if (digit == 0xFF) + break; + code = code * 16 + digit; + ++src; + } + insert_coded_character(dest, code); // Put character in output + } + else + { + unsigned long code = 0; + src += 2; // Skip &# + while (1) + { + unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast(*src)]; + if (digit == 0xFF) + break; + code = code * 10 + digit; + ++src; + } + insert_coded_character(dest, code); // Put character in output + } + if (*src == Ch(';')) + ++src; + else + RAPIDXML_PARSE_ERROR("expected ;", src); + continue; + + // Something else + default: + // Ignore, just copy '&' verbatim + break; + + } + } + } + + // If whitespace condensing is enabled + if (Flags & parse_normalize_whitespace) + { + // Test if condensing is needed + if (whitespace_pred::test(*src)) + { + *dest = Ch(' '); ++dest; // Put single space in dest + ++src; // Skip first whitespace char + // Skip remaining whitespace chars + while (whitespace_pred::test(*src)) + ++src; + continue; + } + } + + // No replacement, only copy character + *dest++ = *src++; + + } + + // Return new end + text = src; + return dest; + + } + + /////////////////////////////////////////////////////////////////////// + // Internal parsing functions + + // Parse BOM, if any + template + void parse_bom(Ch *&text) + { + // UTF-8? + if (static_cast(text[0]) == 0xEF && + static_cast(text[1]) == 0xBB && + static_cast(text[2]) == 0xBF) + { + text += 3; // Skup utf-8 bom + } + } + + // Parse XML declaration ( + xml_node *parse_xml_declaration(Ch *&text) + { + // If parsing of declaration is disabled + if (!(Flags & parse_declaration_node)) + { + // Skip until end of declaration + while (text[0] != Ch('?') || text[1] != Ch('>')) + { + if (!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 2; // Skip '?>' + return 0; + } + + // Create declaration + xml_node *declaration = this->allocate_node(node_declaration); + + // Skip whitespace before attributes or ?> + skip(text); + + // Parse declaration attributes + parse_node_attributes(text, declaration); + + // Skip ?> + if (text[0] != Ch('?') || text[1] != Ch('>')) + RAPIDXML_PARSE_ERROR("expected ?>", text); + text += 2; + + return declaration; + } + + // Parse XML comment (' + return 0; // Do not produce comment node + } + + // Remember value start + Ch *value = text; + + // Skip until end of comment + while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>')) + { + if (!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + // Create comment node + xml_node *comment = this->allocate_node(node_comment); + comment->value(value, text - value); + + // Place zero terminator after comment value + if (!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + + text += 3; // Skip '-->' + return comment; + } + + // Parse DOCTYPE + template + xml_node *parse_doctype(Ch *&text) + { + // Remember value start + Ch *value = text; + + // Skip to > + while (*text != Ch('>')) + { + // Determine character type + switch (*text) + { + + // If '[' encountered, scan for matching ending ']' using naive algorithm with depth + // This works for all W3C test files except for 2 most wicked + case Ch('['): + { + ++text; // Skip '[' + int depth = 1; + while (depth > 0) + { + switch (*text) + { + case Ch('['): ++depth; break; + case Ch(']'): --depth; break; + case 0: RAPIDXML_PARSE_ERROR("unexpected end of data", text); + } + ++text; + } + break; + } + + // Error on end of text + case Ch('\0'): + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + + // Other character, skip it + default: + ++text; + + } + } + + // If DOCTYPE nodes enabled + if (Flags & parse_doctype_node) + { + // Create a new doctype node + xml_node *doctype = this->allocate_node(node_doctype); + doctype->value(value, text - value); + + // Place zero terminator after value + if (!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + + text += 1; // skip '>' + return doctype; + } + else + { + text += 1; // skip '>' + return 0; + } + + } + + // Parse PI + template + xml_node *parse_pi(Ch *&text) + { + // If creation of PI nodes is enabled + if (Flags & parse_pi_nodes) + { + // Create pi node + xml_node *pi = this->allocate_node(node_pi); + + // Extract PI target name + Ch *name = text; + skip(text); + if (text == name) + RAPIDXML_PARSE_ERROR("expected PI target", text); + pi->name(name, text - name); + + // Skip whitespace between pi target and pi + skip(text); + + // Remember start of pi + Ch *value = text; + + // Skip to '?>' + while (text[0] != Ch('?') || text[1] != Ch('>')) + { + if (*text == Ch('\0')) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + // Set pi value (verbatim, no entity expansion or whitespace normalization) + pi->value(value, text - value); + + // Place zero terminator after name and value + if (!(Flags & parse_no_string_terminators)) + { + pi->name()[pi->name_size()] = Ch('\0'); + pi->value()[pi->value_size()] = Ch('\0'); + } + + text += 2; // Skip '?>' + return pi; + } + else + { + // Skip to '?>' + while (text[0] != Ch('?') || text[1] != Ch('>')) + { + if (*text == Ch('\0')) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 2; // Skip '?>' + return 0; + } + } + + // Parse and append data + // Return character that ends data. + // This is necessary because this character might have been overwritten by a terminating 0 + template + Ch parse_and_append_data(xml_node *node, Ch *&text, Ch *contents_start) + { + // Backup to contents start if whitespace trimming is disabled + if (!(Flags & parse_trim_whitespace)) + text = contents_start; + + // Skip until end of data + Ch *value = text, *end; + if (Flags & parse_normalize_whitespace) + end = skip_and_expand_character_refs(text); + else + end = skip_and_expand_character_refs(text); + + // Trim trailing whitespace if flag is set; leading was already trimmed by whitespace skip after > + if (Flags & parse_trim_whitespace) + { + if (Flags & parse_normalize_whitespace) + { + // Whitespace is already condensed to single space characters by skipping function, so just trim 1 char off the end + if (*(end - 1) == Ch(' ')) + --end; + } + else + { + // Backup until non-whitespace character is found + while (whitespace_pred::test(*(end - 1))) + --end; + } + } + + // If characters are still left between end and value (this test is only necessary if normalization is enabled) + // Create new data node + if (!(Flags & parse_no_data_nodes)) + { + xml_node *data = this->allocate_node(node_data); + data->value(value, end - value); + node->append_node(data); + } + + // Add data to parent node if no data exists yet + if (!(Flags & parse_no_element_values)) + if (*node->value() == Ch('\0')) + node->value(value, end - value); + + // Place zero terminator after value + if (!(Flags & parse_no_string_terminators)) + { + Ch ch = *text; + *end = Ch('\0'); + return ch; // Return character that ends data; this is required because zero terminator overwritten it + } + + // Return character that ends data + return *text; + } + + // Parse CDATA + template + xml_node *parse_cdata(Ch *&text) + { + // If CDATA is disabled + if (Flags & parse_no_data_nodes) + { + // Skip until end of cdata + while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>')) + { + if (!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 3; // Skip ]]> + return 0; // Do not produce CDATA node + } + + // Skip until end of cdata + Ch *value = text; + while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>')) + { + if (!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + // Create new cdata node + xml_node *cdata = this->allocate_node(node_cdata); + cdata->value(value, text - value); + + // Place zero terminator after value + if (!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + + text += 3; // Skip ]]> + return cdata; + } + + // Parse element node + template + xml_node *parse_element(Ch *&text) + { + // Create element node + xml_node *element = this->allocate_node(node_element); + + // Extract element name + Ch *name = text; + skip(text); + if (text == name) + RAPIDXML_PARSE_ERROR("expected element name", text); + element->name(name, text - name); + + // Skip whitespace between element name and attributes or > + skip(text); + + // Parse attributes, if any + parse_node_attributes(text, element); + + // Determine ending type + if (*text == Ch('>')) + { + ++text; + parse_node_contents(text, element); + } + else if (*text == Ch('/')) + { + ++text; + if (*text != Ch('>')) + RAPIDXML_PARSE_ERROR("expected >", text); + ++text; + } + else + RAPIDXML_PARSE_ERROR("expected >", text); + + // Place zero terminator after name + if (!(Flags & parse_no_string_terminators)) + element->name()[element->name_size()] = Ch('\0'); + + // Return parsed element + return element; + } + + // Determine node type, and parse it + template + xml_node *parse_node(Ch *&text) + { + // Parse proper node type + switch (text[0]) + { + + // <... + default: + // Parse and append element node + return parse_element(text); + + // (text); + } + else + { + // Parse PI + return parse_pi(text); + } + + // (text); + } + break; + + // (text); + } + break; + + // (text); + } + + } // switch + + // Attempt to skip other, unrecognized node types starting with ')) + { + if (*text == 0) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + ++text; // Skip '>' + return 0; // No node recognized + + } + } + + // Parse contents of the node - children, data etc. + template + void parse_node_contents(Ch *&text, xml_node *node) + { + // For all children and text + while (1) + { + // Skip whitespace between > and node contents + Ch *contents_start = text; // Store start of node contents before whitespace is skipped + skip(text); + Ch next_char = *text; + + // After data nodes, instead of continuing the loop, control jumps here. + // This is because zero termination inside parse_and_append_data() function + // would wreak havoc with the above code. + // Also, skipping whitespace after data nodes is unnecessary. + after_data_node: + + // Determine what comes next: node closing, child node, data node, or 0? + switch (next_char) + { + + // Node closing or child node + case Ch('<'): + if (text[1] == Ch('/')) + { + // Node closing + text += 2; // Skip '(text); + if (!internal::compare(node->name(), node->name_size(), closing_name, text - closing_name, true)) + RAPIDXML_PARSE_ERROR("invalid closing tag name", text); + } + else + { + // No validation, just skip name + skip(text); + } + // Skip remaining whitespace after node name + skip(text); + if (*text != Ch('>')) + RAPIDXML_PARSE_ERROR("expected >", text); + ++text; // Skip '>' + return; // Node closed, finished parsing contents + } + else + { + // Child node + ++text; // Skip '<' + if (xml_node *child = parse_node(text)) + node->append_node(child); + } + break; + + // End of data - error + case Ch('\0'): + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + + // Data node + default: + next_char = parse_and_append_data(node, text, contents_start); + goto after_data_node; // Bypass regular processing after data nodes + + } + } + } + + // Parse XML attributes of the node + template + void parse_node_attributes(Ch *&text, xml_node *node) + { + // For all attributes + while (attribute_name_pred::test(*text)) + { + // Extract attribute name + Ch *name = text; + ++text; // Skip first character of attribute name + skip(text); + if (text == name) + RAPIDXML_PARSE_ERROR("expected attribute name", name); + + // Create new attribute + xml_attribute *attribute = this->allocate_attribute(); + attribute->name(name, text - name); + node->append_attribute(attribute); + + // Skip whitespace after attribute name + skip(text); + + // Skip = + if (*text != Ch('=')) + RAPIDXML_PARSE_ERROR("expected =", text); + ++text; + + // Add terminating zero after name + if (!(Flags & parse_no_string_terminators)) + attribute->name()[attribute->name_size()] = 0; + + // Skip whitespace after = + skip(text); + + // Skip quote and remember if it was ' or " + Ch quote = *text; + if (quote != Ch('\'') && quote != Ch('"')) + RAPIDXML_PARSE_ERROR("expected ' or \"", text); + ++text; + + // Extract attribute value and expand char refs in it + Ch *value = text, *end; + const int AttFlags = Flags & ~parse_normalize_whitespace; // No whitespace normalization in attributes + if (quote == Ch('\'')) + end = skip_and_expand_character_refs, attribute_value_pure_pred, AttFlags>(text); + else + end = skip_and_expand_character_refs, attribute_value_pure_pred, AttFlags>(text); + + // Set attribute value + attribute->value(value, end - value); + + // Make sure that end quote is present + if (*text != quote) + RAPIDXML_PARSE_ERROR("expected ' or \"", text); + ++text; // Skip quote + + // Add terminating zero after value + if (!(Flags & parse_no_string_terminators)) + attribute->value()[attribute->value_size()] = 0; + + // Skip whitespace after attribute value + skip(text); + } + } + + }; + + //! \cond internal + namespace internal + { + + // Whitespace (space \n \r \t) + template + const unsigned char lookup_tables::lookup_whitespace[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F + }; + + // Node name (anything but space \n \r \t / > ? \0) + template + const unsigned char lookup_tables::lookup_node_name[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Text (i.e. PCDATA) (anything but < \0) + template + const unsigned char lookup_tables::lookup_text[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 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, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Text (i.e. PCDATA) that does not require processing when ws normalization is disabled + // (anything but < \0 &) + template + const unsigned char lookup_tables::lookup_text_pure_no_ws[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Text (i.e. PCDATA) that does not require processing when ws normalizationis is enabled + // (anything but < \0 & space \n \r \t) + template + const unsigned char lookup_tables::lookup_text_pure_with_ws[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute name (anything but space \n \r \t / < > = ? ! \0) + template + const unsigned char lookup_tables::lookup_attribute_name[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute data with single quote (anything but ' \0) + template + const unsigned char lookup_tables::lookup_attribute_data_1[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 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, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute data with single quote that does not require processing (anything but ' \0 &) + template + const unsigned char lookup_tables::lookup_attribute_data_1_pure[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute data with double quote (anything but " \0) + template + const unsigned char lookup_tables::lookup_attribute_data_2[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute data with double quote that does not require processing (anything but " \0 &) + template + const unsigned char lookup_tables::lookup_attribute_data_2_pure[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Digits (dec and hex, 255 denotes end of numeric character reference) + template + const unsigned char lookup_tables::lookup_digits[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 0 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 1 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 2 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,255,255,255,255,255,255, // 3 + 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 4 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 5 + 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 6 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 7 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 8 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 9 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // A + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // B + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // C + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // D + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // E + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 // F + }; + + // Upper case conversion + template + const unsigned char lookup_tables::lookup_upcase[256] = + { + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A B C D E F + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // 0 + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, // 1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 2 + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 3 + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 4 + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, // 5 + 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 6 + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123,124,125,126,127, // 7 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 8 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 9 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // A + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, // B + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // C + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // D + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // E + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // F + }; + } + //! \endcond + +} + +// Undefine internal macros +#undef RAPIDXML_PARSE_ERROR + +// On MSVC, restore warnings state +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#endif diff --git a/src/lib/rapidxml/rapidxml_iterators.hpp b/src/lib/rapidxml/rapidxml_iterators.hpp new file mode 100644 index 0000000..52ebc29 --- /dev/null +++ b/src/lib/rapidxml/rapidxml_iterators.hpp @@ -0,0 +1,174 @@ +#ifndef RAPIDXML_ITERATORS_HPP_INCLUDED +#define RAPIDXML_ITERATORS_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml_iterators.hpp This file contains rapidxml iterators + +#include "rapidxml.hpp" + +namespace rapidxml +{ + + //! Iterator of child nodes of xml_node + template + class node_iterator + { + + public: + + typedef typename xml_node value_type; + typedef typename xml_node &reference; + typedef typename xml_node *pointer; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + node_iterator() + : m_node(0) + { + } + + node_iterator(xml_node *node) + : m_node(node->first_node()) + { + } + + reference operator *() const + { + assert(m_node); + return *m_node; + } + + pointer operator->() const + { + assert(m_node); + return m_node; + } + + node_iterator& operator++() + { + assert(m_node); + m_node = m_node->next_sibling(); + return *this; + } + + node_iterator operator++(int) + { + node_iterator tmp = *this; + ++this; + return tmp; + } + + node_iterator& operator--() + { + assert(m_node && m_node->previous_sibling()); + m_node = m_node->previous_sibling(); + return *this; + } + + node_iterator operator--(int) + { + node_iterator tmp = *this; + ++this; + return tmp; + } + + bool operator ==(const node_iterator &rhs) + { + return m_node == rhs.m_node; + } + + bool operator !=(const node_iterator &rhs) + { + return m_node != rhs.m_node; + } + + private: + + xml_node *m_node; + + }; + + //! Iterator of child attributes of xml_node + template + class attribute_iterator + { + + public: + + typedef typename xml_attribute value_type; + typedef typename xml_attribute &reference; + typedef typename xml_attribute *pointer; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + attribute_iterator() + : m_attribute(0) + { + } + + attribute_iterator(xml_node *node) + : m_attribute(node->first_attribute()) + { + } + + reference operator *() const + { + assert(m_attribute); + return *m_attribute; + } + + pointer operator->() const + { + assert(m_attribute); + return m_attribute; + } + + attribute_iterator& operator++() + { + assert(m_attribute); + m_attribute = m_attribute->next_attribute(); + return *this; + } + + attribute_iterator operator++(int) + { + attribute_iterator tmp = *this; + ++this; + return tmp; + } + + attribute_iterator& operator--() + { + assert(m_attribute && m_attribute->previous_attribute()); + m_attribute = m_attribute->previous_attribute(); + return *this; + } + + attribute_iterator operator--(int) + { + attribute_iterator tmp = *this; + ++this; + return tmp; + } + + bool operator ==(const attribute_iterator &rhs) + { + return m_attribute == rhs.m_attribute; + } + + bool operator !=(const attribute_iterator &rhs) + { + return m_attribute != rhs.m_attribute; + } + + private: + + xml_attribute *m_attribute; + + }; + +} + +#endif diff --git a/src/lib/rapidxml/rapidxml_print.hpp b/src/lib/rapidxml/rapidxml_print.hpp new file mode 100644 index 0000000..0ae2b14 --- /dev/null +++ b/src/lib/rapidxml/rapidxml_print.hpp @@ -0,0 +1,421 @@ +#ifndef RAPIDXML_PRINT_HPP_INCLUDED +#define RAPIDXML_PRINT_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml_print.hpp This file contains rapidxml printer implementation + +#include "rapidxml.hpp" + +// Only include streams if not disabled +#ifndef RAPIDXML_NO_STREAMS + #include + #include +#endif + +namespace rapidxml +{ + + /////////////////////////////////////////////////////////////////////// + // Printing flags + + const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function. + + /////////////////////////////////////////////////////////////////////// + // Internal + + //! \cond internal + namespace internal + { + + /////////////////////////////////////////////////////////////////////////// + // Internal character operations + + // Copy characters from given range to given output iterator + template + inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out) + { + while (begin != end) + *out++ = *begin++; + return out; + } + + // Copy characters from given range to given output iterator and expand + // characters into references (< > ' " &) + template + inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out) + { + while (begin != end) + { + if (*begin == noexpand) + { + *out++ = *begin; // No expansion, copy character + } + else + { + switch (*begin) + { + case Ch('<'): + *out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('>'): + *out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('\''): + *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';'); + break; + case Ch('"'): + *out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('&'): + *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';'); + break; + default: + *out++ = *begin; // No expansion, copy character + } + } + ++begin; // Step to next character + } + return out; + } + + // Fill given output iterator with repetitions of the same character + template + inline OutIt fill_chars(OutIt out, int n, Ch ch) + { + for (int i = 0; i < n; ++i) + *out++ = ch; + return out; + } + + // Find character + template + inline bool find_char(const Ch *begin, const Ch *end) + { + while (begin != end) + if (*begin++ == ch) + return true; + return false; + } + + /////////////////////////////////////////////////////////////////////////// + // Internal printing operations + + // Print node + template + inline OutIt print_node(OutIt out, const xml_node *node, int flags, int indent) + { + // Print proper node type + switch (node->type()) + { + + // Document + case node_document: + out = print_children(out, node, flags, indent); + break; + + // Element + case node_element: + out = print_element_node(out, node, flags, indent); + break; + + // Data + case node_data: + out = print_data_node(out, node, flags, indent); + break; + + // CDATA + case node_cdata: + out = print_cdata_node(out, node, flags, indent); + break; + + // Declaration + case node_declaration: + out = print_declaration_node(out, node, flags, indent); + break; + + // Comment + case node_comment: + out = print_comment_node(out, node, flags, indent); + break; + + // Doctype + case node_doctype: + out = print_doctype_node(out, node, flags, indent); + break; + + // Pi + case node_pi: + out = print_pi_node(out, node, flags, indent); + break; + + // Unknown + default: + assert(0); + break; + } + + // If indenting not disabled, add line break after node + if (!(flags & print_no_indenting)) + *out = Ch('\n'), ++out; + + // Return modified iterator + return out; + } + + // Print children of the node + template + inline OutIt print_children(OutIt out, const xml_node *node, int flags, int indent) + { + for (xml_node *child = node->first_node(); child; child = child->next_sibling()) + out = print_node(out, child, flags, indent); + return out; + } + + // Print attributes of the node + template + inline OutIt print_attributes(OutIt out, const xml_node *node, int flags) + { + for (xml_attribute *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute()) + { + if (attribute->name() && attribute->value()) + { + // Print attribute name + *out = Ch(' '), ++out; + out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out); + *out = Ch('='), ++out; + // Print attribute value using appropriate quote type + if (find_char(attribute->value(), attribute->value() + attribute->value_size())) + { + *out = Ch('\''), ++out; + out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out); + *out = Ch('\''), ++out; + } + else + { + *out = Ch('"'), ++out; + out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out); + *out = Ch('"'), ++out; + } + } + } + return out; + } + + // Print data node + template + inline OutIt print_data_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_data); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out); + return out; + } + + // Print data node + template + inline OutIt print_cdata_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_cdata); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'); ++out; + *out = Ch('!'); ++out; + *out = Ch('['); ++out; + *out = Ch('C'); ++out; + *out = Ch('D'); ++out; + *out = Ch('A'); ++out; + *out = Ch('T'); ++out; + *out = Ch('A'); ++out; + *out = Ch('['); ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch(']'); ++out; + *out = Ch(']'); ++out; + *out = Ch('>'); ++out; + return out; + } + + // Print element node + template + inline OutIt print_element_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_element); + + // Print element name and attributes, if any + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + out = print_attributes(out, node, flags); + + // If node is childless + if (node->value_size() == 0 && !node->first_node()) + { + // Print childless node tag ending + *out = Ch('/'), ++out; + *out = Ch('>'), ++out; + } + else + { + // Print normal node tag ending + *out = Ch('>'), ++out; + + // Test if node contains a single data node only (and no other nodes) + xml_node *child = node->first_node(); + if (!child) + { + // If node has no children, only print its value without indenting + out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out); + } + else if (child->next_sibling() == 0 && child->type() == node_data) + { + // If node has a sole data child, only print its value without indenting + out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out); + } + else + { + // Print all children with full indenting + if (!(flags & print_no_indenting)) + *out = Ch('\n'), ++out; + out = print_children(out, node, flags, indent + 1); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + } + + // Print node end + *out = Ch('<'), ++out; + *out = Ch('/'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + *out = Ch('>'), ++out; + } + return out; + } + + // Print declaration node + template + inline OutIt print_declaration_node(OutIt out, const xml_node *node, int flags, int indent) + { + // Print declaration start + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('?'), ++out; + *out = Ch('x'), ++out; + *out = Ch('m'), ++out; + *out = Ch('l'), ++out; + + // Print attributes + out = print_attributes(out, node, flags); + + // Print declaration end + *out = Ch('?'), ++out; + *out = Ch('>'), ++out; + + return out; + } + + // Print comment node + template + inline OutIt print_comment_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_comment); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('!'), ++out; + *out = Ch('-'), ++out; + *out = Ch('-'), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('-'), ++out; + *out = Ch('-'), ++out; + *out = Ch('>'), ++out; + return out; + } + + // Print doctype node + template + inline OutIt print_doctype_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_doctype); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('!'), ++out; + *out = Ch('D'), ++out; + *out = Ch('O'), ++out; + *out = Ch('C'), ++out; + *out = Ch('T'), ++out; + *out = Ch('Y'), ++out; + *out = Ch('P'), ++out; + *out = Ch('E'), ++out; + *out = Ch(' '), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('>'), ++out; + return out; + } + + // Print pi node + template + inline OutIt print_pi_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_pi); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('?'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + *out = Ch(' '), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('?'), ++out; + *out = Ch('>'), ++out; + return out; + } + + } + //! \endcond + + /////////////////////////////////////////////////////////////////////////// + // Printing + + //! Prints XML to given output iterator. + //! \param out Output iterator to print to. + //! \param node Node to be printed. Pass xml_document to print entire document. + //! \param flags Flags controlling how XML is printed. + //! \return Output iterator pointing to position immediately after last character of printed text. + template + inline OutIt print(OutIt out, const xml_node &node, int flags = 0) + { + return internal::print_node(out, &node, flags, 0); + } + +#ifndef RAPIDXML_NO_STREAMS + + //! Prints XML to given output stream. + //! \param out Output stream to print to. + //! \param node Node to be printed. Pass xml_document to print entire document. + //! \param flags Flags controlling how XML is printed. + //! \return Output stream. + template + inline std::basic_ostream &print(std::basic_ostream &out, const xml_node &node, int flags = 0) + { + print(std::ostream_iterator(out), node, flags); + return out; + } + + //! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process. + //! \param out Output stream to print to. + //! \param node Node to be printed. + //! \return Output stream. + template + inline std::basic_ostream &operator <<(std::basic_ostream &out, const xml_node &node) + { + return print(out, node); + } + +#endif + +} + +#endif diff --git a/src/lib/rapidxml/rapidxml_utils.hpp b/src/lib/rapidxml/rapidxml_utils.hpp new file mode 100644 index 0000000..37c2953 --- /dev/null +++ b/src/lib/rapidxml/rapidxml_utils.hpp @@ -0,0 +1,122 @@ +#ifndef RAPIDXML_UTILS_HPP_INCLUDED +#define RAPIDXML_UTILS_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful +//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective. + +#include "rapidxml.hpp" +#include +#include +#include +#include + +namespace rapidxml +{ + + //! Represents data loaded from a file + template + class file + { + + public: + + //! Loads file into the memory. Data will be automatically destroyed by the destructor. + //! \param filename Filename to load. + file(const char *filename) + { + using namespace std; + + // Open stream + basic_ifstream stream(filename, ios::binary); + if (!stream) + throw runtime_error(string("cannot open file ") + filename); + stream.unsetf(ios::skipws); + + // Determine stream size + stream.seekg(0, ios::end); + size_t size = stream.tellg(); + stream.seekg(0); + + // Load data and add terminating 0 + m_data.resize(size + 1); + stream.read(&m_data.front(), static_cast(size)); + m_data[size] = 0; + } + + //! Loads file into the memory. Data will be automatically destroyed by the destructor + //! \param stream Stream to load from + file(std::basic_istream &stream) + { + using namespace std; + + // Load data and add terminating 0 + stream.unsetf(ios::skipws); + m_data.assign(istreambuf_iterator(stream), istreambuf_iterator()); + if (stream.fail() || stream.bad()) + throw runtime_error("error reading stream"); + m_data.push_back(0); + } + + //! Gets file data. + //! \return Pointer to data of file. + Ch *data() + { + return &m_data.front(); + } + + //! Gets file data. + //! \return Pointer to data of file. + const Ch *data() const + { + return &m_data.front(); + } + + //! Gets file data size. + //! \return Size of file data, in characters. + std::size_t size() const + { + return m_data.size(); + } + + private: + + std::vector m_data; // File data + + }; + + //! Counts children of node. Time complexity is O(n). + //! \return Number of children of node + template + inline std::size_t count_children(xml_node *node) + { + xml_node *child = node->first_node(); + std::size_t count = 0; + while (child) + { + ++count; + child = child->next_sibling(); + } + return count; + } + + //! Counts attributes of node. Time complexity is O(n). + //! \return Number of attributes of node + template + inline std::size_t count_attributes(xml_node *node) + { + xml_attribute *attr = node->first_attribute(); + std::size_t count = 0; + while (attr) + { + ++count; + attr = attr->next_attribute(); + } + return count; + } + +} + +#endif diff --git a/src/modules/Helpers/Helpers.hpp b/src/modules/Helpers/Helpers.hpp new file mode 100644 index 0000000..2de7ddd --- /dev/null +++ b/src/modules/Helpers/Helpers.hpp @@ -0,0 +1,32 @@ +/*++ + +Program name: + + apostol + +Module Name: + + Helpers.hpp + +Notices: + + Apostol Web Service + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_HELPERS_HPP +#define APOSTOL_HELPERS_HPP +//---------------------------------------------------------------------------------------------------------------------- + +static inline void CreateHelpers(CModuleProcess *AProcess) { + +} + +#endif //APOSTOL_HELPERS_HPP diff --git a/src/modules/Modules.hpp b/src/modules/Modules.hpp new file mode 100644 index 0000000..b62546b --- /dev/null +++ b/src/modules/Modules.hpp @@ -0,0 +1,35 @@ +/*++ + +Library name: + + apostol-core + +Module Name: + + Workers.hpp + +Notices: + + Add-ons: Workers + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_MODULES_HPP +#define APOSTOL_MODULES_HPP +//---------------------------------------------------------------------------------------------------------------------- + +#include "Header.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#include "Workers/Workers.hpp" +#include "Helpers/Helpers.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#endif //APOSTOL_MODULES_HPP diff --git a/src/modules/Workers/WebService/WebService.cpp b/src/modules/Workers/WebService/WebService.cpp new file mode 100644 index 0000000..0d418e4 --- /dev/null +++ b/src/modules/Workers/WebService/WebService.cpp @@ -0,0 +1,1831 @@ +/*++ + +Library name: + + apostol-module + +Module Name: + + WebService.cpp + +Notices: + + WebService - Deal Module Web Service + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +//---------------------------------------------------------------------------------------------------------------------- + +#include "Core.hpp" +#include "WebService.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +#include "jwt.h" +//---------------------------------------------------------------------------------------------------------------------- + +#include +//---------------------------------------------------------------------------------------------------------------------- + +#define BPS_PGP_HASH "SHA512" +#define BM_PREFIX "BM-" +//---------------------------------------------------------------------------------------------------------------------- + +#define SYSTEM_PROVIDER_NAME "system" +#define SERVICE_APPLICATION_NAME "service" +#define CONFIG_SECTION_NAME "module" +//---------------------------------------------------------------------------------------------------------------------- + +extern "C++" { + +namespace Apostol { + + namespace Workers { + + CString to_string(unsigned long Value) { + TCHAR szString[_INT_T_LEN + 1] = {0}; + sprintf(szString, "%lu", Value); + return CString(szString); + } + + //-------------------------------------------------------------------------------------------------------------- + + //-- CWebService ----------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + CWebService::CWebService(CModuleProcess *AProcess): CApostolModule(AProcess, "web service") { + m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD; + m_ServerIndex = -1; + m_KeyIndex = 0; + + m_FixedDate = 0; + m_RandomDate = 0; + + m_Status = psStopped; + + m_DefaultServer.Name() = BPS_BM_SERVER_ADDRESS; + m_DefaultServer.Value().URI = BPS_SERVER_URL; + m_DefaultServer.Value().PGP.Name = BPS_BM_SERVER_ADDRESS; + + CWebService::InitMethods(); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::InitMethods() { +#if defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 9) + m_pMethods->AddObject(_T("GET") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoGet(Connection); })); + m_pMethods->AddObject(_T("POST") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoPost(Connection); })); + m_pMethods->AddObject(_T("HEAD") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoHead(Connection); })); + m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoOptions(Connection); })); + m_pMethods->AddObject(_T("PUT") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); + m_pMethods->AddObject(_T("DELETE") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); + m_pMethods->AddObject(_T("TRACE") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); + m_pMethods->AddObject(_T("PATCH") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); + m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); +#else + m_pMethods->AddObject(_T("GET"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoGet, this, _1))); + m_pMethods->AddObject(_T("POST"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoPost, this, _1))); + m_pMethods->AddObject(_T("HEAD"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoHead, this, _1))); + m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoOptions, this, _1))); + m_pMethods->AddObject(_T("PUT"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); + m_pMethods->AddObject(_T("DELETE"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); + m_pMethods->AddObject(_T("TRACE"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); + m_pMethods->AddObject(_T("PATCH"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); + m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); +#endif + } + //-------------------------------------------------------------------------------------------------------------- + + CDateTime CWebService::GetRandomDate(int a, int b, CDateTime Date) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> time(a, b); + CDateTime delta = time(gen); + return Date + (CDateTime) (delta / SecsPerDay); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::LoadOAuth2(const CString &FileName, const CString &ProviderName, const CString &ApplicationName, + CProviders &Providers) { + + CString ConfigFile(FileName); + + if (!path_separator(ConfigFile.front())) { + ConfigFile = Config()->Prefix() + ConfigFile; + } + + if (FileExists(ConfigFile.c_str())) { + CJSONObject Json; + Json.LoadFromFile(ConfigFile.c_str()); + + int index = Providers.IndexOfName(ProviderName); + if (index == -1) + index = Providers.AddPair(ProviderName, CProvider(ProviderName)); + auto& Provider = Providers[index].Value(); + Provider.Applications().AddPair(ApplicationName, Json); + } else { + Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, ConfigFile.c_str()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + bool CWebService::FindURLInLine(const CString &Line, CStringList &List) { + CString URL; + + TCHAR ch; + int length = 0; + size_t startPos, pos; + + pos = 0; + while ((startPos = Line.Find(HTTP_PREFIX, pos)) != CString::npos) { + + URL.Clear(); + + pos = startPos + HTTP_PREFIX_SIZE; + if (Line.Length() < 5) + return false; + + URL.Append(HTTP_PREFIX); + ch = Line.at(pos); + if (ch == 's') { + URL.Append(ch); + pos++; + } + + if (Line.Length() < 7 || Line.at(pos++) != ':' || Line.at(pos++) != '/' || Line.at(pos++) != '/') + return false; + + URL.Append("://"); + + length = 0; + ch = Line.at(pos); + while (ch != 0 && (IsChar(ch) || IsNumeral(ch) || ch == ':' || ch == '.' || ch == '-')) { + URL.Append(ch); + length++; + ch = Line.at(++pos); + } + + if (length < 3) { + return false; + } + + if (startPos == 0) { + List.Add(URL); + } else { + ch = Line.at(startPos - 1); + switch (ch) { + case ' ': + case ',': + case ';': + List.Add(URL); + break; + default: + return false; + } + } + } + + return true; + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::CheckKeyForNull(LPCTSTR Key, LPCTSTR Value) { + if (Value == nullptr) + throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", Key); + } + //-------------------------------------------------------------------------------------------------------------- + + int CWebService::CheckFee(const CString &Fee) { + if (!Fee.IsEmpty()) { + + if (Fee.Length() >= 10) + return -1; + + size_t numbers = 0; + size_t delimiter = 0; + size_t percent = 0; + + size_t pos = 0; + TCHAR ch; + + ch = Fee.at(pos); + while (ch != 0) { + if (IsNumeral(ch)) + numbers++; + if (ch == '.') + delimiter++; + if (ch == '%') + percent++; + ch = Fee.at(++pos); + } + + if (numbers == 0 || delimiter > 1 || percent > 1 || ((numbers + percent + delimiter) != Fee.Length())) + return -1; + + return 1; + } + + return 0; + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::ExceptionToJson(int ErrorCode, const std::exception &E, CString& Json) { + Json.Format(R"({"error": {"code": %u, "message": "%s"}})", ErrorCode, Delphi::Json::EncodeJsonString(E.what()).c_str()); + } + //-------------------------------------------------------------------------------------------------------------- + + CString CWebService::CreateToken(const CProvider &Provider, const CString &Application) { + auto token = jwt::create() + .set_issuer(Provider.Issuer(Application)) + .set_audience(Provider.ClientId(Application)) + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{std::string(Provider.Secret(Application))}); + + return token; + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::FetchAccessToken(const CString &URI, const CString &Assertion, COnSocketExecuteEvent &&OnDone, + COnSocketExceptionEvent &&OnFailed) { + + auto OnRequest = [](CHTTPClient *Sender, CHTTPRequest *ARequest) { + + const auto &token_uri = Sender->Data()["token_uri"]; + const auto &grant_type = Sender->Data()["grant_type"]; + const auto &assertion = Sender->Data()["assertion"]; + + ARequest->Content = _T("grant_type="); + ARequest->Content << CHTTPServer::URLEncode(grant_type); + + ARequest->Content << _T("&assertion="); + ARequest->Content << CHTTPServer::URLEncode(assertion); + + CHTTPRequest::Prepare(ARequest, _T("POST"), token_uri.c_str(), _T("application/x-www-form-urlencoded")); + + DebugRequest(ARequest); + }; + + auto OnException = [this](CTCPConnection *Sender, const Delphi::Exception::Exception &E) { + + auto pConnection = dynamic_cast (Sender); + auto pClient = dynamic_cast (pConnection->Client()); + + DebugReply(pConnection->Reply()); + + m_FixedDate = Now() + (CDateTime) 30 / SecsPerDay; + + Log()->Error(APP_LOG_ERR, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); + }; + + CLocation token_uri(URI); + + auto pClient = GetClient(token_uri.hostname, token_uri.port); + + pClient->Data().Values("token_uri", token_uri.pathname); + pClient->Data().Values("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"); + pClient->Data().Values("assertion", Assertion); + + pClient->OnRequest(OnRequest); + pClient->OnExecute(static_cast(OnDone)); + pClient->OnException(OnFailed == nullptr ? OnException : static_cast(OnFailed)); + + pClient->Active(true); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::CreateAccessToken(const CProvider &Provider, const CString &Application, CStringList &Tokens) { + + auto OnDone = [this, &Tokens](CTCPConnection *Sender) { + + auto pConnection = dynamic_cast (Sender); + auto pReply = pConnection->Reply(); + + DebugReply(pReply); + + const CJSON Json(pReply->Content); + + Tokens.Values("access_token", Json["access_token"].AsString()); + + m_Status = psRunning; + + return true; + }; + + CString server_uri("http://localhost:4977"); + + const auto &token_uri = Provider.TokenURI(Application); + const auto &service_token = CreateToken(Provider, Application); + + Tokens.Values("service_token", service_token); + + if (!token_uri.IsEmpty()) { + FetchAccessToken(token_uri.front() == '/' ? server_uri + token_uri : token_uri, service_token, OnDone); + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::FetchCerts(CProvider &Provider, const CString &Application) { + + const auto& URI = Provider.CertURI(Application); + + if (URI.IsEmpty()) { + Log()->Error(APP_LOG_INFO, 0, _T("Certificate URI in provider \"%s\" is empty."), Provider.Name().c_str()); + return; + } + + Log()->Error(APP_LOG_INFO, 0, _T("Trying to fetch public keys from: %s"), URI.c_str()); + + auto OnRequest = [&Provider, &Application](CHTTPClient *Sender, CHTTPRequest *ARequest) { + const auto& client_x509_cert_url = std::string(Provider.Applications()[Application]["client_x509_cert_url"].AsString()); + + Provider.KeyStatusTime(Now()); + Provider.KeyStatus(ksFetching); + + CLocation Location(client_x509_cert_url); + CHTTPRequest::Prepare(ARequest, "GET", Location.pathname.c_str()); + }; + + auto OnExecute = [this, &Provider, &Application](CTCPConnection *AConnection) { + + auto pConnection = dynamic_cast (AConnection); + auto pReply = pConnection->Reply(); + + try { + DebugRequest(pConnection->Request()); + DebugReply(pReply); + + Provider.KeyStatusTime(Now()); + + Provider.Keys().Clear(); + Provider.Keys() << pReply->Content; + + Provider.KeyStatus(ksSuccess); + + CreateAccessToken(Provider, Application, m_Tokens[Provider.Name()]); + } catch (Delphi::Exception::Exception &E) { + Provider.KeyStatus(ksFailed); + Log()->Error(APP_LOG_ERR, 0, "[Certificate] Message: %s", E.what()); + } + + pConnection->CloseConnection(true); + return true; + }; + + auto OnException = [&Provider](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { + auto pConnection = dynamic_cast (AConnection); + auto pClient = dynamic_cast (pConnection->Client()); + + Provider.KeyStatusTime(Now()); + Provider.KeyStatus(ksFailed); + + Log()->Error(APP_LOG_ERR, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); + }; + + CLocation Location(URI); + auto pClient = GetClient(Location.hostname, Location.port); + + pClient->OnRequest(OnRequest); + pClient->OnExecute(OnExecute); + pClient->OnException(OnException); + + pClient->Active(true); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::FetchProviders() { + for (int i = 0; i < m_Providers.Count(); i++) { + auto& Provider = m_Providers[i].Value(); + for (int j = 0; j < Provider.Applications().Count(); ++j) { + const auto &app = Provider.Applications().Members(j); + if (app["type"].AsString() == "service_account") { + if (!app["auth_provider_x509_cert_url"].AsString().IsEmpty()) { + if (Provider.KeyStatus() == ksUnknown) { + FetchCerts(Provider, app.String()); + } + } else { + if (Provider.KeyStatus() == ksUnknown) { + Provider.KeyStatusTime(Now()); + CreateAccessToken(Provider, app.String(), m_Tokens[Provider.Name()]); + Provider.KeyStatus(ksSuccess); + } + } + } + } + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::CheckProviders() { + for (int i = 0; i < m_Providers.Count(); i++) { + auto& Provider = m_Providers[i].Value(); + if (Provider.KeyStatus() != ksUnknown) { + Provider.KeyStatusTime(Now()); + Provider.KeyStatus(ksUnknown); + } + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::DoVerbose(CSocketEvent *Sender, CTCPConnection *AConnection, LPCTSTR AFormat, va_list args) { + Log()->Debug(0, AFormat, args); + } + //-------------------------------------------------------------------------------------------------------------- + + bool CWebService::DoProxyExecute(CTCPConnection *AConnection) { + + auto pConnection = dynamic_cast (AConnection); + auto pProxy = dynamic_cast (pConnection->Client()); + + if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) { + auto pProxyConnection = pProxy->Connection(); + auto pServerRequest = pProxyConnection->Request(); + auto pServerReply = pProxyConnection->Reply(); + auto pProxyReply = pConnection->Reply(); + + const auto &caFormat = pServerRequest->Params["payload"]; + if (!caFormat.IsEmpty()) { + + if (caFormat == "html") { + pServerReply->ContentType = CHTTPReply::html; + } else if (caFormat == "json") { + pServerReply->ContentType = CHTTPReply::json; + } else if (caFormat == "xml") { + pServerReply->ContentType = CHTTPReply::xml; + } else { + pServerReply->ContentType = CHTTPReply::text; + } + + if (pProxyReply->Status == CHTTPReply::ok) { + if (!pProxyReply->Content.IsEmpty()) { + const CJSON json(pProxyReply->Content); + pServerReply->Content = base64_decode(json["payload"].AsString()); + } + pProxyConnection->SendReply(pProxyReply->Status, nullptr, true); + } else { + pProxyConnection->SendStockReply(pProxyReply->Status, true); + } + } else { + if (pProxyReply->Status == CHTTPReply::ok) { + pServerReply->Content = pProxyReply->Content; + pProxyConnection->SendReply(pProxyReply->Status, nullptr, true); + } else { + pProxyConnection->SendStockReply(pProxyReply->Status, true); + } + } + } + + pConnection->CloseConnection(true); + + return true; + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::DoProxyException(CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { + auto pConnection = dynamic_cast (AConnection); + auto pProxy = dynamic_cast (pConnection->Client()); + + if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) { + auto pProxyConnection = pProxy->Connection(); + auto pServerRequest = pProxyConnection->Request(); + auto pServerReply = pProxyConnection->Reply(); + + const auto &Format = pServerRequest->Params["format"]; + if (Format == "html") { + pServerReply->ContentType = CHTTPReply::html; + } + + try { + pProxyConnection->SendStockReply(CHTTPReply::bad_gateway, true); + } catch (...) { + + } + } + + Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pProxy->Host().c_str(), pProxy->Port(), + E.what()); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::DoEventHandlerException(CPollEventHandler *AHandler, const Delphi::Exception::Exception &E) { + auto pConnection = dynamic_cast (AHandler->Binding()); + auto pProxy = dynamic_cast (pConnection->Client()); + + if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) { + auto pProxyConnection = pProxy->Connection(); + auto pReply = pProxyConnection->Reply(); + ExceptionToJson(0, E, pReply->Content); + pProxyConnection->SendReply(CHTTPReply::internal_server_error, nullptr, true); + } + + Log()->Error(APP_LOG_EMERG, 0, E.what()); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::DoProxyConnected(CObject *Sender) { + auto pConnection = dynamic_cast (Sender); + if (pConnection != nullptr) { + Log()->Message(_T("[%s:%d] Client connected."), pConnection->Socket()->Binding()->PeerIP(), + pConnection->Socket()->Binding()->PeerPort()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::DoProxyDisconnected(CObject *Sender) { + auto pConnection = dynamic_cast (Sender); + if (pConnection != nullptr) { + Log()->Message(_T("[%s:%d] Client disconnected."), pConnection->Socket()->Binding()->PeerIP(), + pConnection->Socket()->Binding()->PeerPort()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + CHTTPProxy *CWebService::GetProxy(CHTTPServerConnection *AConnection) { + auto pProxy = m_ProxyManager.Add(AConnection); +#if defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 9) + pProxy->OnVerbose([this](auto && Sender, auto && AConnection, auto && AFormat, auto && args) { DoVerbose(Sender, AConnection, AFormat, args); }); + + pProxy->OnExecute([this](auto && AConnection) { return DoProxyExecute(AConnection); }); + + pProxy->OnException([this](auto && AConnection, auto && E) { DoProxyException(AConnection, E); }); + pProxy->OnEventHandlerException([this](auto && AHandler, auto && E) { DoEventHandlerException(AHandler, E); }); + + pProxy->OnConnected([this](auto && Sender) { DoProxyConnected(Sender); }); + pProxy->OnDisconnected([this](auto && Sender) { DoProxyDisconnected(Sender); }); +#else + pProxy->OnVerbose(std::bind(&CWebService::DoVerbose, this, _1, _2, _3, _4)); + + pProxy->OnExecute(std::bind(&CWebService::DoProxyExecute, this, _1)); + + pProxy->OnException(std::bind(&CWebService::DoProxyException, this, _1, _2)); + pProxy->OnEventHandlerException(std::bind(&CWebService::DoEventHandlerException, this, _1, _2)); + + pProxy->OnConnected(std::bind(&CWebService::DoProxyConnected, this, _1)); + pProxy->OnDisconnected(std::bind(&CWebService::DoProxyDisconnected, this, _1)); +#endif + return pProxy; + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::RouteUser(CHTTPServerConnection *AConnection, const CString& Method, const CString& URI) { + + auto pProxy = GetProxy(AConnection); + auto pServerRequest = AConnection->Request(); + auto pProxyRequest = pProxy->Request(); + + const auto& caModuleAddress = Config()->IniFile().ReadString("module", "address", ""); + + const auto& caAuthorization = pServerRequest->Headers["authorization"]; + const auto& caOrigin = pServerRequest->Headers["origin"]; + const auto& caUserAddress = pServerRequest->Params["address"]; + + const auto& pgpValue = pServerRequest->Params["pgp"]; + + const auto& caServerParam = pServerRequest->Params["server"]; + const auto& caServer = caServerParam.IsEmpty() ? CurrentServer().Value().URI : caServerParam; + + CLocation Location(caServer); + + pProxy->Host() = Location.hostname; + pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port); + + CStringList caClearText; + CString sPayload; + + if (!pServerRequest->Content.IsEmpty()) { + + const auto& ContentType = pServerRequest->Headers.Values(_T("content-type")); + + if (ContentType.Find("application/x-www-form-urlencoded") == 0) { + + const CStringList &FormData = pServerRequest->FormData; + + const auto& formDate = FormData["date"]; + const auto& formAddress = FormData["address"]; + const auto& formBitmessage = FormData["bitmessage"]; + const auto& formKey = FormData["key"]; + const auto& formPGP = FormData["pgp"]; + const auto& formURL = FormData["url"]; + const auto& formFlags = FormData["flags"]; + const auto& formSign = FormData["sign"]; + + if (!formDate.IsEmpty()) { + caClearText << formDate; + } + + if (!formAddress.IsEmpty()) { + caClearText << formAddress; + } + + if (!formFlags.IsEmpty()) { + caClearText << formFlags; + } + + if (!formBitmessage.IsEmpty()) { + caClearText << formBitmessage; + } + + if (!formKey.IsEmpty()) { + caClearText << formKey; + } + + if (!formPGP.IsEmpty()) { + caClearText << formPGP; + } + + if (!formURL.IsEmpty()) { + caClearText << formURL; + } + + if (!formSign.IsEmpty()) { + caClearText << formSign; + } + + } else if (ContentType.Find("multipart/form-data") == 0) { + + CFormData FormData; + CHTTPRequestParser::ParseFormData(pServerRequest, FormData); + + const auto& formDate = FormData.Data("date"); + const auto& formAddress = FormData.Data("address"); + const auto& formBitmessage = FormData.Data("bitmessage"); + const auto& formKey = FormData.Data("key"); + const auto& formPGP = FormData.Data("pgp"); + const auto& formURL = FormData.Data("url"); + const auto& formFlags = FormData.Data("flags"); + const auto& formSign = FormData.Data("sign"); + + if (!formDate.IsEmpty()) { + caClearText << formDate; + } + + if (!formAddress.IsEmpty()) { + caClearText << formAddress; + } + + if (!formFlags.IsEmpty()) { + caClearText << formFlags; + } + + if (!formBitmessage.IsEmpty()) { + caClearText << formBitmessage; + } + + if (!formKey.IsEmpty()) { + caClearText << formKey; + } + + if (!formPGP.IsEmpty()) { + caClearText << formPGP; + } + + if (!formURL.IsEmpty()) { + caClearText << formURL; + } + + if (!formSign.IsEmpty()) { + caClearText << formSign; + } + + } else if (ContentType.Find("application/json") == 0) { + + const CJSON contextJson(pServerRequest->Content); + + const auto& jsonDate = contextJson["date"].AsString(); + const auto& jsonAddress = contextJson["address"].AsString(); + const auto& jsonBitmessage = contextJson["bitmessage"].AsString(); + const auto& jsonKey = contextJson["key"].AsString(); + const auto& jsonPGP = contextJson["pgp"].AsString(); + const auto& jsonFlags = contextJson["flags"].AsString(); + const auto& jsonSign = contextJson["sign"].AsString(); + + if (!jsonDate.IsEmpty()) { + caClearText << jsonDate; + } + + if (!jsonAddress.IsEmpty()) { + caClearText << jsonAddress; + } + + if (!jsonFlags.IsEmpty()) { + caClearText << jsonFlags; + } + + if (!jsonBitmessage.IsEmpty()) { + caClearText << jsonBitmessage; + } + + if (!jsonKey.IsEmpty()) { + caClearText << jsonKey; + } + + if (!jsonPGP.IsEmpty()) { + caClearText << jsonPGP; + } + + const CJSONValue &jsonURL = contextJson["url"]; + if (jsonURL.IsArray()) { + const CJSONArray &arrayURL = jsonURL.Array(); + for (int i = 0; i < arrayURL.Count(); i++) { + caClearText << arrayURL[i].AsString(); + } + } + + if (!jsonSign.IsEmpty()) { + caClearText << jsonSign; + } + + } else { + caClearText = pServerRequest->Content; + } + + const auto& caPGPPrivateFile = Config()->IniFile().ReadString("pgp", "private", ""); + const auto& caPGPPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); + + if (!FileExists(caPGPPrivateFile.c_str())) + throw Delphi::Exception::Exception("PGP: Private key file not opened."); + + CString sPGPPrivate; + sPGPPrivate.LoadFromFile(caPGPPrivateFile.c_str()); + + if (pgpValue == "off" || pgpValue == "false") { + sPayload = caClearText.Text(); + } else { + Apostol::PGP::CleartextSignature( + sPGPPrivate, + caPGPPassphrase, + BPS_PGP_HASH, + caClearText.Text(), + sPayload); + } + } + + //DebugMessage("[RouteUser] Server request:\n%s\n", pServerRequest->Content.c_str()); + //DebugMessage("[RouteUser] sPayload:\n%s\n", sPayload.c_str()); + + CJSON Json(jvtObject); + + Json.Object().AddPair("id", ApostolUID()); + Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress); + + if (!sPayload.IsEmpty()) + Json.Object().AddPair("payload", base64_encode(sPayload)); + + pProxyRequest->Clear(); + + pProxyRequest->Location = pServerRequest->Location; + pProxyRequest->CloseConnection = false; + pProxyRequest->ContentType = CHTTPRequest::json; + pProxyRequest->Content << Json; + + CHTTPRequest::Prepare(pProxyRequest, Method.c_str(), URI.c_str()); + + if (!caAuthorization.IsEmpty()) + pProxyRequest->AddHeader("Authorization", caAuthorization); + + if (!caModuleAddress.IsEmpty()) + pProxyRequest->AddHeader("Module-Address", caModuleAddress); + + if (!caOrigin.IsEmpty()) + pProxyRequest->AddHeader("Origin", caOrigin); + + AConnection->CloseConnection(false); + pProxy->Active(true); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::CheckDeal(const CDeal &Deal) { + + const auto& Data = Deal.Data(); + + const auto DateTime = UTC(); + const auto Date = StringToDate(Data.Date); + + if (Data.Order == doCreate) { + if (DateTime < Date) + throw ExceptionFrm("Invalid deal date."); + + if ((DateTime - Date) > (CDateTime) 180 / SecsPerDay) + throw ExceptionFrm("Deal date expired."); + } + + if (Data.Order == doComplete) { + const CDateTime LeaveBefore = StringToDate(Data.FeedBack.LeaveBefore); + if (DateTime > LeaveBefore) + throw ExceptionFrm("Date feedback expired."); + } + + if (Odd(int(Data.Order)) || Data.Order == doExecute || Data.Order == doDelete) + throw ExceptionFrm("Invalid \"order\" value for deal module."); + + if (Data.Order == doCancel) { + const CDateTime Until = StringToDate(Data.Payment.Until); + if (DateTime > Until) + throw ExceptionFrm("Deal cancellation expired."); + + CString message(Data.Payment.Address); + if (!Data.FeedBack.Comments.IsEmpty()) { + message += LINEFEED; + message += Data.FeedBack.Comments; + } + + if (Data.Seller.Signature.IsEmpty() || !VerifyMessage(message, Data.Seller.Address, Data.Seller.Signature)) + throw ExceptionFrm("The deal is not signed by the seller."); + } + + if (Data.Order == doFeedback) { + CString message(Data.Payment.Address); + if (!Data.FeedBack.Comments.IsEmpty()) { + message += LINEFEED; + message += Data.FeedBack.Comments; + } + + if (Data.Customer.Signature.IsEmpty() || !VerifyMessage(message, Data.Customer.Address, Data.Customer.Signature)) + throw ExceptionFrm("The deal is not signed by the customer."); + } + + if (!valid_address(Data.Seller.Address)) + throw ExceptionFrm("Invalid Seller address: %s.", Data.Seller.Address.c_str()); + + if (!valid_address(Data.Customer.Address)) + throw ExceptionFrm("Invalid Customer address: %s.", Data.Customer.Address.c_str()); + + if (!valid_address(Data.Payment.Address)) + throw ExceptionFrm("Invalid Payment address: %s.", Data.Payment.Address.c_str()); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::RouteDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) { + + auto pProxy = GetProxy(AConnection); + auto pServerRequest = AConnection->Request(); + auto pProxyRequest = pProxy->Request(); + + const auto& caModuleAddress = Config()->IniFile().ReadString("module", "address", ""); + const auto& caModuleFee = Config()->IniFile().ReadString("module", "fee", "0.1%"); + + const auto checkFee = CheckFee(caModuleFee); + if (checkFee == -1) + throw ExceptionFrm("Invalid module fee value: %s", caModuleFee.c_str()); + + const auto& caAuthorization = pServerRequest->Headers["authorization"]; + const auto& caOrigin = pServerRequest->Headers["origin"]; + const auto& caUserAddress = pServerRequest->Params["address"]; + + const auto& pgpValue = pServerRequest->Params["pgp"]; + + const auto& caServerParam = pServerRequest->Params["server"]; + const auto& caServer = caServerParam.IsEmpty() ? CurrentServer().Value().URI : caServerParam; + + CLocation Location(caServer); + + pProxy->Host() = Location.hostname; + pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port); + + YAML::Node Node; + + CString sPayload; + + if (!pServerRequest->Content.IsEmpty()) { + + const auto& ContentType = pServerRequest->Headers.Values(_T("content-type")); + + if (ContentType.Find("application/x-www-form-urlencoded") == 0) { + + const CStringList &FormData = pServerRequest->FormData; + + const auto& formType = FormData["type"]; + const auto& formAt = FormData["at"]; + const auto& formDate = FormData["date"]; + const auto& formSellerAddress = FormData["seller_address"]; + const auto& formSellerRating = FormData["seller_rating"]; + const auto& formSellerSignature = FormData["seller_signature"]; + const auto& formCustomerAddress = FormData["customer_address"]; + const auto& formCustomerRating = FormData["customer_rating"]; + const auto& formCustomerSignature = FormData["customer_signature"]; + const auto& formPaymentAddress = FormData["payment_address"]; + const auto& formPaymentUntil = FormData["payment_until"]; + const auto& formPaymentSum = FormData["payment_sum"]; + const auto& formFeedbackLeaveBefore = FormData["feedback_leave_before"]; + const auto& formFeedbackStatus = FormData["feedback_status"]; + const auto& formFeedbackComments = FormData["feedback_comments"]; + + CheckKeyForNull("order", Action.c_str()); + CheckKeyForNull("type", formType.c_str()); + CheckKeyForNull("at", formAt.c_str()); + CheckKeyForNull("date", formDate.c_str()); + CheckKeyForNull("seller_address", formSellerAddress.c_str()); + CheckKeyForNull("customer_address", formCustomerAddress.c_str()); + CheckKeyForNull("payment_sum", formPaymentSum.c_str()); + + if (Action == "cancel") { + CheckKeyForNull("seller_signature", formSellerSignature.c_str()); + } + + if (Action == "feedback") { + CheckKeyForNull("customer_signature", formCustomerSignature.c_str()); + } + + YAML::Node Deal = Node["deal"]; + + Deal["order"] = Action.c_str(); + Deal["type"] = formType.c_str(); + + Deal["at"] = formAt.c_str(); + Deal["date"] = formDate.c_str(); + + YAML::Node Seller = Deal["seller"]; + + Seller["address"] = formSellerAddress.c_str(); + + if (!formSellerRating.IsEmpty()) + Seller["rating"] = formSellerRating.c_str(); + + if (!formSellerSignature.IsEmpty()) + Seller["signature"] = formSellerSignature.c_str(); + + YAML::Node Customer = Deal["customer"]; + + Customer["address"] = formCustomerAddress.c_str(); + + if (!formCustomerRating.IsEmpty()) + Customer["rating"] = formCustomerRating.c_str(); + + if (!formCustomerSignature.IsEmpty()) + Customer["signature"] = formCustomerSignature.c_str(); + + YAML::Node Payment = Deal["payment"]; + + if (!formPaymentAddress.IsEmpty()) + Payment["address"] = formPaymentAddress.c_str(); + + if (!formPaymentUntil.IsEmpty()) + Payment["until"] = formPaymentUntil.c_str(); + + Payment["sum"] = formPaymentSum.c_str(); + + if (!formFeedbackLeaveBefore.IsEmpty()) { + YAML::Node Feedback = Deal["feedback"]; + + Feedback["leave-before"] = formFeedbackLeaveBefore.c_str(); + + if (!formFeedbackStatus.IsEmpty()) + Feedback["status"] = formFeedbackStatus.c_str(); + + if (!formFeedbackComments.IsEmpty()) + Feedback["comments"] = formFeedbackComments.c_str(); + } + + } else if (ContentType.Find("multipart/form-data") == 0) { + + CFormData FormData; + CHTTPRequestParser::ParseFormData(pServerRequest, FormData); + + const auto& formType = FormData.Data("type"); + const auto& formAt = FormData.Data("at"); + const auto& formDate = FormData.Data("date"); + const auto& formSellerAddress = FormData.Data("seller_address"); + const auto& formSellerRating = FormData.Data("seller_rating"); + const auto& formSellerSignature = FormData.Data("seller_signature"); + const auto& formCustomerAddress = FormData.Data("customer_address"); + const auto& formCustomerRating = FormData.Data("customer_rating"); + const auto& formCustomerSignature = FormData.Data("customer_signature"); + const auto& formPaymentAddress = FormData.Data("payment_address"); + const auto& formPaymentUntil = FormData.Data("payment_until"); + const auto& formPaymentSum = FormData.Data("payment_sum"); + const auto& formFeedbackLeaveBefore = FormData.Data("feedback_leave_before"); + const auto& formFeedbackStatus = FormData.Data("feedback_status"); + const auto& formFeedbackComments = FormData.Data("feedback_comments"); + + CheckKeyForNull("order", Action.c_str()); + CheckKeyForNull("type", formType.c_str()); + CheckKeyForNull("at", formAt.c_str()); + CheckKeyForNull("date", formDate.c_str()); + CheckKeyForNull("seller_address", formSellerAddress.c_str()); + CheckKeyForNull("customer_address", formCustomerAddress.c_str()); + CheckKeyForNull("payment_sum", formPaymentSum.c_str()); + + if (Action == "cancel") { + CheckKeyForNull("seller_signature", formSellerSignature.c_str()); + } + + if (Action == "feedback") { + CheckKeyForNull("customer_signature", formCustomerSignature.c_str()); + } + + YAML::Node Deal = Node["deal"]; + + Deal["order"] = Action.c_str(); + Deal["type"] = formType.c_str(); + + Deal["at"] = formAt.c_str(); + Deal["date"] = formDate.c_str(); + + YAML::Node Seller = Deal["seller"]; + + Seller["address"] = formSellerAddress.c_str(); + + if (!formSellerRating.IsEmpty()) + Seller["rating"] = formSellerRating.c_str(); + + if (!formSellerSignature.IsEmpty()) + Seller["signature"] = formSellerSignature.c_str(); + + YAML::Node Customer = Deal["customer"]; + + Customer["address"] = formCustomerAddress.c_str(); + + if (!formSellerRating.IsEmpty()) + Customer["rating"] = formCustomerRating.c_str(); + + if (!formCustomerSignature.IsEmpty()) + Customer["signature"] = formCustomerSignature.c_str(); + + YAML::Node Payment = Deal["payment"]; + + if (!formPaymentAddress.IsEmpty()) + Payment["address"] = formPaymentAddress.c_str(); + + if (!formPaymentUntil.IsEmpty()) + Payment["until"] = formPaymentUntil.c_str(); + + Payment["sum"] = formPaymentSum.c_str(); + + if (!formFeedbackLeaveBefore.IsEmpty()) { + YAML::Node Feedback = Deal["feedback"]; + + Feedback["leave-before"] = formFeedbackLeaveBefore.c_str(); + + if (!formFeedbackStatus.IsEmpty()) + Feedback["status"] = formFeedbackStatus.c_str(); + + if (!formFeedbackComments.IsEmpty()) + Feedback["comments"] = formFeedbackComments.c_str(); + } + + } else if (ContentType.Find("application/json") == 0) { + + const CJSON jsonData(pServerRequest->Content); + + const auto& formOrder = jsonData["order"].AsString().Lower(); + const auto& formType = jsonData["type"].AsString(); + + const auto& formAt = jsonData["at"].AsString(); + const auto& formDate = jsonData["date"].AsString(); + + const CJSONValue &jsonSeller = jsonData["seller"]; + + const auto& formSellerAddress = jsonSeller["address"].AsString(); + const auto& formSellerRating = jsonSeller["rating"].AsString(); + const auto& formSellerSignature = jsonSeller["signature"].AsString(); + + const CJSONValue &jsonCustomer = jsonData["customer"]; + + const auto& formCustomerAddress = jsonCustomer["address"].AsString(); + const auto& formCustomerRating = jsonCustomer["rating"].AsString(); + const auto& formCustomerSignature = jsonCustomer["signature"].AsString(); + + const CJSONValue &jsonPayment = jsonData["payment"]; + + const auto& formPaymentAddress = jsonPayment["address"].AsString(); + const auto& formPaymentUntil = jsonPayment["until"].AsString(); + const auto& formPaymentSum = jsonPayment["sum"].AsString(); + + const CJSONValue &jsonFeedback = jsonData["feedback"]; + + const auto& formFeedbackLeaveBefore = jsonFeedback["leave-before"].AsString(); + const auto& formFeedbackStatus = jsonFeedback["status"].AsString(); + const auto& formFeedbackComments = jsonFeedback["comments"].AsString(); + + const auto &action = Action.IsEmpty() ? formOrder : Action; + + CheckKeyForNull("order", action.c_str()); + CheckKeyForNull("type", formType.c_str()); + CheckKeyForNull("at", formAt.c_str()); + CheckKeyForNull("date", formDate.c_str()); + CheckKeyForNull("seller.address", formSellerAddress.c_str()); + CheckKeyForNull("customer.address", formCustomerAddress.c_str()); + CheckKeyForNull("payment.sum", formPaymentSum.c_str()); + + if (action == "cancel") { + CheckKeyForNull("seller.signature", formSellerSignature.c_str()); + } + + if (action == "feedback") { + CheckKeyForNull("customer.signature", formCustomerSignature.c_str()); + } + + YAML::Node Deal = Node["deal"]; + + Deal["order"] = action.c_str(); + Deal["type"] = formType.c_str(); + + Deal["at"] = formAt.c_str(); + Deal["date"] = formDate.c_str(); + + YAML::Node Seller = Deal["seller"]; + + Seller["address"] = formSellerAddress.c_str(); + + if (!formSellerRating.IsEmpty()) + Seller["rating"] = formSellerRating.c_str(); + + YAML::Node Customer = Deal["customer"]; + + Customer["address"] = formCustomerAddress.c_str(); + + if (!formSellerRating.IsEmpty()) + Customer["rating"] = formCustomerRating.c_str(); + + YAML::Node Payment = Deal["payment"]; + + if (!formPaymentAddress.IsEmpty()) + Payment["address"] = formPaymentAddress.c_str(); + + if (!formPaymentUntil.IsEmpty()) + Payment["until"] = formPaymentUntil.c_str(); + + Payment["sum"] = formPaymentSum.c_str(); + + if (!formFeedbackLeaveBefore.IsEmpty()) { + YAML::Node Feedback = Deal["feedback"]; + + Feedback["leave-before"] = formFeedbackLeaveBefore.c_str(); + + if (!formFeedbackStatus.IsEmpty()) + Feedback["status"] = formFeedbackStatus.c_str(); + + if (!formFeedbackComments.IsEmpty()) + Feedback["comments"] = formFeedbackComments.c_str(); + } + + } else { + Node = YAML::Load(pServerRequest->Content.c_str()); + } + + if (m_BTCKeys.Count() < 2) + throw ExceptionFrm("Bitcoin keys cannot be empty."); + + for (int i = 0; i < m_BTCKeys.Count(); ++i) { + if (m_BTCKeys[i].IsEmpty()) + throw ExceptionFrm("Bitcoin KEY%d cannot be empty.", i); + } + + CDeal Deal(Node); + + auto& Data = Deal.Data(); + + if (Data.Order == doCreate) { + Data.Payment.Address = Deal.GetPaymentHD(m_BTCKeys.Names(0), m_BTCKeys.Names(1), + Deal.Data().Transaction.Key, BitcoinConfig.version_hd, BitcoinConfig.version_script); + + Node["deal"]["date"] = Data.Date.c_str(); + + YAML::Node Payment = Node["deal"]["payment"]; + Payment.remove("sum"); + + Payment["address"] = Data.Payment.Address.c_str(); + Payment["until"] = Data.Payment.Until.c_str(); + Payment["sum"] = Data.Payment.Sum.c_str(); + + Node["deal"]["feedback"]["leave-before"] = Data.FeedBack.LeaveBefore.c_str(); + } + + CheckDeal(Deal); + + const auto& caPGPPrivateFile = Config()->IniFile().ReadString("pgp", "private", ""); + const auto& caPGPPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); + + if (!FileExists(caPGPPrivateFile.c_str())) + throw Delphi::Exception::Exception("PGP: Private key file not opened."); + + CString sPGPPrivate; + sPGPPrivate.LoadFromFile(caPGPPrivateFile.c_str()); + + const CString caClearText(YAML::Dump(Node)); + + if (pgpValue == "off" || pgpValue == "false") { + sPayload = caClearText; + } else { + Apostol::PGP::CleartextSignature( + sPGPPrivate, + caPGPPassphrase, + BPS_PGP_HASH, + caClearText, + sPayload); + } + } + + //DebugMessage("[RouteDeal] Server request:\n%s\n", pServerRequest->Content.c_str()); + //DebugMessage("[RouteDeal] sPayload:\n%s\n", sPayload.c_str()); + + CJSON Json(jvtObject); + + Json.Object().AddPair("id", ApostolUID()); + if (!caUserAddress.IsEmpty()) + Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress); + + if (!sPayload.IsEmpty()) + Json.Object().AddPair("payload", base64_encode(sPayload)); + + pProxyRequest->Clear(); + + pProxyRequest->Location = pServerRequest->Location; + pProxyRequest->CloseConnection = true; + pProxyRequest->ContentType = CHTTPRequest::json; + pProxyRequest->Content << Json; + + CHTTPRequest::Prepare(pProxyRequest, Method.c_str(), URI.c_str()); + + if (!caAuthorization.IsEmpty()) + pProxyRequest->AddHeader("Authorization", caAuthorization); + + if (!caModuleAddress.IsEmpty()) + pProxyRequest->AddHeader("Module-Address", caModuleAddress); + + if (!caModuleFee.IsEmpty()) + pProxyRequest->AddHeader("Module-Fee", caModuleFee); + + if (!caOrigin.IsEmpty()) + pProxyRequest->AddHeader("Origin", caOrigin); + + AConnection->CloseConnection(false); + pProxy->Active(true); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::RouteSignature(CHTTPServerConnection *AConnection) { + + auto pRequest = AConnection->Request(); + auto pReply = AConnection->Reply(); + + if (pRequest->Content.IsEmpty()) { + AConnection->SendStockReply(CHTTPReply::no_content); + return; + } + + const auto& caServerKey = CurrentServer().Value().PGP.Key; + + if (caServerKey.IsEmpty()) + throw ExceptionFrm("Server PGP key not added."); + + CString message; + CJSON Json(jvtObject); + + const auto& caContentType = pRequest->Headers["content-type"]; + + if (caContentType.Find("application/x-www-form-urlencoded") == 0) { + const CStringList &FormData = pRequest->FormData; + + const auto& caClearText = FormData["message"]; + CheckKeyForNull("message", caClearText.c_str()); + + const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); + Json.Object().AddPair("verified", bVerified); + } else if (caContentType.Find("multipart/form-data") == 0) { + CFormData FormData; + CHTTPRequestParser::ParseFormData(pRequest, FormData); + + const auto& caClearText = FormData.Data("message"); + CheckKeyForNull("message", caClearText.c_str()); + + const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); + Json.Object().AddPair("verified", bVerified); + } else if (caContentType.Find("application/json") == 0) { + const CJSON jsonData(pRequest->Content); + + const auto& caClearText = jsonData["message"].AsString(); + CheckKeyForNull("message", caClearText.c_str()); + + const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); + Json.Object().AddPair("verified", bVerified); + } else { + const auto& caClearText = pRequest->Content; + const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); + Json.Object().AddPair("verified", bVerified); + } + + Json.Object().AddPair("message", message); + + AConnection->CloseConnection(true); + pReply->Content << Json; + + AConnection->SendReply(CHTTPReply::ok); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::DoAPI(CHTTPServerConnection *AConnection) { + + auto pRequest = AConnection->Request(); + auto pReply = AConnection->Reply(); + + pReply->ContentType = CHTTPReply::json; + + CStringList slRouts; + SplitColumns(pRequest->Location.pathname, slRouts, '/'); + + if (slRouts.Count() < 3) { + AConnection->SendStockReply(CHTTPReply::not_found); + return; + } + + const auto& caService = slRouts[0].Lower(); + const auto& caVersion = slRouts[1].Lower(); + const auto& caCommand = slRouts[2].Lower(); + const auto& caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : ""; + + if (caService != "api") { + AConnection->SendStockReply(CHTTPReply::not_found); + return; + } + + if (caVersion != "v1") { + AConnection->SendStockReply(CHTTPReply::not_found); + return; + } + + CString sRoute; + for (int I = 0; I < slRouts.Count(); ++I) { + sRoute.Append('/'); + sRoute.Append(slRouts[I]); + } + + try { + if (caCommand == "ping") { + + AConnection->SendStockReply(CHTTPReply::ok); + + } else if (caCommand == "time") { + + pReply->Content << "{\"serverTime\": " << to_string(MsEpoch()) << "}"; + + AConnection->SendReply(CHTTPReply::ok); + + } else if (caCommand == "help") { + + pRequest->Content.Clear(); + + RouteUser(AConnection, "GET", sRoute); + + } else if (caCommand == "account" && caAction == "status") { + + pRequest->Content.Clear(); + + RouteUser(AConnection, "GET", sRoute); + + } else if (caCommand == "deal" && caAction == "status") { + + pRequest->Content.Clear(); + + RouteDeal(AConnection, "GET", sRoute, caAction); + + } else { + + AConnection->SendStockReply(CHTTPReply::not_found); + + } + + } catch (std::exception &e) { + CHTTPReply::CStatusType LStatus = CHTTPReply::internal_server_error; + + ExceptionToJson(0, e, pReply->Content); + + AConnection->SendReply(LStatus); + Log()->Error(APP_LOG_EMERG, 0, e.what()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::DoGet(CHTTPServerConnection *AConnection) { + + auto pRequest = AConnection->Request(); + + CString sPath(pRequest->Location.pathname); + + // Request sPath must be absolute and not contain "..". + if (sPath.empty() || sPath.front() != '/' || sPath.find(_T("..")) != CString::npos) { + AConnection->SendStockReply(CHTTPReply::bad_request); + return; + } + + if (sPath.SubString(0, 5) == "/api/") { + DoAPI(AConnection); + return; + } + + SendResource(AConnection, sPath); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::DoPost(CHTTPServerConnection *AConnection) { + + auto pRequest = AConnection->Request(); + auto pReply = AConnection->Reply(); + + pReply->ContentType = CHTTPReply::json; + + CStringList slRouts; + SplitColumns(pRequest->Location.pathname, slRouts, '/'); + + if (slRouts.Count() < 3) { + AConnection->SendStockReply(CHTTPReply::not_found); + return; + } + + const auto& caService = slRouts[0].Lower(); + const auto& caVersion = slRouts[1].Lower(); + const auto& caCommand = slRouts[2].Lower(); + const auto& caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : ""; + + if (caService != "api") { + AConnection->SendStockReply(CHTTPReply::not_found); + return; + } + + if (caVersion != "v1") { + AConnection->SendStockReply(CHTTPReply::not_found); + return; + } + + CString sRoute; + for (int I = 0; I < slRouts.Count(); ++I) { + sRoute.Append('/'); + sRoute.Append(slRouts[I]); + } + + try { + + if (caCommand == "account") { + + RouteUser(AConnection, "POST", sRoute); + + } else if (caCommand == "deal") { + + RouteDeal(AConnection, "POST", sRoute, caAction); + + } else if (caCommand == "signature") { + + RouteSignature(AConnection); + + } else { + + AConnection->SendStockReply(CHTTPReply::not_found); + + } + + } catch (std::exception &e) { + ExceptionToJson(0, e, pReply->Content); + + AConnection->SendReply(CHTTPReply::internal_server_error); + Log()->Error(APP_LOG_EMERG, 0, e.what()); + } + } + //-------------------------------------------------------------------------------------------------------------- + + int CWebService::NextServerIndex() { + m_ServerIndex++; + if (m_ServerIndex >= m_Servers.Count()) + m_ServerIndex = -1; + return m_ServerIndex; + } + //-------------------------------------------------------------------------------------------------------------- + + const CServer &CWebService::CurrentServer() const { + if (m_Servers.Count() == 0 && m_ServerIndex == -1) + return m_DefaultServer; + if (m_ServerIndex == -1) + return m_Servers[0]; + return m_Servers[m_ServerIndex]; + } + //-------------------------------------------------------------------------------------------------------------- + + bool CWebService::CheckVerifyPGPSignature(int VerifyPGPSignature, CString &Message) { + if (VerifyPGPSignature == -2) { + Message = "PGP public key is not meaningful."; + } else if (VerifyPGPSignature == -1) { + Message = "PGP signature not valid."; + } else if (VerifyPGPSignature == 0) { + Message = "PGP signature not found."; + } else if (VerifyPGPSignature > 1) { + Message = "PGP signature status: Unknown."; + } + return VerifyPGPSignature == 1; + } + //-------------------------------------------------------------------------------------------------------------- + + int CWebService::VerifyPGPSignature(const CString &caClearText, const CString &Key, CString &Message) { + const OpenPGP::Key signer(Key.c_str()); + const OpenPGP::CleartextSignature cleartext(caClearText.c_str()); + + if (!cleartext.meaningful()) + return -2; + + const int verified = OpenPGP::Verify::cleartext_signature(signer, cleartext); + + if (verified) { + Message = cleartext.get_message().c_str(); + } + + return verified; + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::ParsePGPKey(const CString &Key, CStringPairs& ServerList, CStringList& BTCKeys) { + if (Key.IsEmpty()) + return; + + const Apostol::PGP::Key pgp(Key.c_str()); + if (!pgp.meaningful()) + return; + + CStringList Data; + CPGPUserIdList List; + CStringList KeyList; + + pgp.ExportUID(List); + + for (int i = 0; i < List.Count(); i++) { + + const auto& uid = List[i]; + const auto& name = uid.Name.Lower(); + const auto& data = uid.Desc.Lower(); + + if (name == "technical_data") { + SplitColumns(data, Data, ';'); + + DebugMessage("Technical data: "); + if (Data.Count() == 3) { + m_SyncPeriod = StrToIntDef(Data[1].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD); + DebugMessage("\n - Fee : %s", Data[0].Trim().c_str()); + DebugMessage("\n - Period: %s", Data[1].Trim().c_str()); + DebugMessage("\n - Keys : %s", Data[2].Trim().c_str()); + } else if (Data.Count() == 2) { + m_SyncPeriod = StrToIntDef(Data[0].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD); + DebugMessage("\n - Period: %s", Data[0].Trim().c_str()); + DebugMessage("\n - Keys : %s", Data[1].Trim().c_str()); + } else if (Data.Count() == 1) { + m_SyncPeriod = StrToIntDef(Data[0].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD); + DebugMessage("\n - Period: %s", Data[0].Trim().c_str()); + } else { + DebugMessage("Unknown error."); + } + + } if (uid.Name.Length() >= 35 && uid.Name.SubString(0, 3) == BM_PREFIX) { + CStringList urlList; + if (FindURLInLine(uid.Desc, urlList)) { + for (int l = 0; l < urlList.Count(); l++) { + ServerList.AddPair(uid.Name, urlList[l]); + } + } + } else if (name.Find("bitcoin_key") != CString::npos) { + const auto& key = wallet::ec_public(data.c_str()); + if (verify(key)) + KeyList.AddPair(name, key.encoded()); + } + } + + CString Name; + for (int i = 1; i <= KeyList.Count(); i++) { + Name = "bitcoin_key"; + Name << i; + const auto& key = KeyList[Name]; + if (!key.IsEmpty()) + BTCKeys.Add(key); + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::JsonStringToKey(const CString &jsonString, CString& Key) { + CJSON Json; + Json << jsonString; + + if (Json.HasOwnProperty("error")) { + const auto &error = Json["error"]; + if (error.ValueType() == jvtObject) + throw Delphi::Exception::Exception(error["message"].AsString().c_str()); + } + + if (Json.HasOwnProperty("data")) { + Key = Json["data"].AsString(); + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::FetchPGP(CKeyContext &PGP) { + + const auto& caServerContext = CurrentServer().Value(); + + Log()->Debug(0, "Trying to fetch a PGP key \"%s\" from: %s", PGP.Name.c_str(), caServerContext.URI.c_str()); + + auto OnRequest = [this, &PGP](CHTTPClient *Sender, CHTTPRequest *ARequest) { + PGP.StatusTime = Now(); + PGP.Status = CKeyContext::ksFetching; + + CHTTPRequest::Prepare(ARequest, "GET", CString().Format("/api/v1/key/public?account=%s", PGP.Name.c_str()).c_str()); + + ARequest->AddHeader("Authorization", "Bearer " + m_Tokens[SYSTEM_PROVIDER_NAME]["access_token"]); + }; + + auto OnExecute = [this, &PGP](CTCPConnection *AConnection) { + + auto pConnection = dynamic_cast (AConnection); + auto pReply = pConnection->Reply(); + + try { + PGP.StatusTime = Now(); + PGP.Status = CKeyContext::ksError; + + JsonStringToKey(pReply->Content, PGP.Key); + + if (PGP.Key.IsEmpty()) + throw Delphi::Exception::Exception("Not found."); + + DebugMessage("[PGP] Key:\n%s\n", PGP.Key.c_str()); + + CStringPairs ServerList; + CStringList BTCKeys; + + ParsePGPKey(PGP.Key, ServerList, BTCKeys); + + if (ServerList.Count() != 0) { + CStringPairs::ConstEnumerator em(ServerList); + while (em.MoveNext()) { + const auto ¤t = em.Current(); + if (m_Servers.IndexOfName(current.Name()) == -1) { + m_Servers.AddPair(current.Name(), CServerContext(current.Value())); + } + } + } + + m_BTCKeys = BTCKeys; + PGP.Status = CKeyContext::ksSuccess; + m_RandomDate = GetRandomDate(10 * 60, m_SyncPeriod * 60, Now()); // 10..m_SyncPeriod min + } catch (Delphi::Exception::Exception &e) { + Log()->Error(APP_LOG_INFO, 0, "[PGP] Message: %s", e.what()); + PGP.Status = CKeyContext::ksError; + m_RandomDate = Now() + (CDateTime) 30 / SecsPerDay; + } + + pConnection->CloseConnection(true); + return true; + }; + + auto OnException = [this, &PGP](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { + auto pConnection = dynamic_cast (AConnection); + auto pClient = dynamic_cast (pConnection->Client()); + + Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); + + PGP.Status = CKeyContext::ksError; + m_RandomDate = Now() + (CDateTime) 5 / SecsPerDay; + }; + + CLocation Location(caServerContext.URI); + auto pClient = GetClient(Location.hostname, Location.port == 0 ? BPS_SERVER_PORT : Location.port); + + pClient->OnRequest(OnRequest); + pClient->OnExecute(OnExecute); + pClient->OnException(OnException); + + pClient->Active(true); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::FetchBTC(CKeyContext &BTC) { + + const auto& caServerContext = CurrentServer().Value(); + + Log()->Debug(0, "Trying to fetch a BTC KEY%d from: %s", m_KeyIndex + 1, caServerContext.URI.c_str()); + + auto OnRequest = [this, &BTC](CHTTPClient *Sender, CHTTPRequest *ARequest) { + const auto& caServerContext = CurrentServer().Value(); + BTC.StatusTime = Now(); + BTC.Status = CKeyContext::ksFetching; + CString URI("/api/v1/key?type=BTC-PUBLIC&name=KEY"); + URI << m_KeyIndex + 1; + CHTTPRequest::Prepare(ARequest, "GET", URI.c_str()); + }; + + auto OnExecute = [this, &BTC](CTCPConnection *AConnection) { + auto pConnection = dynamic_cast (AConnection); + auto pReply = pConnection->Reply(); + + try { + CString Key; + JsonStringToKey(pReply->Content, Key); + if (!Key.IsEmpty()) { + m_BTCKeys.Add(Key); + m_KeyIndex++; + //FetchBTC(); + } + } catch (Delphi::Exception::Exception &e) { + Log()->Error(APP_LOG_INFO, 0, "[BTC] Message: %s", e.what()); + if (m_BTCKeys.Count() == 0) { + BTC.Status = CKeyContext::ksError; + } else { + BTC.Status = CKeyContext::ksSuccess; + } + } + + pConnection->CloseConnection(true); + + return true; + }; + + auto OnException = [&BTC](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { + auto pConnection = dynamic_cast (AConnection); + auto pClient = dynamic_cast (pConnection->Client()); + + Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); + BTC.Status = CKeyContext::ksError; + }; + + CLocation Location(caServerContext.URI); + auto pClient = GetClient(Location.hostname, Location.port == 0 ? BPS_SERVER_PORT : Location.port); + + pClient->OnRequest(OnRequest); + pClient->OnExecute(OnExecute); + pClient->OnException(OnException); + + pClient->Active(true); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::FetchKeys() { + m_KeyIndex = 0; + if (NextServerIndex() != -1) { + m_PGP.Status = CKeyContext::ksUnknown; + FetchPGP(m_PGP); + FetchPGP(m_Servers[m_ServerIndex].Value().PGP); + } + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::Reload() { + + m_Providers.Clear(); + + const auto &oauth2 = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json"); + const auto &provider = CString(SYSTEM_PROVIDER_NAME); + const auto &application = CString(SERVICE_APPLICATION_NAME); + + if (!oauth2.empty()) { + m_Tokens.AddPair(provider, CStringList()); + LoadOAuth2(oauth2, provider, application, m_Providers); + } + + m_FixedDate = 0; + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::Initialization(CModuleProcess *AProcess) { + CApostolModule::Initialization(AProcess); + Reload(); + } + //-------------------------------------------------------------------------------------------------------------- + + void CWebService::Heartbeat() { + const auto now = Now(); + + if ((now >= m_FixedDate)) { + m_FixedDate = now + (CDateTime) 55 / MinsPerDay; // 55 min + CheckProviders(); + FetchProviders(); + } + + if (m_Servers.Count() == 0) { +#ifdef _DEBUG + int Index = m_Servers.AddPair("BM-2cXtL92m3CavBKx8qsV2LbZtAU3eQxW2rB", CServerContext("http://localhost:4988")); + m_Servers[Index].Value().PGP.Name = m_Servers[Index].Name(); +#else + m_Servers.Add(m_DefaultServer); +#endif + } + +// const auto& caServer = CurrentServer().Value(); + + if (m_Status == psRunning) { + if (now >= m_RandomDate) { + FetchKeys(); + } + } + } + //-------------------------------------------------------------------------------------------------------------- + + bool CWebService::Enabled() { + if (m_ModuleStatus == msUnknown) + m_ModuleStatus = msEnabled; + return m_ModuleStatus == msEnabled; + } + //-------------------------------------------------------------------------------------------------------------- + + } +} +} \ No newline at end of file diff --git a/src/modules/Workers/WebService/WebService.hpp b/src/modules/Workers/WebService/WebService.hpp new file mode 100644 index 0000000..a4da078 --- /dev/null +++ b/src/modules/Workers/WebService/WebService.hpp @@ -0,0 +1,199 @@ +/*++ + +Library name: + + apostol-module + +Module Name: + + WebService.hpp + +Notices: + + WebService - Deal Module Web Service + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_WEBSERVICE_HPP +#define APOSTOL_WEBSERVICE_HPP +//---------------------------------------------------------------------------------------------------------------------- +#define BPS_DEFAULT_SYNC_PERIOD 30 +#define BPS_SERVER_PORT 4988 +#define BPS_SERVER_URL "http://65.21.101.151:4988" +#define BPS_BM_SERVER_ADDRESS "BM-2cX8y9u9yEi3fdqQfndx9F6NdR5Hv79add" +//---------------------------------------------------------------------------------------------------------------------- + +extern "C++" { + +namespace Apostol { + + namespace Workers { + + struct CKeyContext { + + CString Name; + CString Key; + + enum CKeyStatus { + ksUnknown = -1, + ksFetching, + ksSuccess, + ksError, + } Status; + + CDateTime StatusTime; + + CKeyContext(): Status(ksUnknown), StatusTime(Now()) { + Name = "DEFAULT"; + } + + CKeyContext(const CString& Name, const CString& Key): CKeyContext() { + this->Name = Name; + this->Key = Key; + } + + }; + + struct CServerContext { + + CString URI; + CKeyContext PGP; + + CServerContext() = default; + + explicit CServerContext(const CString& URI) { + this->URI = URI; + } + + }; + + typedef TPair CServer; + typedef TPairs CServerList; + + //-------------------------------------------------------------------------------------------------------------- + + //-- CWebService ----------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------------------- + + class CWebService: public CApostolModule { + private: + + CProcessStatus m_Status; + + CServer m_DefaultServer; + + int m_SyncPeriod; + int m_ServerIndex; + int m_KeyIndex; + + CDateTime m_FixedDate; + CDateTime m_RandomDate; + + CKeyContext m_PGP; + + CServerList m_Servers; + + CStringList m_BTCKeys; + + CProviders m_Providers; + CStringListPairs m_Tokens; + + CHTTPProxyManager m_ProxyManager; + + CHTTPProxy *GetProxy(CHTTPServerConnection *AConnection); + + void InitMethods() override; + + void FetchCerts(CProvider &Provider, const CString &Application); + + void FetchProviders(); + void CheckProviders(); + + void RouteUser(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI); + void RouteDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action); + + void RouteSignature(CHTTPServerConnection *AConnection); + + void FetchAccessToken(const CString &URI, const CString &Assertion, + COnSocketExecuteEvent && OnDone, COnSocketExceptionEvent && OnFailed = nullptr); + void CreateAccessToken(const CProvider &Provider, const CString &Application, CStringList &Tokens); + + static CString CreateToken(const CProvider& Provider, const CString &Application); + + protected: + + int NextServerIndex(); + + const CServer &CurrentServer() const; + + void FetchPGP(CKeyContext& PGP); + void FetchBTC(CKeyContext& BTC); + + static int CheckFee(const CString& Fee); + + static void CheckKeyForNull(LPCTSTR Key, LPCTSTR Value); + + void DoAPI(CHTTPServerConnection *AConnection); + + void DoGet(CHTTPServerConnection *AConnection) override; + void DoPost(CHTTPServerConnection *AConnection); + + void DoVerbose(CSocketEvent *Sender, CTCPConnection *AConnection, LPCTSTR AFormat, va_list args); + bool DoProxyExecute(CTCPConnection *AConnection); + void DoProxyException(CTCPConnection *AConnection, const Delphi::Exception::Exception &E); + void DoEventHandlerException(CPollEventHandler *AHandler, const Delphi::Exception::Exception &E); + + void DoProxyConnected(CObject *Sender); + void DoProxyDisconnected(CObject *Sender); + + public: + + explicit CWebService(CModuleProcess *AProcess); + + ~CWebService() override = default; + + static class CWebService *CreateModule(CModuleProcess *AProcess) { + return new CWebService(AProcess); + } + + void Initialization(CModuleProcess *AProcess) override; + + void Heartbeat() override; + + bool Enabled() override; + + void FetchKeys(); + + void Reload(); + + static void JsonStringToKey(const CString& jsonString, CString& Key); + + void ParsePGPKey(const CString& Key, CStringPairs& ServerList, CStringList& BTCKeys); + + static void CheckDeal(const CDeal& Deal); + + static bool CheckVerifyPGPSignature(int VerifyPGPSignature, CString &Message); + static int VerifyPGPSignature(const CString &ClearText, const CString &Key, CString &Message); + + static bool FindURLInLine(const CString &Line, CStringList &List); + static void ExceptionToJson(int ErrorCode, const std::exception &AException, CString& Json); + + static CDateTime GetRandomDate(int a, int b, CDateTime Date = Now()); + + static void LoadOAuth2(const CString &FileName, const CString &ProviderName, const CString &ApplicationName, + CProviders &Providers); + }; + } +} + +using namespace Apostol::Workers; +} +#endif //APOSTOL_WEBSERVICE_HPP diff --git a/src/modules/Workers/Workers.hpp b/src/modules/Workers/Workers.hpp new file mode 100644 index 0000000..73ae3f0 --- /dev/null +++ b/src/modules/Workers/Workers.hpp @@ -0,0 +1,35 @@ +/*++ + +Library name: + + apostol-core + +Module Name: + + Workers.hpp + +Notices: + + Add-ons: Workers + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_WORKERS_HPP +#define APOSTOL_WORKERS_HPP +//---------------------------------------------------------------------------------------------------------------------- + +#include "WebService/WebService.hpp" +//---------------------------------------------------------------------------------------------------------------------- + +static inline void CreateWorkers(CModuleProcess *AProcess) { + CWebService::CreateModule(AProcess); +} + +#endif //APOSTOL_WORKERS_HPP diff --git a/src/processes/Processes.hpp b/src/processes/Processes.hpp new file mode 100644 index 0000000..b3b280a --- /dev/null +++ b/src/processes/Processes.hpp @@ -0,0 +1,32 @@ +/*++ + +Library name: + + apostol-core + +Module Name: + + Processes.hpp + +Notices: + + Application others processes + +Author: + + Copyright (c) Prepodobny Alen + + mailto: alienufo@inbox.ru + mailto: ufocomp@gmail.com + +--*/ + +#ifndef APOSTOL_PROCESSES_HPP +#define APOSTOL_PROCESSES_HPP +//---------------------------------------------------------------------------------------------------------------------- + +static inline void CreateProcesses(CCustomProcess *AParent, CApplication *AApplication) { + +} + +#endif //APOSTOL_PROCESSES_HPP diff --git a/www/assets/css/docs.css b/www/assets/css/docs.css new file mode 100644 index 0000000..918ded5 --- /dev/null +++ b/www/assets/css/docs.css @@ -0,0 +1,102 @@ +@media (min-width: 1200px) { + .bs-docs-sidebar.affix-bottom, .bs-docs-sidebar.affix-top, .bs-docs-sidebar.affix { + width: auto; + } +} + +@media (min-width: 768px) { + .bs-docs-sidebar { + padding-left: 0; + margin-left: -15px; + } +} + +.img-docs { margin: 16px 0 16px 0 } + +.bs-docs-sidenav { + margin-top: 0; + margin-bottom: 0; +} + +.back-to-top:hover { color: #4D4D4D; text-decoration: none; } +.back-to-top:focus { color: #999; text-decoration: none; } +.back-to-top { + padding: 0 5px; + margin-top: 10px; + font-size: 12px; + font-weight: 500; + color: #999; +} + +/* All levels of nav */ +.bs-docs-sidebar .nav > li > a { + display: block; + font-size: 13px; + font-weight: normal; + color: #999; + padding: 0px 15px; + white-space: nowrap; +} + +.bs-docs-sidebar .nav > li > a:hover, +.bs-docs-sidebar .nav > li > a:focus { + padding-left: 14px; + color: #4D4D4D; + text-decoration: none; + background-color: transparent; + border-left: 1px solid #424242; +} + +.bs-docs-sidebar .nav > .active > a, +.bs-docs-sidebar .nav > .active:hover > a, +.bs-docs-sidebar .nav > .active:focus > a { + padding-left: 13px; + font-weight: bold; + color: #4D4D4D; + background-color: transparent; + border-left: 2px solid #424242; +} + +h1.title { + line-height: 24px; + font-size: 22px; + margin-bottom: 18px; + display: block; + padding-bottom: 2px; + border-bottom: 1px solid #ddd; +} + +h1.title img { margin: -5px 5px 0 0 } + +.h4, .h5, .h6, h4, h5, h6 { margin-top: 8px; margin-bottom: 8px; } + +h1, h2, h3, h4, h6, .h1, .h2, .h3, .h4, .h6 { + font-family: OSRoboto, ALSStory, Arial, sans-serif !important; + font-weight: normal !important; +} + +.toptable { border-collapse: collapse; border-spacing: 0; width: 100% } +.thumbnail { padding: 5px } +.thumbnail .caption { margin-top: 5px; border-top: 1px solid #eaeaea; padding: 15px 9px 7px 9px } +.uley { padding-left: 19px } +.nowrap { white-space: nowrap } +.panel-heading .pull-right { font-size: 14px } +.panel-title { font-size: 18px; font-weight: normal !important } + +.h1, .h2, .h3, h1, h2, h3 { margin-top: 10px; } +.t6 { margin-top: 6px } +.hidehead { white-space: nowrap; line-height: 18px; font-size: 13px } + +#head1 { display: inline-block; margin: 41px 0 0 4px } +#alts { margin-bottom: -6px } + +.text-muted { color: #999999 } +.text-number { + font-size: 75%; + color: #a94442; +} + +h1[id] { + padding-top: 20px; + margin-top: 0; +} diff --git a/www/assets/css/flag-icon.min.css b/www/assets/css/flag-icon.min.css new file mode 100644 index 0000000..ffd24ad --- /dev/null +++ b/www/assets/css/flag-icon.min.css @@ -0,0 +1 @@ +.flag-icon-background{background-size:contain;background-position:50%;background-repeat:no-repeat}.flag-icon{background-size:contain;background-position:50%;background-repeat:no-repeat;position:relative;display:inline-block;width:1.33333333em;line-height:1em}.flag-icon:before{content:"\00a0"}.flag-icon.flag-icon-squared{width:1em}.flag-icon-ad{background-image:url(../flags/4x3/ad.svg)}.flag-icon-ad.flag-icon-squared{background-image:url(../flags/1x1/ad.svg)}.flag-icon-ae{background-image:url(../flags/4x3/ae.svg)}.flag-icon-ae.flag-icon-squared{background-image:url(../flags/1x1/ae.svg)}.flag-icon-af{background-image:url(../flags/4x3/af.svg)}.flag-icon-af.flag-icon-squared{background-image:url(../flags/1x1/af.svg)}.flag-icon-ag{background-image:url(../flags/4x3/ag.svg)}.flag-icon-ag.flag-icon-squared{background-image:url(../flags/1x1/ag.svg)}.flag-icon-ai{background-image:url(../flags/4x3/ai.svg)}.flag-icon-ai.flag-icon-squared{background-image:url(../flags/1x1/ai.svg)}.flag-icon-al{background-image:url(../flags/4x3/al.svg)}.flag-icon-al.flag-icon-squared{background-image:url(../flags/1x1/al.svg)}.flag-icon-am{background-image:url(../flags/4x3/am.svg)}.flag-icon-am.flag-icon-squared{background-image:url(../flags/1x1/am.svg)}.flag-icon-ao{background-image:url(../flags/4x3/ao.svg)}.flag-icon-ao.flag-icon-squared{background-image:url(../flags/1x1/ao.svg)}.flag-icon-aq{background-image:url(../flags/4x3/aq.svg)}.flag-icon-aq.flag-icon-squared{background-image:url(../flags/1x1/aq.svg)}.flag-icon-ar{background-image:url(../flags/4x3/ar.svg)}.flag-icon-ar.flag-icon-squared{background-image:url(../flags/1x1/ar.svg)}.flag-icon-as{background-image:url(../flags/4x3/as.svg)}.flag-icon-as.flag-icon-squared{background-image:url(../flags/1x1/as.svg)}.flag-icon-at{background-image:url(../flags/4x3/at.svg)}.flag-icon-at.flag-icon-squared{background-image:url(../flags/1x1/at.svg)}.flag-icon-au{background-image:url(../flags/4x3/au.svg)}.flag-icon-au.flag-icon-squared{background-image:url(../flags/1x1/au.svg)}.flag-icon-aw{background-image:url(../flags/4x3/aw.svg)}.flag-icon-aw.flag-icon-squared{background-image:url(../flags/1x1/aw.svg)}.flag-icon-ax{background-image:url(../flags/4x3/ax.svg)}.flag-icon-ax.flag-icon-squared{background-image:url(../flags/1x1/ax.svg)}.flag-icon-az{background-image:url(../flags/4x3/az.svg)}.flag-icon-az.flag-icon-squared{background-image:url(../flags/1x1/az.svg)}.flag-icon-ba{background-image:url(../flags/4x3/ba.svg)}.flag-icon-ba.flag-icon-squared{background-image:url(../flags/1x1/ba.svg)}.flag-icon-bb{background-image:url(../flags/4x3/bb.svg)}.flag-icon-bb.flag-icon-squared{background-image:url(../flags/1x1/bb.svg)}.flag-icon-bd{background-image:url(../flags/4x3/bd.svg)}.flag-icon-bd.flag-icon-squared{background-image:url(../flags/1x1/bd.svg)}.flag-icon-be{background-image:url(../flags/4x3/be.svg)}.flag-icon-be.flag-icon-squared{background-image:url(../flags/1x1/be.svg)}.flag-icon-bf{background-image:url(../flags/4x3/bf.svg)}.flag-icon-bf.flag-icon-squared{background-image:url(../flags/1x1/bf.svg)}.flag-icon-bg{background-image:url(../flags/4x3/bg.svg)}.flag-icon-bg.flag-icon-squared{background-image:url(../flags/1x1/bg.svg)}.flag-icon-bh{background-image:url(../flags/4x3/bh.svg)}.flag-icon-bh.flag-icon-squared{background-image:url(../flags/1x1/bh.svg)}.flag-icon-bi{background-image:url(../flags/4x3/bi.svg)}.flag-icon-bi.flag-icon-squared{background-image:url(../flags/1x1/bi.svg)}.flag-icon-bj{background-image:url(../flags/4x3/bj.svg)}.flag-icon-bj.flag-icon-squared{background-image:url(../flags/1x1/bj.svg)}.flag-icon-bl{background-image:url(../flags/4x3/bl.svg)}.flag-icon-bl.flag-icon-squared{background-image:url(../flags/1x1/bl.svg)}.flag-icon-bm{background-image:url(../flags/4x3/bm.svg)}.flag-icon-bm.flag-icon-squared{background-image:url(../flags/1x1/bm.svg)}.flag-icon-bn{background-image:url(../flags/4x3/bn.svg)}.flag-icon-bn.flag-icon-squared{background-image:url(../flags/1x1/bn.svg)}.flag-icon-bo{background-image:url(../flags/4x3/bo.svg)}.flag-icon-bo.flag-icon-squared{background-image:url(../flags/1x1/bo.svg)}.flag-icon-bq{background-image:url(../flags/4x3/bq.svg)}.flag-icon-bq.flag-icon-squared{background-image:url(../flags/1x1/bq.svg)}.flag-icon-br{background-image:url(../flags/4x3/br.svg)}.flag-icon-br.flag-icon-squared{background-image:url(../flags/1x1/br.svg)}.flag-icon-bs{background-image:url(../flags/4x3/bs.svg)}.flag-icon-bs.flag-icon-squared{background-image:url(../flags/1x1/bs.svg)}.flag-icon-bt{background-image:url(../flags/4x3/bt.svg)}.flag-icon-bt.flag-icon-squared{background-image:url(../flags/1x1/bt.svg)}.flag-icon-bv{background-image:url(../flags/4x3/bv.svg)}.flag-icon-bv.flag-icon-squared{background-image:url(../flags/1x1/bv.svg)}.flag-icon-bw{background-image:url(../flags/4x3/bw.svg)}.flag-icon-bw.flag-icon-squared{background-image:url(../flags/1x1/bw.svg)}.flag-icon-by{background-image:url(../flags/4x3/by.svg)}.flag-icon-by.flag-icon-squared{background-image:url(../flags/1x1/by.svg)}.flag-icon-bz{background-image:url(../flags/4x3/bz.svg)}.flag-icon-bz.flag-icon-squared{background-image:url(../flags/1x1/bz.svg)}.flag-icon-ca{background-image:url(../flags/4x3/ca.svg)}.flag-icon-ca.flag-icon-squared{background-image:url(../flags/1x1/ca.svg)}.flag-icon-cc{background-image:url(../flags/4x3/cc.svg)}.flag-icon-cc.flag-icon-squared{background-image:url(../flags/1x1/cc.svg)}.flag-icon-cd{background-image:url(../flags/4x3/cd.svg)}.flag-icon-cd.flag-icon-squared{background-image:url(../flags/1x1/cd.svg)}.flag-icon-cf{background-image:url(../flags/4x3/cf.svg)}.flag-icon-cf.flag-icon-squared{background-image:url(../flags/1x1/cf.svg)}.flag-icon-cg{background-image:url(../flags/4x3/cg.svg)}.flag-icon-cg.flag-icon-squared{background-image:url(../flags/1x1/cg.svg)}.flag-icon-ch{background-image:url(../flags/4x3/ch.svg)}.flag-icon-ch.flag-icon-squared{background-image:url(../flags/1x1/ch.svg)}.flag-icon-ci{background-image:url(../flags/4x3/ci.svg)}.flag-icon-ci.flag-icon-squared{background-image:url(../flags/1x1/ci.svg)}.flag-icon-ck{background-image:url(../flags/4x3/ck.svg)}.flag-icon-ck.flag-icon-squared{background-image:url(../flags/1x1/ck.svg)}.flag-icon-cl{background-image:url(../flags/4x3/cl.svg)}.flag-icon-cl.flag-icon-squared{background-image:url(../flags/1x1/cl.svg)}.flag-icon-cm{background-image:url(../flags/4x3/cm.svg)}.flag-icon-cm.flag-icon-squared{background-image:url(../flags/1x1/cm.svg)}.flag-icon-cn{background-image:url(../flags/4x3/cn.svg)}.flag-icon-cn.flag-icon-squared{background-image:url(../flags/1x1/cn.svg)}.flag-icon-co{background-image:url(../flags/4x3/co.svg)}.flag-icon-co.flag-icon-squared{background-image:url(../flags/1x1/co.svg)}.flag-icon-cr{background-image:url(../flags/4x3/cr.svg)}.flag-icon-cr.flag-icon-squared{background-image:url(../flags/1x1/cr.svg)}.flag-icon-cu{background-image:url(../flags/4x3/cu.svg)}.flag-icon-cu.flag-icon-squared{background-image:url(../flags/1x1/cu.svg)}.flag-icon-cv{background-image:url(../flags/4x3/cv.svg)}.flag-icon-cv.flag-icon-squared{background-image:url(../flags/1x1/cv.svg)}.flag-icon-cw{background-image:url(../flags/4x3/cw.svg)}.flag-icon-cw.flag-icon-squared{background-image:url(../flags/1x1/cw.svg)}.flag-icon-cx{background-image:url(../flags/4x3/cx.svg)}.flag-icon-cx.flag-icon-squared{background-image:url(../flags/1x1/cx.svg)}.flag-icon-cy{background-image:url(../flags/4x3/cy.svg)}.flag-icon-cy.flag-icon-squared{background-image:url(../flags/1x1/cy.svg)}.flag-icon-cz{background-image:url(../flags/4x3/cz.svg)}.flag-icon-cz.flag-icon-squared{background-image:url(../flags/1x1/cz.svg)}.flag-icon-de{background-image:url(../flags/4x3/de.svg)}.flag-icon-de.flag-icon-squared{background-image:url(../flags/1x1/de.svg)}.flag-icon-dj{background-image:url(../flags/4x3/dj.svg)}.flag-icon-dj.flag-icon-squared{background-image:url(../flags/1x1/dj.svg)}.flag-icon-dk{background-image:url(../flags/4x3/dk.svg)}.flag-icon-dk.flag-icon-squared{background-image:url(../flags/1x1/dk.svg)}.flag-icon-dm{background-image:url(../flags/4x3/dm.svg)}.flag-icon-dm.flag-icon-squared{background-image:url(../flags/1x1/dm.svg)}.flag-icon-do{background-image:url(../flags/4x3/do.svg)}.flag-icon-do.flag-icon-squared{background-image:url(../flags/1x1/do.svg)}.flag-icon-dz{background-image:url(../flags/4x3/dz.svg)}.flag-icon-dz.flag-icon-squared{background-image:url(../flags/1x1/dz.svg)}.flag-icon-ec{background-image:url(../flags/4x3/ec.svg)}.flag-icon-ec.flag-icon-squared{background-image:url(../flags/1x1/ec.svg)}.flag-icon-ee{background-image:url(../flags/4x3/ee.svg)}.flag-icon-ee.flag-icon-squared{background-image:url(../flags/1x1/ee.svg)}.flag-icon-eg{background-image:url(../flags/4x3/eg.svg)}.flag-icon-eg.flag-icon-squared{background-image:url(../flags/1x1/eg.svg)}.flag-icon-eh{background-image:url(../flags/4x3/eh.svg)}.flag-icon-eh.flag-icon-squared{background-image:url(../flags/1x1/eh.svg)}.flag-icon-er{background-image:url(../flags/4x3/er.svg)}.flag-icon-er.flag-icon-squared{background-image:url(../flags/1x1/er.svg)}.flag-icon-es{background-image:url(../flags/4x3/es.svg)}.flag-icon-es.flag-icon-squared{background-image:url(../flags/1x1/es.svg)}.flag-icon-et{background-image:url(../flags/4x3/et.svg)}.flag-icon-et.flag-icon-squared{background-image:url(../flags/1x1/et.svg)}.flag-icon-fi{background-image:url(../flags/4x3/fi.svg)}.flag-icon-fi.flag-icon-squared{background-image:url(../flags/1x1/fi.svg)}.flag-icon-fj{background-image:url(../flags/4x3/fj.svg)}.flag-icon-fj.flag-icon-squared{background-image:url(../flags/1x1/fj.svg)}.flag-icon-fk{background-image:url(../flags/4x3/fk.svg)}.flag-icon-fk.flag-icon-squared{background-image:url(../flags/1x1/fk.svg)}.flag-icon-fm{background-image:url(../flags/4x3/fm.svg)}.flag-icon-fm.flag-icon-squared{background-image:url(../flags/1x1/fm.svg)}.flag-icon-fo{background-image:url(../flags/4x3/fo.svg)}.flag-icon-fo.flag-icon-squared{background-image:url(../flags/1x1/fo.svg)}.flag-icon-fr{background-image:url(../flags/4x3/fr.svg)}.flag-icon-fr.flag-icon-squared{background-image:url(../flags/1x1/fr.svg)}.flag-icon-ga{background-image:url(../flags/4x3/ga.svg)}.flag-icon-ga.flag-icon-squared{background-image:url(../flags/1x1/ga.svg)}.flag-icon-gb{background-image:url(../flags/4x3/gb.svg)}.flag-icon-gb.flag-icon-squared{background-image:url(../flags/1x1/gb.svg)}.flag-icon-gd{background-image:url(../flags/4x3/gd.svg)}.flag-icon-gd.flag-icon-squared{background-image:url(../flags/1x1/gd.svg)}.flag-icon-ge{background-image:url(../flags/4x3/ge.svg)}.flag-icon-ge.flag-icon-squared{background-image:url(../flags/1x1/ge.svg)}.flag-icon-gf{background-image:url(../flags/4x3/gf.svg)}.flag-icon-gf.flag-icon-squared{background-image:url(../flags/1x1/gf.svg)}.flag-icon-gg{background-image:url(../flags/4x3/gg.svg)}.flag-icon-gg.flag-icon-squared{background-image:url(../flags/1x1/gg.svg)}.flag-icon-gh{background-image:url(../flags/4x3/gh.svg)}.flag-icon-gh.flag-icon-squared{background-image:url(../flags/1x1/gh.svg)}.flag-icon-gi{background-image:url(../flags/4x3/gi.svg)}.flag-icon-gi.flag-icon-squared{background-image:url(../flags/1x1/gi.svg)}.flag-icon-gl{background-image:url(../flags/4x3/gl.svg)}.flag-icon-gl.flag-icon-squared{background-image:url(../flags/1x1/gl.svg)}.flag-icon-gm{background-image:url(../flags/4x3/gm.svg)}.flag-icon-gm.flag-icon-squared{background-image:url(../flags/1x1/gm.svg)}.flag-icon-gn{background-image:url(../flags/4x3/gn.svg)}.flag-icon-gn.flag-icon-squared{background-image:url(../flags/1x1/gn.svg)}.flag-icon-gp{background-image:url(../flags/4x3/gp.svg)}.flag-icon-gp.flag-icon-squared{background-image:url(../flags/1x1/gp.svg)}.flag-icon-gq{background-image:url(../flags/4x3/gq.svg)}.flag-icon-gq.flag-icon-squared{background-image:url(../flags/1x1/gq.svg)}.flag-icon-gr{background-image:url(../flags/4x3/gr.svg)}.flag-icon-gr.flag-icon-squared{background-image:url(../flags/1x1/gr.svg)}.flag-icon-gs{background-image:url(../flags/4x3/gs.svg)}.flag-icon-gs.flag-icon-squared{background-image:url(../flags/1x1/gs.svg)}.flag-icon-gt{background-image:url(../flags/4x3/gt.svg)}.flag-icon-gt.flag-icon-squared{background-image:url(../flags/1x1/gt.svg)}.flag-icon-gu{background-image:url(../flags/4x3/gu.svg)}.flag-icon-gu.flag-icon-squared{background-image:url(../flags/1x1/gu.svg)}.flag-icon-gw{background-image:url(../flags/4x3/gw.svg)}.flag-icon-gw.flag-icon-squared{background-image:url(../flags/1x1/gw.svg)}.flag-icon-gy{background-image:url(../flags/4x3/gy.svg)}.flag-icon-gy.flag-icon-squared{background-image:url(../flags/1x1/gy.svg)}.flag-icon-hk{background-image:url(../flags/4x3/hk.svg)}.flag-icon-hk.flag-icon-squared{background-image:url(../flags/1x1/hk.svg)}.flag-icon-hm{background-image:url(../flags/4x3/hm.svg)}.flag-icon-hm.flag-icon-squared{background-image:url(../flags/1x1/hm.svg)}.flag-icon-hn{background-image:url(../flags/4x3/hn.svg)}.flag-icon-hn.flag-icon-squared{background-image:url(../flags/1x1/hn.svg)}.flag-icon-hr{background-image:url(../flags/4x3/hr.svg)}.flag-icon-hr.flag-icon-squared{background-image:url(../flags/1x1/hr.svg)}.flag-icon-ht{background-image:url(../flags/4x3/ht.svg)}.flag-icon-ht.flag-icon-squared{background-image:url(../flags/1x1/ht.svg)}.flag-icon-hu{background-image:url(../flags/4x3/hu.svg)}.flag-icon-hu.flag-icon-squared{background-image:url(../flags/1x1/hu.svg)}.flag-icon-id{background-image:url(../flags/4x3/id.svg)}.flag-icon-id.flag-icon-squared{background-image:url(../flags/1x1/id.svg)}.flag-icon-ie{background-image:url(../flags/4x3/ie.svg)}.flag-icon-ie.flag-icon-squared{background-image:url(../flags/1x1/ie.svg)}.flag-icon-il{background-image:url(../flags/4x3/il.svg)}.flag-icon-il.flag-icon-squared{background-image:url(../flags/1x1/il.svg)}.flag-icon-im{background-image:url(../flags/4x3/im.svg)}.flag-icon-im.flag-icon-squared{background-image:url(../flags/1x1/im.svg)}.flag-icon-in{background-image:url(../flags/4x3/in.svg)}.flag-icon-in.flag-icon-squared{background-image:url(../flags/1x1/in.svg)}.flag-icon-io{background-image:url(../flags/4x3/io.svg)}.flag-icon-io.flag-icon-squared{background-image:url(../flags/1x1/io.svg)}.flag-icon-iq{background-image:url(../flags/4x3/iq.svg)}.flag-icon-iq.flag-icon-squared{background-image:url(../flags/1x1/iq.svg)}.flag-icon-ir{background-image:url(../flags/4x3/ir.svg)}.flag-icon-ir.flag-icon-squared{background-image:url(../flags/1x1/ir.svg)}.flag-icon-is{background-image:url(../flags/4x3/is.svg)}.flag-icon-is.flag-icon-squared{background-image:url(../flags/1x1/is.svg)}.flag-icon-it{background-image:url(../flags/4x3/it.svg)}.flag-icon-it.flag-icon-squared{background-image:url(../flags/1x1/it.svg)}.flag-icon-je{background-image:url(../flags/4x3/je.svg)}.flag-icon-je.flag-icon-squared{background-image:url(../flags/1x1/je.svg)}.flag-icon-jm{background-image:url(../flags/4x3/jm.svg)}.flag-icon-jm.flag-icon-squared{background-image:url(../flags/1x1/jm.svg)}.flag-icon-jo{background-image:url(../flags/4x3/jo.svg)}.flag-icon-jo.flag-icon-squared{background-image:url(../flags/1x1/jo.svg)}.flag-icon-jp{background-image:url(../flags/4x3/jp.svg)}.flag-icon-jp.flag-icon-squared{background-image:url(../flags/1x1/jp.svg)}.flag-icon-ke{background-image:url(../flags/4x3/ke.svg)}.flag-icon-ke.flag-icon-squared{background-image:url(../flags/1x1/ke.svg)}.flag-icon-kg{background-image:url(../flags/4x3/kg.svg)}.flag-icon-kg.flag-icon-squared{background-image:url(../flags/1x1/kg.svg)}.flag-icon-kh{background-image:url(../flags/4x3/kh.svg)}.flag-icon-kh.flag-icon-squared{background-image:url(../flags/1x1/kh.svg)}.flag-icon-ki{background-image:url(../flags/4x3/ki.svg)}.flag-icon-ki.flag-icon-squared{background-image:url(../flags/1x1/ki.svg)}.flag-icon-km{background-image:url(../flags/4x3/km.svg)}.flag-icon-km.flag-icon-squared{background-image:url(../flags/1x1/km.svg)}.flag-icon-kn{background-image:url(../flags/4x3/kn.svg)}.flag-icon-kn.flag-icon-squared{background-image:url(../flags/1x1/kn.svg)}.flag-icon-kp{background-image:url(../flags/4x3/kp.svg)}.flag-icon-kp.flag-icon-squared{background-image:url(../flags/1x1/kp.svg)}.flag-icon-kr{background-image:url(../flags/4x3/kr.svg)}.flag-icon-kr.flag-icon-squared{background-image:url(../flags/1x1/kr.svg)}.flag-icon-kw{background-image:url(../flags/4x3/kw.svg)}.flag-icon-kw.flag-icon-squared{background-image:url(../flags/1x1/kw.svg)}.flag-icon-ky{background-image:url(../flags/4x3/ky.svg)}.flag-icon-ky.flag-icon-squared{background-image:url(../flags/1x1/ky.svg)}.flag-icon-kz{background-image:url(../flags/4x3/kz.svg)}.flag-icon-kz.flag-icon-squared{background-image:url(../flags/1x1/kz.svg)}.flag-icon-la{background-image:url(../flags/4x3/la.svg)}.flag-icon-la.flag-icon-squared{background-image:url(../flags/1x1/la.svg)}.flag-icon-lb{background-image:url(../flags/4x3/lb.svg)}.flag-icon-lb.flag-icon-squared{background-image:url(../flags/1x1/lb.svg)}.flag-icon-lc{background-image:url(../flags/4x3/lc.svg)}.flag-icon-lc.flag-icon-squared{background-image:url(../flags/1x1/lc.svg)}.flag-icon-li{background-image:url(../flags/4x3/li.svg)}.flag-icon-li.flag-icon-squared{background-image:url(../flags/1x1/li.svg)}.flag-icon-lk{background-image:url(../flags/4x3/lk.svg)}.flag-icon-lk.flag-icon-squared{background-image:url(../flags/1x1/lk.svg)}.flag-icon-lr{background-image:url(../flags/4x3/lr.svg)}.flag-icon-lr.flag-icon-squared{background-image:url(../flags/1x1/lr.svg)}.flag-icon-ls{background-image:url(../flags/4x3/ls.svg)}.flag-icon-ls.flag-icon-squared{background-image:url(../flags/1x1/ls.svg)}.flag-icon-lt{background-image:url(../flags/4x3/lt.svg)}.flag-icon-lt.flag-icon-squared{background-image:url(../flags/1x1/lt.svg)}.flag-icon-lu{background-image:url(../flags/4x3/lu.svg)}.flag-icon-lu.flag-icon-squared{background-image:url(../flags/1x1/lu.svg)}.flag-icon-lv{background-image:url(../flags/4x3/lv.svg)}.flag-icon-lv.flag-icon-squared{background-image:url(../flags/1x1/lv.svg)}.flag-icon-ly{background-image:url(../flags/4x3/ly.svg)}.flag-icon-ly.flag-icon-squared{background-image:url(../flags/1x1/ly.svg)}.flag-icon-ma{background-image:url(../flags/4x3/ma.svg)}.flag-icon-ma.flag-icon-squared{background-image:url(../flags/1x1/ma.svg)}.flag-icon-mc{background-image:url(../flags/4x3/mc.svg)}.flag-icon-mc.flag-icon-squared{background-image:url(../flags/1x1/mc.svg)}.flag-icon-md{background-image:url(../flags/4x3/md.svg)}.flag-icon-md.flag-icon-squared{background-image:url(../flags/1x1/md.svg)}.flag-icon-me{background-image:url(../flags/4x3/me.svg)}.flag-icon-me.flag-icon-squared{background-image:url(../flags/1x1/me.svg)}.flag-icon-mf{background-image:url(../flags/4x3/mf.svg)}.flag-icon-mf.flag-icon-squared{background-image:url(../flags/1x1/mf.svg)}.flag-icon-mg{background-image:url(../flags/4x3/mg.svg)}.flag-icon-mg.flag-icon-squared{background-image:url(../flags/1x1/mg.svg)}.flag-icon-mh{background-image:url(../flags/4x3/mh.svg)}.flag-icon-mh.flag-icon-squared{background-image:url(../flags/1x1/mh.svg)}.flag-icon-mk{background-image:url(../flags/4x3/mk.svg)}.flag-icon-mk.flag-icon-squared{background-image:url(../flags/1x1/mk.svg)}.flag-icon-ml{background-image:url(../flags/4x3/ml.svg)}.flag-icon-ml.flag-icon-squared{background-image:url(../flags/1x1/ml.svg)}.flag-icon-mm{background-image:url(../flags/4x3/mm.svg)}.flag-icon-mm.flag-icon-squared{background-image:url(../flags/1x1/mm.svg)}.flag-icon-mn{background-image:url(../flags/4x3/mn.svg)}.flag-icon-mn.flag-icon-squared{background-image:url(../flags/1x1/mn.svg)}.flag-icon-mo{background-image:url(../flags/4x3/mo.svg)}.flag-icon-mo.flag-icon-squared{background-image:url(../flags/1x1/mo.svg)}.flag-icon-mp{background-image:url(../flags/4x3/mp.svg)}.flag-icon-mp.flag-icon-squared{background-image:url(../flags/1x1/mp.svg)}.flag-icon-mq{background-image:url(../flags/4x3/mq.svg)}.flag-icon-mq.flag-icon-squared{background-image:url(../flags/1x1/mq.svg)}.flag-icon-mr{background-image:url(../flags/4x3/mr.svg)}.flag-icon-mr.flag-icon-squared{background-image:url(../flags/1x1/mr.svg)}.flag-icon-ms{background-image:url(../flags/4x3/ms.svg)}.flag-icon-ms.flag-icon-squared{background-image:url(../flags/1x1/ms.svg)}.flag-icon-mt{background-image:url(../flags/4x3/mt.svg)}.flag-icon-mt.flag-icon-squared{background-image:url(../flags/1x1/mt.svg)}.flag-icon-mu{background-image:url(../flags/4x3/mu.svg)}.flag-icon-mu.flag-icon-squared{background-image:url(../flags/1x1/mu.svg)}.flag-icon-mv{background-image:url(../flags/4x3/mv.svg)}.flag-icon-mv.flag-icon-squared{background-image:url(../flags/1x1/mv.svg)}.flag-icon-mw{background-image:url(../flags/4x3/mw.svg)}.flag-icon-mw.flag-icon-squared{background-image:url(../flags/1x1/mw.svg)}.flag-icon-mx{background-image:url(../flags/4x3/mx.svg)}.flag-icon-mx.flag-icon-squared{background-image:url(../flags/1x1/mx.svg)}.flag-icon-my{background-image:url(../flags/4x3/my.svg)}.flag-icon-my.flag-icon-squared{background-image:url(../flags/1x1/my.svg)}.flag-icon-mz{background-image:url(../flags/4x3/mz.svg)}.flag-icon-mz.flag-icon-squared{background-image:url(../flags/1x1/mz.svg)}.flag-icon-na{background-image:url(../flags/4x3/na.svg)}.flag-icon-na.flag-icon-squared{background-image:url(../flags/1x1/na.svg)}.flag-icon-nc{background-image:url(../flags/4x3/nc.svg)}.flag-icon-nc.flag-icon-squared{background-image:url(../flags/1x1/nc.svg)}.flag-icon-ne{background-image:url(../flags/4x3/ne.svg)}.flag-icon-ne.flag-icon-squared{background-image:url(../flags/1x1/ne.svg)}.flag-icon-nf{background-image:url(../flags/4x3/nf.svg)}.flag-icon-nf.flag-icon-squared{background-image:url(../flags/1x1/nf.svg)}.flag-icon-ng{background-image:url(../flags/4x3/ng.svg)}.flag-icon-ng.flag-icon-squared{background-image:url(../flags/1x1/ng.svg)}.flag-icon-ni{background-image:url(../flags/4x3/ni.svg)}.flag-icon-ni.flag-icon-squared{background-image:url(../flags/1x1/ni.svg)}.flag-icon-nl{background-image:url(../flags/4x3/nl.svg)}.flag-icon-nl.flag-icon-squared{background-image:url(../flags/1x1/nl.svg)}.flag-icon-no{background-image:url(../flags/4x3/no.svg)}.flag-icon-no.flag-icon-squared{background-image:url(../flags/1x1/no.svg)}.flag-icon-np{background-image:url(../flags/4x3/np.svg)}.flag-icon-np.flag-icon-squared{background-image:url(../flags/1x1/np.svg)}.flag-icon-nr{background-image:url(../flags/4x3/nr.svg)}.flag-icon-nr.flag-icon-squared{background-image:url(../flags/1x1/nr.svg)}.flag-icon-nu{background-image:url(../flags/4x3/nu.svg)}.flag-icon-nu.flag-icon-squared{background-image:url(../flags/1x1/nu.svg)}.flag-icon-nz{background-image:url(../flags/4x3/nz.svg)}.flag-icon-nz.flag-icon-squared{background-image:url(../flags/1x1/nz.svg)}.flag-icon-om{background-image:url(../flags/4x3/om.svg)}.flag-icon-om.flag-icon-squared{background-image:url(../flags/1x1/om.svg)}.flag-icon-pa{background-image:url(../flags/4x3/pa.svg)}.flag-icon-pa.flag-icon-squared{background-image:url(../flags/1x1/pa.svg)}.flag-icon-pe{background-image:url(../flags/4x3/pe.svg)}.flag-icon-pe.flag-icon-squared{background-image:url(../flags/1x1/pe.svg)}.flag-icon-pf{background-image:url(../flags/4x3/pf.svg)}.flag-icon-pf.flag-icon-squared{background-image:url(../flags/1x1/pf.svg)}.flag-icon-pg{background-image:url(../flags/4x3/pg.svg)}.flag-icon-pg.flag-icon-squared{background-image:url(../flags/1x1/pg.svg)}.flag-icon-ph{background-image:url(../flags/4x3/ph.svg)}.flag-icon-ph.flag-icon-squared{background-image:url(../flags/1x1/ph.svg)}.flag-icon-pk{background-image:url(../flags/4x3/pk.svg)}.flag-icon-pk.flag-icon-squared{background-image:url(../flags/1x1/pk.svg)}.flag-icon-pl{background-image:url(../flags/4x3/pl.svg)}.flag-icon-pl.flag-icon-squared{background-image:url(../flags/1x1/pl.svg)}.flag-icon-pm{background-image:url(../flags/4x3/pm.svg)}.flag-icon-pm.flag-icon-squared{background-image:url(../flags/1x1/pm.svg)}.flag-icon-pn{background-image:url(../flags/4x3/pn.svg)}.flag-icon-pn.flag-icon-squared{background-image:url(../flags/1x1/pn.svg)}.flag-icon-pr{background-image:url(../flags/4x3/pr.svg)}.flag-icon-pr.flag-icon-squared{background-image:url(../flags/1x1/pr.svg)}.flag-icon-ps{background-image:url(../flags/4x3/ps.svg)}.flag-icon-ps.flag-icon-squared{background-image:url(../flags/1x1/ps.svg)}.flag-icon-pt{background-image:url(../flags/4x3/pt.svg)}.flag-icon-pt.flag-icon-squared{background-image:url(../flags/1x1/pt.svg)}.flag-icon-pw{background-image:url(../flags/4x3/pw.svg)}.flag-icon-pw.flag-icon-squared{background-image:url(../flags/1x1/pw.svg)}.flag-icon-py{background-image:url(../flags/4x3/py.svg)}.flag-icon-py.flag-icon-squared{background-image:url(../flags/1x1/py.svg)}.flag-icon-qa{background-image:url(../flags/4x3/qa.svg)}.flag-icon-qa.flag-icon-squared{background-image:url(../flags/1x1/qa.svg)}.flag-icon-re{background-image:url(../flags/4x3/re.svg)}.flag-icon-re.flag-icon-squared{background-image:url(../flags/1x1/re.svg)}.flag-icon-ro{background-image:url(../flags/4x3/ro.svg)}.flag-icon-ro.flag-icon-squared{background-image:url(../flags/1x1/ro.svg)}.flag-icon-rs{background-image:url(../flags/4x3/rs.svg)}.flag-icon-rs.flag-icon-squared{background-image:url(../flags/1x1/rs.svg)}.flag-icon-ru{background-image:url(../flags/4x3/ru.svg)}.flag-icon-ru.flag-icon-squared{background-image:url(../flags/1x1/ru.svg)}.flag-icon-rw{background-image:url(../flags/4x3/rw.svg)}.flag-icon-rw.flag-icon-squared{background-image:url(../flags/1x1/rw.svg)}.flag-icon-sa{background-image:url(../flags/4x3/sa.svg)}.flag-icon-sa.flag-icon-squared{background-image:url(../flags/1x1/sa.svg)}.flag-icon-sb{background-image:url(../flags/4x3/sb.svg)}.flag-icon-sb.flag-icon-squared{background-image:url(../flags/1x1/sb.svg)}.flag-icon-sc{background-image:url(../flags/4x3/sc.svg)}.flag-icon-sc.flag-icon-squared{background-image:url(../flags/1x1/sc.svg)}.flag-icon-sd{background-image:url(../flags/4x3/sd.svg)}.flag-icon-sd.flag-icon-squared{background-image:url(../flags/1x1/sd.svg)}.flag-icon-se{background-image:url(../flags/4x3/se.svg)}.flag-icon-se.flag-icon-squared{background-image:url(../flags/1x1/se.svg)}.flag-icon-sg{background-image:url(../flags/4x3/sg.svg)}.flag-icon-sg.flag-icon-squared{background-image:url(../flags/1x1/sg.svg)}.flag-icon-sh{background-image:url(../flags/4x3/sh.svg)}.flag-icon-sh.flag-icon-squared{background-image:url(../flags/1x1/sh.svg)}.flag-icon-si{background-image:url(../flags/4x3/si.svg)}.flag-icon-si.flag-icon-squared{background-image:url(../flags/1x1/si.svg)}.flag-icon-sj{background-image:url(../flags/4x3/sj.svg)}.flag-icon-sj.flag-icon-squared{background-image:url(../flags/1x1/sj.svg)}.flag-icon-sk{background-image:url(../flags/4x3/sk.svg)}.flag-icon-sk.flag-icon-squared{background-image:url(../flags/1x1/sk.svg)}.flag-icon-sl{background-image:url(../flags/4x3/sl.svg)}.flag-icon-sl.flag-icon-squared{background-image:url(../flags/1x1/sl.svg)}.flag-icon-sm{background-image:url(../flags/4x3/sm.svg)}.flag-icon-sm.flag-icon-squared{background-image:url(../flags/1x1/sm.svg)}.flag-icon-sn{background-image:url(../flags/4x3/sn.svg)}.flag-icon-sn.flag-icon-squared{background-image:url(../flags/1x1/sn.svg)}.flag-icon-so{background-image:url(../flags/4x3/so.svg)}.flag-icon-so.flag-icon-squared{background-image:url(../flags/1x1/so.svg)}.flag-icon-sr{background-image:url(../flags/4x3/sr.svg)}.flag-icon-sr.flag-icon-squared{background-image:url(../flags/1x1/sr.svg)}.flag-icon-ss{background-image:url(../flags/4x3/ss.svg)}.flag-icon-ss.flag-icon-squared{background-image:url(../flags/1x1/ss.svg)}.flag-icon-st{background-image:url(../flags/4x3/st.svg)}.flag-icon-st.flag-icon-squared{background-image:url(../flags/1x1/st.svg)}.flag-icon-sv{background-image:url(../flags/4x3/sv.svg)}.flag-icon-sv.flag-icon-squared{background-image:url(../flags/1x1/sv.svg)}.flag-icon-sx{background-image:url(../flags/4x3/sx.svg)}.flag-icon-sx.flag-icon-squared{background-image:url(../flags/1x1/sx.svg)}.flag-icon-sy{background-image:url(../flags/4x3/sy.svg)}.flag-icon-sy.flag-icon-squared{background-image:url(../flags/1x1/sy.svg)}.flag-icon-sz{background-image:url(../flags/4x3/sz.svg)}.flag-icon-sz.flag-icon-squared{background-image:url(../flags/1x1/sz.svg)}.flag-icon-tc{background-image:url(../flags/4x3/tc.svg)}.flag-icon-tc.flag-icon-squared{background-image:url(../flags/1x1/tc.svg)}.flag-icon-td{background-image:url(../flags/4x3/td.svg)}.flag-icon-td.flag-icon-squared{background-image:url(../flags/1x1/td.svg)}.flag-icon-tf{background-image:url(../flags/4x3/tf.svg)}.flag-icon-tf.flag-icon-squared{background-image:url(../flags/1x1/tf.svg)}.flag-icon-tg{background-image:url(../flags/4x3/tg.svg)}.flag-icon-tg.flag-icon-squared{background-image:url(../flags/1x1/tg.svg)}.flag-icon-th{background-image:url(../flags/4x3/th.svg)}.flag-icon-th.flag-icon-squared{background-image:url(../flags/1x1/th.svg)}.flag-icon-tj{background-image:url(../flags/4x3/tj.svg)}.flag-icon-tj.flag-icon-squared{background-image:url(../flags/1x1/tj.svg)}.flag-icon-tk{background-image:url(../flags/4x3/tk.svg)}.flag-icon-tk.flag-icon-squared{background-image:url(../flags/1x1/tk.svg)}.flag-icon-tl{background-image:url(../flags/4x3/tl.svg)}.flag-icon-tl.flag-icon-squared{background-image:url(../flags/1x1/tl.svg)}.flag-icon-tm{background-image:url(../flags/4x3/tm.svg)}.flag-icon-tm.flag-icon-squared{background-image:url(../flags/1x1/tm.svg)}.flag-icon-tn{background-image:url(../flags/4x3/tn.svg)}.flag-icon-tn.flag-icon-squared{background-image:url(../flags/1x1/tn.svg)}.flag-icon-to{background-image:url(../flags/4x3/to.svg)}.flag-icon-to.flag-icon-squared{background-image:url(../flags/1x1/to.svg)}.flag-icon-tr{background-image:url(../flags/4x3/tr.svg)}.flag-icon-tr.flag-icon-squared{background-image:url(../flags/1x1/tr.svg)}.flag-icon-tt{background-image:url(../flags/4x3/tt.svg)}.flag-icon-tt.flag-icon-squared{background-image:url(../flags/1x1/tt.svg)}.flag-icon-tv{background-image:url(../flags/4x3/tv.svg)}.flag-icon-tv.flag-icon-squared{background-image:url(../flags/1x1/tv.svg)}.flag-icon-tw{background-image:url(../flags/4x3/tw.svg)}.flag-icon-tw.flag-icon-squared{background-image:url(../flags/1x1/tw.svg)}.flag-icon-tz{background-image:url(../flags/4x3/tz.svg)}.flag-icon-tz.flag-icon-squared{background-image:url(../flags/1x1/tz.svg)}.flag-icon-ua{background-image:url(../flags/4x3/ua.svg)}.flag-icon-ua.flag-icon-squared{background-image:url(../flags/1x1/ua.svg)}.flag-icon-ug{background-image:url(../flags/4x3/ug.svg)}.flag-icon-ug.flag-icon-squared{background-image:url(../flags/1x1/ug.svg)}.flag-icon-um{background-image:url(../flags/4x3/um.svg)}.flag-icon-um.flag-icon-squared{background-image:url(../flags/1x1/um.svg)}.flag-icon-us{background-image:url(../flags/4x3/us.svg)}.flag-icon-us.flag-icon-squared{background-image:url(../flags/1x1/us.svg)}.flag-icon-uy{background-image:url(../flags/4x3/uy.svg)}.flag-icon-uy.flag-icon-squared{background-image:url(../flags/1x1/uy.svg)}.flag-icon-uz{background-image:url(../flags/4x3/uz.svg)}.flag-icon-uz.flag-icon-squared{background-image:url(../flags/1x1/uz.svg)}.flag-icon-va{background-image:url(../flags/4x3/va.svg)}.flag-icon-va.flag-icon-squared{background-image:url(../flags/1x1/va.svg)}.flag-icon-vc{background-image:url(../flags/4x3/vc.svg)}.flag-icon-vc.flag-icon-squared{background-image:url(../flags/1x1/vc.svg)}.flag-icon-ve{background-image:url(../flags/4x3/ve.svg)}.flag-icon-ve.flag-icon-squared{background-image:url(../flags/1x1/ve.svg)}.flag-icon-vg{background-image:url(../flags/4x3/vg.svg)}.flag-icon-vg.flag-icon-squared{background-image:url(../flags/1x1/vg.svg)}.flag-icon-vi{background-image:url(../flags/4x3/vi.svg)}.flag-icon-vi.flag-icon-squared{background-image:url(../flags/1x1/vi.svg)}.flag-icon-vn{background-image:url(../flags/4x3/vn.svg)}.flag-icon-vn.flag-icon-squared{background-image:url(../flags/1x1/vn.svg)}.flag-icon-vu{background-image:url(../flags/4x3/vu.svg)}.flag-icon-vu.flag-icon-squared{background-image:url(../flags/1x1/vu.svg)}.flag-icon-wf{background-image:url(../flags/4x3/wf.svg)}.flag-icon-wf.flag-icon-squared{background-image:url(../flags/1x1/wf.svg)}.flag-icon-ws{background-image:url(../flags/4x3/ws.svg)}.flag-icon-ws.flag-icon-squared{background-image:url(../flags/1x1/ws.svg)}.flag-icon-ye{background-image:url(../flags/4x3/ye.svg)}.flag-icon-ye.flag-icon-squared{background-image:url(../flags/1x1/ye.svg)}.flag-icon-yt{background-image:url(../flags/4x3/yt.svg)}.flag-icon-yt.flag-icon-squared{background-image:url(../flags/1x1/yt.svg)}.flag-icon-za{background-image:url(../flags/4x3/za.svg)}.flag-icon-za.flag-icon-squared{background-image:url(../flags/1x1/za.svg)}.flag-icon-zm{background-image:url(../flags/4x3/zm.svg)}.flag-icon-zm.flag-icon-squared{background-image:url(../flags/1x1/zm.svg)}.flag-icon-zw{background-image:url(../flags/4x3/zw.svg)}.flag-icon-zw.flag-icon-squared{background-image:url(../flags/1x1/zw.svg)}.flag-icon-es-ct{background-image:url(../flags/4x3/es-ct.svg)}.flag-icon-es-ct.flag-icon-squared{background-image:url(../flags/1x1/es-ct.svg)}.flag-icon-eu{background-image:url(../flags/4x3/eu.svg)}.flag-icon-eu.flag-icon-squared{background-image:url(../flags/1x1/eu.svg)}.flag-icon-gb-eng{background-image:url(../flags/4x3/gb-eng.svg)}.flag-icon-gb-eng.flag-icon-squared{background-image:url(../flags/1x1/gb-eng.svg)}.flag-icon-gb-nir{background-image:url(../flags/4x3/gb-nir.svg)}.flag-icon-gb-nir.flag-icon-squared{background-image:url(../flags/1x1/gb-nir.svg)}.flag-icon-gb-sct{background-image:url(../flags/4x3/gb-sct.svg)}.flag-icon-gb-sct.flag-icon-squared{background-image:url(../flags/1x1/gb-sct.svg)}.flag-icon-gb-wls{background-image:url(../flags/4x3/gb-wls.svg)}.flag-icon-gb-wls.flag-icon-squared{background-image:url(../flags/1x1/gb-wls.svg)}.flag-icon-un{background-image:url(../flags/4x3/un.svg)}.flag-icon-un.flag-icon-squared{background-image:url(../flags/1x1/un.svg)} \ No newline at end of file diff --git a/www/assets/css/floating-labels.css b/www/assets/css/floating-labels.css new file mode 100644 index 0000000..c15c5cb --- /dev/null +++ b/www/assets/css/floating-labels.css @@ -0,0 +1,100 @@ +html, +body { + height: 100%; +} + +body { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + background-color: #f5f5f5; +} + +.form-signin { + width: 100%; + max-width: 420px; + padding: 15px; + margin: auto; +} + +.form-label-group { + position: relative; + margin-bottom: 1rem; +} + +.form-label-group > input, +.form-label-group > label { + height: 3.125rem; + padding: .75rem; +} + +.form-label-group > label { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + margin-bottom: 0; /* Override default `